获取没有特定祖先 xml xpath 的节点

Posted

技术标签:

【中文标题】获取没有特定祖先 xml xpath 的节点【英文标题】:Get nodes that don't have specific ancestor xml xpath 【发布时间】:2011-05-16 01:26:37 【问题描述】:

我在非常复杂的 xpath 上苦苦挣扎了几天,但我无法制定它。 我有一个来自 c++ 的语法树,比如语言解析器,我想要一个 xpath 查询,它选择所有不在函数名中的名称。

具体来说,我有这样的xml文档

(整个xml文档在问题的最后,它很大,我在这里粘贴一个简单的文档结构概述) 有四种节点类型 a - 此元素包含一个节点 b - 包含节点的信息(例如“CALL_EXPRESSION”) c - 包含实际文本(例如“printf”、变量名...) d - 包含当前节点的后代(a 元素)

CALL_EXPRESSION DOT_EXPRESSION NAME_EXPRESSION 姓名 NAME_EXPRESSION 姓名 参数 NAME_EXPRESSION 姓名 CALL_EXPRESSION NAME_EXPRESSION 姓名 参数 NAME_EXPRESSION 姓名 ASSIGNMENT_EXPRESSION NAME_EXPRESSION 姓名 NAME_EXPRESSION 姓名

我想制定 Xpath 查询,它将选择所有不是 CALL_EXPRESSION/*[1] 后代的名称。 (这意味着我想选择所有变量而不是函数名)。

要选择所有函数名,我可以像这样使用 Xpath

//a[b="CALL_EXPRESSION"]/d/a[1]

这里没问题。现在,如果我想选择不是该节点后代的所有节点。我会使用 not(ancestor::X)。

但是问题来了,如果我像这样制定 Xpath 表达式:

//*[b="NAME"][not(ancestor::a[b="CALL_EXPRESSION"]/d/a[1])]

它只选择根本没有具有子 b="CALL_EXPRESSION" 的节点的节点。在我们的示例中,它仅从 ASSIGNMENT_EXPRESSION 子树中选择 NAME。

我怀疑,问题在于,祖先:: 仅采用第一个元素(在我们的例子中为 a[b="CALL_EXPRESSION"])并根据其谓词进行限制,并进一步 / 被丢弃。所以我修改了这样的xpath查询:

//*[b="NAME"][not(ancestor::a[../../b="CALL_EXPRESSION" 和位置()=1])]

这似乎只适用于更简单的 CALL_EXPRESSION(没有 DOT_EXPRESSION)。我怀疑,[] 中的路径可能仅与当前节点相关,而不与潜在祖先相关。 但是当我使用查询时

//*[b="NAME"][not(ancestor::a[b="CALL_EXPRESSION"])]

它按照人们的假设工作(选择了所有没有祖先 CALL_EXPRESSION 的名称)。

有什么方法可以制定我需要的查询吗?为什么查询不起作用?

提前致谢:)

XML

<a>
 <b>CALL_EXPRESSION</b>
 <c>object.method(a)</c>
 <d>
   <a>
     <b>DOT_EXPRESSION</b>
     <c>object.method</c>
     <d>
       <a>
         <b>NAME_EXPRESSION</b>
         <c>object</c>
         <d>
           <a>
             <b>NAME</b>
             <c>object</c>
             <d>
             </d>
           </a>
         </d>
       </a>
       <a>
         <b>NAME_EXPRESSION</b>
         <c>method</c>
         <d>
           <a>
             <b>NAME</b>
             <c>method</c>
             <d>
             </d>
           </a>
         </d>
       </a>
     </d>
   </a>
   <a>
     <b>PARAMS</b>
     <c>(a)</c>
     <d>
       <a>
         <b>NAME_EXPRESSION</b>
         <c>a</c>
         <d>
           <a>
             <b>NAME</b>
             <c>a</c>
             <d>
             </d>
           </a>
         </d>
       </a>
     </d>
   </a>
 </d>
</a>

<a>
 <b>CALL_EXPRESSION</b>
 <c>puts(b)</c>
 <d>
   <a>
     <b>NAME_EXPRESSION</b>
     <c>puts</c>
     <d>
       <a>
         <b>NAME</b>
         <c>puts</c>
         <d>
         </d>
       </a>
     </d>
   </a>
   <a>
     <b>PARAMS</b>
     <c>(b)</c>
     <d>
       <a>
         <b>NAME_EXPRESSION</b>
         <c>b</c>
         <d>
           <a>
             <b>NAME</b>
             <c>b</c>
             <d>
             </d>
           </a>
         </d>
       </a>
     </d>
   </a>
 </d>
</a>

<a>
 <b>ASSIGNMENT_EXPRESSION</b>
 <c>c=d;</c>
 <d>
   <a>
     <b>NAME_EXPRESSION</b>
     <c>c</c>
     <d>
       <a>
         <b>NAME</b>
         <c>c</c>
         <d>
         </d>
       </a>
     </d>
   </a>
   <a>
     <b>NAME_EXPRESSION</b>
     <c>d</c>
     <d>
       <a>
         <b>NAME</b>
         <c>d</c>
         <d>
         </d>
       </a>
     </d>
   </a>
 </d>
</a>

【问题讨论】:

哦,对不起,我没有意识到代码会丢失缩进和xml标签。我在这里重新粘贴所有代码。这是结构:pastebin.com/VbRBG5LA,这是 xml 文档:pastebin.com/ajPtqprf。如果有人能解决这个问题,我将不胜感激。 抱歉,不清楚您究竟想选择什么。请提供尽可能小的 XML 文档(不必是同一类型,因为您的问题似乎很笼统)只有几个级别和节点,并定义您想要选择的确切节点。请编辑您的问题,或使用更简单、更精确的定义提出新问题。 好问题,+1。请参阅我对两个 XPath 表达式的回答,它们显示了如何选择不是 XML 文档中给定元素的后代的节点。 我用更简单的例子重申了这个问题,没有任何描述。希望这有助于***.com/q/6012713/754982 是的,我几分钟前就回答了。 【参考方案1】:

您没有说这是 XPath 1.0 还是 2.0。在 XPath 2.0 中,您可以使用 except 运算符:例如

//* except //x//*

选择所有没有 x 作为祖先的元素。

except 运算符也可以在 XPath 1.0 中使用等价来模拟

E1 except E2 ==> E1[count(.|E2)!=count(E2)]

(但要注意评估 E2 的上下文)。

【讨论】:

【参考方案2】:

问题不是很清楚,提供的 XML 不是格式良好的 XML 文档

无论如何,这是我根据对这个问题文本的理解尝试回答的问题。

让我们有以下简单的 XML 文档

<t>
 <x>
   <y>
     <z>Text 1</z>
   </y>
 </x>
 <x>
  <y>
    <z> Text 2</z>
  </y>
 </x>
</t>

我们要选择所有不是z 后代的z 元素 /t/x[1]

使用这个 XPath 表达式:

/t/z | /t/x[position() > 1]//z

或者这个:

//z[not(ancestor::x
             [count(ancestor::*) = 1
            and
              not(preceding-sibling::x)
             ]
        )
    ]

我当然会推荐第一个 XPath 表达式,因为它显然更简单、更短且更容易理解。

意思是:选择XML文档顶部元素t的所有z子元素以及顶部元素t的任何x子元素的所有z后代不是第一个这样的x 孩子(其在t 的所有x 孩子中的位置不是1)。

第二个表达式的意思是:选择 XML 文档中没有一个元素 x 作为祖先的所有 z 元素,该元素只有一个元素祖先(是顶部元素)并且之前没有名为 x 的兄弟姐妹(换句话说,它是其父级的第一个 x 子级)。

最后,快速验证一下这两个 XPath 表达式的正确性

<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:copy-of select=
  "//z[not(ancestor::x
             [count(ancestor::*) = 1
            and
              not(preceding-sibling::x)
             ]
          )
      ]
  "/>

-------------------

 <xsl:copy-of select="/t/z | /t/x[position() > 1]//z"/>
 </xsl:template>
</xsl:stylesheet>

当这个转换应用于简单的 XML 文档(如上所示)时,我们看到两个表达式都准确地选择了想要的 z 元素。转换的结果是:

<z> Text 2</z>

-------------------

 <z> Text 2</z>

【讨论】:

很抱歉我没有很好地表达自己。我需要文档中任何地方的节点。它不必像本例中那样是 节点的后代。实际上我没有关于 元素位置的信息,我只知道它不能是匹配 //t/x[1] 的任何节点的后代。我重申了这个问题,我希望我在那里更清楚:)。 ***.com/q/6012713/754982 @tach: t/z 是为了完整性而添加的。如果您确定z 只能出现在x 下,那么您可以省略表达式/t/z

以上是关于获取没有特定祖先 xml xpath 的节点的主要内容,如果未能解决你的问题,请参考以下文章

Xpath 获取父节点,其中子节点的两个属性具有特定值

如何在 Java 中使用 XPath 从 XML 中获取特定节点?

如何使用XPath在XML中获取特定节点名称(使用Groovy)

通过 XPATH 查找没有特定父节点的节点

XPath 查找包含 CSS 类的祖先节点

Python爬虫编程思想(44):XPath实战:节点轴选择