setting flag in XSLT to detect the first time through a loop - variables

New to xslt. Using it in umbraco, so it's version 1.0.
Trying to insert separators between links in an inline footer list, so will put them before each link except the first one. This is a simple programming problem, where you set a flag to 0 outside the loop, then once in, you test if the flag is 1 and if it's not, you skip adding a separator before the link. As the last bit in the first pass through the loop, you set the flag to 1.
How can I accomplish this in xslt? I'm a bit flummoxed by the syntax and reading that once a variable value is set you can't change it. Anyone have a simple exmaple?

Here is a simple example:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pBase" select="'http://myBase.com/'"/>
<xsl:template match="num">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
01, 02, 03, 04, 05, 06, 07, 08, 09, 10
and it is displayed in the browser as:
01, 02, 03, 04, 05, 06, 07, 08, 09, 10
Explanation:
XSLT is a functional language -- among other things this means that variables, once defined, cannot be changed.
The standard XPath position() function can be used to check if the position of the current node has a specific value.
The builtin XSLT templates are used and the one that matches an element has this body: <xsl:apply-templates/> . This is an abbreviation for: <xsl:apply-templates select="child::node()"/> and applies templates to all node children of the current node -- in document order.
Because we have specified <xsl:strip-space elements="*"/>, the whitespace-only children of any element has been stripped during the parsing of the XML document. This leaves only non-white-space children nodes of the top element nums and they are all num elements.
The value of the position() function inside a template is the position of the current node in the node-list formed when the <xsl:apply-templates> instruction is executed. This means that the value of the position() function inside the template matching num is 1 when the first num element is processed, 2 when the 2nd num element is processed, ..., 10 when the 10th num element is processed.
The generated output for all num elements where the value of position() is greater than 1 starts with the string ", " -- exactly as per the requirements of this question. For the first num element the string ", " isn't generated, because the value of position() in this case is 1.

There is no such thing as time in XSLT. You can't detect the "first time through a loop" because there is no concept of time, therefore no first time, in fact, it's not a loop in the traditional programming sense at all. All the items in the input list are conceptually processed in parallel. That's why you can't set variables for use in later iterations - there is no such thing as a "later iteration".
What you can do is to detect that you are processing the first item in the input sequence (or the first item in the input sequence after sorting, if you use xsl:sort). You do that with the test position() = 1.

Related

XSLT: variables and "empty" labels

I have an XML datafile containing among other things a string of arbitrarily many comma separated values. I want those values to be displayed in a web browser as a list with one value per line. So I wrote an XSLT template that takes this string, displays the first value followed by a linebreak tag (<br/>), properly name-spaced, and resources with the remainder of the string. In effect, the commas are being replaced by HTML <br/> tags.
Now, when I store the result of calling that template in a xsl:variable, and display that through xsl:value-of, then the HTML tags disappear: what is shown is the string minus the commas.
When I display the result directly by having the xsl:call-template in place of the xsl:value-of, all is fine, and the values appear in a list.
So, what's going on?
Is this behavior an implementation artifact, or is it standard XSLT?
Use xsl:copy-of instead of xsl:value-of if you want to output nodes (like your br elements), xsl:value-of creates a simple text node with the string value(s) selected.
Here is an example that shows the difference between xsl:value-of and xsl:copy-of, you will note that it is not the use of the variable with newly created br elements that makes the difference, it is simply the use of xsl:value-of that creates a text() node with the string conversion of the selection:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes" version="5" doctype-system="about:legacy-doctype"/>
<xsl:variable name="var">Phrase 1.<br/>Phrase 2.<br/>Phrase 3.</xsl:variable>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<section>
<h1>Example 1: value-of</h1>
<xsl:value-of select="$var"/>
</section>
<section>
<h1>Example 2: copy-of</h1>
<xsl:copy-of select="$var"/>
</section>
<xsl:apply-templates select="//p"/>
<xsl:apply-templates select="//p" mode="copy-of"/>
</body>
</html>
</xsl:template>
<xsl:template match="p">
<section>
<h1>Example 1: value-of</h1>
<xsl:value-of select="."/>
</section>
</xsl:template>
<xsl:template match="p" mode="copy-of">
<section>
<h1>Example 1: copy-of</h1>
<xsl:copy-of select="."/>
</section>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/gWmuiJy/1
Output is
Example 1: value-of
Phrase 1.Phrase 2.Phrase 3.
Example 2: copy-of
Phrase 1.
Phrase 2.
Phrase 3.
Example 1: value-of
Line 1.Line 2.Line 3.
Example 1: copy-of
Line 1.
Line 2.
Line 3.
It seems that you hit the boundaries of the RTF ("Result tree fragment"):
When you use an XML fragment to initialize a variable or a parameter, then the variable or parameter is of the
"result tree fragment" datatype. This is an XSLT 1.0 specific datatype [just like node-set, but slightly different].
A result tree fragment is equivalent to a node-set that contains just the root node.
You cannot apply operators like "/", "//" or predicate on a result tree fragments. They are only applicable for node-set datatypes.
[...]
a) In XSLT 1.0
The resolution of this is to convert the result tree fragment into a node-set. I am not aware of any oracle specific xpath extension functions that can do this trick for you.
You could use EXSLT to achieve this.
b) Use XSLT 2.0
You can code your transformations in XSLT 2.0. XSLT 2.0 deprecates ResultTreeFragments i.e. if you are modeling an XSLT 2.0 transformation, and you create a variable or a parameter that holds a tree fragment, it is implicitly a node sequence.
So without using an XSLT version greater than 1, you're out of luck. So better use XSLT-2.0 or 3.0 to solve this problem.
Is this behavior an implementation artifact, or is it standard XSLT?
It is standard for XSLT-1.0, but not for XSLT-2.0+.

