XPath表达式在多个祖先上带有条件

3

我正在开发的应用程序接收类似以下 XML 结构:

<Root>
    <Valid>
        <Child name="Child1" />
        <Container>
            <Child name="Child2" />
        </Container>
        <Container>
            <Container>
                <Child name="Child3"/>
                <Child name="Child4"/>
            </Container>
        </Container>
        <Wrapper>
            <Child name="Child5" />
        </Wrapper>
        <Wrapper>
            <Container>
                <Child name="Child19" />
            </Container>
        </Wrapper>
        <Container>
            <Wrapper>
                <Child name="Child6" />
            </Wrapper>
        </Container>
        <Container>
            <Wrapper>
                <Container>
                    <Child name="Child20" />
                </Container>
            </Wrapper>
        </Container>
    </Valid>
    <Invalid>
        <Child name="Child7" />
        <Container>
            <Child name="Child8" />
        </Container>
        <Container>
            <Container>
                <Child name="Child9"/>
                <Child name="Child10"/>
            </Container>
        </Container>
        <Wrapper>
            <Child name="Child11" />
        </Wrapper>
        <Container>
            <Wrapper>
                <Child name="Child12" />
            </Wrapper>
        </Container>
    </Invalid>
</Root>

我需要根据以下条件获取Child元素列表:

  1. ChildValid祖先的n代后代。
  2. Child可以是Container祖先的m代后代,而该Container祖先是Valid祖先的o代后代。
  3. Child元素的有效祖先是将m代祖先作为Container元素和将第一代祖先作为Valid元素。

其中m,n,o是自然数。

我需要编写以下XPath表达式:

Valid/Child
Valid/Container/Child
Valid/Container/Container/Child
Valid/Container/Container/Container/Child
...

作为一个单一的 XPath 表达式。
对于提供的示例,XPath 表达式将仅返回具有名称属性等于 Child1、Child2、Child3 和 Child4 的子元素
我找到的最接近解决方案的表达式如下。
Valid/Child | Valid//*[self::Container]/Child

然而,这会选择名称属性为Child19和Child20的Child元素。XPath语法是否支持元素的可选出现或在前面的示例中设置类似于所有祖先之间的条件,其位于Child和Valid元素之间?

1
有趣的问题。+1。我只能想到一个简单的联合,如Valid/Child | Valid/Container/Child | Valid/Container/Container/Child。也许@Alejandro或@Dimite会提供一种跳过Container步骤的简便方法。 - Flack
我希望@Dimitre有解决方案。 - user595010
1
@Rest Wing,这个例子对你来说也足够了:Valid//Child[not(ancestor::Wrapper)] - Flack
好问题,+1。请查看我的答案,这是目前为止最短、最简单的解决方案,可能也是最有效的。 :) - Dimitre Novatchev
@Rest Wing: 我觉得你应该重新表达问题,使其最后一部分更有意义,并更直接地与 @Dimitre 的好答案相匹配。 - user357812
显示剩余2条评论
2个回答

4

用途:

//Child[ancestor::*
          [not(self::Container)][1]
                            [self::Valid]
       ]

当在提供的XML文档上评估此XPath表达式时:
<Root>
    <Valid>
        <Child name="Child1" />
        <Container>
            <Child name="Child2" />
        </Container>
        <Container>
            <Container>
                <Child name="Child3"/>
                <Child name="Child4"/>
            </Container>
        </Container>
        <Wrapper>
            <Child name="Child5" />
        </Wrapper>
        <Wrapper>
            <Container>
                <Child name="Child19" />
            </Container>
        </Wrapper>
        <Container>
            <Wrapper>
                <Child name="Child6" />
            </Wrapper>
        </Container>
        <Container>
            <Wrapper>
                <Container>
                    <Child name="Child20" />
                </Container>
            </Wrapper>
        </Container>
    </Valid>
    <Invalid>
        <Child name="Child7" />
        <Container>
            <Child name="Child8" />
        </Container>
        <Container>
            <Container>
                <Child name="Child9"/>
                <Child name="Child10"/>
            </Container>
        </Container>
        <Wrapper>
            <Child name="Child11" />
        </Wrapper>
        <Container>
            <Wrapper>
                <Child name="Child12" />
            </Wrapper>
        </Container>
    </Invalid>
</Root>

已经精确选择所需节点:

<Child name="Child1"/>
<Child name="Child2"/>
<Child name="Child3"/>
<Child name="Child4"/>

解释:

表达式:

//Child[ancestor::*
          [not(self::Container)][1]
                            [self::Valid]
       ]

意思:

从文档中的所有Child元素中,仅选择第一个祖先不是Container而是Valid的元素。


@Dimitre:+1。非常棒,简单易懂。可惜我不能给予更多的赞 :) - user595010
使用这个答案,我能够轻松地处理嵌套的 Valid 元素,只需将当前条件与 count(ancestor::Valid) = 1 进行 and 运算即可。 - user595010
@RestWing:没错,XPath是一门很棒的语言。更不用说XPath 2.0和XPath 3.0了。最近我在我的博客中完全使用XPath 3.0实现了二叉搜索树数据结构--欢迎查看。 - Dimitre Novatchev
@Dimitre:好的。我想只有.NET框架支持XPath 1.0,对吗? - user595010
@RestWing:没错。但是至少有两个.NET XSLT 2.0处理器:Saxon和XQSharp,这两个处理器都支持XPath 2.0。Saxon EE9.3.04(付费版本)具有XPath 3.0和XSLT 3.0的早期实现。 - Dimitre Novatchev
+1 为更加关注点赞。我没有看到问题的最后一个定义。 - user357812

2
//Valid
 //Child[count(ancestor::Container[ancestor::Valid])
          = count(ancestor::*[ancestor::Valid])]

解释:

//Valid//Child

返回所有作为Valid节点的后代的Child节点。
count(ancestor::Container[ancestor::Valid]])

返回当前节点(Child)祖先中有一个名为Valid的祖先,并且它们自己也是Container标签的数量。
count(ancestor::*[ancestor::Valid])

返回所有祖先标签的数量,这些标签包括当前节点(Child),并且它们本身有一个名为Valid的祖先标签。
因此,只有在ValidChild之间的所有标记都被称为Container时,两个值才相等。
但是,该表达式假定不会有任何嵌套的Valid标签,即它将不接受/Valid/Valid/Child更新:再次查看您的xml,这样是否更容易?
//Valid//Child[not(ancestor::Wrapper)]

+1。很棒。从更新中的表达式可能会更容易。但是,它比第一个要宽松一些。请参见有关Flack提出的答案的问题评论。我可以假设不会有Valid元素嵌套 :) - user595010
虽然你的解决方案可行,但我已将@Dimitre提供的解决方案标记为最终答案。不过,我不会剥夺你的荣誉,你值得拥有它们 :) - user595010

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接