How to do a "while"-like loop in XSLT? - sql

I'm a novice in XSLT and I'm struggling a lot with this issue: I need to do a while-like loop in XSLT. I don't think the for-each will be enough to solve this problem.
I have a variable that is the result of a SELECT statement. It can return 0 or an integer. If the value is 0, it needs to do the SELECT again sending another parameter to see if the value is different.
I can only think in using a while-like loop, but maybe it has another way of achieving this? Like using a template and calling itself in the end? Is it possible?
Something like that:
<!-- initiate TEMPLATE -->
<!-- WHILE $VALUE = 0 -->
<xsl:variable name="sql.query">
<sql:param name="SQL_QUERY">SELECT $value FROM date_table WHERE date='$date'</mx:param>
</xsl:variable>
<xsl:variable name="VALUE">
<xsl:value-of select="sql:exec-formula('generic.sql', exsl:node-set($sql.query)//sql:param)" /> <!-- this will bring the result of the SELECT in the variable -->
</xsl:variable>
<xsl:variable name="date">
<xsl:value-of select="$date-1" /> <!-- something like that, it doesn't matter -->
</xsl:variable>
<xsl:if test="$VALUE ='0'">
<!-- call template again -->
</xsl:if>
<!-- end of template -->

<!-- recursive named template -->
<xsl:template name="while">
<xsl:variable name="VALUE">
<!-- your evaluation -->
</xsl:variable>
<!-- evaluate and recurse -->
<xsl:if test="$VALUE=0">
<xsl:call-template name="while"/>
</xsl:if>
</xsl:template>

Like using a template and calling itself in the end?
Correct: tail recursion is the usual solution to this problem, as illustrated by empo. The better XSLT processors will optimize tail recursion so it doesn't comsume the stack. The worse ones will run out of stack space after 500 or so iterations, in which case you need to look for a different solution.
Note: try to avoid this kind of construct
<xsl:variable name="date">
<xsl:value-of select="$date - 1" />
</xsl:variable>
when you could do
<xsl:variable name="date" select="$date - 1" />
It's unnecessarily verbose, and can also be very inefficient because the variable value is a tree rather than a simple string or number.

Related

How to preserve attributes in xsl:variable body? [duplicate]

This question already has an answer here:
Setting a variable conditionally with no cut of ancestor axis
(1 answer)
Closed 5 months ago.
data
<mydoc>
<setting>foo</setting>
<foo sound="aaaa"/>
<bar sound="brrr"/>
<cat sound="meow"/>
</mydoc>
if no conditional logic is needed,
<xsl:variable name="result1" select="/mydoc/foo"/>
works fine.
but if I need conditional logic,
<xsl:variable name="result2">
<xsl:choose>
<xsl:when test="/setting='foo'">
<!-- what to put here -->
</xsl:when>
<xsl:when test="/setting='cat'">
<!-- what to put here -->
</xsl:when>
<xsl:otherwise>
<!-- what to put here -->
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
desired output:
when setting = 'foo', result = <foo sound="aaa"/> (the whole element complete with attribute)
when setting = 'cat', result = <cat sound="meow"/> (the whole element complete with attribute)
otherwise, result = <bar sound="brrr"/> (the whole element complete with attribute)
How do I get the result2 variable to be an element with attributes, as it does in result1? I want to use $result2/sound, and some other elements / attributes of $result2 and / or it's children.
<xsl:variable name="result2" select="/mydoc[setting='foo']/foo |
/mydoc[setting='cat']/cat |
/mydoc[not(setting='foo' or setting='cat')]/bar"/>
Because of the conditions in square brackets exactly one member of the union will effectively be selected.
If you put something inside the <xsl:variable> element rather than use select, your variable will contain a result tree fragment, not a nodeset.
If you insist on using xsl:choose (why?) then the only option open to you is to use xsl:copy. However, this has some side effects:
This will create a copy of the selected node/s and make it a child of the variable; in effect, it will create a separate document and the copied nodes will no longer be descendants of the XML input's root;
The variable will be a result-tree-fragment and you won't be able to process it in any way unless you convert it to a node-set first. Here, "process" includes accessing individual nodes inside the variable.
The following example demonstrates this:
XML
<mydoc>
<setting>foo</setting>
<foo bar="a"/>
<bar foo="b"/>
</mydoc>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/mydoc">
<xsl:variable name="temp">
<xsl:choose>
<xsl:when test="setting='foo'">
<xsl:copy-of select="foo"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="bar"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<output>
<xsl:value-of select="exsl:node-set($temp)/foo/#bar" />
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>a</output>
Do note that if you change:
<xsl:value-of select="exsl:node-set($temp)/foo/#bar" />
to:
<xsl:value-of select="$temp/foo/#bar" />
you will get an error.

How to put double and single quote in value of AllowedSymbols variable to use in XSLT Translation

