我有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"]
这是一个更好的纯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,但是满足严格的要求。
您可以做:
html.css('.s > p').map {|node| node.text.strip }
我将从这里开始:
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变平,删除所有缩进,从而可以相信目标之后的下一个节点就是想要的节点。
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"]