Подтвердить что ты не робот

Преобразование XML в хеширование: Nori удаляет атрибуты самых глубоких элементов XML

Резюме

Я использую Ruby (ruby 2.1.2p95 (2014-05-08) [x86_64-linux-gnu] на моей машине, ruby 1.9.3p484 (2013-11-22 revision 43786) [x86_64-linux] в производственной среде) и Nori для преобразования XML-документа (первоначально обработанного с помощью Nokogiri для некоторой проверки) в Ruby Hash, но позже я обнаружил, что Nori отбрасывает атрибуты самых глубоких элементов XML.

Сведения о выпуске и воспроизведение

Для этого я использую код, похожий на следующий:

xml  = Nokogiri::XML(File.open('file.xml')) { |config| config.strict.noblanks }
hash = Nori.new.parse xml.to_s

Код обычно работает по назначению, за исключением одного случая. Всякий раз, когда Нори анализирует XML-текст, он отбрасывает атрибуты элемента из листовых элементов (т.е. Элементов, у которых нет дочерних элементов).

Например, следующий документ:

<?xml version="1.0"?>
<root>
  <objects>
    <object>
      <fields>
        <id>1</id>
        <name>The name</name>
        <description>A description</description>
      </fields>
    </object>
  </objects>
</root>

... преобразуется в ожидаемый хэш (некоторый вывод опущен для краткости):

irb(main):066:0> xml = Nokogiri::XML(txt) { |config| config.strict.noblanks }
irb(main):071:0> ap Nori.new.parse(xml.to_s), :indent => -2
{
  "root" => {
    "objects" => {
      "object" => {
        "fields" => {
          "id"   => "1",
          "name" => "The name"
          "description" => "A description"
        }
      }
    }
  }
}

Проблема возникает, когда атрибуты элементов используются для элементов без детей. Например, следующий документ не преобразуется, как ожидалось:

<?xml version="1.0"?>
<root>
  <objects>
    <object id="1">
      <fields>
        <field name="Name">The name</field>
        <field name="Description">A description</field>
      </fields>
    </object>
  </objects>
</root>

Тот же Nori.new.parse(xml.to_s), отображаемый awesome_print, показывает, что атрибуты самых глубоких элементов <field> отсутствуют:

irb(main):131:0> ap Nori.new.parse(xml.to_s), :indent => -2
{
  "root" => {
    "objects" => {
      "object" => {
        "fields" => {
          "field" => [
            [0] "The name",
            [1] "A description"
          ]
        },
        "@id"    => "1"
      }
    }
  }
}

У Хэша только свои значения как список, который я не хотел. Я ожидал, что элементы <field> сохраняют свои атрибуты так же, как их родительские элементы (например, см. @id="1" для <object>), а не для того, чтобы их атрибуты были отрублены.

Даже если документ изменен, чтобы выглядеть следующим образом, он все равно не работает должным образом:

<?xml version="1.0"?>
<root>
  <objects>
    <object id="1">
      <fields>
        <Name type="string">The name</Name>
        <Description type="string">A description</Description>
      </fields>
    </object>
  </objects>
</root>

Он создает следующий хэш:

{
  "root" => {
    "objects" => {
      "object" => {
        "fields" => {
          "Name"        => "The name",
          "Description" => "A description"
        },
        "@id"    => "1"
      }
    }
  }
}

Что не хватает атрибутов type="whatever" для каждого ввода поля.

Поиск в конечном итоге приведет меня к Issue # 59 с последним сообщением (с августа 2015 года), в котором он не может "найти ошибку" в коде Nori. "

Заключение

Итак, мой вопрос: Кто-нибудь из вас знает, как работать вокруг проблемы Nori (например, возможно, настройка), которая позволила бы мне использовать мою исходную схему (то есть та, атрибуты в элементах без детей)? Если да, можете ли вы поделиться фрагментом кода, который будет обрабатывать это правильно?

Мне пришлось перепроектировать мою XML-схему и изменить код примерно три раза, чтобы заставить ее работать, поэтому, если есть способ заставить Nori вести себя, и я просто не знаю об этом, я бы хотел знать, что это такое.

Я хотел бы избежать установки большего количества библиотек в максимально возможной степени, чтобы это нормально работало с структурой схемы, которую я первоначально хотел использовать, но я открыт для возможности, если она доказала свою эффективность. (Мне придется снова заново пересчитать код...) Рамки, безусловно, слишком завышены для этого, поэтому, пожалуйста, не предлагайте Ruby on Rails или аналогичный полный -статические решения.