Using XSLT 1.0, in the XLST template below, I want to add the single and double quote to the list of allowed values. Getting error on vAllowedSymbols2 saying that "string literal not closed".
<xsl:template name="CleanAlphaField">
<xsl:param name="inputText" />
<xsl:param name="maxLength" />
<xsl:variable name="vAllowedSymbols2" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !#$%()+-_,.;:=[]{}\?"&apos;'"/>
<xsl:variable name="vAllowedSymbols" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !#$%()+-_,.;:=[]{}\?'"/>
<xsl:variable name="truncatedInputText" select="substring($inputText,1,$maxLength)" />
<!-- return the revised string -->
<xsl:value-of select="translate($truncatedInputText,translate($truncatedInputText, $vAllowedSymbols, ''),'')"/>
</xsl:template>
You might need to create these as separate variables for XML escaping reasons.
<xsl:variable name="singleQuote" select='"&apos;"' />
<xsl:variable name="doubleQuote" select="'"'" />
Having done that, you can concat these together
<xsl:variable name="vAllowedSymbols2"
select="concat($vAllowedSymbols, $singleQuote, $doubleQuote)" />
This happens because the XML entity expansion happens before things reach the XSLT processor, so in the minimal case
<xsl:variable name="invalid" select="'&apos;'" />
the value of #select gets expanded and the XSLT engine sees an attribute (name={}select, value=''') and doesn't know that it came from an entity expansion; it just knows that three single quotes doesn't make a valid XPath expression.
You could do simply:
<xsl:variable name="vAllowedSymbols2">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !#$%()+-_,.;:=[]{}\?"'</xsl:variable>

How to multiply numbers with each other in xslt 1.0

I have a number for example 123, this is stored in an element let's say
<Number>123</Number>. I am looking for a solution written in XSLT 1.0 which can do something like: 1*2*3 and provide me the result as 6. The value in Number element can be in any length. I know i can do this through substring function and by storing the values one by one in variables but the problem is, i dont know the length of this field.
I could not write any xslt for this.
Can anyone help or suggest a solution for this?
You can do this by calling a recursive named template:
<xsl:template name="digit-product">
<xsl:param name="digits"/>
<xsl:param name="prev-product" select="1"/>
<xsl:variable name="product" select="$prev-product * substring($digits, 1, 1)" />
<xsl:choose>
<xsl:when test="string-length($digits) > 1">
<!-- recursive call -->
<xsl:call-template name="digit-product">
<xsl:with-param name="digits" select="substring($digits, 2)"/>
<xsl:with-param name="prev-product" select="$product"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$product"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Working example: http://xsltransform.net/3NJ38YJ
Note that this assumes the value of the passed digits parameter is an integer.

Looping a variable length array with namespaces in XSLT

My previous question[1] is related to this. I found the answer for that. Now I want to loop a variable length array with namespaces. My array:
<ns:array xmlns:ns="http://www.example.org">
<value>755</value>
<value>5861</value>
<value>4328</value>
<value>2157</value>
<value>1666</value>
</ns:array>
My XSLT code:(have added the namespace in the root)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://www.example.org">
<xsl:template match="/">
<xsl:variable name="number" select="ns:array" />
<xsl:for-each select="$number">
<xsl:value-of select="$number" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
[1]https://stackoverflow.com/questions/20287219/looping-a-variable-length-array-in-xslt
IMHO you confused yourself by introducing a variable called number which actually contains a node set of value tags. Then, as a consequence you used your variable as singe item/node which does not yield the desired result (presumingly, since you did not really tell us what you want to do with the values).
Also, I think your question does not really have anything to with namespace issues as such. You just have to make sure that the namespaces in your select expressions match the namespaces in your input file.
I would suggest to do without the variable and change the way you retrieve the current value:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://www.example.org">
<xsl:template match="/">
<xsl:for-each select="ns:array">
<!-- Inside here you can work with the `value` tag as the _current node_.
There are two most likely ways to do this. -->
<!-- a) Copy the whole tag to the output: -->
<xsl:copy-of select="." />
<!-- or b1) Copy the text part contained in the tag to the output: -->
<xsl:value-of select="." />
<!-- If you want to be on the safe side with respect to white space
you can also use this b2). This would handle the case that your output
is required not to have any white space in it but your imput XML has
some. -->
<xsl:value-of select="normalize-space(.)" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

xslt - how to create a variable with text content of space characters of a dynamically determined length that will perform well

I would like to create a variable that contains a text value that is a number of space characters but the number of characters is not known until runtime. This needs to be done a great many times so i would like to use something that will perform very well.
One option is substring() function on a previously declared node, but this limits the length to no more than that of the original text.
Another would be to use a recursive template with concat() function, but not sure about the performance implications.
What is a way to do this that will perform very well?
I would create a global variable of length say 4000 spaces. Then if the desired string is less than 4000 spaces, use substring(); if greater, use a recursive approach as outlined by Durkin. In 2.0 of course the code is much simpler to write but choosing an approach that performs well is still an interesting little problem.
You can use recursion and divide and conquer. This will give you run-time cost of order (log N). Alternatively, if your XSLT processor implements tail-end recursion optimisation, then you can use a tail-end recursion solution for better safety at scale. Here are the two solutions...
For non tail-end recursion optimising XSLT processors...
<xsl:template name="spaces">
<xsl:param name="count" />
<xsl:choose>
<xsl:when test="$count <= 10">
<xsl:value-of select="substring(' ',1,$count)" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="spaces">
<xsl:with-param name="count" select="$count idiv 2" />
</xsl:call-template>
<xsl:call-template name="spaces">
<xsl:with-param name="count" select="$count - ($count idiv 2)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
For tail-end recursion optimising XSLT processors...
<xsl:template name="spaces">
<xsl:param name="count" />
<xsl:choose>
<xsl:when test="$count <= 10">
<xsl:value-of select="substring(' ',1,$count)" />
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
<xsl:call-template name="spaces">
<xsl:with-param name="count" select="$count - 10" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
For an efficient (both time and space) XSLT 1.0 solution, see this:
http://www.sourceware.org/ml/xsl-list/2001-07/msg01040.html