XMLからハッシュへの変換: Noriは最も深いXML要素の属性を削除します 質問する

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 ハッシュに変換していますが、後で Nori が XML の最も深い要素の属性を削除していることに気付きました。

問題の詳細と再現

これを実行するには、次のようなコードを使用します。

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

コードは、1 つのケースを除いて、通常、意図したとおりに動作します。Nori が 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"各フィールドエントリの属性が欠けています。

検索すると最終的に第59号最後の投稿(2015年8月)では、「Nori のコードにバグは見つからない」と述べています。

結論

それで、私の質問は次のとおりです:元のスキーマ (つまり、子のない要素に属性があるスキーマ) を使用できるようにする Nori の問題を回避する方法 (たとえば、設定など) をご存知の方はいらっしゃいますか? もしそうなら、これを正しく処理するコード スニペットを共有していただけますか?

動作させるために、XML スキーマを再設計し、コードを 3 回ほど変更する必要がありました。そのため、Nori を正常に動作させる方法があり、私がそれを知らないだけであれば、その方法を知りたいと思います。

そうしたいです。避ける当初使用したいと思っていたスキーマ構造でこれを適切に動作させるために、できるだけ多くのライブラリをインストールしましたが、動作することが証明されれば可能性はあります。(もう一度コードをリファクタリングする必要があります...)フレームワークは明らかにやりすぎなので、次のことを行ってください。ない提案するルビーオンレールまたは同様のフルスタックソリューション。

私の現在のソリューションは、(不本意ながら)再設計されたスキーマに基づいており、機能していますが、元のソリューションよりも生成と処理が複雑であるため、よりシンプルで浅いスキーマに戻りたいと思っています。

ベストアンサー1

Nori は実際には属性を削除しているわけではなく、単に印刷されていないだけです。

Ruby スクリプトを実行すると:

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オーバーロードできます。inspectNori::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}"

これにより、テキスト項目が 2 つの要素を持つ配列のように見えるため、データを 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"}]
                    ]
                }
            }
        }
    }
}

おすすめ記事