【问题标题】:Xpath query to get the ancester nodes based on element valueXpath 查询根据元素值获取祖先节点
【发布时间】:2012-02-14 02:43:40
【问题描述】:

我正在尝试查找所有遵循以下两条规则的元素名称。


1.元素应该有<set>erase</set>

2。如果两个或多个元素在层次结构中具有<set>erase</set>(例如:<b><d> 都具有<set>erase</set>),则只需打印父节点名称(即在这种情况下为<b>)。

所以下面的xml需要的结果是:

b , y , p

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a>
    <b>
        <set>erase</set>
        <d>
        <set>erase</set>
        </d>
    </b>

    <c>
        <x>
        </x>
    </c>

    <e>

        <y>
                    <set>erase</set>
            <q>
            </q>
        </y>
        <z>
            <p>
            <set>erase</set>
            </p>
        </z>
    </e>
</a>

当我使用 query = (//set[contains(.,'erase')])[1] 时,我在结果集中只得到节点 b
当我使用 query = //set[contains(.,'erase')] 时,我会在结果集中得到所有节点列表 b,d,y,p

谁能帮我找到产生 nodeList byp 的查询。

这是我使用的java代码sn-p。

        XPath xpath = factory.newXPath();
    String query = "//set[contains(.,'erase')]";
            XPathExpression expr=null;
    try {
        expr = xpath.compile(query);
    } catch (XPathExpressionException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
        Object result = null;
    try {
        result = expr.evaluate(doc, XPathConstants.NODESET);
    } catch (XPathExpressionException e) {
        e.printStackTrace();
    }
    NodeList nodes = (NodeList) result;


    for (int i = 0; i < nodes.getLength(); i++) {
        String x = "";
        Node n = nodes.item(i).getParentNode();
        x=n.getNodeName();
        while(!n.getNodeName().equalsIgnoreCase(request.getClass().getSimpleName())){
            if ((n = n.getParentNode())!=null){
                x=n.getNodeName()+"."+x;
            }
        }



        System.out.println("Path: "+x);

输出:

a.b
a.b.d
a.e.y
a.e.z.p

谁能帮我找出只导致a.b , a.e.y and a.e.z.p的查询 如果您需要更多详细信息,请告诉我。或任何其他用例。

【问题讨论】:

  • user1207560:您接受了错误的答案——只需在代码中使用建议的表达式来验证它没有选择想要的元素。我的回答中解释了为什么会这样。

标签: xml xslt xpath xml-parsing


【解决方案1】:

准确选择所需元素的一个表达式是

      //*[set[. = 'erase' and not(node()[2])]
         and
          not(ancestor::*
                 [set
                    [. = 'erase' and not(node()[2])]
                 ]
              )
          ]

基于 XSLT 的验证

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

     <xsl:template match="/">
         <xsl:for-each select=
         "//*[set[. = 'erase' and not(node()[2])]
             and
              not(ancestor::*
                     [set
                        [. = 'erase' and not(node()[2])]
                     ]
                  )
              ]">

          <xsl:value-of select="name()"/>
          <xsl:text>&#xA;</xsl:text>
        </xsl:for-each>
     </xsl:template>
</xsl:stylesheet>

当应用于 Sean B. Durkin 提供的 XML 文档时,此转换

<a>
    <b>
        <set>erase</set>
        <set>
            <a/>erase
        </set>
        <d>
            <set>erase</set>
        </d>
    </b>
    <c>
        <x>         </x>
    </c>
    <e>
        <y>
            <set>erase</set>
            <q>             </q>
        </y>
        <z>
            <p>
                <set>erase</set>
            </p>
        </z>
    </e>
</a>

评估上面的 XPath 表达式并输出所选元素的名称——产生想要的正确结果

b
y
p

请注意以下两个表达方式完全不正确:

*[set[text()='erase']][not(ancestor::*[set[text()='erase']])]  

或者:

*[set[text()='erase']][ancestor::*[set[text()!='erase']]] 

这两种表达方式不止一个问题

  1. 它们是相对表达式,无论它们应用于哪个初始上下文,它们都无法选择层次结构中具有未定义深度和结构的所有想要的元素。

  2. set[text()='erase'] 不仅选择表单元素:

...

<set>erase</set>

还有表单的元素:

<set>
xyz
 <a/>erase</set>   

.3。同样:

set[text()!='erase']   

选择表单的元素:

<set>
xyz
 <a/>erase</set> 

【讨论】:

  • 这可能是一个陈旧的问题,但我对 Novatchev 的回答持怀疑态度。如果您将以下文档应用于 Novatchev 的自检程序(“基于 XSLT 的验证”),则会产生错误的输出(至少据我了解提问者的要求):我无法将反例放入一个评论,因为它太大了,所以我把它附加到我的答案中。
  • @SeanB.Durkin:感谢您提供了很好的 XML 文档示例。我使用 XPath 表达式编辑了我的答案,该表达式选择了正确且预期的节点集。
  • 只是想知道...可以将“[. = 'erase' and not(node()[2])]”简化为“['erase' and not(node()[2] )]"?此外,您似乎更喜欢使用索引谓词测试序列长度,而不是使用 count() 函数?这种偏好只是风格问题吗?还是谓词方法有真正的好处(清晰度、性能等)?
  • @SeanB.Durkin: 不,在['erase' and not(node()[2])]and 运算符的第一个参数是一个非空字符串,它被转换为true()——结果与@987654335 完全不同@。至于测试节点集中没有第二个节点,这比使用count() 效率高得多——想象一下必须计算 1000 个节点才能看到这个数字 > 1。
  • @Dimitre:只是一个小问题。如果我想选择 元素而不是它们的父元素 // .... 我该怎么做?我试过 //set[contains(.,'erase')] 将全选.. 但是如何忽略后代?
【解决方案2】:

这是我的第二次尝试:

//*[                    set[count(node())=1 and text()='erase'] and
      not( ancestor::*[ set[count(node())=1 and text()='erase']])
   ]

