2017-05-09 6 views
0

J'ai un projet qui prend de grandes quantités de données XML et les transmet à Nokogiri, ajoutant finalement chaque élément à un hachage sortant dans un fichier YAML.Comment ajouter des clés dupliquées de XML à un hachage

Cela fonctionne jusqu'à ce que l'ensemble de données XML contienne des clés en double.

Exemple de données:

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

Dans l'élément homes je peux avoir plusieurs maisons, mais chaque home contiendra toujours un contenu différent.

Ces données devraient une structure comme celui-ci a finalement sortie:

title: 
    main-title: Foo 
homes: 
    home: 
    home-name: home 1 
    home-price: 10 
    home: 
    home-name: home 2 
    home-price: 20 

Cependant tout ce que je reçois est le dernier élément à l'intérieur homes

title: 
     main-title: Foo 
    homes: 
     home: 
     home-name: home 2 
     home-price: 20 

Je crois que ce soit parce que, lors de l'ajout de chaque élément au hachage, il écrase simplement la clé si elle existe déjà, me donnant toujours la dernière clé.

C'est le code utilisé pour ajouter des éléments au hachage:

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 

Je crois

content_hash[element.name] = child_content 

est le coupable, mais ce code crée des fichiers YAML similaires qui ont ces types de clés en double , et je voudrais préserver cette fonctionnalité, donc je ne veux pas simplement ajouter une clé unique au hachage de données car cela signifierait que je devrais modifier de nombreuses méthodes et mettre à jour comment ils tirent des données du fichier YAML.

Je lis environ compare_by_identity mais je ne sais pas si je mettrais cela en œuvre.


J'ai essayé d'utiliser compare_by_identity mais seulement des résultats dans un fichier YAML vide, alors peut-être il est de générer le hachage mais il ne peut pas écrire dans le fichier 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

Qu'en est-il d'avoir une clé et tout mettre 'maisons' au-dessous dans un tableau? La structure que vous proposez ne fonctionnera pas, car elle vous donnera le même résultat (avec seulement la dernière: clé d'accueil) lors du chargement dans votre application. – Severin

+0

Hmm J'essaie de ne pas modifier la structure actuelle, mais c'est peut-être le seul moyen. – chinds

+0

La sortie YAML souhaitée n'est pas possible. Le YAML, une fois analysé, se traduira par un hachage avec des clés en double, que vous avez déjà découvert n'est pas possible. Vous devez utiliser un tableau de hachages. –

Répondre

1

compare_by_identity est facile en principe:

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"}} 

(j'utilise String.new pour forcer les chaînes dans le code source pour être différents objets Vous auriez pas besoin de cela, comme Nokogiri serait dynamiquement construire des objets de chaîne. , et ils auraient différents object_id.)

littéralement tout ce que vous devez faire est d'appeler .compare_by_identity sur chaque Hash que vous faites. Cependant, ce n'est pas sans prix: l'accès cesse de fonctionner.si vous le mettez en YAML ou JSON

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"}] 

Comme le souligne Severin, il aura aussi des répercussions désastreuses, que vous ne pourrez pas:

hash["home"] 
# => nil 

Vous devez explicitement vérifier maintenant l'égalité de chaque élément pour le charger correctement à nouveau. Une autre approche que vous pourriez prendre, et beaucoup préférée, est de laisser les particularités XML à XML, et de transformer la structure en quelque chose de plus JSON-y (et donc directement représentable par Hash et Array objets). Par exemple,

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"} 

Le léger problème ici est qu'il est pas vraiment possible de distinguer, si vous avez un seul enfant, si cet enfant doit être un tableau d'un, ou une valeur simple. Ainsi, lorsque vous lisez, lorsque vous souhaitez traiter la valeur sous forme de tableau, utilisez l'une des réponses here. Par exemple, si vous n'êtes pas défavorable à 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

Wow, merci beaucoup pour l'écriture, en lisant vos commentaires, je comprends le problème avec 'compare_by_identity'. Id aiment faire un essai. Cependant, je n'arrive toujours pas à le faire fonctionner, s'il vous plaît voir la mise à jour post – chinds

+1

Vous n'avez pas montré comment vous êtes en train d'analyser le XML. Votre code sera bloqué sur les nœuds de texte d'espaces, car au lieu de les ignorer vous 'renvoyez' hors de la fonction. Si vous les déposez, c'est pour moi: 'doc = Nokogiri :: XML.parse (xml) do | config | fin de config.noblanks; map_content (doc.children, {}) '. Voir [Comment éviter de créer des nœuds de texte d'espaces blancs non significatifs lors de la création d'un objet 'Nokogiri :: XML' ou' Nokogiri :: HTML'] (http://stackoverflow.com/questions/21114933/how-to-avoid- création-non-significatif-blanc-espace-texte-noeuds-lors de la création d'un no) – Amadan

0

Je ferais comme ça:

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 
} 

qui, lorsqu'il est converti en YAML, les résultats dans:

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 

Vous ne pouvez pas créer:

homes: 
    home: 
    home-name: home 1 
    home-price: 10 
    home: 
    home-name: home 2 
    home-price: 20 

bec ause les éléments home: sont des clés dans le hachage homes. Il n'est pas possible d'avoir plusieurs clés avec le même nom; La seconde écrasera la première. Au lieu de cela, ils doivent être un tableau de hachages désigné - home comme dans la sortie ci-dessus.

Tenez compte de ces:

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}}} 

contre:

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}}]}