2009-08-05 4 views
60

Có cách nào dễ dàng để chuyển đổi tài liệu XML Nokogiri thành Hash không?Chuyển đổi tài liệu Nokogiri thành Ruby Hash

Giống như Rails 'Hash.from_xml.

+1

Thực ra, Rails 'Hash.from_xml được gói gọn trong phần MiniXML của mã Rails. Tôi đã có ý nghĩa để trích xuất nó kể từ khi tôi viết nó. Hãy cho tôi một cú huých nếu bạn không sớm nghe về nó. –

+0

Có điều gì không phù hợp với 'Hash.from_xml (nokogiri_doc.to_xml)'? – JellicleCat

+0

http://amolnpujari.wordpress.com/2012/03/31/reading_huge_xml-rb/ Tôi tìm thấy bò gấp 5 lần so với nokogiri, do đó ở đây một ví dụ trong ox - https://gist.github.com/amolpujari/5966431 , tìm kiếm bất kỳ phần tử nào và lấy nó ở dạng băm –

Trả lời

13

Tôi sử dụng mã này với libxml-ruby (1.1.3). Tôi đã không sử dụng nokogiri bản thân mình, nhưng tôi hiểu rằng nó sử dụng libxml-ruby anyway. Tôi cũng khuyến khích bạn xem ROXML (http://github.com/Empact/roxml/tree) để ánh xạ các phần tử xml vào các đối tượng ruby; nó được xây dựng trên đỉnh 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 
+0

Tuyệt vời! Tôi chỉ cần thay đổi '= strict' thành' = false'. Cảm ơn! – Ivan

+0

Hmmm, thực sự mã đó không xử lý các thuộc tính ... – Ivan

+0

Ah ... Xin lỗi về điều đó, các tệp tôi đang làm việc không có bất kỳ thuộc tính nào (legacy xml!). –

-2

Hãy xem xét kết hợp đơn giản mà tôi đã tạo cho nút Nokogiri XML Node.

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

Dưới đây là một ví dụ sử dụng:

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

Tôi tìm thấy điều này trong khi cố gắng để đơn giản là chuyển đổi XML để Băm (không phải trong Rails). Tôi đã nghĩ rằng tôi sẽ sử dụng Nokogiri, nhưng cuối cùng đã đi với Nori.

Sau đó, mã của tôi là trival:

response_hash = Nori.parse(response) 

người dùng khác đã chỉ ra rằng điều này không làm việc. Tôi chưa xác minh, nhưng có vẻ như phương thức phân tích cú pháp đã được di chuyển từ lớp này sang cá thể khác. Mã của tôi ở trên làm việc tại một số điểm. Mới (chưa được xác minh) mã sẽ là:

response_hash = Nori.new.parse(response) 
+0

Tôi nghĩ đây là giải pháp tốt nhất cho các ứng dụng không sử dụng Rails. –

+0

Dòng * un * đã xác minh hoạt động. Tuy nhiên, nếu bạn có một tài liệu 'Nokogiri :: XML', trước tiên bạn phải gọi phương thức' to_s' của nó. Ví dụ. 'xml = Nokogiri :: XML (File.open ('file.xml'))' và sau đó 'hash = Nori.new.parse (xml.to_s)', nhưng các trường xuất hiện để được trả về dưới dạng 'Mảng' mà không có tên trường. – ray

+0

Sau khi đập đầu vào tường cố gắng sử dụng Nokogiri, tôi cuối cùng cũng bắt gặp nó. Là BY FAR giải pháp tốt nhất! Cảm ơn vì bài đăng. –

0

Nếu nút mà bạn đã chọn trong Nokogiri chỉ bao gồm một thẻ, bạn có thể trích xuất các phím, giá trị và nén chúng vào một băm, như vậy:

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

Xem http://www.ruby-forum.com/topic/125944 để biết thêm thông tin về cách hợp nhất mảng Ruby.

92

Nếu bạn muốn chuyển đổi một tài liệu XML Nokogiri để băm, chỉ cần làm như sau:

require 'active_support/core_ext/hash/conversions' 
hash = Hash.from_xml(nokogiri_document.to_s) 
+1

Vui lòng giải thích từ 'from_xml' đến từ đâu. Nó không phải là một phương thức chuẩn của Ruby. –

+4

@theTinMan from_xml đến từ ActiveSupport – ScottJShea

+1

Nó xuất phát từ đây: http://api.rubyonrails.org/classes/Hash.html#method-c-from_xml, mã là: 'typecast_xml_value (unrename_keys (ActiveSupport :: XmlMini.parse (xml))) ' – Dorian

15

Dưới đây là một phiên bản đơn giản hơn nhiều mà tạo ra một Hash mạnh mẽ bao gồm thông tin không gian tên, cả hai cho các yếu tố và các thuộc tính:

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 

Seen trong hành động:

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=>"!" 
#=>   } 
#=>  ] 
#=>  } 
#=> ] 
#=> } 
+1

chỉ là tuyệt vời! –

3

Nếu bạn Defi ne một cái gì đó như thế này trong cấu hình của bạn:

ActiveSupport::XmlMini.backend = 'Nokogiri' 

nó bao gồm một mô-đun trong Nokogiri và bạn có được phương pháp to_hash.

7

Sử dụng Nokogiri để phân tích cú pháp phản hồi XML thành băm băm. Nó khá nhanh.

doc = Nokogiri::XML(response_body) 
Hash.from_xml(doc.to_s) 
+8

'doc.to_s' trả về những gì bạn đã có trong' response_body', vì vậy nokogiri là vô ích trong ví dụ của bạn – alesguzik

+1

@alesguzik là đúng về cơ bản trong câu lệnh đó bạn đang phân tích cú pháp xml hai lần Hash.from_xml sẽ sử dụng REXML theo mặc định không Nokogiri cũng không chắc chắn nếu bạn có thể thay đổi điều này –

+0

Nokogiri đôi khi linh hoạt hơn để phân tích cú pháp XML được tạo hình hoặc mã hóa kém. Tôi có ví dụ nơi Hash.from_xml (xml_str) sẽ thất bại, nhưng điều này vẫn sẽ làm việc. Vì vậy, nó có thể là một dự phòng cho Hash.from_xml (xml_str) – user4887419