How can we create a node set from values....
I have n numbers 1,2,3.......n.
I want to create a node set
<MYNMUMS>
<MYNUM>1</MYNUM>
<MYNUM>2</MYNUM>
<MYNUM>3</MYNUM>
<MYNUM>4</MYNUM>
....
<MYNUM>N</MYNUM>
</MYNMUMS>
As easy as that:
XSLT 1.0 solution:
This transformation:
<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="/">
<MyNums>
<xsl:call-template name="generateNumNodes">
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="10"/>
</xsl:call-template>
</MyNums>
</xsl:template>
<xsl:template name="generateNumNodes">
<xsl:param name="pStart"/>
<xsl:param name="pEnd"/>
<xsl:if test="$pEnd >= $pStart">
<xsl:variable name="vNumNodes"
select="$pStart -$pEnd+1"/>
<xsl:choose>
<xsl:when test="$vNumNodes = 1">
<MyNum><xsl:value-of select="$pStart"/></MyNum>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vHalf" select=
"floor(($pStart+$pEnd) div 2)"/>
<xsl:call-template name="generateNumNodes">
<xsl:with-param name="pStart" select="$pStart"/>
<xsl:with-param name="pEnd" select="$vHalf"/>
</xsl:call-template>
<xsl:call-template name="generateNumNodes">
<xsl:with-param name="pStart" select="$vHalf+1"/>
<xsl:with-param name="pEnd" select="$pEnd"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used), produces the desired output:
<MyNums>
<MyNum>1</MyNum>
<MyNum>2</MyNum>
<MyNum>3</MyNum>
<MyNum>4</MyNum>
<MyNum>5</MyNum>
<MyNum>6</MyNum>
<MyNum>7</MyNum>
<MyNum>8</MyNum>
<MyNum>9</MyNum>
<MyNum>10</MyNum>
</MyNums>
Do note the following:
The template generateNumNodes calls itself recursively.
This recursion is both time ( O(N) ), and space ( O(log2(N)) ) efficient and practically does overflow the stack -- no SO here!
The above feature is achieved by implementing the recursion in a DVC (DiVide and Conquer) style.
Unlike tail-recursion it will be successfully executed on any compliant XSLT processor.
The maximum recursion depth needed to generate 1000000 (one million numbers) is just 19.
XSLT 2.0 solution:
Even more elementary, no recursion, just using the XPath 2.0 to operator:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<MyNums>
<xsl:for-each select="1 to 10">
<MyNums>
<xsl:sequence select="."/>
</MyNums>
</xsl:for-each>
</MyNums>
</xsl:template>
</xsl:stylesheet>
XSLT is a transformation language. It is usually used when you already have some data in the form of an XML document, that you wish to transform into a different document (that may or may not be in XML format).
For the task of starting with "raw" data and generating an XML representation, XSLT is not well-suited.
I suggest you look into different langauges to solve this.
Related
I am writing a function to split string without breaking word. For that i have return function which is doing recursive call to itself. It's giving me below error
When i am calling function with function prefix then getting error as "splitLine function under namespace http://whatever is not defined.".
When i am calling function without function prefix then getting Parse error in the function.
When i Try to use
<xsl:value-of select="fn:splitLine($inString,$length - 1)"/>
in otherwise condition of function get function not defined error.
When i try to use without function prefix :
<xsl:value-of select="fn:splitLine($inString,$length - 1)"/>
I get parse error in Function.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://whatever">
<xsl:output omit-xml-declaration="no" />
<xsl:output method="xml" />
<xsl:function name="fn:splitLine" as="xs:string">
<xsl:param name="inString" as="xs:string"/>
<xsl:param name="length" as="xs:numeric"/>
<xsl:variable name="delimiters"> ,."!?()</xsl:variable>
<xsl:choose>
<xsl:when test="0.0 >= $length ">
<xsl:value-of select="$inString"/>
</xsl:when>
<xsl:when test="$length >= string-length($inString)">
<xsl:value-of select="$inString"/>
</xsl:when>
<xsl:when test="contains($delimiters,substring($inString,$length + 1,1))">
<xsl:value-of select="substring($inString,1,$length)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="fn:splitLine($inString,$length - 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="/">
<xsl:value-of select="fn:splitLine('3 ZHANLANGUAN RD XICHENG, , , BEIJING, , CN, ',35)"/>
</xsl:template>
</xsl:stylesheet>
I am expecting output to be "3 ZHANLANGUAN RD XICHENG, , ," without breaking word.
This must be a quirk of your XSLT processor.
I checked your XSLT-2.0 file with Saxon-HE 9.9.1.4J and it works as expected after adding the following namespace to the xsl:stylesheet element:
xmlns:xs="http://www.w3.org/2001/XMLSchema"
So, I guess, that your XSLT processor returned a different namespace as missing, because after adding the above one, all commands could be processed and the result is as expected.
Below is the result for the processor and Version : Processor : Oracle Corporation. : Version :2 –
Right. Oracle (before they acquired Sun) were well down the path to developing an XSLT 2.0 processor as part of the Oracle XDK (XML development kit). But they never really finished it, and it never achieved a high level of conformance with the spec.
It looks to me as if you have encountered a restriction in that processor - and I'm sure there are many others. I don't think many people are using the Oracle XDK these days, and I think it has had very little development since Oracle acquired Sun (and hence Java).
#Tim Thanks for the suggestion. Instead of using function , i Used named template and it worked perfectly fine :
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://whatever">
<xsl:output omit-xml-declaration="no" />
<xsl:output method="xml" />
<xsl:template name="splitLine">
<xsl:param name="inString"/>
<xsl:param name="length"/>
<xsl:variable name="delimiters"> ,."!?()</xsl:variable>
<xsl:choose>
<xsl:when test="0.0 >= $length ">
<xsl:value-of select="$inString"/>
</xsl:when>
<xsl:when test="$length >= string-length($inString)">
<xsl:value-of select="$inString"/>
</xsl:when>
<xsl:when test="contains($delimiters,substring($inString,$length + 1,1))">
<xsl:value-of select="substring($inString,1,$length)"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="splitLine">
<xsl:with-param name="inString" select="$inString" />
<xsl:with-param name="length" select="$length - 1" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="splitLine">
<xsl:with-param name="inString" select="'fullnameofsample supplier in the block" />
<xsl:with-param name="length" select="35" />
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
I'm generating a CSV file from an XML using XSL. The XML contains Main elements with child elements Tags, which in turn contain varying amounts of child elements Tag. A part of the XML looks for example like this:
<Main>
<Tags>
<Tag>tag1</Tag>
<Tag>tag2</Tag>
<Tag>tag3</Tag>
</Tags>
</Main>
<Main>
<Tags>
<Tag>tag1</Tag>
<Tag>tag2</Tag>
<Tag>tag3</Tag>
<Tag>tag4</Tag>
<Tag>tag5</Tag>
<Tag>tag6</Tag>
</Tags>
</Main>
In the XSL I have a for each loop that goes through all my Main elements of my XML file. I want to print the values for all the Tag elements. I do this in another for-each loop which is inside the major loop. However, I always want to iterate 10 times, regardless of the amount of Tag elements. I want to print some text in each of the remaining iterations when I have exceeded the amount of printable Tag.
This is the output I'm after:
tag1,tag2,tag3,1,1,1,1,1,1,1,
tag1,tag2,tag3,tag4,tag5,tag6,1,1,1,1,
After the Tag for each loop, I'm calling a template, providing a variable with the amount of Tag in Tags. I then want the template to call itself recursively until it has done the varying amount of remaining iterations for the Tag elements of the current Main element. The amount of Tag elements changes with each Main iteration, which I suspect is a problem in my current solution (which causes my transformation software, Notepad++ with XML Tools, to crash):
<xsl:template match="/">
<xsl:for-each select="Main">
<xsl:for-each select="Tags/Tag">
<xsl:value-of select="Tag"/>
<xsl:text>,</xsl:text>
</xsl:for-each>
<xsl:call-template name="repeatable">
<xsl:with-param name="tagamount" select="count(Tags/*)"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="repeatable">
<xsl:param name="tagamount"/>
<xsl:param name="index" select="0" />
<xsl:text>1,</xsl:text>
<xsl:if test="not($index = 10-$tagamount)">
<xsl:call-template name="repeatable">
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Does anyone have any idea if it's possible to do this type of varying iteration, or am I out of luck?
Edit:
I managed to solve it. The problem was I had forgotten to pass on the variable tagamount with each recursive call. See my solution further below.
I couldn't wrap my head around your code. How about something simpler?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="sep" select="','"/>
<xsl:variable name="LF" select="'
'"/>
<xsl:variable name="filler" select="'1,2,3,4,5,6,7,8,9,10'"/>
<xsl:template match="/">
<xsl:for-each select="rt/Main/Tags">
<xsl:for-each select="Tag">
<xsl:value-of select="concat(., $sep)"/>
</xsl:for-each>
<xsl:value-of select="substring($filler, 2*count(Tag)+1)"/>
<xsl:value-of select="$LF"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note:
1. Your XML is missing a root element: I am using "rt" as a placeholder.
2. For testing purposes, I have changed "1,1,1,..." into "1,2.3...".
Here is one way to do it.
This XSLT stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<!-- Sets the number of iterations per Tags element. -->
<xsl:variable name="maximum" select="10"/>
<!-- Matches all the Tags elements and calls a recursive template, intializing the count to 1. -->
<xsl:template match="//Tags">
<xsl:call-template name="output-tags">
<xsl:with-param name="count" select="1"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- A recursive template that will repeat itself until its count reaches the maximum value.
If the count is equal to or less then the number of Tag elements inside the current Tags
element, then find the Tag element in the count position and print its value. Otherwise,
print 1. -->
<xsl:template name="output-tags">
<xsl:param name="count"/>
<xsl:if test="$count <= $maximum">
<xsl:choose>
<xsl:when test="$count <= count(Tag)">
<xsl:value-of select="Tag[count(preceding-sibling::Tag) = $count - 1]"/>
<xsl:text>,</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>1,</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="output-tags">
<xsl:with-param name="count" select="$count + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
produces the following output when applied to your example input XML:
tag1,tag2,tag3,1,1,1,1,1,1,1,
tag1,tag2,tag3,tag4,tag5,tag6,1,1,1,1,
Thanks for the answers!
I managed to solve it right after I posted. The problem was I had forgotten to send the tagamount variable with the recursive call. After adding it, it works. The repeatable template then looks like this:
<xsl:param name="tagamount"/>
<xsl:param name="index" select="0" />
<xsl:text>1,</xsl:text>
<xsl:if test="not($index = 10-$tagamount)">
<xsl:call-template name="repeatable">
<xsl:with-param name="tagamount" select="$tagamount"/> <-----------
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
I'd like to write the alphabet with a link for each letter. So I used templates but I don't how to make this letter I tried that but I had a normal mistake : (A decimal representation must imediately follow the &# in a character reference).
<xsl:template name="alphabet">
<xsl:param name="iLetter"/>
<xsl:if test="$iLetter < 91">
<a><xsl:attribute name="href">req.html?X_letter=&#<xsl:value-of select="$iLetter"/>;</xsl:attribute>&#<xsl:value-of select="$iLetter"/>;</xsl:attribute></a>
<xsl:call-template name="alphabet">
<xsl:with-param name="iLetter" select="number($iLetter)+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
And I call this template ilke that:
<xsl:call-template name="alphabet">
<xsl:with-param name="iLetter" select="number(65)"/>
</xsl:call-template>
So, I'd like to obtain this result:
A B C D ..... X Y Z without ... of course :)
The currently accepted answer is incorrect, because it doesn't produce correctly the text child of any a element.
Here is a correct 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:variable name="vAlpha" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template match="/">
<xsl:call-template name="alphabet"/>
</xsl:template>
<xsl:template name="alphabet">
<xsl:param name="pCode" select="65"/>
<xsl:if test="not($pCode > 90)">
<xsl:variable name="vChar" select=
"substring($vAlpha, $pCode - 64, 1)"/>
<a href="req.html?X_letter={$vChar}">
<xsl:value-of select="$vChar"/>
</a>
<xsl:call-template name="alphabet">
<xsl:with-param name="pCode" select="$pCode+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used), the wanted, correct result is produced:
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="xs my"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:output omit-xml-declaration="yes" method="xhtml" indent="yes"/>
<xsl:param name="pStart" as="xs:integer" select="65"/>
<xsl:param name="pEnd" as="xs:integer" select="90"/>
<xsl:variable name="vCodes" as="xs:integer*" select=
"for $i in $pStart to $pEnd
return $i
"/>
<xsl:template match="/">
<html>
<xsl:sequence select="my:alphabet()"/>
</html>
</xsl:template>
<xsl:function name="my:alphabet" as="element()*">
<xsl:for-each select="$vCodes">
<xsl:variable name="vChar" select="codepoints-to-string(.)"/>
<a href="req.html?X_letter={$vChar}">
<xsl:sequence select="$vChar"/>
</a>
</xsl:for-each>
</xsl:function>
</xsl:stylesheet>
As Martin suggests it would be better to avoid using disable-output-escaping. You don't need it either, if you are would be satisfied with a plain ascii character instead of the numerical character reference. If so, you can use substring and a alphabet lookup-string like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="alphabet" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template name="alphabet">
<xsl:param name="iLetter" select="65"/>
<xsl:if test="$iLetter < 91">
<a>
<xsl:attribute name="href">req.html?X_letter=<xsl:value-of select="substring($alphabet, $iLetter - 64, 1)"/></xsl:attribute>
<xsl:value-of select="substring($alphabet, $iLetter - 64, 1)"/>
</a>
<xsl:call-template name="alphabet">
<xsl:with-param name="iLetter" select="number($iLetter)+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="alphabet"/>
</xsl:template>
</xsl:stylesheet>
Cheers!
Inside of the a element content you could disable output escaping as in
<a href="req.html?X_letter={$iLetter}">
<xsl:value-of select="concat('&#', $iLetter, ';')" disable-output-escaping="yes"/>
</a>
That approach does not work within attribute nodes however so I left that part to pass the character code, not the character.
Also be warned that disable-output-escaping is an optional serialization feature that is not supported with all XSLT processors, for instance Firefox/Mozilla's built-in XSLT processor does not serialize the result tree but simply renders it so there the approach is not going to work.
XSLT 2.0 has the function codepoints-to-string(). With many XSLT 1.0 processors it should be easy enough to implement the same function as an extension function, though it will make your code dependent on that processor.
I'm trying to think functional, in XSLT terms, as much as possible, but in this case, I really don't see how to do it without tweaking. I have roughly this data structure:
<transactions>
<trx>
<text>abc</text>
<text>def</text>
<detail>
<text>xxx</text>
<text>yyy</text>
<text>zzz</text>
</detail>
</trx>
</transactions>
Which I roughly want to flatten into this form
<row>abc</row>
<row>def</row>
<row>xxx</row>
<row>yyy</row>
<row>zzz</row>
But the tricky thing is: I want to create chunks of 40 text-rows and transactions mustn't be split across chunks. I.e. if my current chunk already has 38 rows, the above transaction would have to go into the next chunk. The current chunk would need to be filled with two empty rows to complete the 40:
<row/>
<row/>
In imperative/procedural programming, it's very easy. Just create a global iterator variable counting to multiples of 40, and insert empty rows if needed (I have provided an answer showing how to tweak XSLT/Xalan to allow for such variables). But how to do it with XSLT? N.B: I'm afraid recursion is not possible considering the size of data I'm processing... But maybe I'm wrong on that
I. Here is an XSLT 1.0 solution (the XSLT 2.0 solution is much easier):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pChunkSize" select="8"/>
<xsl:param name="vChunkSize" select="$pChunkSize+1"/>
<xsl:variable name="vSheet" select="document('')"/>
<xsl:variable name="vrtfEmptyChunk">
<xsl:for-each select=
"($vSheet//node())[not(position() > $pChunkSize)]">
<row/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vEmptyChunk" select=
"ext:node-set($vrtfEmptyChunk)/*"/>
<xsl:variable name="vrtfDummy">
<delete/>
</xsl:variable>
<xsl:variable name="vDummy" select="ext:node-set($vrtfDummy)/*"/>
<xsl:template match="/*">
<chunks>
<xsl:call-template name="fillChunks">
<xsl:with-param name="pNodes" select="trx"/>
<xsl:with-param name="pCurChunk" select="$vDummy"/>
</xsl:call-template>
</chunks>
</xsl:template>
<xsl:template name="fillChunks">
<xsl:param name="pNodes"/>
<xsl:param name="pCurChunk"/>
<xsl:choose>
<xsl:when test="not($pNodes)">
<chunk>
<xsl:apply-templates mode="rename" select="$pCurChunk[self::text]"/>
<xsl:copy-of select=
"$vEmptyChunk[not(position() > $vChunkSize - count($pCurChunk))]"/>
</chunk>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vAvailable" select=
"$vChunkSize - count($pCurChunk)"/>
<xsl:variable name="vcurNode" select="$pNodes[1]"/>
<xsl:variable name="vTrans" select="$vcurNode//text"/>
<xsl:variable name="vNumNewNodes" select="count($vTrans)"/>
<xsl:choose>
<xsl:when test="not($vNumNewNodes > $vAvailable)">
<xsl:variable name="vNewChunk"
select="$pCurChunk | $vTrans"/>
<xsl:call-template name="fillChunks">
<xsl:with-param name="pNodes" select="$pNodes[position() > 1]"/>
<xsl:with-param name="pCurChunk" select="$vNewChunk"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<chunk>
<xsl:apply-templates mode="rename" select="$pCurChunk[self::text]"/>
<xsl:copy-of select=
"$vEmptyChunk[not(position() > $vAvailable)]"/>
</chunk>
<xsl:call-template name="fillChunks">
<xsl:with-param name="pNodes" select="$pNodes"/>
<xsl:with-param name="pCurChunk" select="$vDummy"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="text" mode="rename">
<row>
<xsl:value-of select="."/>
</row>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (based on the provided one, but with three trxelements):
<transactions>
<trx>
<text>abc</text>
<text>def</text>
<detail>
<text>xxx</text>
<text>yyy</text>
<text>zzz</text>
</detail>
</trx>
<trx>
<text>abc2</text>
<text>def2</text>
</trx>
<trx>
<text>abc3</text>
<text>def3</text>
<detail>
<text>xxx3</text>
<text>yyy3</text>
<text>zzz3</text>
</detail>
</trx>
</transactions>
the wanted, correct result (two chunks with size 8) is produced:
<chunks>
<chunk>
<row>abc</row>
<row>def</row>
<row>xxx</row>
<row>yyy</row>
<row>zzz</row>
<row>abc2</row>
<row>def2</row>
<row/>
</chunk>
<chunk>
<row>abc3</row>
<row>def3</row>
<row>xxx3</row>
<row>yyy3</row>
<row>zzz3</row>
<row/>
<row/>
<row/>
</chunk>
</chunks>
Do note:
The first two transactions' text elements total number is 7 and they fit in one 8-place chunk.
The third transaction has 5 text elements and doesn't fit in the remaining space of the first chunk -- it is put in a new chunk.
II. XSLT 2.0 Solution (using FXSL)
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:dvc-foldl-func="dvc-foldl-func"
exclude-result-prefixes="f dvc-foldl-func"
>
<xsl:import href="../f/func-dvc-foldl.xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pChunkSize" select="8"/>
<dvc-foldl-func:dvc-foldl-func/>
<xsl:variable name="vPadding">
<row/>
</xsl:variable>
<xsl:variable name="vFoldlFun" select="document('')/*/dvc-foldl-func:*[1]"/>
<xsl:template match="/">
<xsl:variable name="vpaddingChunk" select=
"for $i in 1 to $pChunkSize
return ' '
"/>
<xsl:variable name="vfoldlResult" select=
"f:foldl($vFoldlFun, (), /*/trx),
$vpaddingChunk
"/>
<xsl:variable name="vresultCount"
select="count($vfoldlResult)"/>
<xsl:variable name="vFinalResult"
select="subsequence($vfoldlResult, 1,
$vresultCount - $vresultCount mod $pChunkSize
)"/>
<result>
<xsl:for-each select="$vFinalResult">
<row>
<xsl:value-of select="."/>
</row>
</xsl:for-each>
<xsl:text>
</xsl:text>
</result>
</xsl:template>
<xsl:template match="dvc-foldl-func:*" mode="f:FXSL">
<xsl:param name="arg1"/>
<xsl:param name="arg2"/>
<xsl:variable name="vCurCount" select="count($arg1)"/>
<xsl:variable name="vNewCount" select="count($arg2//text)"/>
<xsl:variable name="vAvailable" select=
"$pChunkSize - $vCurCount mod $pChunkSize"/>
<xsl:choose>
<xsl:when test="$vNewCount le $vAvailable">
<xsl:sequence select="$arg1, $arg2//text"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$arg1"/>
<xsl:for-each select="1 to $vAvailable">
<xsl:sequence select="$vPadding/*"/>
</xsl:for-each>
<xsl:sequence select="$arg2//text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct, wanted result is produced:
<result>
<row>abc</row>
<row>def</row>
<row>xxx</row>
<row>yyy</row>
<row>zzz</row>
<row>abc2</row>
<row>def2</row>
<row/>
<row>abc3</row>
<row>def3</row>
<row>xxx3</row>
<row>yyy3</row>
<row>zzz3</row>
<row> </row>
<row> </row>
<row> </row>
</result>
Do note:
The use of the f:foldl() function.
A special DVC (Divide and Conquer) variant of f:foldl() so that recursion stack overflow is avoided for all practical purposes -- for example, the maximum recursion stack depth for 1000000 (1M) trx elements is just 19.
Build the complete XML data structure as you need in Java. Then, do the simple iteration in XSL over prepared XML.
You might save a lot of effort and provide a maintainable solution.
As promised a simplified example answer showing how Xalan can be tweaked to allow for incrementing such global iterators:
<xsl:stylesheet version="1.0" xmlns:f="xalan://com.example.Functions">
<!-- the global row counter variable -->
<xsl:variable name="row" select="0"/>
<xsl:template match="trx">
<!-- wherever needed, the $row variable can be globally incremented -->
<xsl:variable name="iteration" value="f:increment('row')"/>
<!-- based upon this variable, calculations can be made -->
<xsl:variable name="remaining-rows-in-chunk"
value="40 - (($iteration - 1) mod 40) "/>
<xsl:if test="count(.//text) > $remaining-rows-in-chunk">
<xsl:call-template name="empty-row">
<xsl:with-param name="rows" select="$remaining-rows-in-chunk"/>
</xsl:call-template>
</xsl:if>
<!-- process transaction now, that previous chunk has been filled [...] -->
</xsl:template>
<xsl:template name="empty-row">
<xsl:param name="rows"/>
<xsl:if test="$rows > 0">
<row/>
<xsl:variable name="dummy" select="f:increment('row')"/>
<xsl:call-template name="empty-row">
<xsl:with-param name="rows" select="$rows - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
And the contents of com.example.Functions:
public class Functions {
public static String increment(ExpressionContext context, String nodeName) {
XNumber n = null;
try {
// Access the $row variable
n = ((XNumber) context.getVariableOrParam(new QName(nodeName)));
// Make it "mutable" using this tweak. I feel horrible about
// doing this, though ;-)
Field m_val = XNumber.class.getDeclaredField("m_val");
m_val.setAccessible(true);
// Increment it
m_val.setDouble(n, m_val.getDouble(n) + 1.0);
} catch (Exception e) {
log.error("Error", e);
}
return n == null ? null : n.str();
}
}
I am having a difficult time trying to do something that seems like it should be really easy to do. I basically want to multiply 2 numbers in a node and then sum the total of those numbers for all the nodes. Here is the XSLT code I have tried.
<xsl:value-of select="sum(Parts/Part/Quantity * Parts/Part/Rate)"/>
This code results in an error that says "Argument 1 of function sum cannot be converted to a node set."
Does anyone have an idea of what is wrong or how I can accomplish what I am trying to do?
Here are three possible solutions:
Solution1 XSLT2:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select="sum(/*/*/(rate * quantity))"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<parts>
<part>
<rate>0.37</rate>
<quantity>10</quantity>
</part>
<part>
<rate>0.03</rate>
<quantity>10</quantity>
</part>
</parts>
The wanted result is produced:
4
The XSLT 2.0 solution uses the fact that in XPath 2.0 it is allowed that the right argument of the last "/" operator can be an expression or generally a function. This expression/function is applied for each of the nodes selected so far acting as the context node, and each function application produces one result.
Solution2 XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="sumProducts">
<xsl:with-param name="pList" select="*/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="sumProducts">
<xsl:param name="pList"/>
<xsl:param name="pAccum" select="0"/>
<xsl:choose>
<xsl:when test="$pList">
<xsl:variable name="vHead" select="$pList[1]"/>
<xsl:call-template name="sumProducts">
<xsl:with-param name="pList" select="$pList[position() > 1]"/>
<xsl:with-param name="pAccum"
select="$pAccum + $vHead/rate * $vHead/quantity"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pAccum"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied on the above XML document, the correct result is produced:
4
This is a typical XSLT 1.0 recursive solution. Do note how the sumProducts template calls itself recursively, until the entire input list, passed in the parameter $pList is processed.
Solution3 FXSL (XSLT 1.0):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:test-map-product="test-map-product"
exclude-result-prefixes="xsl ext test-map-product"
>
<xsl:import href="sum.xsl"/>
<xsl:import href="map.xsl"/>
<xsl:import href="product.xsl"/>
<!-- This transformation is to be applied on:
salesMap.xml
It contains the code of the "sum of products" from the
article "The Functional Programming Language XSLT"
-->
<test-map-product:test-map-product/>
<xsl:output method="text"/>
<xsl:template match="/">
<!-- Get: map product /sales/sale -->
<xsl:variable name="vSalesTotals">
<xsl:variable name="vTestMap" select="document('')/*/test-map-product:*[1]"/>
<xsl:call-template name="map">
<xsl:with-param name="pFun" select="$vTestMap"/>
<xsl:with-param name="pList1" select="/sales/sale"/>
</xsl:call-template>
</xsl:variable>
<!-- Get sum map product /sales/sale -->
<xsl:call-template name="sum">
<xsl:with-param name="pList" select="ext:node-set($vSalesTotals)/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="makeproduct" match="*[namespace-uri() = 'test-map-product']">
<xsl:param name="arg1"/>
<xsl:call-template name="product">
<xsl:with-param name="pList" select="$arg1/*"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<sales>
<sale>
<price>3.5</price>
<quantity>2</quantity>
<Discount>0.75</Discount>
<Discount>0.80</Discount>
<Discount>0.90</Discount>
</sale>
<sale>
<price>3.5</price>
<quantity>2</quantity>
<Discount>0.75</Discount>
<Discount>0.80</Discount>
<Discount>0.90</Discount>
</sale>
</sales>
The correct result is produced:
7.5600000000000005
In the last case for each sale we calculate the product of price, quantity and all available (variable number of) discount-s.
FXSL is a pure XSLT implementation of higher order functions. In this example the higher-order function f:map() is used to map the function f:product() on the list of children of every sale element. Then the results are summed to produce the final result.
All of Dimitre's solutions work and he's right that you don't need to use extension functions but sometimes it makes life easier. It's not too harmful, especially when you use exslt extensions which are supported across multiple XSLT processors. Also, the reason you're getting the sequence errors is probably because you're using an XSLT 1 processor.
If you want to persist with your chosen solution, you'll need to use Saxon or some other XSLT processor that supports XSLT 2.
Otherwise, here's an alternative method of doing it in XSLT 1. This will work in most XSLT processors and some peope might find it easier to grok than the recursive version. Personally, I prefer the recursive version (Dimitre's 3rd proposal) because it is more portable.
<xsl:stylesheet version="1.0"
xmlns:ex="http://exslt.org/common"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template name="GetProducts">
<xsl:param name="left"/>
<xsl:param name="right"/>
<xsl:for-each select="$left/text()">
<product>
<xsl:value-of select="number(.) * number($right[position()])"/>
</product>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="products">
<xsl:call-template name="GetProducts">
<xsl:with-param name="left" select="Parts/Part/Rate"/>
<xsl:with-param name="right" select="Parts/Part/Quantity"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="sum(ex:node-set($products)/product)"/>
</xsl:template>
</xsl:stylesheet>