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

Posted

技术标签:

【中文标题】Jsoup :has() 选择器未按预期工作【英文标题】:Jsoup :has() selector not working as expected 【发布时间】:2016-06-22 09:36:49 【问题描述】:

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

<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。

【问题讨论】:

:root 不起作用,因为无论选择器的范围如何,它总是引用文档根 - 在 HTML 中,这通常是 。在 selectors-4 中,这通常由 :scope 表示 - 我怀疑它在 jsoup 中是否有效,但...... 也许 :has(&gt; b &gt;...) 不起作用,因为 jsoup 也不支持相对选择器,在这种情况下,您可能不得不求助于选择 [id^=...] 元素并分别检查其子元素。在这种情况下,您可以使用选择器获得的最接近的是:has(b &gt;...),但我相信您知道这并不意味着完全相同。 您的完整 HTML 结构如何?我的意思是,&lt;td&gt; 元素是否存在于&lt;table&gt; 标签中?我问你这个是因为根据this answer,JSoup 无法检测到“孤独”tds。 @user2340612:好吧,考虑到问题的第一句话,我假设单元格实际上存在于表格中。 @user2340612:无论哪种方式都明确无害 :) 【参考方案1】:

:has(selector) 中的选择器包含父元素。我不认为 &gt;b 是 JSoup 中的有效选择器,但 *&gt;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

类似于solution of user2340612,您可以通过将选择器分成两部分来确保。首先,我们匹配所有 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);
  

【讨论】:

请注意,:has(* &gt; b) 等价于 :has(b),即使 b 不是具有 id 的元素的子元素,而是其中某个其他元素的子元素,它也会匹配. 感谢您的回复。您的第二个选项对我来说很好,因为 topic ID 没有嵌套。【参考方案2】:

我认为不可能为您的需要编写单个选择器,因为 JSoup 不支持像 :has(&gt; 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>

返回:

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

【讨论】:

以上是关于Jsoup :has() 选择器未按预期工作的主要内容,如果未能解决你的问题,请参考以下文章

Sendgrid 替换包装器未按预期工作

迭代器未按预期运行

CSS选择器级联/特异性未按预期工作

联系人选择器未在 Android Studio 中提供预期结果

选择内部功能未按预期工作

VB 选择案例未按预期工作