开发者

XPath for selecting parent nodes based on contents of current node?

开发者 https://www.devze.com 2022-12-10 04:00 出处:网络
I have an XML document with a section similar to the following: <release_list> <release> <id>100</id>

I have an XML document with a section similar to the following:

<release_list>
  <release>
    <id>100</id>
    <file_list>
      <file>
        <id>20</id>
      </file>
      <file>
        <id>21</id>
      </file>
    </file_list>
  </release>
  <release>
    <id>101</id>
    <file_list>
      <file>
        <id>22</id>
      </file>
      <file>
        <id>21</id>
      </file>
    </file_list>
  </release>
  <release>
    <id>102</id>
    <file_list>
      <file>
        <id>22</id>
      </file>开发者_运维问答
      <file>
        <id>23</id>
      </file>
    </file_list>
  </release>
</release_list>

At some point I have XSL for-each statements looping through each release, and in turn through each list of files. While I'm looping through each file, I want to get information about which other releases DO NOT contain that same file ID. (So for example, when I'm iterating through file 20 in release 100, I want to get pointers to both releases 101 and 102, but when I'm iterating through file 21 in that same package I want only a pointer to release 102.)

Is there a way to do this with XPath? The closest thing I've come up with is:

../../../release[ not( file_list/file/id = ./id ) ]

...which of course fails because within the square brackets the './' refers to the release in front of the brackets, not the file that the XPath statement is being called from.

Anything I'm missing here?


A good approach in XSLT to things like this is to use a variable at the outer level, and then you can compare against it in your selection path. So, for instance, something like

<xsl:variable name="id" select="id" />
<xsl:apply-templates select="/release_list/release[not(file_list/file/id = $id)]" />

may help you.


Within a predicate (the expression in square brackets) you can get the current node (as opposed to the context node returned by .) using the current() function.

However you should change your approach to something like Peter Cooper Jr's answer. In XSLT, it's almost always better to filter at the outermost level, so as to only process what you need, than to try to process everything and then impose exclusions from the inside.

It's also worth understanding that you aren't using a loop at all; for-each in XSLT is a mapping, not a loop. In other words, there is no concept of doing the first element, then the second element, then the third... in a conceptual sense, all matched elements are processed simultaneously. This is why the oft-asked question "How do I break out of a loop in XSLT?" can only be answered with the Matrix-inspired words: "There is no loop."


Here is a complete solution that does what you seem to want. When applied to your sample input XML, the following XSLT 1.0 stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="release">
    <xsl:copy>
      <xsl:copy-of select="id" />
      <xsl:apply-templates select="file_list/file" mode="show-other" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="file" mode="show-other">
    <xsl:copy>
      <xsl:copy-of select="id" />

      <!-- select all releases that do not contain any file with the same
           id as the current file -->
      <xsl:variable name="other" select="
        /release_list/release[not(current()/id = file_list/file/id)]
      " />
      <releases-without-that-file>
        <xsl:copy-of select="$other/id" />
      </releases-without-that-file>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

produces:

<release>
  <id>100</id>
  <file>
    <id>20</id>
    <releases-without-that-file>
      <id>101</id>
      <id>102</id>
    </releases-without-that-file>
  </file>
  <file>
    <id>21</id>
    <releases-without-that-file>
      <id>102</id>
    </releases-without-that-file>
  </file>
</release>
<release>
  <id>101</id>
  <file>
    <id>22</id>
    <releases-without-that-file>
      <id>100</id>
    </releases-without-that-file>
  </file>
  <file>
    <id>21</id>
    <releases-without-that-file>
      <id>102</id>
    </releases-without-that-file>
  </file>
</release>
<release>
  <id>102</id>
  <file>
    <id>22</id>
    <releases-without-that-file>
      <id>100</id>
    </releases-without-that-file>
  </file>
  <file>
    <id>23</id>
    <releases-without-that-file>
      <id>100</id>
      <id>101</id>
    </releases-without-that-file>
  </file>
</release>

Note that

/release_list/release[not(current()/id = file_list/file/id)]

is not the same as

/release_list/release[current()/id != file_list/file/id]

since the = compares against any node in a node set (and returns true if all nodes match only), whereas the != operator compares against the first node of a node-set only.

0

精彩评论

暂无评论...
验证码 换一张
取 消