将 Nokogiri 文档转换为 Ruby 哈希
Posted
技术标签:
【中文标题】将 Nokogiri 文档转换为 Ruby 哈希【英文标题】:Convert a Nokogiri document to a Ruby Hash 【发布时间】:2010-11-16 20:59:47 【问题描述】:有没有一种简单的方法可以将 Nokogiri XML 文档转换为哈希?
类似于 Rails 的 Hash.from_xml
。
【问题讨论】:
实际上,Rails 的 Hash.from_xml 巧妙地包含在 Rails 代码的 MiniXML 部分中。自从我写它以来,我一直想提取它。如果您没有很快听到,请轻推一下。 我发布了一个修改版的阿山阿里代码works with attributes and uses NokogiriHash.from_xml(nokogiri_doc.to_xml)
有什么不足之处吗?
amolnpujari.wordpress.com/2012/03/31/reading_huge_xml-rb 我发现 ox 比 nokogiri 快 5 倍,因此这里有一个 ox 示例 - gist.github.com/amolpujari/5966431,搜索任何元素并以哈希形式获取它
@JellicleCat,是的。不要浪费 CPU 使用 Nokogiri 解析 XML,只是为了让 Nokogiri 将其输出到 XML 以供其他东西解析。只需传递原始 XML 并完成它。
【参考方案1】:
如果您想将 Nokogiri XML 文档转换为哈希,只需执行以下操作:
require 'active_support/core_ext/hash/conversions'
hash = Hash.from_xml(nokogiri_document.to_s)
【讨论】:
请解释from_xml
的来源。这不是标准的 Ruby 方法。
@theTinMan from_xml 来自 ActiveSupport
来自这里:api.rubyonrails.org/classes/Hash.html#method-c-from_xml,代码是:typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml)))
这应该是最干净的答案,+1 给这位父亲
注意: OP 知道from_xml
并提到需要类似的东西。使用 from_xml
并不能回答问题。此外,如果文档已经是 Nokogiri 文档,则不要将其转换为字符串,只是为了使用其他 XML 解析器对其进行解析。相反,传递原始 XML 并忽略 Nokogiri 解析。其他任何事情都是在浪费 CPU 时间。【参考方案2】:
这是一个更简单的版本,它创建了一个包含命名空间信息的健壮哈希,包括元素和属性:
require 'nokogiri'
class Nokogiri::XML::Node
TYPENAMES = 1=>'element',2=>'attribute',3=>'text',4=>'cdata',8=>'comment'
def to_hash
kind:TYPENAMES[node_type],name:name.tap do |h|
h.merge! nshref:namespace.href, nsprefix:namespace.prefix if namespace
h.merge! text:text
h.merge! attr:attribute_nodes.map(&:to_hash) if element?
h.merge! kids:children.map(&:to_hash) if element?
end
end
end
class Nokogiri::XML::Document
def to_hash; root.to_hash; end
end
实际操作:
xml = '<r a="b" xmlns:z="foo"><z:a>Hello <b z:m="n" x="y">World</b>!</z:a></r>'
doc = Nokogiri::XML(xml)
p doc.to_hash
#=>
#=> :kind=>"element",
#=> :name=>"r",
#=> :text=>"Hello World!",
#=> :attr=>[
#=>
#=> :kind=>"attribute",
#=> :name=>"a",
#=> :text=>"b"
#=>
#=> ],
#=> :kids=>[
#=>
#=> :kind=>"element",
#=> :name=>"a",
#=> :nshref=>"foo",
#=> :nsprefix=>"z",
#=> :text=>"Hello World!",
#=> :attr=>[],
#=> :kids=>[
#=>
#=> :kind=>"text",
#=> :name=>"text",
#=> :text=>"Hello "
#=> ,
#=>
#=> :kind=>"element",
#=> :name=>"b",
#=> :text=>"World",
#=> :attr=>[
#=>
#=> :kind=>"attribute",
#=> :name=>"m",
#=> :nshref=>"foo",
#=> :nsprefix=>"z",
#=> :text=>"n"
#=> ,
#=>
#=> :kind=>"attribute",
#=> :name=>"x",
#=> :text=>"y"
#=>
#=> ],
#=> :kids=>[
#=>
#=> :kind=>"text",
#=> :name=>"text",
#=> :text=>"World"
#=>
#=> ]
#=> ,
#=>
#=> :kind=>"text",
#=> :name=>"text",
#=> :text=>"!"
#=>
#=> ]
#=>
#=> ]
#=>
【讨论】:
太棒了!【参考方案3】:我将此代码与 libxml-ruby (1.1.3) 一起使用。我自己没有使用过 nokogiri,但我知道它无论如何都使用 libxml-ruby。我还鼓励您查看将 xml 元素映射到 ruby 对象的 ROXML (http://github.com/Empact/roxml/tree);它构建在 libxml 之上。
# USAGE: Hash.from_libxml(YOUR_XML_STRING)
require 'xml/libxml'
# adapted from
# http://movesonrails.com/articles/2008/02/25/libxml-for-active-resource-2-0
class Hash
class << self
def from_libxml(xml, strict=true)
begin
XML.default_load_external_dtd = false
XML.default_pedantic_parser = strict
result = XML::Parser.string(xml).parse
return result.root.name.to_s => xml_node_to_hash(result.root)
rescue Exception => e
# raise your custom exception here
end
end
def xml_node_to_hash(node)
# If we are at the root of the document, start the hash
if node.element?
if node.children?
result_hash =
node.each_child do |child|
result = xml_node_to_hash(child)
if child.name == "text"
if !child.next? and !child.prev?
return result
end
elsif result_hash[child.name.to_sym]
if result_hash[child.name.to_sym].is_a?(Object::Array)
result_hash[child.name.to_sym] << result
else
result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << result
end
else
result_hash[child.name.to_sym] = result
end
end
return result_hash
else
return nil
end
else
return node.content.to_s
end
end
end
end
【讨论】:
太棒了!我只需要将= strict
更改为= false
。谢谢!
啊...抱歉,我一直在使用的文件没有任何属性(旧版 xml!)。
Nokogiri 不使用 libxml-ruby,它使用 libxml2,这是一个 C 库。【参考方案4】:
我在尝试简单地将 XML 转换为 Hash(不在 Rails 中)时发现了这一点。我想我会使用 Nokogiri,但最终选择了 Nori。
然后我的代码很简单:
response_hash = Nori.parse(response)
其他用户指出这不起作用。我还没有验证,但似乎 parse 方法已从类移动到实例。我上面的代码在某些时候有效。新的(未经验证的)代码是:
response_hash = Nori.new.parse(response)
【讨论】:
我认为这是不使用 Rails 的应用程序的最佳解决方案。 unverified 行有效。但是,如果您有一个Nokogiri::XML
文档,则必须首先调用它的to_s
方法。例如。 xml = Nokogiri::XML(File.open('file.xml'))
,然后是 hash = Nori.new.parse(xml.to_s)
,但这些字段似乎以不带字段名称的 Array
形式返回。
在尝试使用 Nokogiri 将我的头撞到墙上后,我终于遇到了这个。这是迄今为止最好的解决方案!感谢您的帖子。
我喜欢它的输出属性前面带有@
。【参考方案5】:
使用Nokogiri 将 XML 响应解析为 ruby 哈希。速度挺快的。
doc = Nokogiri::XML(response_body)
Hash.from_xml(doc.to_s)
【讨论】:
doc.to_s
返回您在response_body
中已有的内容,因此 nokogiri 在您的示例中无用
@alesguzik 基本上在那个声明中是正确的,你正在解析 xml 两次 Hash.from_xml 默认情况下将使用 REXML 而不是 Nokogiri 也不确定你是否可以改变这个
Nokogiri 有时在解析格式不佳或编码不佳的 XML 时更具弹性。我有 Hash.from_xml(xml_str) 失败的例子,但这仍然有效。所以它可以作为 Hash.from_xml(xml_str) 的后备
请注意,如果准确性很重要,则不应使用 Hash.from_xml
函数。这个函数在完全省略某些值的更复杂的 xml 文档上开始失效。【参考方案6】:
如果您在配置中定义这样的内容:
ActiveSupport::XmlMini.backend = 'Nokogiri'
它在 Nokogiri 中包含一个模块,您可以获得to_hash
方法。
【讨论】:
【参考方案7】:如果您在 Nokogiri 中选择的节点仅包含一个标签,您可以提取键、值并将它们压缩到一个哈希中,如下所示:
@doc ||= Nokogiri::XML(File.read("myxmldoc.xml"))
@node = @doc.at('#uniqueID') # this works if this selects only one node
nodeHash = Hash[*@node.keys().zip(@node.values()).flatten]
有关 Ruby 数组合并的更多信息,请参阅 http://www.ruby-forum.com/topic/125944。
【讨论】:
【参考方案8】:看看我为 Nokogiri XML 节点制作的简单混合。
http://github.com/kuroir/Nokogiri-to-Hash
这是一个用法示例:
require 'rubygems'
require 'nokogiri'
require 'nokogiri_to_hash'
html = '
<div id="hello" class="container">
<p>Hello! visit my site <a href="http://kuroir.com">Kuroir.com</a></p>
</div>
'
p Nokogiri.HTML(html).to_hash
=> [:div=>:class=>["container"], :children=>[:p=>:children=>[:a=>:href=>["http://kuroir.com"], :children=>[]]], :id=>["hello"]]
【讨论】:
以上是关于将 Nokogiri 文档转换为 Ruby 哈希的主要内容,如果未能解决你的问题,请参考以下文章
如何表示将 ruby 哈希转换为 yaml 的 aws 内部函数
[翻译][Ruby教程]Nokogiri - 解析HTML/XML文档 / Parsing an HTML/XML Document