XPath:如何选择满足某些条件的所有兄弟节点?

时间:2015-02-25 00:31:32

标签: java xpath xhtml

我正在尝试写一个XPath表达式,将所有兄弟节点返回到一个,满足特定条件。在我的特定情况下,我有一个(X)HTML列表,其中列表项有一些具有特定的类和其他没有类的元素。

要想象: 我站在其中一个列有“foo”类的列表项(例如包含文本“D”的li,我想得到一个包含“E”,“F”和“G”的后续列表,但没有后续项目包含“H”,“I”和“J”。

...
<li class="foo">A</li>
<li>B</li>
<li>C</li>
<li class="foo">D</li>
<li>E</li>
<li>F</li>
<li>G</li>
<li class="foo">H</li>
<li>I</li>
<li>J</li>
...

我站在其中一个列有“foo”类的列表项(例如包含文本“D”的li,我想得到一个包含“E”,“F”的后续li的列表, “G”,但后续项目均不包含“H”,“I”和“J”。

我正在使用Java v1.8及其内置的javax.xml.xpath包访问以前解析过的org.w3c.dom.Document。

注意:我已经广泛搜索了一个解决方案,我知道有很多非常相似的示例,即使是在StackOverflow上,但这些都不适合我!无论我尝试和适应手头的情况总是只给我第一个元素(在这个例子中为“E”)或根本没有。 : - (

稍后补充:

由于我显然表达得非常糟糕,我正在附加一个测试程序:

package pull_lis;

import java.io.FileInputStream;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.tidy.Tidy;

public class TestXPathExpression
{
    public static void main(String[] args) throws Exception {
        Tidy tidy = new Tidy();
        XPathFactory xpathfactory = XPathFactory.newInstance();
        XPath xpath = xpathfactory.newXPath();

        Document doc = tidy.parseDOM(new FileInputStream("sample.xml"), System.out);

        XPathExpression expr1 = xpath.compile("//li[@class='foo']");

//      XPathExpression expr2 = xpath.compile("//li[@class='foo'][2]/following-sibling::li[@class='foo'][1]/preceding-sibling::li[preceding-sibling::li[@class='foo'][2]]");
        XPathExpression expr2 = xpath.compile("???"); // <<<< IT IS THIS EXPRESSION THAT I AM SEEKING

        NodeList foos = (NodeList)expr1.evaluate(doc, XPathConstants.NODESET);
        System.out.println(foos.getLength() + " foos found.");

        for (int idx1 = 0; idx1 < foos.getLength(); idx1++) {
            Node foo = foos.item(idx1);
            System.out.println("foo[" + idx1 + "]: " + foo.getChildNodes().item(0).getNodeValue());
            NodeList nodes = (NodeList)expr2.evaluate(foo, XPathConstants.NODESET);
            for (int idx2 = 0; idx2 < nodes.getLength(); idx2++) {
                Node node = nodes.item(idx2);
                System.out.println(non-foo[" + idx2 + "]: " + node.getChildNodes().item(0).getNodeValue());
            }   
        }
    }
}

sample.xml包含:

<html>
    <head>
        <title>Example</title>
    </head>
    <body>
        <ul>
            <li class="foo">A</li>
            <li>B</li>
            <li>C</li>
            <li class="foo">D</li>
            <li>E</li>
            <li>F</li>
            <li>G</li>
            <li class="foo">H</li>
            <li>I</li>
            <li>J</li>
        </ul>
    </body>
</html>

如果我使用kjhughes提供的表达式让上述程序在sample.xml上运行,我得到:

3 foos found.
foo[0]: A
non-foo[0]: E
non-foo[1]: F
non-foo[2]: G
foo[1]: D
non-foo[0]: E
non-foo[1]: F
non-foo[2]: G
foo[2]: H
non-foo[0]: E
non-foo[1]: F
non-foo[2]: G

但我想要/需要的是:

3 foos found.
foo[0]: A
non-foo[0]: B
non-foo[1]: C
foo[1]: D
non-foo[0]: E
non-foo[1]: F
non-foo[2]: G
foo[2]: H
non-foo[0]: I
non-foo[1]: J

希望这次我能让自己更清楚......

微米。

2 个答案:

答案 0 :(得分:3)

鉴于此XHTML:

<ul>
  <li class="foo">A</li>
  <li>B</li>
  <li>C</li>
  <li class="foo">D</li>
  <li>E</li>
  <li>F</li>
  <li>G</li>
  <li class="foo">H</li>
  <li>I</li>
  <li>J</li>
</ul>

此XPath:

//li[. = 'D']/following-sibling::li[@class='foo'][1]/preceding-sibling::li[preceding-sibling::li[. = 'D']]

将在li开始之后但<li>D</li>之前li返回class='foo'

<li>E</li>
<li>F</li>
<li>G</li>

更新

OP在评论中表示,第一个感兴趣的节点不应该标记其内容为&#34; D&#34;但是成为第二个li @class="foo"

以下是根据此新条件启动的上述XPath:

//li[@class='foo'][2]/following-sibling::li[@class='foo'][1]/preceding-sibling::li[preceding-sibling::li[@class='foo'][2]]

选择&#34; E&#34;,&#34; F&#34;和&#34; G&#34; li元素已按要求提供。

答案 1 :(得分:0)

我试图记住我的所有XPath 1.0编程技巧,并且我已经得出结论,它不能在单个XPath 1.0表达式中完成。这是一个大胆的陈述,有人可能证明我错了。

但是,既然您使用的是Java,那么您并不局限于XPath 1.0。获得一个XPath 2.0库(例如Saxon),然后就可以编写

for $N in following-sibling::li[@class='foo'][1] 
return following-sibling::li[. << $N]

或者,既然您正在使用DOM(为什么现在有人使用DOM?)只需迭代Java代码中的以下兄弟,直到找到匹配的那个。