Konvertieren Sie ein Nokogiri-Dokument in einen Ruby Hash

68

Gibt es eine einfache Möglichkeit, ein Nokogiri-XML-Dokument in einen Hash zu konvertieren?

So etwas wie Rails Hash.from_xml.

Ivan
quelle
1
Tatsächlich ist Rails 'Hash.from_xml ordentlich im MiniXML-Abschnitt des Rails-Codes verpackt. Ich wollte es extrahieren, seit ich es geschrieben habe. Gib mir einen Schubs, wenn du nicht bald davon hörst.
Joseph Holsten
Ich habe eine modifizierte Version des Ashan Ali-Codes veröffentlicht, der mit Attributen arbeitet und
Nokogiri
Gibt es etwas Unangemessenes Hash.from_xml(nokogiri_doc.to_xml)?
JellicleCat
amolnpujari.wordpress.com/2012/03/31/reading_huge_xml-rb Ich habe Ochsen 5-mal schneller gefunden als nokogiri, daher hier ein Beispiel in ox - gist.github.com/amolpujari/5966431 , suche nach einem Element und erhalte es in Hash Form
Amol Pujari
@JellicleCat, ja. Verschwenden Sie keine CPU-Analyse von XML mit Nokogiri, nur damit Nokogiri es in XML ausgibt, um es von etwas anderem zu analysieren. Übergeben Sie einfach das unformatierte XML und fertig.
der Blechmann

Antworten:

14

Ich benutze diesen Code mit libxml-ruby (1.1.3). Ich habe selbst kein Nokogiri verwendet, aber ich verstehe, dass es sowieso libxml-ruby verwendet. Ich möchte Sie auch dazu ermutigen, sich ROXML ( http://github.com/Empact/roxml/tree ) anzusehen, das XML-Elemente Ruby-Objekten zuordnet . Es ist auf libxml aufgebaut.

# 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
A.Ali
quelle
Genial! Ich habe gerade zu ändern benötigt = strictzu = false. Vielen Dank!
Ivan
Ah ... Entschuldigung, die Dateien, mit denen ich gearbeitet habe, haben keine Attribute (Legacy-XML!).
A.Ali
9
Nokogiri verwendet NICHT libxml-ruby, sondern libxml2, eine C-Bibliothek.
Skrat
104

Wenn Sie ein Nokogiri-XML-Dokument in einen Hash konvertieren möchten, gehen Sie wie folgt vor:

require 'active_support/core_ext/hash/conversions'
hash = Hash.from_xml(nokogiri_document.to_s)
Guillaume Roderick
quelle
1
Bitte erklären Sie, woher from_xmlkommt. Es ist keine Standard-Ruby-Methode.
der Blechmann
4
@theTinMan from_xml kommt von ActiveSupport
ScottJShea
1
Es kommt von hier: api.rubyonrails.org/classes/Hash.html#method-c-from_xml , der Code ist:typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml)))
Dorian
1
Dies sollte die sauberste Antwort sein, +1 auf diesen Vater
Alexis Rabago Carvajal
6
HINWEIS: Das OP ist sich from_xmlder Notwendigkeit ähnlicher Dinge bewusst und erwähnt diese. Verwenden from_xmlbeantwortet die Frage nicht. Wenn das Dokument bereits ein Nokogiri-Dokument ist, konvertieren Sie es nicht in eine Zeichenfolge, nur um es mit einem anderen XML-Parser zu analysieren. Übergeben Sie stattdessen das unformatierte XML und ignorieren Sie das Parsen mit Nokogiri. Alles andere ist eine Verschwendung von CPU-Zeit.
der Blechmann
18

Hier ist eine weitaus einfachere Version, die einen robusten Hash erstellt, der Namespace-Informationen sowohl für Elemente als auch für Attribute enthält:

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

In Aktion gesehen:

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=>"!"
#=>         }
#=>       ]
#=>     }
#=>   ]
#=> }
Phrogz
quelle
1
das ist einfach toll!
Steffen Roller
12

Ich habe dies beim Versuch gefunden, XML einfach in Hash zu konvertieren (nicht in Rails). Ich dachte, ich würde Nokogiri benutzen, ging aber schließlich zu Nori .

Dann war mein Code trival:

response_hash = Nori.parse(response)

Andere Benutzer haben darauf hingewiesen, dass dies nicht funktioniert. Ich habe nicht überprüft, aber es scheint, dass die Analysemethode von der Klasse in die Instanz verschoben wurde. Mein Code oben hat irgendwann funktioniert. Neuer (nicht verifizierter) Code wäre:

response_hash = Nori.new.parse(response)
John Hinnegan
quelle
Ich denke, dies ist die beste Lösung für Apps, die keine Rails verwenden.
B Seven
Die nicht verifizierte Zeile funktioniert. Wenn Sie jedoch ein Nokogiri::XMLDokument haben, müssen Sie to_szuerst dessen Methode aufrufen . ZB xml = Nokogiri::XML(File.open('file.xml'))und dann hash = Nori.new.parse(xml.to_s), aber die Felder scheinen als Arrayohne die Feldnamen zurückgegeben zu werden.
Code_Dredd
Nachdem ich meinen Kopf gegen die Wand geschlagen hatte, um Nokogiri zu benutzen, stieß ich schließlich darauf. Das ist bei weitem die beste Lösung! Danke für den Beitrag.
Albert Rannetsperger
11

Verwenden Sie Nokogiri , um die XML-Antwort auf Ruby-Hash zu analysieren. Es ist ziemlich schnell.

doc = Nokogiri::XML(response_body) 
Hash.from_xml(doc.to_s)
PythonDev
quelle
9
doc.to_sgibt zurück, was Sie bereits haben response_body, so dass nokogiri in Ihrem Beispiel nutzlos ist
alesguzik
1
@alesguzik ist im Grunde richtig in dieser Aussage, dass Sie die XML zweimal analysieren Hash.from_xml wird standardmäßig REXML verwenden, nicht Nokogiri auch nicht sicher, ob Sie dies ändern können
Jesse Whitham
2
Nokogiri ist manchmal widerstandsfähiger, schlecht geformte oder codierte XMLs zu analysieren. Ich habe Beispiele, bei denen Hash.from_xml (xml_str) fehlschlagen würde, aber dies würde immer noch funktionieren. So kann es ein Fallback für Hash.from_xml (xml_str) sein
user4887419
Beachten Sie, dass die Hash.from_xmlFunktion nicht verwendet werden sollte, wenn Genauigkeit wichtig ist. Diese Funktion fällt bei komplexeren XML-Dokumenten flach, wobei bestimmte Werte vollständig weggelassen werden.
PyRabbit
3

Wenn Sie in Ihrer Konfiguration so etwas definieren:

ActiveSupport::XmlMini.backend = 'Nokogiri'

Es enthält ein Modul in Nokogiri und Sie erhalten die to_hashMethode.

Pierre Schambacher
quelle
0

Wenn der in Nokogiri ausgewählte Knoten nur aus einem Tag besteht, können Sie die Schlüssel und Werte extrahieren und wie folgt in einen Hash komprimieren:

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

Weitere Informationen zum Zusammenführen von Ruby-Arrays finden Sie unter http://www.ruby-forum.com/topic/125944 .

Juanfe
quelle
-1

Schauen Sie sich das einfache Mix-In an, das ich für den Nokogiri XML-Knoten erstellt habe.

http://github.com/kuroir/Nokogiri-to-Hash

Hier ist ein Anwendungsbeispiel:

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