in XSLT, what does <xsl:if test=".[foo or #bar]"> mean and what is the cross-browser solution? - xslt

XSL:
<xsl:if test=".[foo or #bar]">
something
</xsl:if>
is it testing that if target is with 'foo' tag name or has a 'bar' attribute?
it works only in IE, I'm wondering if there is an error. What is the equivalent sentence but works cross-browser?

The syntax .[x] is not allowed in XSLT 1.0. It was allowed in early drafts of XSLT 1.0, and I suspect it was absent from the final version as a result of an oversight rather than being a deliberate design decision (and as already remarked, it was reinstated in XSLT 2.0). Because IE first shipped its XSLT processor before the 1.0 spec was finalized, it's possible that they support this syntax for backwards compatibility with those early drafts.

<xsl:if test=".[foo or #bar]">
something
</xsl:if>
In XSLT 1.0 this is syntactically invalid and produces an error.
In XSLT 2.0 this is equivalent to:
<xsl:if test="foo or #bar">
something
</xsl:if>
The expression:
foo or #bar
evaluates to true() exactly when the context node (current node) has a child element named foo or the context node has an attribute named bar (or both).
Otherwise this expression evaluates to false().
Therefore, the above code snippet means: If either of these conditions is true: the current node has a child element named foo or the current node has an attribute named bar -- then output the string "something"

It tests whether the current node has a child element named foo or has an attribute called bar. That is, if you change it to
<xsl:if test="foo or #bar">
The syntax you use is rejected in my xsl parser: Invalid XPath expression
Unexpected token - "[foo or #bar]"
The one I use should work cross-browser.
If you wish to select nodes that have name foo or a bar attribute, then use the following:
<xsl:if test="name()='foo' or #bar">

Related

How can I use the concat() function on multiple children within an XPath expression

