How to add a new node to XML
Solution 1
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 Node
s, 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
Solution 2
I'd start with this:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<name>mouse</name>
<manufacturer>Logitech</manufacturer>
</item>
<item>
<name>keyboard</name>
<manufacturer>Logitech - Inc.</manufacturer>
</item>
</items>
EOT
doc.search('manufacturer').each { |n| n.after('<price>10</price>') }
Which results in:
puts doc.to_xml
# >> <?xml version="1.0" encoding="UTF-8"?>
# >> <items>
# >> <item>
# >> <name>mouse</name>
# >> <manufacturer>Logitech</manufacturer><price>10</price>
# >> </item>
# >> <item>
# >> <name>keyboard</name>
# >> <manufacturer>Logitech - Inc.</manufacturer><price>10</price>
# >> </item>
# >> </items>
It's easy to build upon this to insert different values for the price.
![Mr. L](https://i.stack.imgur.com/60awq.jpg?s=256&g=1)
Comments
-
Mr. L about 2 years
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?
-
Mr. L over 13 yearsI saw the exact behavour you explained in debugger but couldn't understand it. Thank you for your explanation and your answer!
-
Phrogz over 13 yearsAlternatively, you may want to simply use
Node#dup
to clone the node before inserting it in a new location. -
Arup Rakshit over 9 yearsNice explanation.. I was having the same issue since last 3-4 days back. Now I found out why it was happening. Thanks for the OP and you.
-
PJP almost 8 yearsIt's easier than this. See my example.