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>'
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.
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.
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.
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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With