【问题标题】:Xpath - How to navigate to a value (Ruby Nokogiri)Xpath - 如何导航到一个值 (Ruby Nokogiri)
【发布时间】:2015-02-11 17:26:35
【问题描述】:

如果我想获取货币汇率,比如“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 文件,但这个似乎更难破解。

【问题讨论】:

  • 在你“继续”之前,请测试我回答中的表达。
  • 我有但仍然没有骰子,对不起,如果我说得好像我没有尝试过。

标签: ruby xml xpath nokogiri


【解决方案1】:
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

【讨论】:

  • 这很好用,但是 target_date 是干什么用的?我想你忘了使用它。虽然我知道无论如何我应该把它放在哪里。非常感谢。你能解释一下为什么使用 xmlns:Cube 吗?
  • @frazerbw Nokogiri 通过输入的redeclaring declarations found on the root element 简化了命名空间声明。 xmlns:Cube 表示 Cube 元素,位于输入文档根元素的 default 命名空间中。
  • @MathiasMüller 好的,这消除了很多困惑。谢谢你帮助我,非常感谢。
  • 7stud,也许您可​​以解释一下这与 OP 的代码有何不同以及它为什么起作用,如果原始代码没有?谢谢!
  • 现在值得一票!只有一个小问题:如果命名空间 URI 在元素上是显式的,则正确的语法使用大括号:&lt;{http://www.gesmes.org/xml/2002-08-01}subject&gt;。也许你应该说,如果这些命名空间声明碰巧在根元素以外的元素上,这将不起作用
【解决方案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 命名空间的新知识。 – 7 螺栓

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给了你正确答案。

【讨论】:

    猜你喜欢
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-21
    • 2010-10-14
    相关资源
    最近更新 更多