How to add an attribute to Nokogiri XML builder? - ruby

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>

Related

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>

Cant find element in clone document

I am using Nokogiri (1.5.9 - java) in JRuby ( 1.6.7.2 ) to copy an XML template and edit it. I'm having problems finding elements in the cloned document.
lblock = doc.xpath(".//lblock[#blockName='WINDOW_LIST']").first
lblock.children = new_children # kind of NodeSet or Node
copy_doc = doc.dup( 1 ) # or dup(0)
lblock = copy_doc.xpath(".//lblock[#blockName='WINDOW_LIST']").first # nil
When print to_s or to_xml, so lblock there is with new_children.
Where is my mistake?
I can't duplicate the problem:
require 'nokogiri'
new_children = Nokogiri::XML::DocumentFragment.parse('<foo>bar</foo>')
doc = Nokogiri::XML(<<EOF)
<xml>
<lblock blockName="WINDOW_LIST" />
</xml>
EOF
lblock = doc.xpath(".//lblock[#blockName='WINDOW_LIST']").first
lblock.children = new_children # kind of NodeSet or Node
copy_doc = doc.dup(1) # or dup(0)
lblock = copy_doc.xpath(".//lblock[#blockName='WINDOW_LIST']").first # nil
puts lblock.to_xml
puts
puts doc.to_xml
Running that outputs:
<lblock blockName="WINDOW_LIST">
<foo>bar</foo>
</lblock>
<?xml version="1.0"?>
<xml>
<lblock blockName="WINDOW_LIST"><foo>bar</foo></lblock>
</xml>
That said, here's code that is cleaned up to show you some simpler ways to write it:
require 'nokogiri'
new_children = '<foo>bar</foo>'
doc = Nokogiri::XML(<<EOF)
<xml>
<lblock blockName="WINDOW_LIST" />
</xml>
EOF
lblock = doc.at_xpath('//lblock')
lblock.children = new_children
copy_doc = doc.dup(1)
lblock = copy_doc.at_css('lblock')
puts lblock.to_xml
puts
puts doc.to_xml
Which outputs this too after running:
<lblock blockName="WINDOW_LIST">
<foo>bar</foo>
</lblock>
<?xml version="1.0"?>
<xml>
<lblock blockName="WINDOW_LIST"><foo>bar</foo></lblock>
</xml>
Dissecting the code:
lblock = doc.at_xpath('//lblock')
lblock = copy_doc.at_css('lblock')
These use two different ways of finding the same thing. In this case, because the sample XML was simple, I used at, which returns the first matching node. at_xpath and at_css work with XPaths and CSS respectively. at would try to figure out whether the string is CSS or XPath, and normally gets it right, though I have seen it fooled.
lblock.children = new_children
In this case, new_children is a String. Nokogiri is smart enough to know it should convert the string into an XML fragment before using it. This makes it very easy to modify XML or HTML documents with strings, instead of having to create DocumentFragments.

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>

How to add a comment with Nokogiri Builder

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.

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