generating clrf in irrespective manner - xslt

having a string length about of 120
here below my message string:
CID_Ultimate_Ben_Details=pabbisettishanmukhpraveenkumarpabbisettishanmukhpraveenkumarpabbisettishanmukhpraveenkumarpabbisettishanmukhpraveenkumar
Ex: the message length of 140
output required as :
1 to 35 chars in first line
36 t0 70 chars in second line
71 to 105 chars in third line
106 to 140 in fourth line
here my xslt logic:
<xsl:if test ="./CID_Ultimate_Ben_Details != '' " >
<xsl:if test ="string-length(./CID_Ultimate_Ben_Details) != '11' and string-length(./CID_Ultimate_Ben_Details) != '8' ">
<xsl:if test="string-length(./CID_Ultimate_Ben_Details) > 1">
<xsl:value-of select="concat(':58D:',substring(./CID_Ultimate_Ben_Details,1,35))" />
</xsl:if>
<xsl:if test="string-length(./CID_Ultimate_Ben_Details) > 35">
<xsl:value-of select="concat('
',substring(./CID_Ultimate_Ben_Details,36,70))" />
</xsl:if>
<xsl:if test="string-length(./CID_Ultimate_Ben_Details) > 70">
<xsl:value-of select="concat('
',substring(./CID_Ultimate_Ben_Details,71,105))" />
</xsl:if>
<xsl:if test="string-length(./CID_Ultimate_Ben_Details) > 105">
<xsl:value-of select="concat('
',substring(./CID_Ultimate_Ben_Details,106,140))" />
</xsl:if>
</xsl:if>
<xsl:text>
</xsl:text >
</xsl:if>
output required as :
:58D:pabbisettishanmukhpraveenkumarpabbi
settishanmukhpraveenkumarpabbisetti
shanmukhpraveenkumarpabbisettishanm
ukhpraveenkumar
but now for the above logic output is coming as such:
:58D:pabbisettishanmukhpraveenkumarpabbi
settishanmukhpraveenkumarpabbisettishanmukhpraveenkumarpabbisettishanm
shanmukhpraveenkumarpabbisettishanmukhpraveenkumar
ukhpraveenkumar
why it was generating clrf in a irrespective manner.can any one suggest me please to achive my required output

Here is a correct and general (working for any text-length and number of output lines) XSLT 1.0 solution (the XSLT 2.0 solution is really elementary):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pmsgLength" select="140"/>
<xsl:param name="pnumLines" select="4"/>
<xsl:variable name="vmaxLineLength" select=
"ceiling($pmsgLength div $pnumLines)"/>
<xsl:template match="/*/text()" name="split">
<xsl:param name="pText" select="."/>
<xsl:param name="pnextLines" select="$pnumLines"/>
<xsl:param name="pHead" select="':58D:'"/>
<xsl:if test="$pnextLines">
<xsl:value-of select=
"concat('
',$pHead, substring($pText, 1, $vmaxLineLength))"/>
<xsl:call-template name="split">
<xsl:with-param name="pText" select="substring($pText, $vmaxLineLength+1)"/>
<xsl:with-param name="pnextLines" select="$pnextLines -1"/>
<xsl:with-param name="pHead" select="''"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>pabbisettishanmukhpraveenkumarpabbisettishanmukhpraveenkumarpabbisettishanmukhpraveenkumarpabbisettishanmukhpraveenkumar</t>
the wanted, correct output is produced:
:58D:pabbisettishanmukhpraveenkumarpabbi
settishanmukhpraveenkumarpabbisetti
shanmukhpraveenkumarpabbisettishanm
ukhpraveenkumar

The third argument of substring() is the length required, not the end position as in Java.

Related

Split Attribute using ; as delimiter in XSLT

