How to add a comment with Nokogiri Builder - ruby

How do I add a <!-- blahblah --> comment to XML with Nokogiri's Builder?
I want to have something like:
<root>
<!--blahblah-->
<child/>
</root>
I try something like this:
Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.comment('blahblah')
xml.child
}
end
But that gives me:
<root>
<comment>blahblah</comment>
<child/>
</root>

You can work around this bug documented future feature not present in the current release by using Builder#<< as follows:
require 'nokogiri'
xml = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml << '<!--blahblah-->'
xml.child
}
end
puts xml.doc.root.to_xml
#=> <root>
#=> <!--blahblah-->
#=> <child/>
#=> </root>
Alternatively, you can monkeypatch in your own version of the future method:
class Nokogiri::XML::Builder
def comment(string)
insert Nokogiri::XML::Comment.new( doc, string.to_s )
end
end

Since V1.6.8 the comment-option is supported, you don't need the work around with <<.
If you need a comment-tag you can use comment_ (with underscore in the end).
Example:
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.comment 'My comment'
xml.comment_ 'My comment-tag'
}
end
puts builder.to_xml
Result:
<?xml version="1.0"?>
<root>
<!--My comment-->
<comment>My comment-tag</comment>
</root>

By the way, it might be obvious but as xml.comment creates a XML comment now, if you have to create an element <comment> you must use
xml << "<comment>#{comment}</comment>"
It just happened to me. Thanks for hinting at the << method.

Related

How to add an attribute to Nokogiri XML builder?

This is what I'm trying to do:
xml = Nokogiri::XML::Builder.new do |x|
x.root do
x.book do
x.attribute('isbn', 12345) # Doesn't work!
x.text("Don Quixot")
end
end
end.doc
I know that I can do x.book(isbn: 12345), but this is not what I want. I want to add an attribute within the do/end block. Is it at all possible?
The XML expected:
<root>
<book isbn="12345">Don Quixot</book>
</root>
Add the attributes to the node like this
xml = Nokogiri::XML::Builder.new do |x|
x.root do
x.book(isbn: 1235) do
x.text('Don Quixot')
end
end
end.doc
Or, after re-rereading your question perhaps you wanted to add it to the parent further in the do block. In that case, this works:
xml = Nokogiri::XML::Builder.new do |x|
x.root do
x.book do
x.parent.set_attribute('isbn', 12345)
x.text('Don Quixot')
end
end
end.doc
Generates:
<?xml version="1.0"?>
<root>
<book isbn="1235">Don Quixot</book>
</root>

Building blank XML tags with Nokogiri?

I'm trying to build up an XML document using Nokogiri. Everything is pretty standard so far; most of my code just looks something like:
builder = Nokogiri::XML::Builder.new do |xml|
...
xml.Tag1(object.attribute_1)
xml.Tag2(object.attribute_2)
xml.Tag3(object.attribute_3)
xml.Tag4(nil)
end
builder.to_xml
However, that results in a tag like <Tag4/> instead of <Tag4></Tag4>, which is what my end user has specified that the output needs to be.
How do I tell Nokogiri to put full tags around a nil value?
SaveOptions::NO_EMPTY_TAGS will get you what you want.
require 'nokogiri'
builder = Nokogiri::XML::Builder.new do |xml|
xml.blah(nil)
end
puts 'broken:'
puts builder.to_xml
puts 'fixed:'
puts builder.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS)
output:
(511)-> ruby derp.rb
broken:
<?xml version="1.0"?>
<blah/>
fixed:
<?xml version="1.0"?>
<blah></blah>

Create one XML file that joins many others

