我正在使用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
这里发生了一些事情:
-
正如其他人指出的那样,您应该解析为XML不是HTML,尽管这实际上对您获得的结果并没有太大影响。
-
您正在作为
DocumentFragment
解析,您应该作为完整的文档解析。有一些问题涉及查询文档片段,特别是从//
开始的查询不正确。 -
location
元素实际上相对于XML中的product
节点位于product_details/location
位置,因此您需要更新查询以考虑到。 -
您正在尝试在
xpath
方法的结果(即Nokogiri::XML::NodeSet
)的结果上使用=~
运算符。NodeSet
未定义=~
方法,因此它使用Object
上的默认方法,该方法仅返回nil
,因此它永远不会匹配。您应该使用at_xpath
仅获取第一个结果,然后在其上调用text
,以获取可以使用=~
匹配的字符串。
(您也使用@doc
和doc
,但我假设这只是错字。)
因此,将这四个点结合在一起,您的代码看起来像:
#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
函数在单个XPATH查询中完成所有操作:
# parse doc as XML document as above
puts doc.xpath("//product[contains(name, 'cool_fish')]/product_details/location")
这起作用是因为您有一个相当简单的正则是对文字字符串的检查。XPATH 1.0不支持正则表达式,因此,如果您的真实用例涉及更复杂的案例,则可能需要"困难的方式"。(在这种情况下,您可以编写自定义XPATH函数,但这是另一个故事。)
写下您的代码如下:
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
方法,但是您正在使用表达式,您应该将其与css
或search
之类的方法一起使用。我使用at_xpath
方法,因为您对#each
block中的单节点匹配感兴趣。。
但是您可以使用at
代替#at_xpath
和search
代替xpath
。
记住search
和at
都可以理解 css 以及 xpath 表达式。search
或xpath
或css
所有方法都将为您提供NodeSet
,其中at
,at_css
或at_xpath
将为您提供Node
。一旦Nokogiri
节点掌握在您的手中,请使用text
方法获取该节点的内容。
我建议使用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#search and Nokogiri :: Node#at Methods
@doc.search("product").each do |x|
puts x.at("location").content if x.at("name").content =~ /cool_fish/
end