我正在使用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
Run Code Online (Sandbox Code Playgroud)
除一种情况外,代码通常按预期工作.每当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>
Run Code Online (Sandbox Code Playgroud)
...被转换为预期的Hash(为简洁起见省略了一些输出):
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"
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
当元素属性用于没有子元素的元素时,会出现问题.例如,以下文档未按预期转换:
<?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>
Run Code Online (Sandbox Code Playgroud)
相同的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"
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Hash只将它们的值作为列表,这不是我想要的.我期待的<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>
Run Code Online (Sandbox Code Playgroud)
它产生以下Hash:
{
"root" => {
"objects" => {
"object" => {
"fields" => {
"Name" => "The name",
"Description" => "A description"
},
"@id" => "1"
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
缺少type="whatever"每个字段条目的属性.
在最后一篇文章(2015年8月)中,搜索最终导致我发行#59,声称他无法"在Nori的代码中找到错误".
所以,我的问题是:你们中的任何人都知道一种解决Nori问题的方法(例如,可能是一个设置),这将允许我使用我的原始模式(即在没有子元素的元素中具有属性的模式)?如果是这样,您可以共享一个能够正确处理此问题的代码段吗?
我不得不重新设计我的XML模式并在大约三次更改代码以使其工作,所以如果有办法让Nori表现出来,而我根本不知道它,我想知道它是什么是.
我想避免尽可能多地安装更多的库,只是为了使我最初想要使用的模式结构正常工作,但是如果它被证明可行的话,我愿意接受它.(我不得不再次重新因子代码...)框架是肯定矫枉过正这一点,所以请:你不认为Ruby on Rails的或类似的全栈的解决方案.
请注意,我当前的解决方案(基于(不情愿)重新设计的架构)正在运行,但生成和处理比原始解决方案更复杂,我想回到更简单/更浅层的架构.
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}"
Run Code Online (Sandbox Code Playgroud)
你应该得到输出
["The name", "A description"]
text: 'The name' data: {"name"=>"Name"}
text: 'A description' data: {"name"=>"Description"}
Run Code Online (Sandbox Code Playgroud)
这清楚地表明属性存在,但方法没有显示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
Run Code Online (Sandbox Code Playgroud)
或者,如果您想更改输出,您可以重载该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
Run Code Online (Sandbox Code Playgroud)
并以元组的形式访问数据
puts "text: '#{data['root']['objects']['object']['fields']['field'][0].first}' data: #{ data['root']['objects']['object']['fields']['field'][0].last}"
Run Code Online (Sandbox Code Playgroud)
这样您就可以将数据作为 JSON 或 YAML,因为文本项看起来像具有 2 个元素的数组。
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"}}}}
Run Code Online (Sandbox Code Playgroud)
这应该做你想做的。
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
Run Code Online (Sandbox Code Playgroud)
输出为:
{
"root" => {
"objects" => {
"object" => {
"fields" => {
"field" => [
[0] ["The name", {"name"=>"Name"}],
[1] ["A description", {"name"=>"Description"}]
]
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)