How to split an elements using ; as delimiter.my requirement is like below.
input:
<Element1>C:KEK39519US; U:085896395195; A:K39519US; B:S2345843</Element1>
output:
<CustItem>KEK39519US</CustItem>
<UNumber>085896395195</UNumber>
<ANumber>K39519US</ANumber>
<BNumber>S2345843</BNumber>
the input is every time not same.some times it comes like C:KEK39519US; U:085896395195; B:S2345843
some time like this C:KEK39519US; A:K39519US; B:S2345843
sometime like this U:085896395195; A:K39519US;
sometime like this C:KEK39519US; U:085896395195; A:K39519US;
To solve this in XSLT 1.0 you may need a named template which recursively calls itself. The template will process of the string before the first semi-colon, and output the element accordingly. It will then recursively call itself with the remaining part of the string after this semi-colon (if there is one)
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Element1">
<xsl:call-template name="outputElements">
<xsl:with-param name="list" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="outputElements">
<xsl:param name="list"/>
<xsl:variable name="first" select="normalize-space(substring-before(concat($list, ';'), ';'))"/>
<xsl:variable name="remaining" select="normalize-space(substring-after($list, ';'))"/>
<xsl:call-template name="createElement">
<xsl:with-param name="element" select="$first" />
</xsl:call-template>
<!-- If there are still elements left in the list, call the template recursively -->
<xsl:if test="$remaining">
<xsl:call-template name="outputElements">
<xsl:with-param name="list" select="$remaining"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="createElement">
<xsl:param name="element"/>
<xsl:variable name="elementName">
<xsl:choose>
<xsl:when test="substring-before($element, ':') = 'C'">CustItem</xsl:when>
<xsl:otherwise><xsl:value-of select="concat(substring-before($element, ':'), 'Number')" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elementName}">
<xsl:value-of select="substring-after($element, ':')" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When applied to you XML, the following is output
<CustItem>KEK39519US</CustItem>
<UNumber>085896395195</UNumber>
<ANumber>K39519US</ANumber>
<BNumber>S2345843</BNumber>
Note the use of Attribute Value Templates in specifying the name of each new element.

How do i remove multiple numbers based on a search of multiple node values?

