【问题标题】:How to filter array with Nokogiri (search, filter, conditions etc)如何使用 Nokogiri 过滤数组(搜索、过滤、条件等)
【发布时间】:2014-12-01 01:58:34
【问题描述】:

我有一个需要用 Nokogiri 解析的 XML 文档,但是我需要过滤掉所有名称与请求的名称不匹配的“角色”节点。

本质上,我想返回一个仅包含名字和姓氏匹配所需的角色的数组。

当前状态:

除了控制器内的一个过滤/搜索行之外,我的所有代码都可以正常工作。我查看了 Nokogiri 的 filtersearch 功能,但似乎无法达到预期的效果。

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

【问题讨论】:

    标签: ruby-on-rails ruby nokogiri


    【解决方案1】:

    您可以让 libXML2 基础使用 XPath 为您完成工作:

    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 &amp; roles_lastnames 让 Ruby 在数组上使用集合交集。每个数组都包含一个包含名字或姓氏的节点列表。每个条目都是父节点的标识符。 &amp; 简化了测试以查看两个数组中的哪些节点是相同的,并且基本上为我们做了一个and 后跟一个uniq

    无论您采用哪种方式,一旦您拥有所需的&lt;role&gt; 节点,就可以轻松地遍历它们并提取子&lt;company&gt; 节点的文本:

    roles.map{ |n| n.at('company').text }
    # => ["Jones Enterprises"]
    

    【讨论】:

      【解决方案2】:

      首先,你选择这样的角色:

      @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 %>
      

      【讨论】:

      • 最好在控制器中处理@roles,并创建一个包含所有需要的子信息的简单哈希或数组,以便在视图中使用。
      • 当然。更好的办法是将这个例程移到某个模型上。
      • 在回答中这样说是可以的。 Stack Overflow 不仅仅是回答所提出的问题,而是以更好的方式来回答问题。人们根据他们手头看到的问题提出问题,但通常,由于经验,我们发现他们所处的位置不是最佳的,他们很可能是通过做错事才到达那里的。 (“XY Question”)并且他们不知道如何询问将他们置于何处的第一个问题。令人讨厌的是,短视/无知的人然后投票给我们。 :-/
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-21
      • 2019-09-19
      • 2017-06-08
      • 1970-01-01
      • 2021-09-13
      • 1970-01-01
      相关资源
      最近更新 更多