我有一个XML文档,我需要用Nokogiri解析它,但是我需要过滤掉所有名称与请求的名称不匹配的"角色"节点。
本质上,我只想返回一个数组,其中名字和姓氏与所需的角色匹配。
现状:
除了控制器中的一个过滤/搜索行外,我的所有代码都在工作。我已经浏览了Nokogiri的过滤器和搜索功能,但似乎无法达到预期的结果。
XML 输入
<xml>
<role xsi:type="director">
<firstName>Thomas</firstName>
<lastName>JONES</lastName>
<company>Jones Enterprises</company>
</role>
<role xsi:type="director">
<firstName>Thomas</firstName>
<lastName>TEST</lastName>
<company>Test Factory</company>
</role>
</xml>
控制器
firstname = 'Thomas'
lastname = 'JONES'
@results = doc.css('role').where((doc.css('firstName').text == @firstname) AND (doc.css('lastName').text == @lastname))
视图
<%= @results.each do |t| %>
<%= t.company %>
<% end %>
所需输出
Jones Enterprises
您可以使用 XPath 让 libXML2 基础为您完成工作:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<xml>
<role xsi:type="director">
<firstName>Thomas</firstName>
<lastName>JONES</lastName>
<company>Jones Enterprises</company>
</role>
<role xsi:type="director">
<firstName>Thomas</firstName>
<lastName>TEST</lastName>
<company>Test Factory</company>
</role>
</xml>
EOT
FIRSTNAME = 'Thomas'
LASTNAME = 'JONES'
roles = doc.search("//role[child::firstName[text()[contains(., 'Thomas')]] and child::lastName[text()[contains(., 'JONES')]]]")
puts roles.to_xml
# >> <role xsi:type="director">
# >> <firstName>Thomas</firstName>
# >> <lastName>JONES</lastName>
# >> <company>Jones Enterprises</company>
# >> </role>
你可以用CSS做同样的事情,只是CSS不允许我们使用逻辑在同一个libXML调用中测试两个子节点的内容。相反,在这一点上,我们必须进行多次调用,让Ruby和Nokogiri过滤所需的节点,这变得更加困难和CPU密集型。像这样的东西有效:
roles_firstnames = doc.search('role firstName:contains("Thomas")').map(&:parent)
roles_lastnames = doc.search('role lastName:contains("JONES")').map(&:parent)
matching_roles = (roles_firstnames & roles_lastnames)
puts matching_roles.map(&:to_xml)
# >> <role xsi:type="director">
# >> <firstName>Thomas</firstName>
# >> <lastName>JONES</lastName>
# >> <company>Jones Enterprises</company>
# >> </role>
通知:
- Nokogiri 让我们使用 jQuery 提供的很多 CSS 扩展,比如
:contains
。 -
roles_firstnames & roles_lastnames
让Ruby在数组上使用集合交集。每个数组都包含一个包含名字或姓氏的节点列表。每个条目都是父节点的标识符。&
简化了测试,以查看两个数组中的哪些节点是共同的,并且基本上为我们执行了一个and
,然后是uniq
。
无论哪种方式,一旦您拥有所需的<role>
节点,就可以轻松迭代它们并提取子节点<company>
的文本:
roles.map{ |n| n.at('company').text }
# => ["Jones Enterprises"]
首先,选择如下角色:
@roles = x.css('role').select {|r| firstname == r.at('firstName').text and lastname == r.at('lastName').text }
您应该使用包含过滤器参数的选择块中的变量。
在您看来,您读取了如下细化的 XML 节点:
<% @roles.each do |r| %>
<%= r.at('company').text %>
<% end %>