Mr. L Mr. L - 3 months ago 9
Ruby Question

How to add a new node to XML

I have a simple XML file, items.xml:

<?xml version="1.0" encoding="UTF-8" ?>

<items>
<item>
<name>mouse</name>
<manufacturer>Logicteh</manufacturer>
</item>
<item>
<name>keyboard</name>
<manufacturer>Logitech - Inc.</manufacturer>
</item>
<item>
<name>webcam</name>
<manufacturer>Logistech</manufacturer>
</item>
</items>


I am trying to insert a new node with the following code:

require 'rubygems'
require 'nokogiri'

f = File.open('items.xml')
@items = Nokogiri::XML(f)
f.close

price = Nokogiri::XML::Node.new "price", @items
price.content = "10"

@items.xpath('//items/item/manufacturer').each do |node|
node.add_next_sibling(price)
end

file = File.open("items_fixed.xml",'w')
file.puts @items.to_xml
file.close


However this code adds a new node only after the last
<manufacturer>
node, items_fixed.xml:

<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<name>mouse</name>
<manufacturer>Logitech</manufacturer>
</item>
<item>
<name>keyboard</name>
<manufacturer>Logitech</manufacturer>
</item>
<item>
<name>webcam</name>
<manufacturer>Logitech</manufacturer><price>10</price>
</item>
</items>


Why?

Answer

It would be helpful to distinguish between a Node (a particular piece of structured XML data at a particular place in a tree), and a "node template" which is the structure of the data.

Nokogiri (and most other XML libraries) only allow you to specify Nodes, not node templates. So when you created price = Nokogiri::XML::Node.new "price", @items, you had a particular piece of data that belongs in a particular place, but hadn't defined the place yet.

When you added it to the first <item>, you defined its place. When you added it to the second <item>, you uprooted it from its place and put it in a new place. At that point this Node appeared only in the second <item>. This continues when you add the same Node to each item, until you reach the last <item>, which is where the node stays.

Nokogiri doesn't have any way to specify a node template. What you need to do is:

@items.xpath('//items/item/manufacturer').each do |node|
  price = Nokogiri::XML::Node.new "price", @items
  price.content = "10"
  node.add_next_sibling(price)
end
Comments