如何使用Ruby提取碎片化XML文档的节点名称?

时间:2014-12-28 19:46:25

标签: ruby xml parsing tags nokogiri

我是一个类似XML的文档,由我无法控制的系统预处理。文件的格式如下:

 <template>
Hello, there <RECALL>first_name</RECALL>.  Thanks for giving me your email.  
<SETPROFILE><NAME>email</NAME><VALUE><star/></VALUE></SETPROFILE>.  I have just sent you something.
</template>

但是,我只能将文本字符串作为<template>标记之间的内容。

我希望能够在解析时提取而不用提前指定标记。如果标签位于字符串的末尾且只有一个,我可以使用Crack gem但

使用Crack,我可以输入一个像

这样的字符串
string = "<SETPROFILE><NAME>email</NAME><VALUE>go@go.com</VALUE></SETPROFILE>"

我的Crack输出是:

{"SETPROFILE"=>{"NAME"=>"email", "VALUE"=>"go@go.com"}}

然后我可以使用case语句来表示我关心的可能值。

鉴于我需要在字符串中有多个<tags>并且它们不能位于字符串的末尾,我如何轻松地解析节点名称和值,类似于我对crack的处理?

还需要删除这些标签。我想继续使用@TinMan的the excellent suggestion

一旦我知道标签的名称,它就能很好地工作。标签的数量是有限的。我知道它后,我会将标签发送到适当的方法,但它需要首先轻松解析。

1 个答案:

答案 0 :(得分:2)

使用Nokogiri,您可以将字符串视为DocumentFragment,然后找到嵌入的节点:

require 'nokogiri'

doc = Nokogiri::XML::DocumentFragment.parse(<<EOT)
Hello, there <RECALL>first_name</RECALL>.  Thanks for giving me your email.  
<SETPROFILE><NAME>email</NAME><VALUE><star/></VALUE></SETPROFILE>.  I have just sent you something.
EOT

nodes = doc.search('*').each_with_object({}){ |n, h|
  h[n] = n.text
}

nodes # => {#<Nokogiri::XML::Element:0x3ff96083b744 name="RECALL" children=[#<Nokogiri::XML::Text:0x3ff96083a09c "first_name">]>=>"first_name", #<Nokogiri::XML::Element:0x3ff96083b5c8 name="SETPROFILE" children=[#<Nokogiri::XML::Element:0x3ff96083a678 name="NAME" children=[#<Nokogiri::XML::Text:0x3ff960836884 "email">]>, #<Nokogiri::XML::Element:0x3ff96083a650 name="VALUE" children=[#<Nokogiri::XML::Element:0x3ff96083a5c4 name="star">]>]>=>"email", #<Nokogiri::XML::Element:0x3ff96083a678 name="NAME" children=[#<Nokogiri::XML::Text:0x3ff960836884 "email">]>=>"email", #<Nokogiri::XML::Element:0x3ff96083a650 name="VALUE" children=[#<Nokogiri::XML::Element:0x3ff96083a5c4 name="star">]>=>"", #<Nokogiri::XML::Element:0x3ff96083a5c4 name="star">=>""}

或者,更清晰:

nodes = doc.search('*').each_with_object({}){ |n, h|
  h[n.name] = n.text
}

nodes # => {"RECALL"=>"first_name", "SETPROFILE"=>"email", "NAME"=>"email", "VALUE"=>"", "star"=>""}

获取特定标签的内容很简单:

nodes['RECALL'] # => "first_name"

迭代所有标签也很容易:

nodes.keys.each do |k| 
  ... 
end

您甚至可以用文字替换标签及其内容:

doc.at('RECALL').replace('Fred')
doc.to_xml # => "Hello, there Fred.  Thanks for giving me your email.  \n<SETPROFILE>\n  <NAME>email</NAME>\n  <VALUE>\n    <star/>\n  </VALUE>\n</SETPROFILE>.  I have just sent you something.\n"

如何替换嵌套标签留给您作为练习。