Using xsl:variable to set another variable using xsl:choose -> Invalid property - xslt

I'm trying to define some standard colours to use elsewhere in an XSLT, but the following gives an error:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="2.0">
<xsl:variable name="rgbWeiss" >rgb(255, 255, 255)</xsl:variable>
<xsl:variable name="rgbHellBlauGrau">rgb(213, 235, 229)</xsl:variable>
<xsl:variable name="rgbDunkelRot" >rgb(128, 0, 0)</xsl:variable>
:
:
<xsl:template match="row">
<xsl:variable name="bgcolor">
<xsl:choose>
<xsl:when test="position() mod 2 = 1">rgb(213, 235, 229)</xsl:when>
<xsl:otherwise >${rgbDunkelRot}</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<fo:table-row background-color="{$bgcolor}" xsl:use-attribute-sets="table-row-attr">
The error message is:
Invalid property value encountered in background-color="${rgbDunkelRot}"
Unfortunately no useful information was provided for the location of the error.
The following works fine though:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="2.0">
:
:
<xsl:template match="row">
<xsl:variable name="bgcolor">
<xsl:choose>
<xsl:when test="position() mod 2 = 1">rgb(213, 235, 229)</xsl:when>
<xsl:otherwise >rgb(128, 0, 0)</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<fo:table-row background-color="{$bgcolor}" xsl:use-attribute-sets="table-row-attr">
Any ideas?

With XSLT 2 (you seem to use) I would simply do
<fo:table-row background-color="{if (position() mod 2 = 1) then $rgbHellBlauGrau else $rgbDunkelRot}" xsl:use-attribute-sets="table-row-attr">
or use that expression in the variable
<xsl:variable name="bgcolor" select="if (position() mod 2 = 1) then $rgbHellBlauGrau else $rgbDunkelRot"/>
Inside of the xsl:choose/xsl:when/xsl:otherwise you have the wrong syntax, you need <xsl:otherwise><xsl:value-of select="$rgbDunkelRot"/></xsl:otherwise> or move to XSLT 3 and expand-text="yes" with e.g. <xsl:otherwise>{$rgbDunkelRot}</xsl:otherwise>.
In "XSLT 4" currently experimented with in Saxon 10 PE or EE as an extension there is also a select attribute on xsl:when and xsl:otherwise: http://saxonica.com/html/documentation/extensions/xslt-syntax-extensions.html. So there you can write <xsl:when test="position() mod 2 = 1" select="$rgbHellBlauGrau"/>.

Related

Recursive call to function giving error in XSLT

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>

xslt keep multiple variables in scope depending on a single test

I have many variables and only two cases.
My original approach goes out of scope:
<xsl:choose>
<xsl:when test='$something = 6'>
<xsl:variable name="foo">x</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
<xsl:when test='$something = 7'>
<xsl:variable name="foo">y</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
</xsl:choose>
ie. later, with saxon, XPST0008: Variable x has not been declared (or its declaration is not in scope)
I think it would work if I would choose inside an xsl:variable tag, but then the tests would be repeated over and over and over and over and over:
<xsl:variable name="foo">
<xsl:choose>
<xsl:when test='$something = 6'>x</xsl:when>
<xsl:when test='$something = 7'>y</xsl:when>
</xsl:choose>
</xsl:variable>
<!-- 50 other variables, the two tests repeated for each... -->
Is there a way to keep the variables in scope but also don't repeat myself?
update 1
adding the complete 'sscce' files on request
original approach:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="something">6</xsl:variable>
<xsl:choose>
<xsl:when test="$something = '6'">
<xsl:variable name="foo">x</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
<xsl:when test="$something = '7'">
<xsl:variable name="foo">y</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
</xsl:choose>
<xsl:value-of select="$foo"/>
</xsl:template>
</xsl:stylesheet>
approach that works but forces to repeat myself:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="something">6</xsl:variable>
<xsl:variable name="foo">
<xsl:choose>
<xsl:when test='$something = 6'>x</xsl:when>
<xsl:when test='$something = 7'>y</xsl:when>
</xsl:choose>
</xsl:variable>
<!-- 50 other variables, the two tests repeated for each... -->
<xsl:value-of select="$foo"/>
</xsl:template>
</xsl:stylesheet>
example xml file: <xml/>. example saxon command-line: java -jar saxon9he.jar -s:in.xml -xsl:in.xsl -o:out.html
Well, I'd rather process that way :
<xsl:variable name="something">6</xsl:variable>
<xsl:variable name="var.set">
<xsl:choose>
<xsl:when test="$something = '6'">
<foo>x</foo>
<bar>xx</bar>
<!-- 50 other variables, to be inserted as tag -->
</xsl:when>
<xsl:when test="$something = '7'">
<foo>y</foo>
<bar>yy</bar>
<!-- 50 other variables, to be inserted as tag -->
</xsl:when>
</xsl:choose>
</xsl:variable>
The variable var.set will be a nodeset that you will be able to read thanks to the exsl:node-set() extension.
For example to retrieve the value for foo (stored as a node and not as a variable anymore), use something like: <xsl:value-of select="exsl:node-set($var.set)/foo" />. Like this you will handle a single variable, quite as if it were an array of values.
PS: on the root tag of your stylesheet, do not forget to add the exsl namespace declaration xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"
If you know your mappings beforehand, you can store them in their own file.
So instead of having something like this
<xsl:variable name="var1">
<xsl:if test="something = 5">x</xsl:if>
</xsl:variable>
<xsl:value-of select="$var1"/>
You could have this
<xsl:value-of select="document('other.xml')/root/scheme[#number = 5]/#value"/>
With this as other.xml
<root>
<scheme number="5" value="x"/>
<scheme number="6" value="y"/>
</root>
You'll probably want a more complex other.xml, with various groups of colours for each colour scheme, but the idea would be the same, and avoids tests and variables completely.