I am fairly new to XSLT and have scanned through several posts on the topic but cant seem to get the final peice i need to make this work. I am attempting to remove entries from a known string of data that appear in the node data that i have. I have peiced together a solution that works for single node values but not multiple values.
here is my xml
<root>
<item>2</item>
<item>9</item>
<item>5</item>
</root>
here is my code that works for one node value:
<xsl:template match="item">
<xsl:copy>
<xsl:call-template name="replaceChars">
<xsl:with-param name="original" select="string('1 2 3 4 5 6 7 8 9 10')"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="replaceChars">
<xsl:param name="original"/>
<xsl:choose>
<xsl:when test="contains($original, current())">
<xsl:value-of select="substring-before($original, current())"/>
<xsl:variable name="after" select="substring-after($original, current())"/>
<xsl:variable name="char" select="substring-before($after, current())"/>
<xsl:value-of select="concat($char, $after)"/>
<xsl:call-template name="replaceChars">
<xsl:with-param name="original" select="substring-after($after, current())"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$original"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
my latest of testing i am attempting to use this:
<xsl:template match="item">
<xsl:copy>
<xsl:call-template name="replaceChars">
<xsl:with-param name="original" select="string('1 2 3 4 5 6 7 8 9 10')"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="replaceChars">
<xsl:param name="original"/>
<xsl:choose>
<xsl:when test="contains($original, current())">
<xsl:variable name="before" select="substring-before($original, current())"/>
<xsl:variable name="after" select="substring-after($original, current())"/>
<xsl:variable name="char" select="substring-before($after, current())"/>
<xsl:variable name="new" select="concat($before, $after)"/>
<xsl:call-template name="replaceChars">
<xsl:with-param name="original" select="$new"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$original"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
i contiune to get the value iterated several times in the response. i would like my output to be the following:
1 3 4 6 7 8 10
I have searched on this extensively as you can see my example is based on an altered searched scenario. any help would be appreciated.
You're close.
If you want to produce the string "1 3 4 6 7 8 10" once, then you probably don't want the code you show to be evaluated once for each item element, but once for each root element.
If you want the recursive template replaceChars to knock out the value of each item in the set of sibling item elements, then the current structure won't do it. Why? Because it knocks out the value of current() and then calls itself recursively without doing anything to change the value of current().
One alternative approach would be to say "I want to build a string consisting of the number 1 (unless it appears in the input), followed by the number 2 (unless it appears in the input), followed by ..." and write (n.b. not tested):
<xsl:template match="root">
<survivors>
<xsl:if test="not(./item = '1')">1 </xsl:if>
<xsl:if test="not(./item = '2')">2 </xsl:if>
<xsl:if test="not(./item = '3')">3 </xsl:if>
<xsl:if test="not(./item = '4')">4 </xsl:if>
<xsl:if test="not(./item = '5')">5 </xsl:if>
<xsl:if test="not(./item = '6')">6 </xsl:if>
<xsl:if test="not(./item = '7')">7 </xsl:if>
<xsl:if test="not(./item = '8')">8 </xsl:if>
<xsl:if test="not(./item = '9')">9 </xsl:if>
</survivors>
</xsl:template>
Or if you really want to do the substring-match thing, you need to ensure that your recursion in replaceChars actually iterates over the item elements:
<xsl:template match="root">
<survivors>
<xsl:call-template name="replaceChars2">
<xsl:with-param name="s" select="'1 2 3 4 5 6 7 8 9'"/>
<xsl:with-param name="item" select="./item[1]"/>
</xsl:call-template>
</survivors>
</xsl:template>
<xsl:template name="replaceChars2">
<xsl:param name="s"/>
<xsl:param name="item"/>
<xsl:variable name="s2" select="string($item)"/>
<xsl:choose>
<xsl:when test="$item">
<xsl:call-template name="replaceChars2">
<xsl:with-param name="s"
select="concat(
substring-before($s,$s2,
' ',
substring-after($s,$s2,
)"/>
<xsl:with-param name="item"
select="./following-sibling::item[1]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$s"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Like the code shown by the OP, this assumes no item element will accidentally match any part of the string that should not be deleted (e.g. the original string will never have a value like '11' if any item will ever have the value '1').
The pattern of iterating over siblings and passing parameters to keep track of what happened with the earlier siblings is an important idiom to learn, for transforms like this one.
This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:sent/>
<xsl:variable name="vSent" select="document('')/*/my:sent"/>
<xsl:param name="pGiven" select="'1 2 3 4 5 6 7 8 9 10'"/>
<xsl:template match="/*">
<xsl:apply-templates select="item[1]"/>
</xsl:template>
<xsl:template match="item">
<xsl:param name="pText" select="$pGiven"/>
<xsl:apply-templates select=
"following-sibling::item[1]|$vSent[not(current()/following-sibling::item)]">
<xsl:with-param name="pText" select=
"concat(substring-before(concat($pText, .), .),
substring-after($pText,.)
)
"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="my:sent">
<xsl:param name="pText"/>
<xsl:value-of select="$pText"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<item>2</item>
<item>9</item>
<item>5</item>
</root>
produces the wanted, correct result:
1 3 4 6 7 8 10
Do note:
There is no XSLT conditional instruction in the whole transformation -- no xsl:choose, no xsl:when, no xsl:otherwise no xsl:if .
There is no named template and no explicit recursion (although xsl:apply-templates recurses implicitly).
Sentinel programming is used in two different ways to significantly simplify the code and make it more efficient.

XSLT count comma values count

