Why is there no sibling axis?

2019-08-06 04:20发布

Looking at the available axes in XSLT I had to find out that there is no sibling axis which would be the union of preceding-sibling and following-sibling. To me this is a little surprising since I already wrote one answer (XSLT issue...CSV issue.?) in which this axis would have been helpful (although I only have about 10 answers so far). Of course, it is obvious that you can always solve the problem by using the union. So this axis is not really required. But it would be very handy every once in a while and like all the other axes IMHO it would make the code more readable and easier to maintain.

Does anybody know why this axis was left out? Is there maybe a non-obvious reason for this?

By the way: I found at least one issue on StackExchange with a warning about a potential performance degrade using the preceding-sibling and following-sibling axes. But I assume this is true for all the axes containing a substantial portion of the XML tree is used in a nested way. So the reason for omission could not have been due to performance.

标签: xslt
1条回答
女痞
2楼-- · 2019-08-06 04:57

Since there has been no activity with this question for a while I would like to answer it myself. Picking up one thought in the comments, it is, of course, hard to retrospectively say why the people responsible of the XSLT 1.0 specification omitted the sibling axis.

One of the most conclusive reasons could have been related to the comments by @JLRiche and @MichaelKay: axis are supposed to go into a specific direction with respect to the reference node and it may be difficult to determine what the direction for sibling would be.

In order to investigate this a little further I set up a test XSLT and a test input XML to check how the axes work (see further below) and in particular what the order of the nodes in the axes are. The result was surprising to me:

  • The preceding-sibling axes does not start at the node closest to the reference node but with node closest to the start of the document.
  • The following-sibling does start at the reference node.

This would actually allow to define

sibling := preceding-sibling | following-sibling

with the nodes in this set being continuously iterated from the beginning of the document to the end. There would be no "jump".

The suggested alternative

../node except .

also works well and yields the same set in the same ordering. However, looking at an unfamiliar XSLT I would assume that a sibling axis would explain the logic better than using the parent-children construct.

Interestingly, the fact that axes do not start at the node closest to the reference node but at the node closest the beginning of the document also applies to preceding and ancestor so for example ancester::node[1] does not return the parent of the node but the root node.

The original motivation for me to ask the question was related to not having to repeat a lengthy CONDITION imposed on the attributes of the nodes, e.g. I did not want to write

preceding-sibling::node[CONDITION] | following-sibling::node[CONDITION]

However, since the expression above can be rewritten as

(preceding-sibling::node | following-sibling::node)[CONDITION]

the disadvantage of having to use two axes instead of a sibling axis is not as bad as thought. Of course, in XSLT 2.0 this also works for

(../node except .)[CONDITION]

So, to answer my question: I don't think there is a good reason not to define a sibling axis. I guess nobody thought of it. :-)

Test Setup

This XML test input

<?xml version="1.0" encoding="ISO-8859-1"?>
<node id="1">
  <node id="2">
    <node id="3">
      <node id="4"/>
      <node id="5"/>
      <node id="6"/>
    </node>
    <node id="7">
      <node id="8"/>
      <node id="9"/>
      <node id="10"/>
    </node>
    <node id="11">
      <node id="12"/>
      <node id="13"/>
      <node id="14"/>
    </node>
  </node>    
  <node id="15">
    <node id="16">
      <node id="17"/>
      <node id="18"/>
      <node id="19"/>
    </node>
    <node id="20">
      <node id="21"/>
      <node id="22"/>
      <node id="23"/>
    </node>
    <node id="24">
      <node id="25"/>
      <node id="26"/>
      <node id="27"/>
    </node>
  </node>
  <node id="28">
    <node id="29">
      <node id="30"/>
      <node id="31"/>
      <node id="32"/>
    </node>
    <node id="33" value="A">
      <node id="34"/>
      <node id="35"/>
      <node id="36"/>
    </node>
    <node id="37">
      <node id="38"/>
      <node id="39"/>
      <node id="40"/>
    </node>
    <node id="41">
      <node id="42"/>
      <node id="43"/>
      <node id="44"/>
    </node>
    <node id="45" value="A">
      <node id="46"/>
      <node id="47"/>
      <node id="48"/>
    </node>
  </node>
</node>

using this XSLT 2.0 sheet

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes" />
  <xsl:strip-space elements="*"/>

  <xsl:variable name="id" select="'37'"/>

  <xsl:template name="dump">
    <xsl:text> </xsl:text>
    <xsl:value-of select="@id"/>
  </xsl:template>

  <xsl:template match="//node[@id = $id]">

    <xsl:text>preceding siblings: </xsl:text>
    <xsl:for-each select="preceding-sibling::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;following siblings: </xsl:text>
    <xsl:for-each select="following-sibling::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;preceding and following siblings: </xsl:text>
    <xsl:for-each select="preceding-sibling::node | following-sibling::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;preceding and following siblings with value A: </xsl:text>
    <xsl:for-each select="(preceding-sibling::node | following-sibling::node)[@value = 'A']">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;following siblings: </xsl:text>
    <xsl:for-each select="following-sibling::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;parent's children: </xsl:text>
    <xsl:for-each select="../node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;parent's children except self: </xsl:text>
    <xsl:for-each select="../node except .">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;parent's children except self with value A: </xsl:text>
    <xsl:for-each select="(../node except .)[@value = 'A']">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;ancestors: </xsl:text>
    <xsl:for-each select="ancestor::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;immediate ancestor: </xsl:text>
    <xsl:for-each select="(ancestor::node)[1]"> 
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;ancestors or self: </xsl:text>
    <xsl:for-each select="ancestor-or-self::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;descendants: </xsl:text>
    <xsl:for-each select="descendant::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;descendants or self: </xsl:text>
    <xsl:for-each select="descendant-or-self::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;preceding: </xsl:text>
    <xsl:for-each select="preceding::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
    <xsl:text>&#10;following: </xsl:text>
    <xsl:for-each select="following::node">
      <xsl:call-template name="dump"/>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

will yield this output

preceding siblings:  29 33
following siblings:  41 45
preceding and following siblings:  29 33 41 45
preceding and following siblings with value A:  33 45
following siblings:  41 45
parent's children:  29 33 37 41 45
parent's children except self:  29 33 41 45
parent's children except self with value A:  33 45
ancestors:  1 28
immediate ancestor:  1
ancestors or self:  1 28 37
descendants:  38 39 40
descendants or self:  37 38 39 40
preceding:  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 29 30 31 32 33 34 35 36
following:  41 42 43 44 45 46 47 48
查看更多
登录 后发表回答