Split node list to parts - xslt

xml:
<mode>1</mode>
<mode>2</mode>
<mode>3</mode>
<mode>4</mode>
<mode>5</mode>
<mode>6</mode>
<mode>7</mode>
<mode>8</mode>
<mode>9</mode>
<mode>10</mode>
<mode>11</mode>
<mode>12</mode>
i need to separate it on parts (for ex. on 4):
xslt:
<xsl:variable name="vNodes" select="mode"/>
<xsl:variable name="vNumParts" select="4"/>
<xsl:variable name="vNumCols" select="ceiling(count($vNodes) div $vNumParts)"/>
<xsl:for-each select="$vNodes[position() mod $vNumCols = 1]">
<xsl:variable name="vCurPos" select="(position()-1)*$vNumCols +1"/>
<ul>
<xsl:for-each select="$vNodes[position() >= $vCurPos and not(position() > $vCurPos + $vNumCols -1)]">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:for-each>
this code is written by Dimitre Novatchev - great coder))
but for the number of nodes less then number of parts (for ex. i have 2 modes) this code does not work - it outputs nothing.
How it upgrade for that case (without choose construction)?

Although the problem is incorrectly defined if the number of nodes is smaller than the number of parts, here is a transformation that I guess produces the output the OP most probably wants (Why didn't he just specify this behavior???):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/t">
<t>
<xsl:variable name="vNodes" select="mode"/>
<xsl:variable name="vNumParts" select="4"/>
<xsl:variable name="vNumCols" select="ceiling(count($vNodes) div $vNumParts)"/>
<xsl:variable name="vrealNum">
<xsl:choose>
<xsl:when test="$vNumCols >1">
<xsl:value-of select="$vNumCols"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="count($vNodes)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:for-each select="$vNodes[position() mod $vrealNum = 1]">
<xsl:variable name="vCurPos" select="(position()-1)*$vrealNum +1"/>
<ul>
<xsl:for-each select="$vNodes[position() >= $vCurPos and not(position() > $vCurPos + $vrealNum -1)]">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:for-each>
</t>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document (he can't even provide a well-formed XML document!):
<t>
<mode>1</mode>
<mode>2</mode>
</t>
the output is what I guess the OP wanted...
<t>
<ul>
<li>1</li>
<li>2</li>
</ul>
</t>

but for the number of nodes less then
number of parts (for ex. i have 2
modes) this code does not work - it
outputs nothing.
Actually, the code works correctly.
Whenever the number of parts is greater than the number of nodes, there is no solution to the problem: "Divide 2 nodes into 4 parts in equal number" -- the only solution is that each part contains 0 nodes.
Now you are solving a new, different problem and no wonder the solution to a different problem does not work for this new problem.
The way to go is to formulate the new problem correctly and to ask it. Then many people will be glad to answer.

Related

XSLT List attributes in the order they appear in the xml file

I have a large number of xml files with a structure similar to the following, although they are far larger:
<?xml version="1.0" encoding="UTF-8"?>
<a a1="3.0" a2="ABC">
<b b1="P1" b2="123">first
</b>
<b b1="P2" b2="456" b3="xyz">second
</b>
</a>
I want to get the following output:
1|1|b1
1|2|b2
2|1|b1
2|2|b2
2|3|b3
where:
Field 1 is the sequence number for nodes /a/b
Field 2 is the sequence number of the attribute as it appears in the xml file
Field 3 is the attribute name (not value)
I don't quite know how to calculate field 2 correctly.
I've prepared the following xslt file:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:for-each select="a/b/#*">
<xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text>
<!-- TODO: This is not correct -->
<xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
but when I run the following command:
xsltproc a.xslt a.xml > a.csv
I get an incorrect output, as field 2 does not represent the attribute sequence number:
1|1|b1
1|1|b2
2|1|b1
2|1|b2
2|1|b3
Do you have any suggestions on how to get the correct output please?
Please notice that the answers provided in XSLT to order attributes do not provide a solution to this problem.
The order of attributes is irrelevant in XML. For instance, <a a1="3.0" a2="ABC"> and <a a1="3.0" a2="ABC"> are equivalent.
However this specific question is part of a larger application where it is essential to establish the order in which attributes appear in given xml files (and not in xml files that are equivalent to them).
Although, as kjhughes says in comments, attribute order is insignificant. However, you can still select them, and use the position() element to get the numbers you are after (You just can't be sure the order they are output will be the order they appear in the XML, although generally this will be the case).
Try this XSLT. Do note the nested use of xsl:for-each to select only b elements first, to get their position, before getting the attributes, which then have their own separate position.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select="a/b">
<xsl:variable name="bPosition" select="position()"/>
<xsl:for-each select="#*">
<xsl:value-of select="$bPosition"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="position()"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You could use the position() of the items in the sequence of attributes that you are iterating over and combine with logic for the position of its parent element.
<xsl:template match="/">
<xsl:for-each select="a/b/#*">
<xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text>
<!-- TODO: This is not correct -->
<xsl:value-of select="position() -
(if (count(../preceding-sibling::*)) then count(../preceding-sibling::*)+1 else 0)"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
Which produces the following output:
1|1|b1
1|2|b2
2|1|b1
2|2|b2
2|3|b3

XSLT how to partition a sequence into sub-sequences

I have an XML file that contains a sequence of strings
<ModsConfigData>
<buildNumber>1393</buildNumber>
<activeMods>
<li>Core</li>
<li>ZhentarFix</li>
<li>WorldPawnGC</li>
<li>HugsLib</li>
<!-- more entries -->
</activeMods>
</ModsConfigData>
And I wish to partition the long list of elements into subsets of a given length -- in my case, lengths of five. The purpose is to then work with the subsets to generate a table structure that is five across and however many rows it takes to consume the full list.
The output I'm wanting to achieve is a subset of BBCode like so:
[table]
[tr]
[td]Core[/td][td]ZhentarFix[/td][td]WorldPawnGC[/td] ...
[/tr]
[tr]
[td] ... (next set of five entries) [/td]
[/tr]
[/table]
It looks like a set of queries to me. There are probably ways to do a for-each loop, and position() mod 5 or position div 5 might be a way to get there. I'm not familiar enough with the grouping instruction to even guess if can be of assistance.
This is what I'm currently using, any improvements welcome.
<xsl:variable name="modsConfigFile" select="document('ModsConfig.xml')"/>
<xsl:variable name="groupSize" select="5"/>
<xsl:template match="/ModsConfigData/activeMods">
<xsl:text>[hr][table]</xsl:text>
<xsl:value-of>
<xsl:apply-templates
select="li[position() mod $groupSize = 1]"/>
</xsl:value-of>
<xsl:text>[/table]</xsl:text>
</xsl:template>
<xsl:template match="li">
<xsl:text>[tr]</xsl:text>
<xsl:apply-templates select=
".|following-sibling::li[position() < $groupSize]"/>
<xsl:text>[/tr]</xsl:text>
<br/>
</xsl:template>
<xsl:template match="li" >
<xsl:text>[td]</xsl:text>
<xsl:value-of select="."/>
<xsl:text>[/td]</xsl:text>
</xsl:template>
</xsl:stylesheet>
This is what I'm currently using,
You can't be "using" that, because it doesn't work. It doesn't work because (among other things):
you cannot place xsl:apply-templates within xsl-value-of; and
you have two templates matching li, with no distinction between them.
To do it the way you have started, you need to do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:variable name="groupSize" select="5"/>
<xsl:template match="/ModsConfigData">
<xsl:text>[table]
</xsl:text>
<xsl:apply-templates select="activeMods/li[position() mod $groupSize = 1]"/>
<xsl:text>[/table]</xsl:text>
</xsl:template>
<xsl:template match="li">
<xsl:text> [tr]
</xsl:text>
<xsl:apply-templates select= ". | following-sibling::li[position() < $groupSize]" mode="cell"/>
<xsl:text>
[/tr]
</xsl:text>
</xsl:template>
<xsl:template match="li" mode="cell">
<xsl:text>[td]</xsl:text>
<xsl:value-of select="."/>
<xsl:text>[/td]</xsl:text>
</xsl:template>
</xsl:stylesheet>
When the above is applied to the following test input:
XML
<ModsConfigData>
<buildNumber>1393</buildNumber>
<activeMods>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
<li>F</li>
<li>G</li>
<li>H</li>
<li>I</li>
<li>J</li>
<li>K</li>
<li>L</li>
</activeMods>
</ModsConfigData>
the result will be:
[table]
[tr]
[td]A[/td][td]B[/td][td]C[/td][td]D[/td][td]E[/td]
[/tr]
[tr]
[td]F[/td][td]G[/td][td]H[/td][td]I[/td][td]J[/td]
[/tr]
[tr]
[td]K[/td][td]L[/td]
[/tr]
[/table]

Get the maximum starting substring of the text node, such that the last word in it isn't truncated, and its length doesn't exceed a given limit

How do I select only n number of comments element using xsl. I am able to select n number of characters but that snaps in the middle of a word and makes it look ugly.
I have been able to get a count of total number of words in 'comments' node but not sure how to only show like 15 words if there are like a total of 20 there.
<div class="NewsDescription">
<xsl:value-of select="string-length(translate(normalize-space(#Comments),'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',''))+1" />
<xsl:value-of select="substring(#Comments,0,120)"/>
<xsl:if test="string-length(#Comments) > 120">…</xsl:if>
Read More
</div>
Actual xml is like this. I am trying to rollup story pieces in "Related Stories' manner on the right side of a dynamic page.
<news>
<title>Story title</title>
<comments> story gist here..a couple of sentences.</comments>
<content> actualy story </content>
Please help. I found this resources by Marc andersen but the xsl is too complex for me filter through and make use of myself..
http://sympmarc.com/2010/07/13/displaying-the-first-n-words-of-announcement-bodies-with-xsl-in-a-dvwp/
Some help from the XSL gurus will be appreciated..
Here is a non-recursive, pure XSLT 1.0 solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vUpper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="vLower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="vDigits" select="'0123456789'"/>
<xsl:variable name="vAlhanum" select="concat($vLower, $vUpper, $vDigits)"/>
<xsl:template match="comments">
<xsl:param name="pText" select="."/>
<xsl:choose>
<xsl:when test="not(string-length($pText) > 120)">
<xsl:value-of select="$pText"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vTruncated" select="substring($pText, 1, 121)"/>
<xsl:variable name="vPunct"
select="translate($vTruncated, $vAlhanum, '')"/>
<xsl:for-each select=
"(document('')//node() | document('')//#* | document('')//namespace::*)
[not(position() > 121)]">
<xsl:variable name="vPos" select="122 - position()"/>
<xsl:variable name="vRemaining" select="substring($vTruncated, $vPos+1)"/>
<xsl:if test=
"contains($vPunct, substring($vTruncated, $vPos, 1))
and
contains($vAlhanum, substring($vTruncated, $vPos -1, 1))
and
string-length(translate($vRemaining, $vPunct, ''))
= string-length($vRemaining)
">
<xsl:value-of select="substring($vTruncated, 1, $vPos -1)"/>
</xsl:if>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<news>
<title>Story title</title>
<comments> story gist here..a couple of sentences. Many more sentences ...
even some more text with lots of meaning and sense aaand a lot of humor. </comments>
<content> actualy story </content>
</news>
the wanted, correct result is produced:
story gist here..a couple of sentences. Many more sentences ...
even some more text with lots of meaning and sense

How can this XSL be refactored to take advantage of apply-templates?

Taking this question about beautiful XSL but getting more specific, how should I refactor this XSL to take advantage of apply-templates and/or keys.
I tend to "over use" for-each elements to control the context of the source and I can imagine that apply-templates can help. Despite much Google-ing, I still don't understand how to control context within the multiple templates.
In the below example, how can the repetitive XPath segments be reduced by refactoring?
<xsl:template match="/">
<xsl:element name="Body">
<xsl:element name="Person">
<xsl:if test="/source/dbSrc/srv/v[#name='name']/text()='false'">
<xsl:element name="PhoneNumber" />
<xsl:element name="Zip">
<xsl:value-of
select="/source/req[1]/personal-info/address-info/zip-code" />
</xsl:element>
</xsl:if>
<xsl:if test="/source/dbSrc/srv/v[#name='name']/text()='true'">
<xsl:element name="PhoneNumber" />
<xsl:element name="Zip">
<xsl:value-of select="/source/req[3]/personal-info/address-info/zip-code" />
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
One initial way of refactoring the given code would be the following:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Body>
<Person>
<PhoneNumber/>
<Zip>
<xsl:apply-templates select=
"/*/dbSrc/srv/v[#name='name']"/>
</Zip>
</Person>
</Body>
</xsl:template>
<xsl:template match="v[#name='name' and .='true']">
<xsl:value-of select=
"/*/req[3]/personal-info/address-info/zip-code"/>
</xsl:template>
<xsl:template match="v[#name='name' and .='false']">
<xsl:value-of select=
"/*/req[1]/personal-info/address-info/zip-code"/>
</xsl:template>
</xsl:stylesheet>
Do note: The refactored code doesn't contain any conditional xslt instructions.
Further refactoring can let us get rid of the last too templates, because in this case additional templates aren't actually needed -- the code only creates a single element and depends on a single condition:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vCond" select=
"/*/dbSrc/srv/v[#name='name']/text()='true'"/>
<xsl:variable name="vInd" select=
"3*$vCond + 1*not($vCond)"/>
<xsl:template match="/">
<Body>
<Person>
<PhoneNumber/>
<Zip>
<xsl:value-of select=
"/*/req[position()=$vInd]
/personal-info/address-info/zip-code"/>
</Zip>
</Person>
</Body>
</xsl:template>
</xsl:stylesheet>
Note: Here we assume that /*/dbSrc/srv/v[#name='name']/text() can have only two possible values: 'true' or 'false'
In XSLT 2.0 I would write this as:
<xsl:template match="/">
<Body>
<Person>
<PhoneNumber/>
<Zip>
<xsl:variable name="index" as="xs:integer"
select="if (/source/dbSrc/srv/v[#name='name']='true') then 3 else 1"/>
<xsl:value-of select="/source/req[$index]/personal-info/address-info/zip-code"/>
</Zip>
</Person>
</Body>
</xsl:template>
With 1.0, the xsl:variable becomes a bit more complicated but otherwise it's the same.
Note the use of literal result elements and variables to reduce the size of the code; also the avoidance of "/text()", which is nearly always bad practice.
There's very little mileage in using template rules here because you're using so little of the input data, and because you seem to know exactly where to find it. Template rules would come into their own if you wanted to be less rigid about knowing exactly where you are looking in the source: they help to make code more resilient to variability and change in the input. But without seeing the source and knowing more of the background, we can't tell you where that flexibility is needed. The hard-coding of the indexes "1" and "3" looks like a danger signal to me, but only you can judge that.

xsl get array of elements

Hi
I need get array of elements (before "-" if exist) by xsl.
xml is
<Cars>
<Car Trunck="511"/>
<Car Trunck="483-20"/>
<Car Trunck="745"/>
</Cars>
xsl is
<xsl:variable name="testarr">
<xsl:for-each select="//Cars//Car/#Trunck">
<xsl:value-of select="number(substring(.,1,3))" />
</xsl:for-each>
</xsl:variable>
(i suppose that all numbers is three-digit number, if someone knows a solution for all conditions will be glad to hear the proposal)
if i do this
i get all numbers in one line: 511483745
and i need get them in array
because i also need get the max value
thanks
Hi I need get array of elements
(before "-" if exist) [...] i need get
them in array because i also need get
the max value
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="/Cars/Car/#Trunck">
<xsl:sort select="concat(substring-before(.,'-'),
substring(., 1 div not(contains(.,'-'))))"
data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:value-of
select="concat(substring-before(.,'-'),
substring(.,1 div not(contains(.,'-'))))"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
745
XPath 2.0 one line:
max(/Cars/Car/#Trunck/number(replace(.,'-.*','')))
You could use the substring-before and substring-after functions: See the excellent ZVON tutorial
http://zvon.org/xxl/XSLTreference/Output/function_substring-after.html
In your example you are only extracting the values (which are strings) which get concatenated. Perhaps you need to wrap the result in your own element
<xsl:for-each select="//Cars//Car/#Trunck">
<truck>
<xsl:value-of select="number(substring(.,1,3))" />
</truck>
</xsl:for-each>
While you have two good answers (especially that by #Alejandro), here's one from me that I think is even better:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pTopNums" select="2"/>
<xsl:template match="/*">
<xsl:apply-templates select="*">
<xsl:sort data-type="number" order="descending"
select="substring-before(concat(#Trunck,'-'),'-')"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Car">
<xsl:if test="not(position() > $pTopNums)">
<xsl:value-of select=
"substring-before(concat(#Trunck,'-'),'-')"/>
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (the originally provided one, slightly changed to be more challenging):
<Cars>
<Car Trunck="483-20"/>
<Car Trunck="311"/>
<Car Trunck="745"/>
</Cars>
produces the wanted, correct result (the top two numbers that are derived from #Trunck as specified in the question):
745
483