XSLT Keep adding variable in a loop - xslt

I am looking for an idea on how to be able to add variable in for-each loop.
<group>
<spare bitCnt="5"/>
<integer name="A" bitCnt="11"/>
<spare bitCnt="15"/>
<integer name="B" bitCnt="1"/>
</group>
Expected header file output:
UINT16 A: 11;
UINT16 spare: 5;
UINT16 B: 1;
UINT16 spare 15;
I need to keep adding the bitCnt of variables in the 'group' and if the variable falls on the word boundary of 2 bytes, I need to swap the order of the elements.
My question is "how can I keep track of the bitCnt as I run the elements in the for-each loop?"
I would something like totalBitCnt in the xslt and in for-each loop, keep adding the totalBitCnt to determine if "totalBitCnt mod 16" is zero.
So, it would increment like 5, 18(5+13), 23(15+18), 24(23+1) as it runs the loop.
Any general approach is highly appreciated.
Thanks,

I'm not sure I grok you particular context so I'll answer in general terms.
Accumulating in a for loop is an iterative approach and XSLT (I guess you're using 1.0) is very limited in that matter. I think your best bet is to set up something recursive.
Named templates could be defined and invoked instead of the for-each loop. You'd define it with parameters such as: a list of element to process and the accumulator.
<xsl:template name="accumulator-template">
<xsl:param name="elements"/>
<xsl:param name="accumulator"/>
<!-- do stuff to the first element -->
<xsl:variable name="elements-left" select="$elements[???]"/> <!-- Exclude element that was processed -->
<xsl:choose>
<xsl:when test="$elements-left">
<!-- LOOPING -->
<xsl:call-template name="accumulator-template">
<xsl:with-param name="elements" select="$elements-left"/>
<xsl:with-param name="accumulator" select="$accumulator + number(#stuff)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- END OF LOOP -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Then you'd invoke it instead of writing an inline for-each loop:
<xsl:call-template name="accumulator-template">
<xsl:with-param name="elements" select="group"/>
<xsl:with-param name="elements" select="0"/> <!-- or any other starting value -->
</xsl:call-template>

Related

How to replace variable inside curly braces with values in different tags in xslt

I am processing an xml file using xslt.
<ns1:declarationStatements>
<ns1:parameterisedEntity>
<ns2:code>NUTSUPSTATE20</ns2:code>
<ns2:localeData>
<ns1:description>
<![CDATA[** When {s} according to instructions {m}g typically weighs {m}g.]]>
</ns1:description>
<ns1:id>20253</ns1:id>
</ns2:localeData>
<ns2:specType>FOOD</ns2:specType>
<ns2:id>6653</ns2:id>
</ns1:parameterisedEntity>
<ns1:textParameters>
<ns1:value>228</ns1:value>
<ns1:id>68225</ns1:id>
<ns1:sequence>2</ns1:sequence>
</ns1:textParameters>
<ns1:textParameters>
<ns1:value>cooked</ns1:value>
<ns1:id>68233</ns1:id>
<ns1:sequence>0</ns1:sequence>
</ns1:textParameters>
<ns1:textParameters>
<ns1:value>255</ns1:value>
<ns1:id>68229</ns1:id>
<ns1:sequence>1</ns1:sequence>
</ns1:textParameters>
<ns1:id>133421</ns1:id>
</ns1:declarationStatements>
I want to get the text inside <ns1:description> i.e.-
**When {s} according to instructions {m}g typically weighs {m}g
But I want {s}, {m} and {m} to be replaced by the values in <ns1:textParameters>/<ns1:value>. It should look like -
**When cooked according to instructions 255g typically weighs 228g.
I tried doing that by using <xsl:value-of select="ns0:declarationStatements"> and the manipulating string but it is becoming very tedious and complex.
The number of such braces may also vary. So do we have anything like List or Array in XSLT?
Is there any other way or trick I can use to solve this problem?
Thanks
Assuming the parameters are meant to be inserted in order of their ns1:sequence value, I would start by defining a key as:
<xsl:key name="text-param" match="ns1:textParameters" use="ns1:sequence" />
then call the following recursive template with ns1:description as the string param:
<xsl:template name="merge-params">
<xsl:param name="string"/>
<xsl:param name="i" select="0"/>
<xsl:choose>
<xsl:when test="contains($string, '{') and contains(substring-after($string, '{'), '}')">
<xsl:value-of select="substring-before($string, '{')" />
<xsl:value-of select="key('text-param', $i)/ns1:value" />
<!-- recursive call -->
<xsl:call-template name="merge-params">
<xsl:with-param name="string" select="substring-after($string, '}')" />
<xsl:with-param name="i" select="$i + 1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Nested XSL Increment Variable

I have the following code:
<xsl:for-each select"/*/cities/city">
<xsl:for-each select="./items/item">
<xsl:variable name="counter" select="position()" />
</xsl:for-each>
</xsl:for-each>
What i want to do is make a global counter that accounts for both the global scope and the inner scope. So, if for example:
The first iteration of the inner loop has 7 items, the count would be 7. When it performs the second iteration, the new count should be 7 + position().
How Do I accomplish something like that?
It's difficult to answer without seeing an input and the expected output. Apparently you could do simply:
<xsl:for-each select"/*/cities/city/items/item">
<xsl:variable name="counter" select="position()" />
</xsl:for-each>
to get the specified result. If for some reason you do need to nest the two xsl:for-each instructions, consider using xsl:number to number the items.

XSLT How to do a classic for x to y loop?

I need to do a classic for i=0 to N loop, how can it be done in xstl 1.0?
thanks.
<xsl:for-each select="¿¿¿$i=0..5???">
<fo:block>
<xsl:value-of select="$i"/>
</fo:block>
</xsl:for-each>
To give an example, I have
<foo>
<bar>Hey!</bar>
</foo>
And want an output of
Hey!
Hey!
XSLT is a functional programming language and as such it is very different to any procedural languages you already know.
Although for loops are possible in XSLT, they do not make use of the inherent strengths of XSLT (and functional programming in general).
for loops are routinely misused to address problems that are best solved with a functional approach instead (that is, matching templates). In other words, a loop is not really a "classic" in XSLT.
So, you might have to double back, identify the problem you are facing instead of discussion your solution. Then, the XSLT community might be able to suggest a solution that is more functional in nature. It might be that you've fallen victim to the XY problem.
Now, among the things XSLT is inherently good at is recursion. Often, problems that are solved with loops in procedural languages are solved with recursive templates in XSLT.
<xsl:template name="recursive-template">
<xsl:param name="var" select="5"/>
<xsl:choose>
<xsl:when test="$var > 0">
<xsl:value-of select="$var"/>
<xsl:call-template name="recursive-template">
<xsl:with-param name="var" select="$var - 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:template>
To summarize, I suggest you look at "classic" recursion instead of "classic" for loops. You find more information about exactly this topic in an IBM article here.
EDIT as a response to your edited question. If your problem really boils down to outputting text content twice:
<?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"/>
<xsl:template match="/foo">
<xsl:apply-templates select="bar"/>
<xsl:apply-templates select="bar"/>
</xsl:template>
</xsl:stylesheet>
This is not feasible of course for a dynamic number of iterations.
XSLT 2.0 has <xsl:for-each select="0 to 5"> but in XSLT 1.0 you can only for-each over node sets, not sequences of atomic values. The easiest way I've found to get around this is to use some kind of sufficiently generic selector expression that will match at least as many nodes as you want iterations, e.g.
<xsl:for-each select="/descendant::node()[position() < 7]">
<fo:block>
<xsl:value-of select="position() - 1"/>
</fo:block>
</xsl:for-each>
or if you don't necessarily know there will be at least 6 nodes in the input document then you can use document('') to treat the stylesheet itself as another input document.
<xsl:for-each select="document('')/descendant::node()[position() < 7]">
In both cases the for-each will change the context node, so you'll need to save the outer context in a variable if you need to access it inside the for-each body
<xsl:variable name="dot" select="." />
Use a named template with parameters $i and $n; call the template with parameters {$i = 0, $N = 5}; have the template call itself recursively with parameters {$i + 1, $N} until $i > $N.
Example:
<xsl:template match="/">
<output>
<!-- stuff before -->
<xsl:call-template name="block-generator">
<xsl:with-param name="N" select="5"/>
</xsl:call-template>
<!-- stuff after -->
</output>
</xsl:template>
<xsl:template name="block-generator">
<xsl:param name="N"/>
<xsl:param name="i" select="0"/>
<xsl:if test="$N >= $i">
<!-- generate a block -->
<fo:block>
<xsl:value-of select="$i"/>
</fo:block>
<!-- recursive call -->
<xsl:call-template name="block-generator">
<xsl:with-param name="N" select="$N"/>
<xsl:with-param name="i" select="$i + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

Find the position of an element within its parent with XSLT / XPath

Apart from rewriting a lot of XSLT code (which I'm not going to do), is there a way to find the position of an element within its parent, when the context is arbitrarily set to something else? Here's an example:
<!-- Here are my records-->
<xsl:for-each select="/path/to/record">
<xsl:variable name="record" select="."/>
<!-- At this point, I could use position() -->
<!-- Set the context to the current record -->
<xsl:for-each select="$record">
<!-- At this point, position() is meaningless because it's always 1 -->
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
</xsl:for-each>
<!-- This template expects the current context being set to a record -->
<xsl:template name="SomeTemplate">
<!-- it does stuff with the record's fields -->
<xsl:value-of select="SomeRecordField"/>
<!-- How to access the record's position in /path/to or in any other path? -->
</xsl:template>
NOTE: This is a simplified example. I have several constraints keeping me from implementing obvious solutions, such as passing new parameters to SomeTemplate, etc. I can really only modify the internals of SomeTemplate.
NOTE: I'm using Xalan 2.7.1 with EXSLT. So those tricks are available
Any ideas?
You could use
<xsl:value-of select="count(preceding-sibling::record)" />
or even, generically,
<xsl:value-of select="count(preceding-sibling::*[name() = name(current())])" />
Of course this approach will not work if you process a list of nodes that is not uniform, i.e.:
<xsl:apply-templates select="here/foo|/somewhere/else/bar" />
Position information is lost in such a case, unless you store it in a variable and pass that to the called template:
<xsl:variable name="pos" select="position()" />
<xsl:for-each select="$record">
<xsl:call-template name="SomeTemplate">
<xsl:with-param name="pos" select="$pos" />
</xsl:call-template>
</xsl:for-each>
but obviously that would mean some code rewriting, which I realize you want to avoid.
Final hint: position() does not tell you the position of the node within its parent. It tells you the position of the current node relative to the list of nodes you are processing right now.
If you only process (i.e. "apply templates to" or "loop over") nodes within one parent, this happens to be the same thing. If you don't, it's not.
Final hint #2: This
<xsl:for-each select="/path/to/record">
<xsl:variable name="record" select="."/>
<xsl:for-each select="$record">
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
</xsl:for-each>
is is equivalent to this:
<xsl:for-each select="/path/to/record">
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
but the latter works without destroying the meaning of position(). Calling a template does not change context, so . will refer to the correct node withing the called template.

Using xsl:variable in a xsl:foreach select statement

I'm trying to iterate through an xml document using xsl:foreach but I need the select=" " to be dynamic so I'm using a variable as the source. Here's what I've tried:
...
<xsl:template name="SetDataPath">
<xsl:param name="Type" />
<xsl:variable name="Path_1">/Rating/Path1/*</xsl:variable>
<xsl:variable name="Path_2">/Rating/Path2/*</xsl:variable>
<xsl:if test="$Type='1'">
<xsl:value-of select="$Path_1"/>
</xsl:if>
<xsl:if test="$Type='2'">
<xsl:value-of select="$Path_2"/>
</xsl:if>
<xsl:template>
...
<!-- Set Data Path according to Type -->
<xsl:variable name="DataPath">
<xsl:call-template name="SetDataPath">
<xsl:with-param name="Type" select="/Rating/Type" />
</xsl:call-template>
</xsl:variable>
...
<xsl:for-each select="$DataPath">
...
The foreach threw an error stating: "XslTransformException - To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
When I use the msxsl:node-set() function though, my results are blank.
I'm aware that I'm setting $DataPath to a string, but shouldn't the node-set() function be creating a node set from it? Am I missing something? When I don't use a variable:
<xsl:for-each select="/Rating/Path1/*">
I get the proper results.
Here's the XML data file I'm using:
<Rating>
<Type>1</Type>
<Path1>
<sarah>
<dob>1-3-86</dob>
<user>Sarah</user>
</sarah>
<joe>
<dob>11-12-85</dob>
<user>Joe</user>
</joe>
</Path1>
<Path2>
<jeff>
<dob>11-3-84</dob>
<user>Jeff</user>
</jeff>
<shawn>
<dob>3-5-81</dob>
<user>Shawn</user>
</shawn>
</Path2>
</Rating>
My question is simple, how do you run a foreach on 2 different paths?
Try this:
<xsl:for-each select="/Rating[Type='1']/Path1/*
|
/Rating[Type='2']/Path2/*">
Standard XSLT 1.0 does not support dynamic evaluation of xpaths. However, you can achieve your desired result by restructuring your solution to invoke a named template, passing the node set you want to process as a parameter:
<xsl:variable name="Type" select="/Rating/Type"/>
<xsl:choose>
<xsl:when test="$Type='1'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path1/*"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$Type='2'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path2/*"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
...
<xsl:template name="DoStuff">
<xsl:param name="Input"/>
<xsl:for-each select="$Input">
<!-- Do stuff with input -->
</xsl:for-each>
</xsl:template>
The node-set() function you mention can convert result tree fragments into node-sets, that's correct. But: Your XSLT does not produce a result tree fragment.
Your template SetDataPath produces a string, which is then stored into your variable $DataPath. When you do <xsl:for-each select="$DataPath">, the XSLT processor chokes on the fact that DataPath does not contain a node-set, but a string.
Your entire stylesheet seems to be revolve around the idea of dynamically selecting/evaluating XPath expressions. Drop that thought, it is neither possible nor necessary.
Show your XML input and specify the transformation your want to do and I can try to show you a way to do it.