Adding a node using Nokogiri
It's easy to add/change/delete HTML:
require 'nokogiri'
doc = Nokogiri::HTML::DocumentFragment.parse('<div class="input">hello</div>')
div = doc.at('div')
div << '<span>Hello</span>'
puts doc.to_html
Which results in:
# >> <div class="input">hello<span>Hello</span>
# >> </div>
Notice that the above code appended a new node to the existing children of the <div>
, because of <<
, which means they were appended after the text-node containing "hello".
If you want to overwrite the children, you can do that easily using children =
:
div.children = '<span>Hello</span>'
puts doc.to_html
Which results in:
# >> <div class="input"><span>Hello</span></div>
children =
can take a single Node which can have multiple other nodes nestled under it, or the HTML text of the node(s) being inserted. That's what node_or_tags
means when you see it in the documentation.
That said, to change just an embedded <label>
, I'd do something like:
doc = Nokogiri::HTML::DocumentFragment.parse('<div class="input"><label>hello</label></div>')
label = doc.at('div label')
label.name = 'span' if label
puts doc.to_html
# >> <div class="input"><span>hello</span></div>
Or:
doc = Nokogiri::HTML::DocumentFragment.parse('<div class="input"><label>hello</label></div>')
label = doc.at('div label')
label.replace("<span>#{ label.text }</span>") if label
puts doc.to_html
# >> <div class="input"><span>hello</span></div>
Nokogiri makes it easy to change the tag's name once you've pointed at it. You can easily change the text inside the <span>
by replacing #{ label.text }
with whatever you desire.
at('div label')
is one way of finding a particular node. It basically means "find the first label tag inside the first div". at
means find the first of something, and is similar to using search(...).first
. There are CSS and XPath equivalents to both at
and search
in the Nokogiri::XML::Node documentation if you need those.
How can I add a child to a node at a specific position?
Thanks Pesto for your almost correct solution.
The working solution is:
node.children.first.add_previous_sibling(span_node)
Creating XML nodes using Nokogiri
Adding nodes in Nokogiri is a lot easier than you think it is, but your question isn't very clear.
If you want to duplicate an existing node:
require 'nokogiri'
xml = <<EOT
<mainnode>
<secnode>
<data1></data2>
<data2></data2>
</secnode>
</mainnode>
EOT
doc = Nokogiri::XML(xml)
secnode = doc.at('secnode')
doc.root.add_child secnode.dup
puts doc.to_xml
Which, when run, results in:
<?xml version="1.0"?>
<mainnode>
<secnode>
<data1/>
<data2/>
</secnode>
<secnode>
<data1/>
<data2/>
</secnode></mainnode>
The funky alignment is the result of appending the intervening text nodes used for indentation. The resulting XML is valid.
If you're adding a different set of nodes, it's still easy:
require 'nokogiri'
xml = <<EOT
<mainnode>
<secnode>
<data1></data2>
<data2></data2>
</secnode>
</mainnode>
EOT
node_to_add = <<EOT
<secnode>
<data3 />
<data4 />
</secnode>
EOT
doc = Nokogiri::XML(xml)
doc.root.add_child node_to_add
puts doc.to_xml
Which outputs:
<?xml version="1.0"?>
<mainnode>
<secnode>
<data1/>
<data2/>
</secnode>
<secnode>
<data3/>
<data4/>
</secnode>
</mainnode>
You can use that as a template:
require 'nokogiri'
xml = <<EOT
<mainnode>
<secnode>
<data1></data2>
<data2></data2>
</secnode>
</mainnode>
EOT
v1 = 'foo'
v2 = 'bar'
node_to_add = <<EOT
<secnode>
<data3>#{ v1 }</data3>
<data4>#{ v2 }</data4>
</secnode>
EOT
doc = Nokogiri::XML(xml)
doc.root.add_child node_to_add
puts doc.to_xml
Which looks like:
<?xml version="1.0"?>
<mainnode>
<secnode>
<data1/>
<data2/>
</secnode>
<secnode>
<data3>foo</data3>
<data4>bar</data4>
</secnode>
</mainnode>
Nokogiri makes it very easy to create nodes to be added by using string representations of the XML or HTML, which it then converts on the fly.
How I can add an element?
It's a lot easier than that:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<root>
<node>
</node>
</root>
EOT
doc.at('node').children = '<child>foo</child>'
doc.to_xml
# => "<?xml version=\"1.0\"?>\n<root>\n <node><child>foo</child></node>\n</root>\n"
children=
is smart enough to see what you're passing in and will do the dirty work for you. So just use a string to define the new node(s) and tell Nokogiri where to insert it.
doc.at('node').class # => Nokogiri::XML::Element
doc.at('//node').class # => Nokogiri::XML::Element
doc.search('node').first # => #<Nokogiri::XML::Element:0x3fd1a88c5c08 name="node" children=[#<Nokogiri::XML::Text:0x3fd1a88eda3c "\n ">]>
doc.search('//node').first # => #<Nokogiri::XML::Element:0x3fd1a88c5c08 name="node" children=[#<Nokogiri::XML::Text:0x3fd1a88eda3c "\n ">]>
search
is the generic "find a node" method that will take either CSS or XPath selectors. at
is equivalent to search('some selector').first
. at_css
and at_xpath
are the specific equivalents of at
, just as css
and xpath
are to search
. Use the specific versions if you want, but in general I use the generic versions.
You can't use:
targets = @xml.xpath("./target")
if targets.empty?
targets << Nokogiri::XML::Node.new('target', @xml)
end
targets
would be []
(actually an empty NodeSet) if ./target
doesn't exist in the DOM. You can't append a node to []
, because NodeSet doesn't have an idea of what you're talking about, resulting in a undefined method 'children=' for nil:NilClass (NoMethodError)
exception.
Instead you MUST find the specific location where you want to insert the node. at
is good for that since it finds just the first location. Of course, if you want to look for multiple places to modify something use search
then iterate over the returned NodeSet and modify based on the individual nodes returned.
How can I insert a tree of nodes with namespaces into an existing XML file using Nokogiri?
A nifty little tick if you want to create a builder instance scoped to a namespace is to use Nokogiri::XML::Builder.with(doc.root)
:
doc = Nokogiri::XML('<?xml version="1.0"?>
<root xmlns:y="foo"></root>')
builder = Nokogiri::XML::Builder.with(doc.root) do |xml|
xml['y'].Shapenode do |sn|
sn.Foo
sn.Bar
end
end
builder.to_xml
outputs:
<?xml version="1.0"?>
<root xmlns:y="foo">
<y:Shapenode>
<y:Foo/>
<y:Bar/>
</y:Shapenode>
</root>
Worth noting though is that it mutates doc
. If I where to do this I would use Nokogiri::XML::Builder.with(doc.root.dup)
which prevents it from mutating the arguments.
You can also just create builders with any arbitrary root with:
builder = Nokogiri::XML::Builder.new do |xml|
xml.root('xmlns:y' => 'bar') do
xml['y'].Shapenode
end
end
builder.doc.xpath('/*').children
will slice out the node set.
Related Topics
Simple Ruby Input Validation Library
Concurrent Requests with Mri Ruby
Understanding Ruby Symbol as Method Call
Verb-Agnostic Matching in Sinatra
Escape Double and Single Backslashes in a String in Ruby
Best Way to Group by Date with Mongoid
How to Configure Mongomapper and Activerecord in Same Ruby Rails Project
Differencebetween 'Include' and 'Prepend' in Ruby
Sinatra Static Assets Are Not Found When Using Rackup
Truncate String to the First N Words
Why Doesn't This Work If in Ruby Everything Is an Object
Carrierwave - Resizing Images to Fixed Width
How to Find Where a Ruby Method Is Declared
How Does Pack() and Unpack() Work in Ruby