Обратите внимание, что мое текущее решение, основанное на (неохотно) переработанной схеме, работает, но сложнее сгенерировать и обработать, чем исходное, и я хотел бы вернуться к более простой/более мелкой схеме.

4b9b3361

Ответ 1

Нори фактически не отбрасывает атрибуты, они просто не печатаются.

Если вы запустите ruby ​​ script:

require 'nori'

data = Nori.new(empty_tag_value: true).parse(<<XML)
<?xml version="1.0"?>
<root>
  <objects>
    <object>
      <fields>
        <field name="Name">The name</field>
        <field name="Description">A description</field>
      </fields>
    </object>
  </objects>
</root>
XML

field_list = data['root']['objects']['object']['fields']['field']

puts "text: '#{field_list[0]}' data: #{field_list[0].attributes}"
puts "text: '#{field_list[1]}' data: #{field_list[1].attributes}"

Вы должны получить вывод

["The name", "A description"]
text: 'The name' data: {"name"=>"Name"}
text: 'A description' data: {"name"=>"Description"}

Что ясно показывает, что атрибут есть, но не отображается методом inspect (функция p(x) совпадает с puts x.inspect).

Вы заметите, что puts field_list.inspect выводит ["The name", "A description"]. но field_list[0].attributes печатает ключ атрибута и данные.

Если вы хотите, чтобы pp отображал это, вы можете перегрузить метод inspect в Nori::StringWithAttributes.

class Nori
  class StringWithAttributes < String
    def inspect
      [attributes, String.new(self)].inspect
    end
  end
end

Или, если вы хотите изменить вывод, вы можете перегрузить метод self.new, чтобы он возвращал другую структуру данных.

class Nori
  class MyText < Array
    def attributes=(data)
      self[1] = data
    end
    attr_accessor :text
    def initialize(text)
      self[0] = text
      self[1] = {}
    end
  end
  class StringWithAttributes < String
    def self.new(x)
      MyText.new(x)
    end
  end
end

И получить доступ к данным в виде кортежа

puts "text: '#{data['root']['objects']['object']['fields']['field'][0].first}' data: #{ data['root']['objects']['object']['fields']['field'][0].last}"

Это позволит сделать так, чтобы данные были как JSON или YAML, так как текстовые элементы выглядели бы как массивы с двумя элементами. pp также работает.

{"root"=>
  {"objects"=>
    {"object"=>
      {"fields"=>
        {"field"=>
          [["The name", {"name"=>"Name"}],
           ["A description", {"name"=>"Description"}]]},
       "bob"=>[{"@id"=>"id1"}, {"@id"=>"id2"}],
       "bill"=>
        [{"p"=>["one", {}], "@id"=>"bid1"}, {"p"=>["two", {}], "@id"=>"bid2"}],
       "@id"=>"1"}}}}

Это должно делать то, что вы хотите.

require 'awesome_print'
require 'nori'

# Copyright (c) 2016 G. Allen Morris III
#
# Awesome Print is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------
module AwesomePrint
  module Nori

    def self.included(base)
      base.send :alias_method, :cast_without_nori, :cast
      base.send :alias_method, :cast, :cast_with_nori
    end

    # Add Nori XML Node and NodeSet names to the dispatcher pipeline.
    #-------------------------------------------------------------------
    def cast_with_nori(object, type)
      cast = cast_without_nori(object, type)
      if defined?(::Nori::StringWithAttributes) && object.is_a?(::Nori::StringWithAttributes)
        cast = :nori_xml_node
      end
      cast
    end

    #-------------------------------------------------------------------
    def awesome_nori_xml_node(object)
      return %Q|["#{object}", #{object.attributes}]|
    end
  end
end

AwesomePrint::Formatter.send(:include, AwesomePrint::Nori)

data = Nori.new(empty_tag_value: true).parse(<<XML)
<?xml version="1.0"?>
<root>
  <objects>
    <object>
      <fields>
        <field name="Name">The name</field>
        <field name="Description">A description</field>
      </fields>
    </object>
  </objects>
</root>
XML

ap data

поскольку вывод:

{
    "root" => {
        "objects" => {
            "object" => {
                "fields" => {
                    "field" => [
                        [0] ["The name", {"name"=>"Name"}],
                        [1] ["A description", {"name"=>"Description"}]
                    ]
                }
            }
        }
    }
}