2017-05-09 35 views
0

我有一个需要大量XML数据并将其传递给Nokogiri的项目,最终将每个元素添加到散列输出到YAML文件。如何将XML中的重复键添加到散列

这是有效的,直到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元件I可以具有多个住宅,但每种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

怎么样在阵列,具有其下一个 '家' 键,把一切?你所建议的结构将不起作用,因为它将在将其加载回应用程序时给出相同的结果(只有最后一个:主键)。 – Severin

+0

嗯我试图不修改当前结构,但这可能是唯一的方法。 – chinds

+0

您所需的YAML输出是不可能的。 YAML在分析时会产生一个带重复键的散列,你已经发现这是不可能的。你必须使用哈希数组。 –

回答

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。)

iee从字面上看,你所需要做的就是致电.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"}] 

由于塞弗林指出,这也将带来可怕的后果,如果你把它放到YAML或JSON,因为你将不能够再次正确加载它。您可以采用的另一种方法是,将XML的特性留给XML,并将结构转换为更多JSON-y(因此可直接由HashArray对象表示)。例如,

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"]] 
+0

哇,非常感谢您的意见,阅读您的意见我了解'compare_by_identity'的问题。我喜欢放弃这一点。 但是,我仍然无法使其工作,请参阅发布更新 – chinds

+1

您尚未显示如何解析XML。你的代码会停留在空白文本节点上,因为你不会忽略它们,而是从函数中返回。如果你删除这些,它对我来说:'doc = Nokogiri :: XML.parse(xml)do | config | config.noblanks结束; map_content(doc.children,{})'。请参见[如何避免在创建Nokogiri :: XML或Nokogiri :: HTML对象时创建非重要的空白文本节点](http://stackoverflow.com/questions/21114933/how-to-avoid-创建-非显著-空白文本节点 - 当创造-A-NO) – Amadan

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 

bec因为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}}]} 
相关问题