如何在Jsoup中生成匹配特定元素的XPath查询?

5

_ 你好,这是我的网页:

<html>
    <head>
    </head>
    <body>
        <div> text div 1</div>
        <div>
            <span>text of first span </span>
            <span>text of second span </span>
        </div>
        <div> text div 3 </div>
    </body>
</html>

我正在使用jsoup解析它,然后浏览页面中的所有元素并获取它们的路径:
 Document doc = Jsoup.parse(new File("C:\\Users\\HC\\Desktop\\dataset\\index.html"), "UTF-8");
 Elements elements = doc.body().select("*");
ArrayList all = new ArrayList();
        for (Element element : elements) {
            if (!element.ownText().isEmpty()) {

                StringBuilder path = new StringBuilder(element.nodeName());
                String value = element.ownText();
                Elements p_el = element.parents();

                for (Element el : p_el) {
                    path.insert(0, el.nodeName() + '/');
                }
                all.add(path + " = " + value + "\n");
                System.out.println(path +" = "+ value);
            }
        }

        return all;

我的代码给了我这个结果:
html/body/div = text div 1
html/body/div/span = text of first span
html/body/div/span = text of second span
html/body/div = text div 3

实际上,我想要的结果是这样的:
html/body/div[1] = text div 1
html/body/div[2]/span[1] = text of first span
html/body/div[2]/span[2] = text of second span
html/body/div[3] = text div 3

请问有人能够提供如何达到这个结果的想法吗?谢谢。

4个回答

2
作为这里的一个想法。即使我相信有更好的解决方案来获取给定节点的xpath。例如使用XSLT,如“从XML节点java生成/获取xpath”的答案中所述。
这里是基于您当前尝试的可能解决方案。
对于每个(父)元素,请检查是否有多个具有此名称的元素。 伪代码:if ( count (el.select('../' + el.nodeName() ) > 1) 如果为真,则计算具有相同名称的preceding-sibling::并添加1。 count (el.select('preceding-sibling::' + el.nodeName() ) +1

1
这是我对这个问题的解决方案:
StringBuilder absPath=new StringBuilder();
Elements parents = htmlElement.parents();

for (int j = parents.size()-1; j >= 0; j--) {
    Element element = parents.get(j);
    absPath.append("/");
    absPath.append(element.tagName());
    absPath.append("[");
    absPath.append(element.siblingIndex());
    absPath.append("]");
}

这个解决方案有多个错误。 - spierce7

0

这是 Kotlin 的解决方案。它是正确的,也能正常工作。其他答案都是错误的,让我浪费了数小时的工作时间。

fun Element.xpath(): String = buildString {
    val parents = parents()

    for (j in (parents.size - 1) downTo 0) {
        val parent = parents[j]
        append("/*[")
        append(parent.siblingIndex() + 1)
        append(']')
    }

    append("/*[")
    append(siblingIndex() + 1)
    append(']')
}

0

如果您从根节点到叶子节点遍历文档,而不是反过来,这将更容易。这样,您可以轻松地按标签名称对元素进行分组,并相应地处理多个出现次数。以下是一种递归方法:

private final List<String> path = new ArrayList<>();
private final List<String> all = new ArrayList<>();

public List<String> getAll() {
    return Collections.unmodifiableList(all);
}

public void parse(Document doc) {
    path.clear();
    all.clear();
    parse(doc.children());
}

private void parse(List<Element> elements) {
    if (elements.isEmpty()) {
        return;
    }
    Map<String, List<Element>> grouped = elements.stream().collect(Collectors.groupingBy(Element::tagName));

    for (Map.Entry<String, List<Element>> entry : grouped.entrySet()) {
        List<Element> list = entry.getValue();
        String key = entry.getKey();
        if (list.size() > 1) {
            int index = 1;
            // use paths with index
            key += "[";
            for (Element e : list) {
                path.add(key + (index++) + "]");
                handleElement(e);
                path.remove(path.size() - 1);
            }
        } else {
            // use paths without index
            path.add(key);
            handleElement(list.get(0));
            path.remove(path.size() - 1);
        }
    }

}

private void handleElement(Element e) {
    String value = e.ownText();
    if (!value.isEmpty()) {
        // add entry
        all.add(path.stream().collect(Collectors.joining("/")) + " = " + value);
    }
    // process children of element
    parse(e.children());
}

你的答案接近我想要的,我只需要做些改变就能完美地运行,因为现在它的输出结果是这样的。 - kivok94
div [1] = 文字 div 1 div [2] / span [1] = 第一个 span 的文本 div [2] / span [2] = 第二个 span 的文本 div [3] = 文字 div 2 body / div [1] = 文字 div 1 body / div [2] / span [1] = 第一个 span 的文本 body / div [2] / span [2] = 第二个 span 的文本 body / div [3] = 文字 div 2 span [1] = 第一个 span 的文本 span [2] = 第二个 span 的文本 - kivok94

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