我的HTML包含:
<div class = "s">
<p> text1 </p>
<div class = "i">
<p> text2 </p>
</div>
</div>
我需要使用距离<div>
最近的"s"
获取所有文字。
例如,我试图获得:
array = []
html.css("s").each do |element|
array << element.text.strip
end
除了在我的阵列中出现"text2"
之外,这一切都很好,我不想要那些。因此,对于"text2"
,最接近的<div>
有"i"
类,我不想在我的数组中看到它。
我该如何解决这个问题?可以有不同的类名和更深的嵌套,例如:
<div class = "s">
<p> text1 </p>
<div class = "i">
<p> text2 </p>
<div class = "s">
<p> text3 </p>
<div class = "p">
<p> text4 </p>
</div>
</div>
</div>
</div>
从这里,我想得到一个数组:["text1", "text3"]
答案 0 :(得分:3)
更新:更好的仅xpath答案。我原来的答案如下。
# Given a Nokogiri::HTML document in the `html` variable:
html.xpath("//text()[normalize-space() and ancestor::div[1][@class='s']]").map(&:text).map(&:strip)
这只是找到所有非空白文本节点,其最近的div
祖先的类别为s
。它与我原来的答案是一样的,除了它完全是在XPath中完成的。
<div class = "s">
<p> text1 </p>
<div class = "i">
<p> text2 </p>
</div>
</div>
# => ["text1"]
<div class = "s">
<p> text1 </p>
<div class = "i">
<p> text2 </p>
<div class = "s">
<p> text3 </p>
<div class = "p">
<p> text4 </p>
</div>
</div>
</div>
</div>
# => ["text1", "text3"]
<div class = "s">
<div class='p'>
text 1
</div>
text 2
</div>
# => ["text 2"]
原始答案:
html.search("//div[@class='s']//text()").
select {|t| t.ancestors("div").first.attr("class") == "s" }.
map(&:text).join.squeeze.strip
# => "text1"
这里的基本思想是我们找到所有来自div.s
的文本节点,然后为每个文本节点找到最近的div
祖先,并且只接受具有最近div祖先的节点等级s
。
它有点CPU密集型,但它符合严格的要求。
答案 1 :(得分:1)
你可以这样做:
html.css('.s > p').map {|node| node.text.strip }
答案 2 :(得分:0)
我从这开始:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT, &:noblanks)
<div class = "s">
<p> text1 </p>
<div class = "i">
<p> text2 </p>
<div class = "s">
<p> text3 </p>
<div class = "p">
<p> text4 </p>
</div>
</div>
</div>
</div>
EOT
doc.search('.s').map{ |div| div.child.text.strip }
# => ["text1", "text3"]
由于HTML格式化,我认为很难找到合适的节点child
节点的".s"
是包含“\ n”的下一个Text节点。忽略它们很困难,因为它们可能不是文本,它们可能是您想要返回的节点。
诀窍是告诉Nokogiri在解析文档时剥离空白节点,这有效地压平了HTML,删除了所有缩进,使得可以相信目标之后的下一个节点是想要的。 / p>
foobar会导致这种技术失败。
是的,它会,并且需要额外的逻辑来清除那些:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT, &:noblanks)
<div class = "s">
<p> text1 </p>
<div class = "i">
<p> text2 </p>
<div class = "s">
<p> text3 </p>
<div class = "p">
<p> text4 </p>
</div>
</div>
<div class='s'><div class='i'>foobar</div></div>
</div>
</div>
EOT
这是旧的逻辑:
doc.search('.s').map{ |div| div.child.text.strip }
# => ["text1", "text3", "foobar"]
快速测试以清除不需要的内容:
doc.search('.s').reject{ |div| div.child['class'] == 'i' }.map{ |div| div.child.text.strip }
# => ["text1", "text3"]