使用Nokogiri从空标签中递归清理XML文档?

时间:2013-11-21 14:10:16

标签: ruby xml recursion nokogiri

我有一个嵌套的XML文档,如下所示:

<?xml version="1.0"?>
<phone>
  <name>test</name>
  <descr>description</descr>
  <empty/>
  <lines>
    <line>12345</line>
    <css/>
  </lines>
</phone>

我需要删除所有空的XML节点,例如<empty/><css/>

我最终得到了类似的东西:

doc = Nokogiri::XML::DocumentFragment.parse <<-EOXML
<phone>
  <name>test</name>
  <descr>description</descr>
  <empty/>
  <lines>
    <line>12345</line>
    <css/>
  </lines>
</phone>
EOXML

phone = doc.css("phone")
phone.children.each do | child |
    child.remove if child.inner_text == ''
end

以上代码仅删除第一个空标记,例如<empty/>。我无法进入嵌套块。我想我需要一些递归策略。我仔细阅读了Nokogiri文档并检查了很多例子,但我还没有找到解决方案。

我该如何解决这个问题?

我正在使用Ruby 1.9.3和Nokogiri 1.5.10。

4 个答案:

答案 0 :(得分:2)

您应该能够使用xpath "/phone//*[not(text())]"找到没有任何文本的所有节点。

require 'nokogiri'

doc = Nokogiri::XML::Document.parse <<-EOXML
<phone>
  <name>test</name>
  <descr>description</descr>
  <empty/>
  <lines>
    <line>12345</line>
    <css/>
  </lines>
</phone>
EOXML

doc.xpath("/phone//*[not(text())]").remove

puts doc.to_s.gsub(/\n\s*\n/, "\n")
#=> <?xml version="1.0"?>
#=> <phone>
#=>   <name>test</name>
#=>   <descr>description</descr>
#=>   <lines>
#=>     <line>12345</line>
#=>   </lines>
#=> </phone>

答案 1 :(得分:2)

具有不同方法的后来者,希望增加额外的洞察力。这种方法消除了烦人的额外新行,并为您提供了保留具有值设置属性的空字段的选项。

require 'nokogiri'

doc = Nokogiri::XML::Document.parse <<-EOXML
<phone>
  <name>test</name>
  <descr>description</descr>
  <empty/>
  <lines>
    <line>12345</line>
    <css/>
  </lines>
</phone>
EOXML

def traverse_and_clean(kid)
  kid.children.map { |child| traverse_and_clean(child) }
  kid.remove if kid.content.blank?
end

traverse_and_clean(doc)

<强>输出

<?xml version="1.0"?>
<phone>
  <name>test</name>
  <descr>description</descr>
  <lines>
    <line>12345</line>
  </lines>
</phone>

如果您发现自己处于特殊情况,需要保留一些具有某些属性设置的空字段。您所要做的就是稍微更改traverse_and_clean方法:

def traverse_and_clean(kid)
  kid.children.map { |child| traverse_and_clean(child) }
  kid.remove if kid.content.blank? && kid.attributes.blank?
end

答案 2 :(得分:1)

require 'nokogiri'

doc = Nokogiri::XML::Document.parse <<-EOXML
<phone>
  <name>test</name>
  <descr>description</descr>
  <empty/>
  <lines>
    <line>12345</line>
    <css/>
  </lines>
</phone>
EOXML

nodes = doc.xpath("//phone//*[not(text())]")

nodes.each{|n| n.remove if n.elem? }

puts doc

<强>输出

<?xml version="1.0"?>
<phone>
  <name>test</name>
  <descr>description</descr>

  <lines>
    <line>12345</line>

  </lines>
</phone>

答案 3 :(得分:1)

与@ JustinKo的回答类似,仅使用CSS选择器:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<?xml version="1.0"?>
<phone>
  <name>test</name>
  <descr>description</descr>
  <empty/>
  <lines>
    <line>12345</line>
    <css/>
  </lines>
</phone>
EOT

doc.search(':empty').remove
puts doc.to_xml

看看它做了什么:

<?xml version="1.0"?>
<phone>
  <name>test</name>
  <descr>description</descr>

  <lines>
    <line>12345</line>

  </lines>
</phone>

Nokogiri实现了很多jQuery的选择器,所以总是值得看看这些扩展可以做什么。