We have run into another xslt bug, which depends on several independent circumstances and often behaves differently being observed. That's clearly a Heisenbug.
Xslt designers failed to realize that a syntactic suggar they introduce into xpath can turn into obscure bugs. Well, it's easy to be wise afterwards...
To the point.
Consider you have a sequence consisting of text nodes and elements, and now you want to "normalize" this sequence wrapping adjacent text nodes into separate elements. The following stylesheet is supposed to do the work:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:t="http://www.nesterovsky-bros.com/xslt/this" exclude-result-prefixes="xs t"> <xsl:template match="/"> <xsl:variable name="nodes" as="node()*"> <xsl:text>Hello, </xsl:text> <string value="World"/> <xsl:text>! </xsl:text> <xsl:text>Well, </xsl:text> <string value="hello"/> <xsl:text>, if not joking!</xsl:text> </xsl:variable> <result> <xsl:sequence select="t:normalize($nodes)"/> </result> </xsl:template> <xsl:function name="t:normalize" as="node()*"> <xsl:param name="nodes" as="node()*"/> <xsl:for-each-group select="$nodes" group-starting-with="*"> <xsl:variable name="string" as="element()?" select="self::string"/> <xsl:variable name="texts" as="node()*" select="current-group() except $string"/> <xsl:sequence select="$string"/> <xsl:if test="exists($texts)"> <string value="{string-join($texts, '')}"/> </xsl:if> </xsl:for-each-group> </xsl:function> </xsl:stylesheet>
We're expecting the following output:
<result> <string value="Hello, "/> <string value="World"/> <string value="! Well, "/> <string value="hello"/> <string value=", if not joking!"/> </result>
But often we're getting other results, like:
<result> <string value="Hello, "/> <string value="World"/> <string value="Well, ! "/> <string value="hello"/> <string value=", if not joking!"/> </result>
Such output may seriously confuse, unless you will recall the rule for the xpath except operator:
except
The except operator takes two node sequences as operands and returns a sequence containing all the nodes that occur in the first operand but not in the second operand. ... these operators eliminate duplicate nodes from their result sequences based on node identity. The resulting sequence is returned in document order.. ... The relative order of nodes in distinct trees is stable but implementation-dependent
These words mean that result sequence may be very different from original sequence.
In contrast, if we change $text definition to:
$text
<xsl:variable name="texts" as="node()*" select="current-group()[not(. is $string)]"/>
then the result becomes stable, but less clear.
See also Xslt Heisenbug
Remember Me
a@href@title, b, blockquote@cite, em, i, strike, strong, sub, super, u