Как преобразовать объект Документа Нокогири в JSON - PullRequest
17 голосов
/ 25 июня 2011

У меня есть несколько проанализированных Nokogiri::XML::Document объектов, которые я хочу напечатать как JSON.

Я могу пойти по пути создания строки, синтаксического анализа ее в хэш, с помощью active-record или Crack, а затем Hash.to_json;но это и уродливо, и в зависимости от слишком манейных библиотек.

Нет ли более простого способа?

В соответствии с запросом в комментарии, например, XML <root a="b"><a>b</a></root> можно представить в виде JSON:

<root a="b"><a>b</a></root> #=> {"root":{"a":"b"}}
<root foo="bar"><a>b</a></root> #=> {"root":{"a":"b","foo":"bar"}}

То естьчто я получаю с Crack сейчас тоже.И, конечно же, коллизии между сущностями и дочерними тегами являются потенциальной проблемой, но я сам строю большую часть XML, поэтому мне проще всего вообще избежать этих коллизий:)

Ответы [ 2 ]

43 голосов
/ 29 мая 2012

Это работает для меня:

Hash.from_xml(@nokogiri_object.to_xml).to_json

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

require 'active_support/core_ext/hash'
14 голосов
/ 27 июня 2011

Вот один из способов сделать это.Как отмечено в моем комментарии, «правильный» ответ зависит от того, каким должен быть ваш вывод.В JSON нет канонического представления узлов XML, и, следовательно, в участвующие библиотеки не встроена такая возможность:

require 'nokogiri'
require 'json'
class Nokogiri::XML::Node
  def to_json(*a)
    {"$name"=>name}.tap do |h|
      kids = children.to_a
      h.merge!(attributes)
      h.merge!("$text"=>text) unless text.empty?
      h.merge!("$kids"=>kids) unless kids.empty?
    end.to_json(*a)
  end
end
class Nokogiri::XML::Document
  def to_json(*a); root.to_json(*a); end
end
class Nokogiri::XML::Text
  def to_json(*a); text.to_json(*a); end
end
class Nokogiri::XML::Attr
  def to_json(*a); value.to_json(*a); end
end

xml = Nokogiri::XML '<root a="b" xmlns:z="zzz">
  <z:a>Hello <b z:x="y">World</b>!</z:a>
</root>'
puts xml.to_json
{
  "$name":"root",
  "a":"b",
  "$text":"Hello World!",
  "$kids":[
    {
      "$name":"a",
      "$text":"Hello World!",
      "$kids":[
        "Hello ",
        {
          "$name":"b",
          "x":"y",
          "$text":"World",
          "$kids":[
            "World"
          ]
        },
        "!"
      ]
    }
  ]
}

Обратите внимание, что приведенное выше полностью игнорирует пространства имен, которые могут или не могут бытьВы хотите.


Преобразование в JsonML

Вот еще одна альтернатива, которая преобразует в JsonML.Хотя это преобразование с потерями (оно не поддерживает узлы комментариев, DTD или URL-адреса пространств имен), а формат немного «тупой» по своему дизайну (первый дочерний элемент имеет [1] или [2] в зависимости от того,атрибуты отсутствуют), указывает префиксы пространства имен для элементов и атрибутов:

require 'nokogiri'
require 'json'
class Nokogiri::XML::Node
  def namespaced_name
    "#{namespace && "#{namespace.prefix}:"}#{name}"
  end
end
class Nokogiri::XML::Element
  def to_json(*a)
    [namespaced_name].tap do |parts|
      unless attributes.empty?
        parts << Hash[ attribute_nodes.map{ |a| [a.namespaced_name,a.value] } ]
      end
      parts.concat(children.select{|n| n.text? ? (n.text=~/\S/) : n.element? })
    end.to_json(*a)
  end
end
class Nokogiri::XML::Document
  def to_json(*a); root.to_json(*a); end
end
class Nokogiri::XML::Text
  def to_json(*a); text.to_json(*a); end
end
class Nokogiri::XML::Attr
  def to_json(*a); value.to_json(*a); end
end

xml = Nokogiri::XML '<root a="b" xmlns:z="zzz">
  <z:a>Hello <b z:x="y">World</b>!</z:a>
</root>'
puts xml.to_json
#=> ["root",{"a":"b"},["z:a","Hello ",["b",{"z:x":"y"},"World"],"!"]]
...