Nokogiri::XML::Reader 跳过命名空间

Nokogiri::XML::Reader skips namespaces

我有多个 XML(如下所示),其中出现了一个可选标记。此标记位于命名空间 mynamespace

  xml = %{<?xml version="1.0" encoding="UTF-8" ?>
    <rss version="2.0" xmlns:mynamespace="http://example.com/ns/1.0">
      <channel>
        <item>
          <title>bar</title>
          <mynamespace:custom_tag>some text</mynamespace:custom_tag>
        </item>
        <item>
          <title>foo</title>
        </item>
      </channel>
    </rss>}

  Nokogiri::XML::Reader(xml).each do |node|
    next if node.name!='item' || node.node_type != Nokogiri::XML::Reader::TYPE_ELEMENT
    node = Nokogiri::XML.parse(node.outer_xml)
    puts "-> node"
    puts node.namespaces
    puts node.xpath("//mynamespace:custom_tag").text
  end

Nokogiri::XML::Reader(xml) 迭代每个 <item> 时,第一个 运行 输出 some text。但是当第二项不包含我的 mynamespace 命名空间的元素被解析时,它会抛出错误。

输出为:

-> node
{"xmlns:mynamespace"=>"http://example.com/ns/1.0"}
some text
-> node
{}
Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: //mynamespace:custom_tag
  • Why does Nokogiri include the namespace in the first item but not in the second item? Only because the first uses the namespace, and the second doesn't?
  • What would be a workaround to search for tags with namespaces, even when this namespace doesn't occur in the current node?
  1. Why does Nokogiri include the namespace in the first item but not in the second item? Only because the first uses the namespace, and the second doesn't?

要了解区别,请看第一个 node.outer_xml returns <item>:

<item xmlns:mynamespace="http://example.com/ns/1.0">
  <title>bar</title>
  <mynamespace:custom_tag>some text</mynamespace:custom_tag>
</item>

...相对于第二个:

<item>
  <title>foo</title>
</item>

您会注意到在第一种情况下 outer_xml 与输入 XML 不同:Nokogiri 有助于在父元素上包含任何子元素的命名空间声明。在第二种情况下,none 个元素具有任何名称空间,因此 Nokogiri 不包含任何名称空间声明。

  1. What would be a workaround to search for tags with namespaces, even when this namespace doesn't occur in the current node?

一个简单的解决方案是使用条件来跳过不包含命名空间的元素:

Nokogiri::XML::Reader(xml).each do |node|
  next unless node.name == 'item' && node.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT
  item_doc = Nokogiri::XML.parse(node.outer_xml)
  puts "-> node"

  unless item_doc.namespaces.key?("xmlns:mynamespace")
    puts "Does not include namespace; skipping"
    next
  end

  puts item_doc.xpath("//mynamespace:custom_tag").text
end
# => -> node
#    some text
#    -> node
#    Element doesn't include namespace; skipping

你会注意到,我还用 item_doc 更改了块内的变量名称 node,因为 Nokogiri::XML.parse returns 是 Nokogiri::XML::Document,而不是节点,命名很混乱。

一个更简单的解决方案是使用 Nokogiri 的内存解析器而不是 XML::Reader:

doc = Nokogiri::XML(xml)
doc.xpath("//rss/channel/item/mynamespace:custom_tag").each do |node|
  puts node.text
end
# => some_text

您可能正在使用 XML::Reader,因为 XML 文档很大,但除非您遇到实际内存或性能问题,否则我建议改用此方法。