Xpath 'instance of' not working in solr xslt

I want use xslt in solr to output result in json without specific solr attributes.
{
"techproducts": [
{
"id": "GB18030TEST",
"price": "0.0"
},
{
"id": "SOLR1000",
"price": "0.0"
},
{
"id": "UTF8TEST",
"price": "0.0"
}
],
"paging": {
"count": 3
}
}
The test of type of field (numeric or boolean) test=". instance of xs:integer" in order to add or not quotes do not work and generate a 500 error.
http://localhost:8090/solr/techproducts/select?q=*&fq=price:0&fl=id,price&wt=xslt&tr=json_paginate.xsl
Caused by: javax.xml.transform.TransformerConfigurationException: solrres:/xslt/json_paginate.xsl: line 37: Attribut 'test' obligatoire manquant.
Any suggestions?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" >
<xsl:strip-space elements="*"/>
<xsl:output method="text" indent="no" media-type="application/json"/>
<xsl:template match='/'>
<xsl:text>{"techproducts":[</xsl:text>
<xsl:apply-templates select="response/result/doc"/>
<xsl:text>],</xsl:text>
<xsl:text>"paging":{"count":</xsl:text><xsl:value-of select="response/result/#numFound"/><xsl:text>}}</xsl:text>
</xsl:template>
<xsl:template match="doc">
<xsl:variable name="pos" select="position()"/>
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>{</xsl:text>
<xsl:apply-templates>
<xsl:with-param name="pos"><xsl:value-of select="$pos"/></xsl:with-param>
</xsl:apply-templates>
<xsl:text>}</xsl:text>
</xsl:template>
<xsl:template match="doc/*">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>"</xsl:text><xsl:value-of select="#name"/><xsl:text>":</xsl:text>
<xsl:choose>
<!-- if integer, do not add quotes -->
<xsl:when test=". instance of xs:integer">
<xsl:value-of select="."/>
</xsl:when>
<!-- if boolean, do not add quotes -->
<xsl:when test=". instance of xs:boolean">
<xsl:value-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
To solve the problem :
I have upgraded xslt version in solr http://wiki.apache.org/solr/XsltResponseWriter and used
<xsl:choose>
<!-- if decimal, do not add quotes -->
<xsl:when test=". castable as xs:decimal">
<xsl:value-of select="."/>
</xsl:when>
<!-- if boolean, do not add quotes -->
<xsl:when test=". castable as xs:boolean">
<xsl:value-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
</xsl:otherwise>
</xsl:choose>
Here is how to achieve in XSLT 1.0 the intended behavior of the provided XSLT 2.0 code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match=
"*[not(*) and text()
and
not(contains('|true|false|', concat('|', ., '|')))]">
<xsl:value-of select='concat(&apos;"&apos;, ., &apos;"&apos;)'/>
</xsl:template>
<xsl:template match="*[not(*) and text() and number(.) = .]" priority="3">
<xsl:value-of select="number(.)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<a>1</a>
<b>true</b>
<c>false</c>
<d>-3</d>
<e>x</e>
<f>.25</f>
</t>
The wanted, correct result is produced:
1
true
false
-3
"x"
0.25
Do note: In addition to the behavior that is intended in the provided XSLT 2.0 code, the above transformation also implements correct output of JSON-acceptable representation for any decimal number.
In case only processing and displaying integer values is wanted, just use:
floor(.) = .
Update:
The OP has just revealed that he could actually use an XSLT 2.0 processor.
Here is one possible XSLT 2.0 solution, which represents correctly the strings "true" and "false" as JSON Booleans, any integers and decimals to JSON numbers, and any other atomic value -- to quoted string:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match=
"*[not(*) and text() and not(. = ('true', 'false'))]">
<xsl:value-of select='concat(&apos;"&apos;, ., &apos;"&apos;)'/>
</xsl:template>
<xsl:template match="*[not(*) and text() and . castable as xs:decimal]" priority="3">
<xsl:value-of select="number(.)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above, the same correct, wanted result is produced:
1
true
false
-3
"x"
0.25
Your goal is to achieve syntactically correct JSON data: no quotes for numbers and the strings "true" and "false". That's what your attempt handling these data types tells me.
In this case the following xsl:choose part should work - at least I hope so, since we don't know your input data:
XSLT-2.0: (*)
<xsl:choose>
<!-- if number, do not add quotes -->
<xsl:when test="number(.)">
<xsl:value-of select="."/>
</xsl:when>
<!-- if boolean, do not add quotes -->
<xsl:when test="matches(.,'true|false','i')">
<xsl:value-of select="lower-case(.)"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
</xsl:otherwise>
</xsl:choose>
It should be mentioned (see comments) that all numbers get printed without quotation marks. Not only integers. Which should be ok for a valid JSON output.
(*) the first version of my answer had a remarkable amount of errors in it which I apologize for (downvoting that answer was quite appropriate). The version shown above has been tested with Saxon and worked as expected.

