0

Can anyone explain this to me? (using latest libxslt):

 <a><xsl:copy-of select="(@attrib|exsl:node-set(0))"/></a>
 <b><xsl:copy-of select="(@attrib|exsl:node-set(0))[position()=1]"/></b>

 <x><xsl:copy-of select="(@attrib|exsl:node-set(0))[1]"/></x>
 <xsl:variable name="value" select="@attrib"/>
 <y><xsl:copy-of select="($value|exsl:node-set(0))[1]"/></y>

Result (for @attrib = 1 at current node):

 <a attrib="1">0</a>
 <b attrib="1"/>

 <x>0</x>
 <y attrib="1"/>

<a> and <b> show expected behavior.
<x> is IMHO incorrect.
But why does putting @attrib into a variable "fix" it for <y>?

BTW: Everything is correct when @attrib is not present. The copy-of used here is for debugging; original usage converts the XPath result to a number, and a missing attribute shall not lead to NaN, but a certain default value.

Stedy
  • 7,359
  • 14
  • 57
  • 77
smilingthax
  • 5,254
  • 1
  • 23
  • 19
  • I strongly recommend *not* to apply `ext:node-set()` on a scalar (non-node-set or non-RTF) argument. In this case the created node is in its separate document and there is no ordering defined between this node and nodes of other documents -- meaning that it may come as first or last, and this is unpredictable. Also note, that no other `xxx:node-set()` extension accepts a scalar argument. Even some implementations of `ext:node-set()` reject a scalar argument as error: Saxon 6.5.4: "Error at xsl:copy-of on line 7 of file:/(Untitled): exslt:node-set(): argument must be a node-set or tree" – Dimitre Novatchev Dec 17 '11 at 23:33
  • @DimitreNovatchev fair point. Still, using `0` and `$default` instead of `0` does not change much. – smilingthax Dec 17 '11 at 23:44
  • @_smilingthax: Yes, nothing is changed because the child of the `xsl:variable` resides in the stylesheet document and the `arrtib` attribute resides inthe source XML document. There is no ordering defined between nodes that reside in two different XML documents. XPath 1.0 doesn't have sequences, as XPath 2.0 and as you have found, sequences cannot be modelled easily. – Dimitre Novatchev Dec 18 '11 at 00:07
  • @DimitreNovatchev: so you're basically saying, what I try to do depends on undefined behavior in xslt 1.1? Is there any specified difference between `[1]` and `[position()=1]` (i.e. `` and ``)? – smilingthax Dec 18 '11 at 00:39
  • @_smilingthax: To start with, there never has been an XSLT 1.1 W3C Recommendation. I am speaking only about XSLT 1.0. As for `[1]` and `[position() = 1]` the former must be interpreted as a shorthand for the latter. This is as per XPath 1.0 spec: "the result is a number, the result will be converted to true if the number is equal to the context position and will be converted to false otherwise; Thus a location path para[3] is equivalent to para[position()=3]. " see: http://www.w3.org/TR/xpath/#predicates – Dimitre Novatchev Dec 18 '11 at 00:54
  • @DimitreNovatchev: Right, I was using XSLT 1.1 in an informal way to refer to the removal of the Result Tree Fragment type (i.e. obliterating the need for ext:node-set) – smilingthax Dec 18 '11 at 01:05

2 Answers2

1

This version of exsl:node-set() presumably creates a node that is in a different tree from from the node @attrib. When two nodes are in different trees, it is implementation-dependent which of them comes first in document order. The construct (X|Y)[position()=1] (or equivalently (X|Y)[1]) selects whichever of X and Y comes first in document order. Therefore it's essentially unpredictable whether it selects X or Y.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • Ok, that means my only option is using `...`, correct? And `[position()=1]` differs from `[1]` in my example probably because slightly different -- but correct -- code-paths are taken in the implementation? – smilingthax Dec 18 '11 at 00:58
  • @smilingthax: [Re: "that means my only option is using ..., correct?"] . . . No, you still can do this with a single XPath 1.0 expression -- have a look at my answer. – Dimitre Novatchev Dec 18 '11 at 03:28
1

From a comment of the OP:

Ok, that means my only option is using <xsl:choose>..., correct?

While the way you attempt it produces unpredictable results due to reasons explained both by @Michael Kay and by me in my comments, there are still ways to do what you want (produce either the value of the attrib attribute or a default value (0):

concat(@attrib,
       substring(0, 2 -not(@attrib))
       )

This produces the value of the attrib attribute (if this attribute is present) or (if not present) the default value 0.

Complete XSLT solution:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="x">
  <xsl:value-of select=
   "concat(@attrib,
           substring('0', 2 -not(@attrib))
           )"/>

==========
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the following XML document:

<t>
 <x attrib="abc"/>
 <x/>
</t>

the wanted, correct result is produced:

 abc

==========

 0

==========
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • Well, even more general: `@attrib|$default[not(current()/@attrib)]`. – smilingthax Dec 18 '11 at 06:35
  • 1
    @smilingthax: Sure, but this requires the default value to be (in) a node -- sometimes this is not convenient or is undesirable. Being able to use just a string or a number (as shown in my answer) is a little bit more tricky and at the same time more convenient. People need to know and benefit from such techniques. – Dimitre Novatchev Dec 18 '11 at 06:41
  • Every technique has is strengths and weaknesses. E.g. with libxslt I can directly use exsl:node-set('foo') without having to think about substring-offsets. Neither solution is particularly pretty in my eyes, though. But understanding what does not work is sometimes even more important. – smilingthax Dec 18 '11 at 06:56
  • @smilingthax: This isn't a question of "prettiness" but a question of "can this be done". Also, is a particular solution portable. Also, how convenient is the technique. Using `xxx:node-set()`, even the exslt one, causes loss of portability. Having to apply `ext:node-set()` to dynamically obtained values is inconvenient, inflates the code size significantly and makes the code unreadable. On the other side, there are single XPath expressions for conditional choice based on two different values. Such single XPath expressions don't use any extension functions and are fully portable and compact. – Dimitre Novatchev Dec 18 '11 at 17:18