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(> b >...)
不起作用,因为 jsoup 也不支持相对选择器,在这种情况下,您可能不得不求助于选择 [id^=...]
元素并分别检查其子元素。在这种情况下,您可以使用选择器获得的最接近的是:has(b >...)
,但我相信您知道这并不意味着完全相同。
您的完整 HTML 结构如何?我的意思是,<td>
元素是否存在于<table>
标签中?我问你这个是因为根据this answer,JSoup 无法检测到“孤独”td
s。
@user2340612:好吧,考虑到问题的第一句话,我假设单元格实际上存在于表格中。
@user2340612:无论哪种方式都明确无害 :)
【参考方案1】:
: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
类似于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(* > b)
等价于 :has(b)
,即使 b
不是具有 id 的元素的子元素,而是其中某个其他元素的子元素,它也会匹配.
感谢您的回复。您的第二个选项对我来说很好,因为 topic
ID 没有嵌套。【参考方案2】:
我认为不可能为您的需要编写单个选择器,因为 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>
返回:
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() 选择器未按预期工作的主要内容,如果未能解决你的问题,请参考以下文章