XSLT 2.0 arithmetic when values can be NULL

I'm using XSLT to transform a result set of numbers from a database into a nicely formatted table, including arithmetic to calculate totals.
The result set is being provided to me as an XML document (via SOAP) without any ability for me to alter the query that's used to generate it.
The problem: when the value should display as zero, this query returns a null/empty value into the XML result set. In order for the arithmetic to work, I need to convert these empty values, when they occur, into zero.
I've tried a variety of approaches, including a variable (see below) with no success - in this case, I get:
Cannot covert string "" to a double
I'm a bit limited in what I can do to pre-process the data: is there any way to achieve this with XSLT?
Input:
<DataResultSet xmlns="urn:...dataservice" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:axis2ns531="urn:...dataservice">
<rows>
<row>
<A_PLAN>438</A_PLAN>
<A_ACTUAL>358</A_ACTUAL>
<B_PLAN />
<B_ACTUAL />
</row>
</rows>
</DataResultSet>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:jdbc="urn:...dataservice" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="html"/>
<xsl:variable name = "A_PLAN" as="xs:double">
<xsl:choose>
<xsl:when test = "jdbc:A_PLAN = ''" ><xsl:text>0</xsl:text></xsl:when>
<xsl:otherwise><xsl:value-of select="jdbc:A_PLAN" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="A_ACTUAL" as="xs:double">
<xsl:choose>
<xsl:when test = "jdbc:A_ACTUAL = ''" ><xsl:text>0</xsl:text></xsl:when>
<xsl:otherwise><xsl:value-of select="jdbc:A_ACTUAL" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="B_PLAN" as="xs:double">
<xsl:choose>
<xsl:when test = "jdbc:B_PLAN = ''" ><xsl:text>0</xsl:text></xsl:when>
<xsl:otherwise><xsl:value-of select="jdbc:B_PLAN" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="B_ACTUAL" as="xs:double">
<xsl:choose>
<xsl:when test = "jdbc:B_ACTUAL = ''" ><xsl:text>0</xsl:text></xsl:when>
<xsl:otherwise><xsl:value-of select="jdbc:B_ACTUAL" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="jdbc:row">|r|A|c|XXX|c|<xsl:value-of select="$A_PLAN"/>|c|<xsl:value-of select="$A_ACTUAL"/>|c|<xsl:value-of select="$A_ACTUAL - $A_PLAN"/>|r|B|c|XXX|c|<xsl:value-of select="$B_PLAN"/>|c|<xsl:value-of select="$B_ACTUAL"/>|c|<xsl:value-of select="$B_ACTUAL - $B_PLAN"/>|r|XXX|c|TTL|c|<xsl:value-of select="$A_PLAN + $B_PLAN"/>|c|<xsl:value-of select="$A_ACTUAL + $B_ACTUAL"/>|c|<xsl:value-of select="($A_ACTUAL - $A_PLAN) + ($B_ACTUAL - $B_PLAN)"/>
</xsl:template>
</xsl:stylesheet>
I would write
<xsl:variable name = "A_PLAN" as="xs:double">
<xsl:choose>
<xsl:when test = "jdbc:A_PLAN = ''" ><xsl:text>0</xsl:text></xsl:when>
<xsl:otherwise><xsl:value-of select="jdbc:A_PLAN" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
as
<xsl:variable name = "A_PLAN" as="xs:double"
select="if (//jdbc:A_PLAN castable as xs:double) then xs:double(//jdbc:A_PLAN) else 0"/>
So I have corrected the path to use //jdbc:A_PLAN as you seem to want to use global variables and then you need to search down in the document and I have used castable as to check the value.
Of course if you have several row elements using global variables does not make sense, I would then move the code into the template e.g.
<xsl:template match="jdbc:row">
<xsl:variable name = "A_PLAN" as="xs:double"
select="if (jdbc:A_PLAN castable as xs:double) then xs:double(jdbc:A_PLAN) else 0"/>

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