XSLT decimal-format causes exception

I am trying to use the xslt:decimal-format element, but I get the same error message whether I use my own code or the example code provided by w3schools.com. This is the w3 sample code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:decimal-format name="euro" decimal-separator="," grouping-separator="."/>
<xsl:template match="/">
<xsl:value-of select="format-number(26825.8, '#,###.00', 'euro')"/>
</xsl:template>
</xsl:stylesheet>
And this is the XsltException it produces when I run it in Visual Studio 2010:
"Format '#,###.00' cannot have zero digit symbol after digit symbol after decimal point."
What's wrong on my side that causes this error?
You have changed the decimal format, called "euro" so that a valid number looks like this "1.232,99" (one thousand, two hundred and thirty two, point nine-nine in words). This does not match the format you have requested which is "#,###.00".
Change your format-number pattern to "#.###,00"

Extract strings between two special charecter($) using XSLT 1.0

Input: "abcdef$ghi$jklmno$pqrst$ wx $yx$"
Expected output:
<tokens>
<token>$ghi$</token>
<token>$pqrst</token>
<token>$yx$</token>
</tokens>
As per sample input string, output must be like Expected output by using XSLT 1.0
Thanks in Advance
For each pair of dollar signs (1,2), (3,4), ... (2 × n - 1, 2 × n) you want a token element with the text between dollar sign number 2 × n - 1 and dollar sign number 2 n.
One of two cases will necessarily apply.
Case 1: there is some fixed maximum value of n, and it's relatively small (say, less than 5 or 10). In this case a simple sequence of xs:variable assignments with appropriate calls to substring-before and substring-after should suffice, and you're done. You'll end up with something like this:
Let s0 be the original string, s1 the substring after the first dollar sign in s0, t1 the substring before the first dollar sign in s1, and r1 the substring after the first dollar sign in s1.
Now let s2 be the substring after the first dollar sign in r1, t2 the substring before the first dollar sign in s2, and r2 the substring after the first dollar sign in s2.
Repeat as needed: for i in 3 to n, let si be the substring after the first dollar sign in ri - 1, ti the substring before the first dollar sign in si, and ri the substring after the first dollar sign in si.
Case 2: there is no maximum n. In this case the straightforward approach in XSLT 1.0 is to write a named template which (a) drops the substring before the first dollar sign, (b) constructs a token element containing a dollar sign, the string after the first dollar sign and before second dollar sign, and (c) calls itself recursively with the substring after the second dollar sign. Add appropriate boundary conditions for empty strings, strings with odd numbers of dollar signs, and other input errors, and you're done.
So: go read up on the string functions of XSLT 1.0 and solve your problem. Come back (and show your work) if you have further trouble.
Hope the below xsl helps you. It works. I tried it myself.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="input">
<xsl:text>abcdef$ghi$jklmno$pqrst$ wx$yx$</xsl:text>
</xsl:variable>
<tokens>
<xsl:text>
</xsl:text>
<xsl:analyze-string select="$input" regex="(\$[a-z]+\$)">
<xsl:matching-substring>
<token>
<xsl:value-of select="regex-group(1)"/>
</token>
<xsl:text>
</xsl:text>
</xsl:matching-substring>
</xsl:analyze-string>
</tokens>
</xsl:template>
</xsl:stylesheet>

XSLT Difference between value-of select and variable select

