Преобразование документа Nokogiri в Ruby Hash

Есть ли простой способ преобразовать XML-документ Nokogiri в Hash?

Что-то вроде Hash.from_xml Rails.


person Ivan    schedule 05.08.2009    source источник
comment
На самом деле, Hash.from_xml Rails аккуратно помещен в раздел MiniXML кода Rails. Я собирался извлечь его с тех пор, как написал. Подтолкните меня, если вы не услышите об этом в ближайшее время.   -  person Joseph Holsten    schedule 12.08.2009
comment
Я опубликовал модифицированную версию кода Ашана Али, которая работает с атрибутами и использует Nokogiri.   -  person dimus    schedule 17.03.2010
comment
Что-то неадекватное с Hash.from_xml(nokogiri_doc.to_xml)?   -  person JellicleCat    schedule 04.02.2014
comment
amolnpujari.wordpress.com/2012/03/31/reading_huge_xml-rb Я нашел ox в 5 раз быстрее, чем nokogiri, поэтому вот один пример в ox — gist.github.com/ amolpujari/5966431, найдите любой элемент и получите его в виде хеша   -  person Amol Pujari    schedule 20.03.2014
comment
@JellicleCat, да. Не тратьте ресурсы ЦП на анализ XML с помощью Nokogiri только для того, чтобы Nokogiri вывел его в XML для анализа каким-то другим способом. Просто передайте необработанный XML и покончите с этим.   -  person the Tin Man    schedule 24.09.2015


Ответы (8)


Я использую этот код с libxml-ruby (1.1.3). Я сам не использовал nokogiri, но я понимаю, что он все равно использует libxml-ruby. Я также рекомендую вам взглянуть на ROXML (http://github.com/Empact/roxml/tree), который сопоставляет элементы xml с объектами ruby; он построен поверх 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
person A.Ali    schedule 05.08.2009
comment
Потрясающий! Мне просто нужно было изменить = strict на = false. Спасибо! - person Ivan; 05.08.2009
comment
Ах ... Извините, файлы, с которыми я работал, не имеют никаких атрибутов (устаревший xml!). - person A.Ali; 06.08.2009
comment
Nokogiri НЕ использует libxml-ruby, он использует libxml2, библиотеку C. - person skrat; 18.04.2011

Если вы хотите преобразовать XML-документ Nokogiri в хэш, просто сделайте следующее:

require 'active_support/core_ext/hash/conversions'
hash = Hash.from_xml(nokogiri_document.to_s)
person Guillaume Roderick    schedule 20.09.2011
comment
Пожалуйста, объясните, откуда from_xml. Это не стандартный метод Ruby. - person the Tin Man; 05.01.2012
comment
@theTinMan from_xml получен от ActiveSupport - person ScottJShea; 29.01.2012
comment
Это взято отсюда: api.rubyonrails.org/classes/Hash.html #method-c-from_xml, код: typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml))) - person Dorian; 07.08.2012
comment
Это должен быть самый чистый ответ, +1 этому сиру - person Alexis Rabago Carvajal; 29.07.2015
comment
ПРИМЕЧАНИЕ. ОП знает о from_xml и упоминает о необходимости чего-то подобного. Использование from_xml не отвечает на вопрос. Кроме того, если документ уже является документом Nokogiri, не преобразовывайте его в строку только для того, чтобы проанализировать его с помощью какого-либо другого анализатора XML. Вместо этого передайте необработанный XML и игнорируйте синтаксический анализ с помощью Nokogiri. Все остальное - пустая трата процессорного времени. - person the Tin Man; 25.09.2015
comment
Чтобы добавить к комментарию @theTinMan. Нет необходимости использовать Nokogiri для синтаксического анализа xml, затем преобразования его в строку, а затем в хэш. Если вы используете active_support, вы можете сразу перейти к использованию Hash::from_xml. Например: Hash.from_xml(File.read('some.xml')) будет работать - person mbigras; 20.02.2017

Вот гораздо более простая версия, которая создает надежный хеш, который включает информацию о пространстве имен как для элементов, так и для атрибутов:

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

Видно в действии:

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=>"!"
#=>         }
#=>       ]
#=>     }
#=>   ]
#=> }
person Phrogz    schedule 13.04.2012
comment
это просто потрясающе! - person Steffen Roller; 09.10.2013

Я обнаружил это, пытаясь просто преобразовать XML в Hash (не в Rails). Я думал использовать Nokogiri, но в итоге остановился на Nori.

Тогда мой код был тривиальным:

response_hash = Nori.parse(response)

Другие пользователи указали, что это не работает. Я не проверял, но кажется, что метод разбора был перенесен из класса в экземпляр. Мой код выше работал в какой-то момент. Новый (непроверенный) код будет таким:

response_hash = Nori.new.parse(response)
person John Hinnegan    schedule 02.06.2011
comment
Я думаю, что это лучшее решение для приложений, которые не используют Rails. - person B Seven; 04.06.2015
comment
непровереннаястрока работает. Однако если у вас есть документ Nokogiri::XML, вы должны сначала вызвать его метод to_s. Например. xml = Nokogiri::XML(File.open('file.xml')), а затем hash = Nori.new.parse(xml.to_s), но поля возвращаются как Array без имен полей. - person code_dredd; 25.02.2016
comment
После того, как я ударился головой о стену, пытаясь использовать Нокогири, я наконец наткнулся на это. Это, безусловно, лучшее решение! Спасибо за сообщение. - person Albert Rannetsperger; 22.09.2016

Используйте Nokogiri для анализа XML-ответа на рубиновый хэш. Это довольно быстро.

doc = Nokogiri::XML(response_body) 
Hash.from_xml(doc.to_s)
person PythonDev    schedule 17.04.2014
comment
doc.to_s возвращает то, что у вас уже есть в response_body, поэтому nokogiri в вашем примере бесполезен - person alesguzik; 22.01.2015
comment
@alesguzik прав в основном в том утверждении, что вы дважды анализируете xml Hash.from_xml по умолчанию будет использовать REXML, а не Nokogiri, также не уверен, что вы можете это изменить - person Jesse Whitham; 11.08.2015
comment
Nokogiri иногда более устойчив к анализу плохо сформированных или закодированных XML-файлов. У меня есть примеры, когда Hash.from_xml(xml_str) потерпит неудачу, но это все равно будет работать. Так что это может быть запасной вариант для Hash.from_xml(xml_str) - person user4887419; 01.07.2016
comment
Имейте в виду, что функцию Hash.from_xml не следует использовать, если важна точность. Эта функция начинает плохо работать с более сложными XML-документами, полностью опуская определенные значения. - person pyRabbit; 25.09.2018

Если вы определите что-то подобное в своей конфигурации:

ActiveSupport::XmlMini.backend = 'Nokogiri'

он включает модуль в Nokogiri, и вы получаете метод to_hash.

person Pierre Schambacher    schedule 21.02.2013

Если узел, который вы выбрали в Nokogiri, состоит только из одного тега, вы можете извлечь ключи, значения и заархивировать их в один хэш, например:

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

См. http://www.ruby-forum.com/topic/125944 для получения дополнительной информации. при слиянии массивов Ruby.

person juanfe    schedule 23.07.2011

Взгляните на простое дополнение, которое я сделал для Nokogiri XML Node.

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

Вот пример использования:

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"]}}]
person MarioRicalde    schedule 21.08.2010