Jsoup :has() 选择器没有按预期工作

Jsoup :has() selector not working as expected

我正在尝试解析包含具有以下结构的多个单元格的 HTML table:

<td id="topic1234">
    <a name='1234'></a>
    <b><a href='/url'>Title</a><b>
    <span class='s'>Details</span>
</td>
<td id="topic2345">
    <a name='2345'></a>
    <b><a href='/url'>Title</a><b>
    <span class='s'>Details</span>
</td>
...

'id'属性,'a'元素带有'href'属性,'span'元素是重要的细节,两个元素直接嵌套。 我尝试使用

select("[id^=topic]"
          + ":has(> b > a[href])"
          + ":has(> span.s)")

但结果列表为空。当我将其更改为:

时它起作用了
select("td[id^=topic]"
          + ":has(td > b > a[href])"
          + ":has(td > span.s)")

但我不希望选择器依赖于根元素是 'td' 这一事实,并且根据文档判断,前者也应该有效。以下也不起作用:

select("[id^=topic]"
          + ":has(:root > b > a[href])"
          + ":has(:root > span.s)")

我是不是做错了什么?顺便用Jsoup 1.8.3

:has(selector) 中的选择器包含父元素。我不认为 >b 是 JSoup 中的有效选择器,但 *>b 应该没问题,它允许任何父元素。所以这应该有效:

select("[id^=topic]"
      + ":has(* > b > a[href])"
      + ":has(* > span.s)")

Edit1 回应评论:

为了使 :has(selector) 的选择器更有可能是 [id^=topic] 的直接子代,您还可以这样做:

select("[id^=topic]"
      + ":has([id^=topic] > b > a[href])"
      + ":has([id^=topic] > span.s)")

这当然仍然不能保证,因为父级的内部子级也可能带有一个以 topic.

开头的 id

编辑2

类似,您可以通过将选择器分成两部分来确保。首先,我们匹配 ID 以 topic 开头的所有元素。然后我们循环这些并构造一个包含特定 id 的新选择器。只要所有元素的 id 都是独立的,这就可以工作。

String html = "<table><tr><td id=\"topic1234\">" +
        "<a name='1234'></a>" +
        "<div><b><a href='/url'>Title</a></b></div>" +
        "<span class='s'>Details</span></td>\n" +
        "<td id=\"topic2345\">\n" +
        "    <a name='2345'></a>\n" +
        "    <b><a href='/url'>Title</a></b>\n" +
        "    <span class='s'>Details</span>\n" +
        "</td>"+
        "<td id=\"topic3456\">\n" +
        "    <div id=\"topic4567\"><a name='3456'></a>\n" +
        "    <b><a href='/url'>Title</a></b>\n" +
        "    <span class='s'>Details</span>\n" +
        "    </div>" +
        "</td></tr></table>";

Document doc = Jsoup.parse(html);
Elements selected = doc.select("[id^=topic]");
for (Element elem : selected) {
  String idStr = elem.attr("id");
  Element el = elem.select(":has(#"+idStr+" > b > a[href]):has(#"+idStr+" > span.s)").first();
  if (el != null){
      System.out.println("found matching element: "+el);
  }
  if (el != null){
      System.out.println("does not really match: "+el);
  }
}

我认为不可能为您的需要编写一个选择器,因为 JSoup 不支持像 :has(> tag).

这样的语法

不过,我认为您可以将选择器分成多个部分:

String html = "<table><td id=\"topic1234\">" +
                  "<a name='1234'></a>" +
                  "<div><b><a href='/url'>Title</a></b></div>" +
                  "<span class='s'>Details</span></td>\n" +
                  "<td id=\"topic2345\">\n" +
                  "    <a name='2345'></a>\n" +
                  "    <b><a href='/url'>Title</a></b>\n" +
                  "    <span class='s'>Details</span>\n" +
                  "</td></table>"

Document doc = Jsoup.parse(html);
Elements selected = doc.select("[id^=topic]");
for (Element elem : selected) {
    // Check if "b > a[href]" is a direct child of "td"
    if (elem.select(":root > b > a[href]").size() > 0) {
        System.out.println("Found: "+elem);
    } else {
        System.out.println("Not found:"+elem);
    }
}

即html 代码如下:

<table>
    <td id="topic1234">
        <a name="1234"></a>
        <div>
            <b><a href="/url">Title</a></b>
        </div>
    <span class="s">Details</span></td>
    <!-- second line -->    
    <td id="topic2345">
        <a name="2345"></a>
        <b><a href="/url">Title</a></b>
        <span class="s">Details</span>
    </td>
</table>

哪个returns:

Not found:<td id="topic1234"><a name="1234"></a>
 <div>
  <b><a href="/url">Title</a></b>
 </div><span class="s">Details</span></td>
Found: <td id="topic2345"> <a name="2345"></a> <b><a href="/url">Title</a></b> <span class="s">Details</span> </td>

显然同样适用于第二个条件(即 span.s

请注意,在这种情况下,:root 选择器有效,因为 elem 的根元素是 td 而不是 table