I need to flip the elements of two nodes. Originally the variable were set with the following command:
<xsl:variable name="matchesLeft" select="$questionObject/descendant::simpleMatchSet[position()=1]/simpleAssociableChoice"/>
<xsl:variable name="matchesRight" select="$questionObject/descendant::simpleMatchSet[position()=2]/simpleAssociableChoice"/>
I now want to flip the variable with the following code:
<xsl:variable name="matchesRight">
<xsl:choose>
<xsl:when test="$flippedQuestions='true'">
<xsl:value-of select="$questionObject/descendant::simpleMatchSet[position()=2]/simpleAssociableChoice"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$questionObject/descendant::simpleMatchSet[position()=1]/simpleAssociableChoice"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
But it only get the value from the first element and not all elements in the node. How can I achive this?
The problem is that xsl:variable/#select gives you a node-set, but xsl:value-of turns a node-set into its string value. You want the node-set. In XSLT 1.0, an xsl:variable with content will always give you a result-tree-fragment; but in the select attribute you're confined to using XPath 1.0 which has no conditional expression.
The best solution of course is to move to XSLT 2.0 which solves all these problems. The number of valid reasons for staying with 1.0 is reducing all the time. If you do have to stay with 1.0, there are convoluted workarounds in XPath 1.0 for the absence of a conditional expression, such as the one shown by Dimitre.
Use:
<xsl:variable name="matchesRight" select=
"$questionObject/descendant::simpleMatchSet
[1+($flippedQuestions='true')]
/simpleAssociableChoice"/>
Explanation:
In XPath whenever a boolean value $someBVal is passed to a numeric operator such as +, the boolean is converted to a number (either 0 or 1) using number($someBVal).
By definition:
number(false()) = 0
and
number(true()) = 1
Thus
1+($flippedQuestions='true')
evaluates to 1 if the string value of flippedQuestions isn't the string "true" and the same expression evaluates to 2 if the string value of flippedQuestions is the string "true" .

Limitations of Eric van der Vlist's RelaxNG simplicification

All,
I am trying to simplify a RelaxNG schema using Eric van der Vlist's simplification.xsl, but I'm getting errors:
runtime error: file ./simplification.xsl line 741 element element
xsl:element: The effective name '' is not a valid QName.
runtime error: file ./simplification.xsl line 751 element element
xsl:element: The effective name '' is not a valid QName.
runtime error: file ./simplification.xsl line 759 element element
xsl:element: The effective name '' is not a valid QName.
runtime error: file ./simplification.xsl line 759 element element
xsl:element: The effective name '' is not a valid QName.
runtime error: file ./simplification.xsl line 759 element element
xsl:element: The effective name '' is not a valid QName.
It seems it has something to do with some names getting constructed dynamically:
<xsl:template match="rng:start[not(preceding-sibling::rng:start) and following-sibling::rng:start]" mode="step7.18">
<xsl:copy>
<xsl:apply-templates select="#*" mode="step7.18"/>
<xsl:element name="{parent::*/rng:start/#combine}">
<xsl:call-template name="start7.18"/>
</xsl:element>
</xsl:copy>
</xsl:template>
I haven't started getting into it any deeper, but perhaps someone already has a clue on what might be causing this.
Is there some reason not to use jing -s instead?
Apparently, I wasn't the first one to run into these issues. This web site also refers to some problems running simplification.xsl, and includes some fixes. I'm just copying it in here, for future reference.
In step 10 : prefix "rng:" was missing in lines <xsl:with-param name="node-name" select="'rng:group'"/> resulting with markups <group> with default namespace (which is not RelaxNG) in output.
In step 14 : add concatenation of prefix "rng:" before value of "combine" attributes in lines <xsl:param name="node-name" select="concat('rng:',parent::*/rng:start/#combine)"/>.
In step 14 : In template <xsl:template match="rng:start[not(preceding-sibling::rng:start) and following-sibling::rng:start]"> I removed the element addition <xsl:element name="{parent::*/rng:start/#combine}"> because it results to an extra <rng:choice> surrounding the other <rng:choice> inside the <rng:start>.
In step 15 : seems that template <xsl:template match="/*"> has a higher priority than template <xsl:template match="/rng:grammar">, so I had to add a precision : <xsl:template match="/*[not(self::rng:grammar)]">.
In step 15 : missing "rng:parentRef/#name" in erasing template : <xsl:template match="rng:define|rng:define/#name|rng:ref/#name|rng:parentRef/#name"/> in order to keep the id generated in "name" attribute of <parentRef>.
After copying in my original RelaxNG grammar in the given web site, the whole transformation completes without any problem.