I am trying to create an XML using some list of XML's.
here is an example list of XML's
java.xml :
<JavaDetails>
<SomeList> ... </SomeList>
....
</JavaDetails>
c.xml
<CDetails>
<SomeList> ... </SomeList>
....
</CDetails>
I want to create a Programming.xml using the above XML's
it should look like:
<programming>
<Java>
<JavaDetails>
<SomeList> ... </SomeList>
....
</JavaDetails>
</Java>
<C>
<CDetails>
<SomeList> ... </SomeList>
....
</CDetails>
</C>
</programming>
I am currently looking into nokogiri to do the same as Performance is a major factor, What I am not sure is how to create nodes for the output XML. any code help in Ruby using Nokogiri is much appreciated.
To create a new XML file with a specific root, it can be as simple as:
doc = Nokogiri.XML("<programming/>")
One way to add a child node to that document:
java = doc.root.add_child('<Java/>').first
To read in another XML file from disk and append it:
java_details = Nokogiri.XML( IO.read )
java << java_details.root
Thus, if you have an array of filenames and you want to construct wrapping elements from each based on the name:
require 'nokogiri'
files = %w[ java.xml c.xml ]
doc = Nokogiri.XML('<programming/>')
files.each do |filename|
wrap_name = File.basename(filename,'.*').capitalize
wrapper = doc.root.add_child("<#{wrap_name} />").first
wrapper << Nokogiri.XML(IO.read(filename)).root
end
puts doc
Alternatively, if you want to use the Builder interface of Nokogiri:
builder = Nokogiri::XML::Builder.new do |xml|
xml.programming do
files.each do |filename|
wrap_name = File.basename(filename,'.*').capitalize
xml.send(wrap_name) do
xml.parent << Nokogiri.XML(IO.read(filename)).root
end
end
end
end
puts builder.to_xml
To install it:
gem install nokogiri
Here's the syntax:
require 'nokogiri'
builder = Nokogiri::XML::Builder.new do |xml|
xml.programming {
xml.Java {
xml.JavaDetails {
xml.SomeList 'List item'
}
}
}
end
The result can be retrieved with to_xml:
builder.to_xml
HTH!

Insert Text After Specific XML Tag in Nokogiri

I'd like to create the following XML:
<?xml version="1.0">
<foo>
<bar/>
TEXT GOES HERE
</foo>
The structure is pretty simple to build with Nokogiri:
builder = Nokogiri::XML::Builder.new do |xml|
xml.foo {
xml.bar {}
}
end
puts builder.to_xml
What I can't figure out is how to insert the TEXT GOES HERE string inside <foo> but after <bar/>.
Obviously, xml.foo("TEXT GOES HERE") produces the text before <bar>. What am I missing?
You want the text method:
require 'nokogiri'
builder = Nokogiri::XML::Builder.new do |xml|
xml.foo {
xml.bar
xml.text "TEXT GOES HERE"
}
end
puts builder.doc
#=> <?xml version="1.0"?>
#=> <foo><bar/>TEXT GOES HERE</foo>

Nokogiri and XML Formatting When Inserting Tags

I'd like to use Nokogiri to insert nodes into an XML document. Nokogiri uses the Nokogiri::XML::Builder class to insert or create new XML.
If I create XML using the new method, I'm able to create nice, formatted XML:
builder = Nokogiri::XML::Builder.new do |xml|
xml.product {
xml.test "hi"
}
end
puts builder
outputs the following:
<?xml version="1.0"?>
<product>
<test>hi</test>
</product>
That's great, but what I want to do is add the above XML to an existing document, not create a new document. According to the Nokogiri documentation, this can be done by using the Builder's with method, like so:
builder = Nokogiri::XML::Builder.with(document.at('products')) do |xml|
xml.product {
xml.test "hi"
}
end
puts builder
When I do this, however, the XML all gets put into a single line with no indentation. It looks like this:
<products><product><test>hi</test></product></products>
Am I missing something to get it to format correctly?
Found the answer in the Nokogiri mailing list:
In XML, whitespace can be considered
meaningful. If you parse a document
that contains whitespace nodes,
libxml2 will assume that whitespace
nodes are meaningful and will not
insert them for you.
You can tell libxml2 that whitespace
is not meaningful by passing the
"noblanks" flag to the parser. To
demonstrate, here is an example that
reproduces your error, then does what
you want:
require 'nokogiri'
def build_from node
builder = Nokogiri::XML::Builder.with(node) do|xml|
xml.hello do
xml.world
end
end
end
xml = DATA.read
doc = Nokogiri::XML(xml)
puts build_from(doc.at('bar')).to_xml
doc = Nokogiri::XML(xml) { |x| x.noblanks }
puts build_from(doc.at('bar')).to_xml
Output:
<root>
<foo>
<bar>
<baz />
</bar>
</foo>
</root>

Resources