Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Edit XML with Ruby

Tags:

xml

ruby

I'm trying to edit an XML file and replace strings with ruby variables. For now this is my code :

<?xml version="1.0" encoding="US-ASCII"?>
<Standart-Profile>
    <class1>
        <class2>
            <class3>
               <value1>old_A</value2>
               <value1>old_B</value2>
               <value1>old_C</value2>
            </class3>
        </class2>
    </class1>
</Standart-Profile>

And this is the Ruby file :

require "rexml/text"
require 'rexml/document'
include REXML

def generate_2
    ...
end

def generate_1
    ...
end


File.open('Standart-Profile.xml') do |config_file|
  config = Document.new(config_file)
  config.root.elements['old_A'].text = 'generate_1'
  config.root.elements['old_B'].text = 'generate_2'
  config.root.elements['old_C'].text = 'generate_1'

  formatter = REXML::Formatters::Default.new
  File.open('New-Profile.xml', 'w') do |result|
  formatter.write(config, result)
  end
end

But I keep getting this error :

Final-Tool-Kit.rb:19:in `block in <main>': undefined method `text=' for nil:NilC
lass (NoMethodError)
        from test.rb:16:in `open'
        from test.rb:16:in `<main>'
like image 321
Ko Ichi Avatar asked Nov 03 '11 02:11

Ko Ichi


People also ask

How do I parse XML in Ruby?

Here is the example: XML parsing can be done in ruby with the help of a gem called Nokogiri. Nokogiri is an HTML, XML, SAX, and Reader parser. Among Nokogiri’s many features is the ability to search documents via XPath or CSS3 selectors.

How do I use xslt4r in Ruby?

XSLT4R uses a simple commandline interface, though it can alternatively be used within a third-party application to transform an XML document. XSLT4R needs XMLScan to operate, which is included within the XSLT4R archive and which is also a 100 percent Ruby module.

What is the best way to manipulate XML?

The most common way to manipulate XML is with the REXML library by Sean Russell. Since 2002, REXML has been part of the standard Ruby distribution. REXML is a pure-Ruby XML processor conforming to the XML 1.0 standard.

How to load HTML and XML documents using Nokogiri in Ruby?

To load HTML or XML documents using the Nokogiri library, you use the Ruby namespace resolution operator and access the loader, either the HTML or XML. The example code should load the HTML contents and save them to the defined variable. To check the source class of the data, we use the .class method.


Video Answer


1 Answers

Your problem is that you're looking for an element with the name old_A when you should be looking for an element with the text contents of old_A.

Here's a solution using Nokogiri, which I find more convenient than REXML:

require 'nokogiri' # gem install nokogiri

xml = "<Standart-Profile>
    <class1>
        <class2>
            <class3>
               <value1>old_A</value2>
               <value1>old_B</value2>
               <value1>old_C</value2>
            </class3>
        </class2>
    </class1>
</Standart-Profile>"

doc = Nokogiri.XML(xml)
doc.at('//text()[.="old_A"]').content = 'generate_1'
doc.at('//text()[.="old_B"]').content = 'generate_2'
doc.at('//text()[.="old_C"]').content = 'generate_3'

File.open('output.xml','w') do |f|
  f.puts doc
end
#=> <?xml version="1.0" encoding="US-ASCII"?>
#=> <Standart-Profile>
#=>     <class1>
#=>         <class2>
#=>             <class3>
#=>                <value1>generate_1</value1>
#=>                <value1>generate_2</value1>
#=>                <value1>generate_3</value1>
#=>             </class3>
#=>         </class2>
#=>     </class1>
#=> </Standart-Profile>

If you actually want to call a generate_1 method (as you have defined) then you would instead use:

...content = generate_1 # no quotes

Edit: Here's one way to do it with XPath in REXML (after I fixed the source XML to be valid):

require 'rexml/document'    
doc = REXML::Document.new(xml)
REXML::XPath.first(doc,'//*[text()="old_A"]').text = 'generate_1'
REXML::XPath.first(doc,'//*[text()="old_B"]').text = 'generate_2'
REXML::XPath.first(doc,'//*[text()="old_C"]').text = 'generate_3'
puts doc
#=> <Standart-Profile>
#=>     <class1>
#=>         <class2>
#=>             <class3>
#=>                <value1>generate_1</value1>
#=>                <value1>generate_2</value1>
#=>                <value1>generate_3</value1>
#=>             </class3>
#=>         </class2>
#=>     </class1>
#=> </Standart-Profile>
like image 158
Phrogz Avatar answered Oct 30 '22 09:10

Phrogz