此选择通过了我的第一个答案中显示的测试用例。

【讨论】:

    【解决方案3】:

    以下 XPath 选择您想要的节点:

    //*[set[text()='erase']][not(ancestor::*[set[text()='erase']])]
    

    我使用以下样式表对其进行了测试

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:output method="xml" encoding="utf-8" indent="yes"/>
    
        <xsl:template match="@*|text()" />
    
        <xsl:template match="//*[set[text()='erase']][not(ancestor::*[set[text()='erase']])]">
            <xsl:text>(</xsl:text>
            <xsl:for-each select="self::*|ancestor::*">
                <xsl:value-of select="name()"/>
                <xsl:text>.</xsl:text>
            </xsl:for-each>
            <xsl:text>) </xsl:text>
        </xsl:template>
    
    </xsl:stylesheet>
    

    它产生了输出

    (a.b.) (a.e.y.) (a.e.z.p.)
    

    【讨论】:

    • 啊.. 非常感谢您的逻辑。
    • @user1207560,根据 Dimitre 的回答,我已对此进行了修改,以便通过单个表达式选择所有所需的节点(将 // 添加到前面)。
    【解决方案4】:

    还是对 Harpo 的回答稍作调整?:

    *[set[text()='erase']][ancestor::*[set[text()!='erase']]]
    

    在我对 Novatchev 的回答发表评论后,请考虑有用的测试用例:

    这是对提问者演示文档的更改。我添加了另一个节点。

    <?xml version="1.0"?>
    <a>
        <b>
            <set>erase</set>
            <set><a/>erase</set>
            <d>
            <set>erase</set>
            </d>
        </b>
        <c>
            <x>
            </x>
        </c>
        <e>
            <y>
            <set>erase</set>
                <q>
                </q>
            </y>
            <z>
                <p>
                <set>erase</set>
                </p>
            </z>
        </e>
    </a>
    

    答案应该是

    b
    y
    p
    

    【讨论】:

    • 只有这个不会选择任何东西。第二个子句说,只给我一个祖先节点,它的祖先有一个 setnot 包含文本“erase”。在本文档中,所有 set 元素都可以。
    • 嗯.. 这没有选择所需的元素。我看到这不起作用的原因是您在文本值“擦除”上设置了 != 而不是在节点上。为了将@harpo 的解决方案放在文字中,他首先选择了所有具有 erase 的节点,然后从结果中添加了一个额外的条件来删除所有没有具有 erase 祖先的节点.
    • XSLT 是多么有趣的语言啊!有两个人总是有正确的答案:诺瓦切夫和凯伊。今天我在一天之内被他们俩纠正了。我想这是我自我提升的幸运日:-)
    • @SeanB.Durkin:如果您购买并阅读 Michael Kay 的书(拼写正确),您会积极地走运。在此处查看这些书籍的一些链接:stackoverflow.com/questions/339930/…
    • @SeanB.Durkin:谢谢你的好收获。我及时更新了我的答案,现在您提供的 XML 文档不再是“反例”... :) 因此,请您更新问题中现在错误的陈述吗? :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-10
    • 1970-01-01
    • 2011-08-26
    • 2015-05-08
    • 2012-04-22
    • 2011-08-17
    相关资源
    最近更新 更多