I have a value like integer="1,2,3,4,5" in the xml. How can I count the total number using XSLT. So that the output gives me a count of 5
Regards,
Sam
Here's one way (there may be others). Simply translate all commas into empty strings, and then compare in difference in length of strings:
<xsl:value-of
select="string-length(#integer)
- string-length(translate(#integer, ',', '')) + 1" />
If you need to handle empty strings, try this instead
<xsl:value-of
select="string-length(#integer)
- string-length(translate(#integer, ',', ''))
+ 1 * (string-length(#integer) != 0)" />
If you want to count the comma-separated-values, but ALSO be able to reference the individual items, you can use a recursive template like such.
This XSLT 1.0 style-sheet will convert the comma-separated-values into nodes and then count them ...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="as-nodes">
<xsl:call-template name="parse-comma-separated-values">
<xsl:with-param name="csv" select="t/#csv" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="count(msxsl:node-set($as-nodes)/*)" />
</xsl:template>
<xsl:template name="parse-comma-separated-values">
<xsl:param name="csv" />
<xsl:choose>
<xsl:when test="$csv = ''"/>
<xsl:when test="not( contains( $csv, ','))">
<value-node value="{$csv}" />
</xsl:when>
<xsl:otherwise>
<value-node value="{substring-before($csv,',')}" />
<xsl:call-template name="parse-comma-separated-values">
<xsl:with-param name="csv" select="substring-after($csv,',')"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
... when applied to this input document ...
<t csv="1,2,3,4,5"/>
... produces ...
5

XSLT : Looping from 1 to 60

What is the best way to loop in XSLT from 1 to 60?
I research in net, there are some templates to do this, is there any other way for example like a built-in function?
In XSLT 2.0,
<xsl:for-each select="1 to 60">...</xsl:for-each>
But I guess that you must be using XSLT 1.0, otherwise you wouldn't be asking.
In XSLT 1.0 you should use recursion: a template that calls itself with a counter that's incremented on each call, and the recursion terminates when the required value is reached.
Alternatively there's a workaround in XSLT 1.0: provided your source document contains at least 60 nodes, you can do
<xsl:for-each select="(//node())[60 >= position()]">...</xsl:for-each>
The problem with simple recursion when processing long sequences is that often the space for the call stack becomes insufficient and the processing ends due to stack overflow. This typically happens with sequence length >= 1000.
A general technique to avoid this (implementable with any XSLT processor, even if it doesn't recognize tail-recursion) is DVC (Divide and Conquer) style recursion.
Here is an example of a transformation that successfully prints the numbers from 1 to 1000000 (1M):
<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="displayNumbers">
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="1000000"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="displayNumbers">
<xsl:param name="pStart"/>
<xsl:param name="pEnd"/>
<xsl:if test="not($pStart > $pEnd)">
<xsl:choose>
<xsl:when test="$pStart = $pEnd">
<xsl:value-of select="$pStart"/>
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vMid" select=
"floor(($pStart + $pEnd) div 2)"/>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$pStart"/>
<xsl:with-param name="pEnd" select="$vMid"/>
</xsl:call-template>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$vMid+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) this transformation produces the wanted result -- all the numbers from 1 to 1000000.
You can use/adapt this transformation for any task that needs to "do something N times".
Very simple check inside the foreach-loop
<xsl:if test="$maxItems > position()">
do something
</xsl:if>
Based on Dimitre Novatchev's answer.
Example:
<xsl:variable name="maxItems" select="10" />
<xsl:variable name="sequence" select="any-sequence"/>
<xsl:for-each select="$sequence">
<!-- Maybe sort first -->
<xsl:sort select="#sort-by" order="descending" />
<!-- where the magic happens -->
<xsl:if test="$maxItems > position()">
do something
</xsl:if>
</xsl:for-each>
The basic example for V1.0 using recursion would it be like this:
<xsl:template match="/">
<Root>
<!-- Main Call to MyTemplate -->
<xsl:call-template name="MyTemplate" />
</Root>
</xsl:template>
<xsl:template name="MyTemplate">
<xsl:param name="index" select="1" />
<xsl:param name="maxValue" select="60" />
<MyCodeHere>
<xsl:value-of select="$index"/>
</MyCodeHere>
<!-- < represents "<" for html entities -->
<xsl:if test="$index < $maxValue">
<xsl:call-template name="MyTemplate">
<xsl:with-param name="index" select="$index + 1" />
<xsl:with-param name="total" select="$maxValue" />
</xsl:call-template>
</xsl:if>
</xsl:template>
XSLT works based on templates and you'll need a template do run that loop.
You'll need to build a template receiving start and end values and, inside it, make a recursive call computing with start + 1. When $start equals $end, you do return your template, without another call.
In practice: http://www.ibm.com/developerworks/xml/library/x-tiploop/index.html

How to do this in XSLT without incrementing variables? (Tweaking Xalan to create a global XSLT iterator. Do I have other options?)

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();
}
}