用于选择没有具有相同名称的子子节点的子节点的XPath

时间:2012-09-03 21:41:53

标签: xslt xpath

我有一个像

这样的XML片段
<main>
    <sub1>
        <element>
            <sub2>
                <element>
                    <sub3/>
                </element>
            </sub2>
        </element>
    </sub1>
    <sub4>
        <sub5>
            <element>
                <sub6>
                    <element>
                        <sub7/>
                    </element>
                </sub6>
            </element>
        </sub5>
    </sub4>
    <sub8>
        <element/>
    </sub8>
</main>

如果main是当前节点,我可以像这样选择所有元素节点:

select=".//element"

在这个例子中,这给了我5个匹配。但是,我如何只选择层次结构中的第一个元素节点,那么在sub1,sub5和sub8之后?我需要忽略它们与原始主节点之间有另一个元素的所有元素。

中间的子节点数可能大于1或2,如上例所示,示例中的主节点可能是上面另一个元素节点的一部分。

感谢 ķ

3 个答案:

答案 0 :(得分:2)

可能的表达方式是

.//element[not(ancestor::element)]

即。查找未被另一个“element”元素包围(在任何级别)的所有“元素”元素。

修改

如上所述,如果当前(main)元素本身被“元素”包围,则此表达式不起作用。如果你在XSLT中,因此可以访问current()函数,那么你可以使用像

这样的技巧
.//element[count(ancestor::element) = count(current()/ancestor::element)]

选择那些与当前“main”元素具有相同数量的封闭“元素”元素的元素(可以为零)。根据您的处理器,使用变量

可能更有效和/或更清晰
<xsl:variable name="depth" select="count(ancestor::element)" />
<xsl:apply-templates select=".//element[count(ancestor::element) = $depth]"/>

答案 1 :(得分:1)

使用

.//element[(ancestor::main|ancestor::element)[last()][self::main]]

以下是使用XSLT的简单验证

<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="/*">
  <xsl:for-each select=
   ".//element
       [(ancestor::main|ancestor::element)
                          [last()][self::main]
       ]">
   <xsl:copy-of select="."/>
==================
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

在提供的XML文档上应用此转换时:

<main>
    <sub1>
        <element>
            <sub2>
                <element>
                    <sub3/>
                </element>
            </sub2>
        </element>
    </sub1>
    <sub4>
        <sub5>
            <element>
                <sub6>
                    <element>
                        <sub7/>
                    </element>
                </sub6>
            </element>
        </sub5>
    </sub4>
    <sub8>
        <element/>
    </sub8>
</main>

使用当前节点作为文档中的顶级节点来评估Xpath表达式,然后使用方便的通知分隔符字符串将此评估的结果复制到输出中:

<element>

   <sub2>

      <element>

         <sub3/>

      </element>

   </sub2>

</element>
==================
  <element>

   <sub6>

      <element>

         <sub7/>

      </element>

   </sub6>

</element>
==================
  <element/>
==================

<强>解释

表达式:

    .//element
       [(ancestor::main|ancestor::element)
                                     [last()]
                                        [self::main]
       ]

选择当前节点的所有descendat element元素,两个可能的祖先中的第一个(向上)mainelementmain

也就是说,没有element是所选element的祖先,并且在main祖先之前发生(在向上的路上)。

要理解为什么last()被用作谓词而不只是[1],我们需要记住,最接近的两个祖先是文档顺序中的最后一个。

请注意

这个XPath表达式正确地选择了所有想要的element元素,即使在当前节点本身具有element祖先的情况下也是如此。

像Ian Roberts的回答中提出的表达

.//element[not(ancestor::element)] 

在这种情况下不选择任何内容


<强>更新

如Michael Kay所述,如果我们进一步推广此问题并允许其他main元素出现在文档中的任何位置,则此答案中的建议表达式可能会选择“误报”。

这是一个更新的表达式(不再是纯/独立的XPath表达式,因为它使用XSLT函数current())在这种情况下仍会产生正确的计数

.//element
   [(ancestor::main|ancestor::element)
                      [last()][self::main]
  and
    count(current()|ancestor::main[1]) = 1
   ]"/>

答案 2 :(得分:1)

我认为这个答案比Dimitre更严格,因为它没有假设缺少其他名为“main”的元素,而这些元素并没有被指定为问题的一部分。

for $this in . return .//element[empty(ancestor::element[.>>$this])]

如果您使用的是XSLT,则可以使用current()代替$ this。