rubyxmlxml-parsinglibxml-ruby

Delete an element after the element is found in Ruby using libxml


I come from C background but working on some stuff related to XML in Ruby, so, please bear with me if my question is naive.

I have one XML document. I am parsing it using libxml:

<test>
 <ready>
  <ex_success>true</ex_success>
 </ready>
 <ath>
  <name>abc</name>
  <pass>123</pass>
  <ex_success>true</ex_success>
 </ath>
</test>

In this document, I am able to read the ex_success element. However, I am not able to delete it from my original file.

Here is my little piece of code:

require 'xml'
test_file = @file_name
parser = XML::Parser.file(test_file)
document = parser.parse

document.root.each_element {|element|

  # Write each element name in the file
  puts 'element.name'

  if val = element.find_first('ex_success')   
    puts val.content   # prints true
    val.remove!    # THIS line does not remove the element from my original file

  else
    puts 'Not found'
  end

What am I doing wrong and what is the right way to delete it?


Solution

  • I'd recommend not using libxml. While it's part of Ruby, it's not the defacto standard for XML parsing for Ruby. Nokogiri is.

    Here's how I'd do it using Nokogiri:

    require 'nokogiri'
    
    doc = Nokogiri::XML::DocumentFragment.parse(<<EOT)
    <test>
     <ready>
      <ex_success>true</ex_success>
     </ready>
     <ath>
      <name>abc</name>
      <pass>123</pass>
      <ex_success>true</ex_success>
     </ath>
    </test>
    EOT
    
    ex_success = doc.at('ex_success')
    ex_success_value = ex_success.text # !> assigned but unused variable - ex_success_value
    ex_success.remove
    
    puts doc.to_xml
    # >> <test>
    # >>  <ready>
    # >>   
    # >>  </ready>
    # >>  <ath>
    # >>   <name>abc</name>
    # >>   <pass>123</pass>
    # >>   <ex_success>true</ex_success>
    # >>  </ath>
    # >> </test>
    

    If you don't want the blank line left by the empty text node, use this:

    ex_success = doc.at('ex_success')
    ex_success_value = ex_success.text # => "true"
    ex_success.parent.children.remove
    
    puts doc.to_xml
    # >> <test>
    # >>  <ready/>
    # >>  <ath>
    # >>   <name>abc</name>
    # >>   <pass>123</pass>
    # >>   <ex_success>true</ex_success>
    # >>  </ath>
    # >> </test>
    

    I used Nokogiri::XML::DocumentFragment.parse, which accepts an XML snippet as-is. It's more common to use Nokogiri::XML(<<EOT), which will add the XML decl if it's not there.