我有一个项目,它需要大量的XML数据并将其传递给Nokogiri,最终将每个元素添加到输出到YAML文件的哈希值。
这在XML数据集包含重复键之前一直有效。
示例数据:
<document>
<form xmlns="">
<title>
<main-title>Foo</main-title>
</title>
<homes>
<home>
<home-name>home 1</home-name>
<home-price>10</home-price>
</home>
<home>
<home-name>home 2</home-name>
<home-price>20</home-price>
</home>
</homes>
</form>
</document>
在homes
元素中,我可以拥有多个家庭,但每个home
将始终包含不同的内容。
此数据最终应输出如下结构:
title:
main-title: Foo
homes:
home:
home-name: home 1
home-price: 10
home:
home-name: home 2
home-price: 20
然而,我得到的只是homes
title:
main-title: Foo
homes:
home:
home-name: home 2
home-price: 20
我相信这是因为,当将每个元素添加到哈希时,如果密钥已经存在,它将简单地覆盖密钥,因此总是给我最后一个密钥。
这是用于将元素附加到哈希的代码:
def map_content(nodes, content_hash)
nodes.map do |element|
case element
when Nokogiri::XML::Element
child_content = map_content(element.children, {})
content_hash[element.name] = child_content unless child_content.empty?
when Nokogiri::XML::Text
return element.content
end
end
content_hash
end
我相信
content_hash[element.name] = child_content
是罪魁祸首,但是这段代码创建了类似的YAML文件,这些文件包含这些类型的重复键,我希望保留这些功能,所以我不想简单地添加一个唯一的密钥。数据哈希这意味着我需要修改许多方法并更新它们从YAML文件中提取数据的方式。
我读到了compare_by_identity
但不确定我是否会实现这一点。
我尝试使用compare_by_identity
,但它只会产生一个空的YAML文件,所以它可能会生成哈希但是无法将其写入YAML文件?
def map_content(nodes, content_hash)
content_hash = content_hash.compare_by_identity
nodes.map do |element|
case element
when Nokogiri::XML::Element
child_content = map_content(element.children, {})
content_hash[element.name] = child_content unless child_content.empty?
when Nokogiri::XML::Text
return element.content
end
end
content_hash
end
end
答案 0 :(得分:1)
compare_by_identity
原则上很容易:
hash = {}.compare_by_identity
hash[String.new("home")] = { "home-name" => "home 1", "home-price" => "10" }
hash[String.new("home")] = { "home-name" => "home 2", "home-price" => "20" }
hash
# => {"home"=>{"home-name"=>"home 1", "home-price"=>"10"}, "home"=>{"home-name"=>"home 2", "home-price"=>"20"}}
(我使用String.new
强制源代码中的文字字符串为不同的对象。你不需要这样,因为Nokogiri会动态构造字符串对象,并且它们会有不同的object_id
。)
即。您需要做的就是在每个.compare_by_identity
上拨打Hash
。然而,这并非没有价格:访问停止工作。
hash["home"]
# => nil
您现在需要明确检查每个元素的相等性:
hash.to_a.select { |k, v| k == "home" }.map { |k, v| v }
# => [{"home-name"=>"home 1", "home-price"=>"10"}, {"home-name"=>"home 2", "home-price"=>"20"}]
正如Severin所说,如果你把它放入YAML或JSON中也会产生可怕的后果,因为你将无法再将它正确加载回来。
您可以采用的另一种方法,也是一种非常优选的方法,是将XML特性保留给XML,并将结构转换为更多JSON-y(因此可以直接由Hash
和Array
表示。对象)。例如,
class MultiValueHash < Hash
def add(key, value)
if !has_key?(key)
self[key] = value
elsif Array === self[key]
self[key] << value
else
self[key] = [self[key], value]
end
end
end
hash = MultiValueHash.new
hash.add("home", { "home-name" => "home 1", "home-price" => "10" })
hash.add("home", { "home-name" => "home 2", "home-price" => "20" })
hash.add("work", { "work-name" => "work 1", "work-price" => "30" })
hash["home"]
# => [{"home-name"=>"home 1", "home-price"=>"10"}, {"home-name"=>"home 2", "home-price"=>"20"}]
hash["work"]
# => {"work-name"=>"work 1", "work-price"=>"30"}
这里的一个小问题是,如果你有一个孩子,那么这个孩子应该是一个数组还是一个简单的数值,这是不可能的。因此,在阅读时,如果要将值视为数组,请使用其中一个答案here。例如,如果您不对monkeypatching不感兴趣,
hash["home"].ensure_array
# => [{"home-name"=>"home 1", "home-price"=>"10"}, {"home-name"=>"home 2", "home-price"=>"20"}]
hash["work"].ensure_array
# => [["work-name", "work 1"], ["work-price", "30"]]
答案 1 :(得分:0)
我这样做:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<document>
<form xmlns="">
<title>
<main-title>Foo</main-title>
</title>
<homes>
<home>
<home-name>home 1</home-name>
<home-price>10</home-price>
</home>
<home>
<home-name>home 2</home-name>
<home-price>20</home-price>
</home>
</homes>
</form>
</document>
EOT
title = doc.at('main-title').text
homes = doc.search('home').map { |home|
{
'home' => {
'home-name' => home.at('home-name').text,
'home-price' => home.at('home-price').text.to_i
}
}
}
hash = {
'title' => {'main-title' => title},
'homes' => homes
}
当转换为YAML时,结果为:
require 'yaml'
puts hash.to_yaml
# >> ---
# >> title:
# >> main-title: Foo
# >> homes:
# >> - home:
# >> home-name: home 1
# >> home-price: 10
# >> - home:
# >> home-name: home 2
# >> home-price: 20
您无法创建:
homes:
home:
home-name: home 1
home-price: 10
home:
home-name: home 2
home-price: 20
因为home:
元素是homes
哈希中的键。不可能有多个具有相同名称的密钥;第二个将覆盖第一个。相反,它们必须是指定为- home
的哈希数组,如上面的输出所示。
考虑这些:
require 'yaml'
foo = YAML.load(<<EOT)
title:
main-title: Foo
homes:
home:
home-name: home 1
home-price: 10
home:
home-name: home 2
home-price: 20
EOT
foo
# => {"title"=>{"main-title"=>"Foo"},
# "homes"=>{"home"=>{"home-name"=>"home 2", "home-price"=>20}}}
与
foo = YAML.load(<<EOT)
title:
main-title: Foo
homes:
- home:
home-name: home 1
home-price: 10
- home:
home-name: home 2
home-price: 20
EOT
foo
# => {"title"=>{"main-title"=>"Foo"},
# "homes"=>
# [{"home"=>{"home-name"=>"home 1", "home-price"=>10}},
# {"home"=>{"home-name"=>"home 2", "home-price"=>20}}]}