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

Xpath - How to navigate to a value (Ruby Nokogiri)

如果我想获取货币汇率,比如 "USD",给定时间,比如“2015-02-09”,我该怎么做?

我尝试了以下方法:

/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 文件,但这个似乎更难破解。

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

<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 代码才能获得帮助。


编辑

回复评论:

I look forward to seeing some examples added to your answer, so that I can learn something new about xml namespaces. – 7stud

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

显式命名空间

首先,如果名称空间 URI 显式出现在元素上,则正确的语法使用花括号,既适用于前缀名称空间,也适用于默认名称空间:

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

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

Nokogiri 中的命名空间

前缀 (gesmes:) 没有任何内在含义。它们可以与任意命​​名空间 URI 相关联,并且每个文档都可以使用 gesmes: 来表示不同的东西。 XPath 引擎 本身 无法使用命名空间声明 - 通常,如果您想在 XPath 表达式中使用前缀,则需要 declare 这个命名空间再次用于 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给你正确答案

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:

代替 url
/xmlns:Cube