【问题标题】:XML parsing with smart tag grouping in Ruby在 Ruby 中使用智能标记分组进行 XML 解析
【发布时间】:2013-10-18 03:27:04
【问题描述】:

这是我尝试实现的转换示例。 源 XML:

<cats>
  <cat>John</cat>
  <cat>Peter</cat>
</cats>

结果:

{'cats' => ['John', 'Peter']}

即使源 XML 中只有一个 &lt;cat&gt;,我也希望结果哈希中的 'cats' 的值是一个数组。

所以,我希望解析器应用规则:

如果节点 xyzs 包含一个或多个名称为 xyz 的子节点(并且 没有其他节点),那么节点 xyzs 应该在结果哈希中表示为一个数组,名称为 xyzs(并且数组的每个元素都应该是相应 xyz 元素的内容)。

下面是使用XmlSimple lib 实现的方法:

XmlSimple.xml_in('cats.xml',{:forcearray=>['cat'], :grouptags=>{"cats"=>"cat"}})

但是,我必须输入目标元素的所有名称,而且似乎没有其他方法可以在 XmlSimple 中定义 forcearray/grouptags 行为。

破解一个提取所有名称然后将它们传递给 xml_in 方法的预处理例程并不难,但可能有更优雅(即已经编写)的方法来做到这一点?

(如果它能够转换,我很乐意使用任何其他 XML 解析库)


UPD:如果重要的话,我的最终目标是将生成的哈希保存到 MongoDB(即整体转换是 XML -> BSON)


UPD2:我再次想指定应该被视为数组的元素的名称,我希望 lib 为我。

【问题讨论】:

  • 听起来像是家庭作业。
  • 没有太多以“Find a lib which..”开头的作业

标签: ruby xml xml-parsing


【解决方案1】:

使用 Nokogiri,我们可以编写以下代码:

require 'inflector'
require 'nokogiri'

def get_xml_stuff(xml, singular)
  plural = Inflector.pluralize(singular)
  return_hash = {plural => []}
  xml.xpath("*/#{plural}/#{singular}").each { |tag| return_hash[plural] << tag.text}
  return return_hash
end

根据我的测试,这解决了与您的 XmlSimple 代码匹配的简单案例。对于您的进一步要求:

如果节点 xyzs 包含一个或多个名称为 xyz 的子节点(并且没有其他节点),则节点 xyzs 应在结果哈希中表示为一个数组,名称为 xyzs(以及每个元素数组的内容应该是对应的xyz 元素的内容)。

def get_xml_stuff(xml, singular)
  plural = Inflector.pluralize(singular)
  return_hash = {plural => []}
  path = xml.xpath("*/#{plural}/#{singular}")
  path.each { |tag| return_hash[plural] << tag.text} unless path.size != xml.xpath("*/#{plural}/*").children.size
  return return_hash
end

不过,如果同一个复数在文件中出现多次,这仍然不完美。


回答 UPD2。我的新版本函数如下:

def get_xml_stuff(xml, plural)
  singular = Inflector.singularize(plural)
  return_hash = {plural => []}
  path = xml.xpath("./#{singular}")
  path.each { |tag| return_hash[plural] << tag.text} unless path.size != xml.xpath("./*").size
  return return_hash
end

这里我们从复数父节点开始,如果所有命名的子节点都具有该单数名称,则收集所有单数子节点。我的新测试代码变成:

sample_xml = Nokogiri::XML(sample_xml_text)
sample_xml.children.xpath("*").each do |child|
  array = get_xml_stuff(child, child.name)
  p array
end

如果没有像我的示例 &lt;pets&gt; 这样的标签,以下应该可以工作:

sample_xml = Nokogiri::XML(sample_xml_text)
array = get_xml_stuff(sample_xml.children.first, sample_xml.children.first.name)
p array

结束UPD2


作为参考,我的测试是:

sample_xml_text = <<-sample
<pets>
  <cats>
    <cat>John</cat>
    <cat>Peter</cat>
  </cats>
  <kitties>
    <kitty>Tibbles</kitty>
    <kitty>Meow-chan</kitty>
    <kitty>Puss</kitty>
  </kitties>
  <giraffes>
    <giraffe>Long Neck</giraffe>
  </giraffes>
  <dogs>
    <dog>Rover</dog>
    <dog>Spot</dog>
    <cat>Peter</cat>
  </dogs>
</pets>
sample

sample_xml = Nokogiri::XML(sample_xml_text)
array = get_xml_stuff(sample_xml, "cat")
p array
array = get_xml_stuff(sample_xml, "kitty")
p array
array = get_xml_stuff(sample_xml, "giraffe")
p array
array = get_xml_stuff(sample_xml, "dog")
p array

【讨论】:

  • 请参阅 UPD2。我不想指定元素名称。
【解决方案2】:

首先查找以s 结尾的元素名称:

names = doc.search('*[name()$="s"]').map(&:name).uniq
#=> ["cats"]

剩下的只是映射和散列:

Hash[names.map{|name| [name, doc.search("#{name} > #{name.sub /s$/, ''}").map(&:text)]}]
#=> {"cats"=>["John", "Peter"]}

【讨论】:

  • 不错!但我也可以将每个元素的内容作为哈希值吗?即,如何将&lt;cats&gt;&lt;cat&gt;&lt;name&gt;John&lt;/name&gt;&lt;age&gt;10&lt;/age&gt;&lt;/cat&gt;&lt;/cats&gt; 变成{'cats'=&gt;[{'name'=&gt;'John','age'=&gt;10}]}
  • 我给你个提示,你把(&amp;:text)改成{|x| Hash[x.search(' &gt; *').map(something else)]}
猜你喜欢
  • 2016-08-28
  • 1970-01-01
  • 2011-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-22
  • 2012-05-04
  • 1970-01-01
相关资源
最近更新 更多