something like this works fine "concat('a', 'b')" but i can't it to work as part of an existing expression, i.e.
<div class='xyz'>
<strong>the</strong>
<sub> one</sub>
</div>
//div[class='xyz']/concat(/strong/text(), /sub/text())
i'd like it to return 'the one'
i'd like it to return 'the one'
In XPath 1.0, you'd have to do:
concat(div[#class='xyz']/strong, div[#class='xyz']/sub)
In XSLT 1.0, you could do:
<xsl:template match="/">
<xsl:for-each select="div[#class='xyz']/*">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
I hope this is XPath 2.0? Otherwise, concat() cannot appear on the rh-side of the / (or either side of |) in an expression, because it returns an atomic value, not a node. This limitation was removed in XPath 2.0 and up.
XPath 2.0
Your expression is almost correct, the difference being the root node. You started your concat-expressions with /strong and /sub, but these elements are not rooted at the document root. The rh-side of the expression takes the focus of the lh-side of the expression, that is, its focus is on div:
//div[#class='xyz']/concat(strong/text(), sub/text())
In addition (thanks to Rubens), you want the attribute class, not the child element class.
If your elements do not contain mixed content and you are interested on all text data inside it, you can simplify as follows:
//div[#class='xyz']/concat(strong, sub)
XPath 1.0
If you are stuck with XPath 1.0, you can do the following, but it is not (quite) the same:
concat(//div[#class='xyz']/strong, //div[#class='xyz']/sub)
If you want to apply this to all divs (as indicated by //) you should resort to XSLT instead.

Saxon error "XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type xs:string"

I have an XSL program which in turn generates an XSL program, which depending on the input might look like this:
<xsl:variable name="patterns"/> <!--empty in this particular case-->
<xsl:template name="token">
<xsl:for-each select="$patterns/pattern">
...
When I then run the generated stylesheet, Saxon, bless its heart, is apparently doing some kind of static analysis and complains:
XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type xs:string
and won't even compile the stylesheet.
My workaround was to generate a dummy element in the $patterns nodeset, but is there any cleaner approach here, or way to suppress the compile error?
According to http://www.w3.org/TR/xslt20/#variable-values, "If the variable-binding element has empty content and has neither a select attribute nor an as attribute, then the supplied value of the variable is a zero-length string.".
So you need to change that, for instance by doing <xsl:variable name="patterns" select="()"/> to bind an empty sequence as the variable value.
In XSLT 1.0 (the same would work also with XSLT 2.0) use:
<xsl:variable name="patterns" select="/.."/>
This provides to the XSLT processor the information, necessary to conclude that the type of the $patterns variable is node-set.

Why do I have to assign this node to a variable in my stylesheet?

I'm working on an XSLT stylesheet. I have a a node (node A) with a bunch of children, and I'm looping through another node's (node B) children. I'm trying to do something each time a child of node B is also a child of node A, so I have this code:
<xsl:if test="$prodbins/bin[./text()=/root/Line[1]/Element[6]/text()]">
But that doesn't work (the test fails; the expression returns false) even though the left and right side of the expression, when evaluated separately, are equal.
But when I do this:
<xsl:variable name="curbin" select="/root/Line[1]/Element[6]/text()"/>
<xsl:if test="$prodbins/bin[./text()=$curbin]">
The expression evaluates to true. Why do I have to use the $curbin variable to get the result I'm expecting?
Can you try <xsl:if test="$prodbins/bin[./text()=current()/root/Line[1]/Element[6]/text()]"> (notice current() function). The reason why it does not work in your original expression is that because you query a variable and / looks up the root node of the content of the variable and not the source document you are transforming. current() should return the context element for the template you are in.
My guess is that $prodbins/bin is a node(-set) belonging to a different document than the document that contains the nodes that are being compared to.
In the expression:
$prodbins/bin[./text()=/root/Line[1]/Element[6]/text()]
the subexpression
/root/Line[1]/Element[6]/text()
selects from the same document as the one that is the document from which the $prodbins/bin nodes are selected.
One way to specify successfully the wanted comparisson is:
<xsl:variable name="vDoc" select="/"/>
<xsl:if test="$prodbins/bin[./text()=$vDoc/root/Line[1]/Element[6]/text()]">

Using dynamic xpath in XSLT

I am using a variable say xpathvar in the XSLT whose value will be supplied at the time of call to XSLT. How can i achieve this?
My XSLT file looks like -
<xsl:param name="xpathvar"/>
<xsl:param name="keyxpath"/>
<xsl:template match="/">
<listofResults>
<xsl:for-each select="$xpathvar">
<keyvalues><xsl:value-of select="xalan:evaluate(substring-before($keyxpath,'||'))"/></keyvalues>
<keyvalues><xsl:value-of select="xalan:evaluate(substring-after($keyxpath,'||'))"/></keyvalues>
</xsl:for-each>
</listofResults>
</xsl:template>
</xsl:stylesheet>
If I mention the variable in the for-each, it throws error. Please guide how can I achieve this.
Thanks!
According to Global xsl:param containing xpath expression string it can't be done purely with plain old XSLT, you need to use the evaluate extension see: Xalan evaluate expression
I dont think this can be done like this you must use xpath so example would be
<template match="Node[name=$xpathvar]" />
<xsl:for-each select="*[name()=$xpathvar]">
</xsl:for-each>
I think it does not let you use the variable directly because it is not a nodeset.
If I mention the variable in the
for-each, it throws error
In XSLT 1.0 (it looks you are using Xalan), you can iterate over node sets, only. So $xpathvar should be an instance of node set data type.
In XSLT 2.0 you can iterate over sequence (including scalar values).
Also, if the string containing the "dynamic" XPath expression is simple enough (only QName test step, maybe positional predicates) this could be done (as is already answered in SO) with standar XSLT.

XSLT: attribute value used as numeric predicate

Given
<xsl:variable name="datePrecision" as="element()*">
<p>Year</p>
<p>Month</p>
<p>Day</p>
<p>Time</p>
<p>Timestamp</p>
</xsl:variable>
The expression
$datePrecision[5]
returns a nodeSet containing one text node with value "Timestamp", as expected.
Later in a template, with a context element having an attribute
#precision="5"
I try the following expressions but all return an empty string:
$datePrecision[#precision]
$datePrecision[number(#precision)]
$datePrecision[xs:decimal(#precision)]
However, the following sequence does what I want
<xsl:variable name="prec" select="number(#precision)"/>
... $datePrecision[$prec] ...
Using Oxygen/XML's debugger I've stepped to the point where the expression is about to be evaluated and display the following in the watch window:
Expression Value Nodes/Values Set
-------------------------- --------------- -----------------------
$datePrecision[5] Node Set(1) #text Timestamp
#precision Node Set(1) precision 5
$datePrecision[#precision]
number(#precision) 5
$datePrecision[number(#precision)]
$prec 5
$datePrecision[$prec] Node Set(1) #text Timestamp
Obviously I've missed something fundamental about how attribute nodes are atomized for use in a predicate, but can't find anything in the docs (Michael Kay's XSLT/XPATH 2.0, 4th ed) that would explain this difference.
Can someone explain why this is occurring, and point me to where, in either the XSLT 2.0 spec or Michael Kay's book, where this is described?
(the XSLT processor is Saxon-PE 9.2.0.3)
Obviously I've missed something
fundamental
Yes. The XPath expression:
$datePrecision[#precision]
means: all elements in $datePrecision that have an attribute named precision.
But you want #precision to mean the attribute named precision of the currnet node that is matched by the template.
XSLT provides the current() function exactly for this purpose. Use:
$datePrecision[current()/#precision]
UPDATE: As Martin Honnen hinted, the OP probably wants to get the 5th element out of $datePrecision -- something not immediately visible from the description of the problem. In this case, it may be necessary to use:
$datePrecision[position() = current()/#precision]