Xpath - 如何导航到值(Ruby Nokogiri)

时间:2015-02-10 20:22:53

标签: ruby xml xpath nokogiri

如果我想获取货币汇率,比如说#34; USD&#34;,给定一定的时间,说&#34; 2015-02-09&#34;,我该怎么做?< / p>

我尝试了以下内容:

/gesmes:Envelope/def:Cube/def:Cube[@time="2014-11-19"]/def:Cube[@currency="USD"]/@rate

虽然我认为由于缺乏理解这是错误的,至少,我知道这是错误的,因为Nokogiri没有运行它。

http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml

编辑:

我要继续猜测我没有正确使用Nokogiri和XPath。

@doc = Nokogiri::XML(File.open("exchange_data.xml"))
@values = @doc.xpath('XPATH HERE')
@values.each {|i| puts i}

我已经阅读了这个教程,并设法让它适用于其他xml文件,但是这个文件似乎更难破解。

2 个答案:

答案 0 :(得分:3)

require 'nokogiri'

doc = Nokogiri::XML(File.open("xml4.xml"))
target_date = "2015-02-09"
target_currency = 'USD'

xpaths = [
  "//gesmes:Envelope",
  "/xmlns:Cube",
  "/xmlns:Cube[@time='#{target_date}']",
  "/xmlns:Cube[@currency='#{target_currency}']",
]
xpath = xpaths.join

target_cube = doc.at_xpath(xpath)
puts target_cube.attribute('rate')

--output:--
1.1297

对评论的回应:

您的根标签:

<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01"
                 xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">

...使用xmlns声明两个名称空间,代表 xml名称空间。命名空间:

xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01"

声明任何名称前缀为gesmes的子标记,例如:

<gesmes:subject>
  ...
</gesmes:subject>

实际上会有一个标记名称,它将指定的url合并到标记名称中,如下所示:

<http://www.gesmes.org/xml/2002-08-01:subject>
  ...
</http://www.gesmes.org/xml/2002-08-01:subject>

您希望使用命名空间的原因是为Cube标记创建唯一名称,以便它不会与其他xml文档的Cube标记冲突。

第二个名称空间声明:

xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref"

默认命名空间声明。它声明任何未指定前缀的子标记都将指定的URL合并到其标记名称中。所以像这样的标签:

<Cube>
  ...
</Cube>

变成这样:

<http://www.ecb.int/vocabulary/2002-08-01/eurofxref:Cube>
  ...
</http://www.ecb.int/vocabulary/2002-08-01/eurofxref:Cube>

但是,在xpath中编写这样的标记名称是不实用的,因此代替网址,您可以使用快捷方式xmlns

/xmlns:Cube

答案 1 :(得分:2)

这可能是由于本文档中的命名空间:

<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">

要测试此假设,请应用以下XPath表达式:

/*[local-name() = 'Envelope']/*[local-name() = 'Cube']/*[local-name() = 'Cube'][@time="2014-11-19"]/*[local-name() = 'Cube'][@currency="USD"]/@rate

让我知道你得到了什么。如果您正确使用XPath,则最终应该:

 rate="1.2535"

如果没有,您没有正确使用Nokogiri的XPath工具,那么您真的需要显示所有Ruby代码才能获得帮助。


修改

回应评论:

  

我期待看到一些示例添加到您的答案中,以便我可以学习有关xml命名空间的新内容。 - 7stud

7stud已经给出了正确的答案,我只会添加我认为在这个答案中缺失的信息。

明确的命名空间

首先,如果在元素上显式出现名称空间URI,则正确的语法使用大括号,对于前缀和默认名称空间都是如此:

<{http://www.gesmes.org/xml/2002-08-01}subject>

在内部,这就是如何在元素上表示名称空间(尽管某些应用程序还有其他方法可以将元素与名称空间相关联)。前缀和默认命名空间用于简化此过程。

Nokogiri的命名空间

前缀(gesmes:)没有任何固有的含义。它们可以与任意名称空间URI相关联,并且每个文档都可以使用gesmes:来表示不同的名称。命名空间声明不适用于XPath引擎本身 - 通常,如果您想在XPath表达式中使用前缀,则需要声明此命名空间再次为XPath处理器。

然而,Nokogiri试图通过重新声明在输入文档的根元素上找到的命名空间声明来简化命名空间处理。这很重要,因为它允许您重用在输入的根元素上声明的前缀,而不实际声明命名空间。对于在根元素上声明的没有前缀的默认名称空间,Nokogiri定义了一种特殊语法:

xmlns:Cube

文档中存在但在根元素以外的元素上声明的命名空间:

<root>
   <child xmlns:gesmes="http://other.com"/>
</root>

必须在Nokogiri中明确声明:

@doc.xpath('//other:Cube', 'other' => 'http://other.com/')

原始代码有什么问题?

您的代码:

/gesmes:Envelope/def:Cube/def:Cube[@time="2014-11-19"]/def:Cube[@currency="USD"]/@rate

不起作用,因为您使用的是未知前缀def:。此前缀未在输入的根元素上声明,也未使用Nokogiri声明它。 Cube元素位于默认命名空间中,正如我们所见,解决它们的正确方法是

/gesmes:Envelope/xmlns:Cube

等等,7stud给了你正确答案。