Nokogiri比较场和投球

时间:2014-03-08 01:31:51

标签: ruby nokogiri

我正在使用Nokogiri来解析XML文档,并希望输出产品名称与字符串匹配的位置列表。

我能够输出所有产品名称的列表或所有位置的列表,但我无法比较两者。正确删除语句的if部分会输出所有位置。我的正则表达式做错了什么?

@doc = Nokogiri::HTML::DocumentFragment.parse <<-EOXML
<?xml version="1.0"?>
<root>
<product>
  <name>cool_fish</name>
  <product_details>
    <location>ocean</location>
    <costs>
      <msrp>9.99</msrp>
      <margin>5.00</margin>
    </costs>
  </product_details>
</product>
<product>
  <name>veggies</name>
  <product_details>
    <location>field</location>
    <costs>
      <msrp>2.99</msrp>
      <margin>1.00</margin>
    </costs>
  </product_details>
</product>    
</root>
EOXML

doc.xpath("//product").each do |x|
  puts x.xpath("location") if x.xpath("name") =~ /cool_fish/
end

3 个答案:

答案 0 :(得分:2)

这里发生了一些事情:

  1. 正如其他人所指出的那样,你应该解析为XML而不是HTML,尽管这对你得到的结果实际上没有太大的影响。

  2. 您正在解析为DocumentFragment,您应该将其解析为完整的文档。查询文档片段存在一些问题,特别是以//开头的查询无法正常工作。

  3. location元素实际上位于XML中product_details/location节点的product位置,因此您需要更新查询以将其考虑在内。< / p>

  4. 您正尝试在xpath method =~的结果上使用Nokogiri::XML::NodeSet运算符。 NodeSet未定义=~方法,因此它使用default one on Object that just returns nil,因此它永远不会匹配。您应该使用at_xpath仅获取第一个结果,然后在其上调用text以获取您可以使用=~匹配的字符串。

  5. (你也使用@docdoc,但我认为这只是一个错字。)

    因此,将您的代码看起来像这四点:

    #parse using XML, and not a fragment
    doc = Nokogiri::XML <<-EOXML
      # ... XML elided for space
    EOXML
    
    doc.xpath("//product").each do |x|
      # correct query, use at_xpath and call text method
      puts x.at_xpath("product_details/location") if x.at_xpath("name").text =~ /cool_fish/
    end
    

    但是在这种情况下,您可以使用contains function在单个XPath查询中完成所有操作:

    # parse doc as XML document as above
    puts doc.xpath("//product[contains(name, 'cool_fish')]/product_details/location")
    

    这是有效的,因为你有一个相当简单的正则表达式,只检查文字字符串。 XPath 1.0不支持正则表达式,所以如果你的真实用例涉及更复杂的用例,你可能需要“艰难的”方式。 (在这种情况下,您可以编写自定义XPath函数,但这是另一个故事。)

答案 1 :(得分:1)

编写如下代码:

require 'nokogiri'

@doc = Nokogiri::XML <<-EOXML
<?xml version="1.0"?>
<root>
<product>
  <name>cool_fish</name>
  <product_details>
    <location>ocean</location>
    <costs>
      <msrp>9.99</msrp>
      <margin>5.00</margin>
    </costs>
  </product_details>
</product>
<product>
  <name>veggies</name>
  <product_details>
    <location>field</location>
    <costs>
      <msrp>2.99</msrp>
      <margin>1.00</margin>
    </costs>
  </product_details>
</product>    
</root>
EOXML


@doc.xpath("//product").each do |x|
    puts x.at_xpath(".//location").text  if x.at_xpath(".//name").text =~ /cool_fish/
end
# >> ocean

您正在解析xml,您应该使用Nokogiri::XML。您的xpath表达式也不正确。您编写了#xpath方法,但是您使用的是表达式,您应该使用csssearch等方法。我使用了at_xpath方法,因为您对#each 中的单节点匹配感兴趣。

但您可以使用at代替#at_xpathsearch代替xpath

请记住searchat都了解 CSS 以及 xpath 表达式。 searchxpathcss所有方法都会为您NodeSet提供atat_cssat_xpath给您的方式一个Node。一旦Nokogiri节点出现在您手中,请使用text方法获取该节点的内容。

答案 2 :(得分:1)

我建议使用Nokogiri :: XML

@doc = Nokogiri::XML::Document.parse <<-EOXML
<?xml version="1.0"?>
<root>
<product>
  <name>cool_fish</name>
  <product_details>
    <location>ocean</location>
    <costs>
      <msrp>9.99</msrp>
      <margin>5.00</margin>
    </costs>
  </product_details>
</product>
<product>
  <name>veggies</name>
  <product_details>
    <location>field</location>
    <costs>
      <msrp>2.99</msrp>
      <margin>1.00</margin>
    </costs>
  </product_details>
</product>    
</root>
EOXML

然后是Nokogiri::Node#searchNokogiri::Node#at方法

@doc.search("product").each do |x|
  puts x.at("location").content if x.at("name").content =~  /cool_fish/
end