In XPath, how can I select groups of following-sib

2019-08-13 01:05发布

I have a xml document like follows,

<sec>
    <p type="Running">aa</p>
    <p type="heading 1">bb</p>
    <p type="body">cc</p>
    <p type="body">dd</p>
    <p type="Running">ee</p>
    <p type="Body">ff</p>
    <p type="heading">gg</p>
    <p type="body">hh</p>
    <p type="body">ii</p>
    <p type="Running">jj</p>
    <p type="list">kk</p>
    <p type="list">ll</p>
    <p type="list">mm</p>
    <p type="list">nn</p>
</sec>

using xpath I need to select consecutive following-siblings of <p> nodes from <p> that has attr value of Running.

SO in above example

    <p type="heading 1">bb</p>
    <p type="body">cc</p>
    <p type="body">dd</p>

and

    <p type="Body">ff</p>
    <p type="heading">gg</p>
    <p type="body">hh</p>
    <p type="body">ii</p>

and

    <p type="list">kk</p>
    <p type="list">ll</p>
    <p type="list">mm</p>
    <p type="list">nn</p>

node groups should be selected.

How can I write a XPath query to select those nodes?

XPath version - 2.0

4条回答
Anthone
2楼-- · 2019-08-13 01:27

This question currently has three answers, but I don't think any of them actually answers the question.

In XPath 2.0, everything is sequences. If you select a set of nodes, in XPath 1.0 you called that a "node set", in XPath 2.0 it is a "sequence of nodes". One property of sequences is that they cannot be nested: (1, (2, 3), 4) is the same as (1, 2, 3, 4).

You ask for a select statement that selects sets of nodes, which implies that you want to do something with each set. The logical thing to do is something like the following:

for $r in sec/p[@type = 'Running']
return $r
    /following-sibling::p
    [not(@type = 'Running')]
    [. << following-sibling::p[@type = 'Running'][1]]

This is a rather complex expression. While it will internally select the subsets you are after, because of sequence normalization, the net effect is a single sequence with a selection equal to sec/p[not(@type = 'Running')].

In XPath 2.0 it is not possible to do this differently, so the natural thing to do then is to use a host language, like XSLT or XQuery, to select the @type = 'Running' nodes, and on each hit, select (and do something) will the following siblings until the next @type = 'Running':

<xsl:template match="sec">
    <xsl:apply-templates select="p[@type = 'Running']" />
</xsl:template>

<xsl:template match="p">
    <!-- do something before the group -->
    <xsl:apply-templates select="following-sibling::p[following-sibling::p[@type = 'Running'] >> .]" mode="group"/>
    <!-- do something after the group -->
</xsl:template>

<xsl:template match="p" mode="group">
   <!-- do something with items in the group -->
</xsl:template>

It is probably easier to use an xsl:for-each-group here, which is meant for this kind of thing.

查看更多
做自己的国王
3楼-- · 2019-08-13 01:29

I don't know XPath 2, but with XPath 1 and a bit of higher-level programming, you can use this kind of XPath expressions, counting preceding siblings with type="Running":

//p[not(@type="Running")
    and count(preceding-sibling::p[@type="Running"])=1]
                                                     ^
                                                     |
                                                1 then 2 then 3
查看更多
smile是对你的礼貌
4楼-- · 2019-08-13 01:30

Below Xpath select everything except type='Running'

/sec/p[not(@type='Running')]
查看更多
5楼-- · 2019-08-13 01:35

This should help you -

var j = new List<string>();
var t = new XmlDocument();
t.Load(new StreamReader("xmlPath"));
var type = t.GetElementsByTagName("p");

for (var i = 0; i < type.Count; i++)
{
    if (((XmlNode)type[i]).Attributes[0].Value != "Running")
    {
       j.Add(((XmlNode)type[i]).OuterXml);
    }
}
查看更多
登录 后发表回答