muski muski -4 years ago 80
Ruby Question

How to update a NodeSet

I am trying to add one more

<EventRouter>
element in the following XML:

<EventRouters>
<EventRouter id="Important Events to Log File" class="com.cyclonecommerce.events2.router.LogFileRouter" active="true">
<Parameters file="../logs/%nodeName%_events.log"
rollOnStart="false" autoFlush="true"
maxFileSize="2M" maxBackupFiles="5"/>
<MetadataProcessorListRef ref="Messaging"/>
<EventFilterRef ref="Important"/>
</EventRouter>
<EventRouter id="Message Detail to Log File" class="com.cyclonecommerce.events2.router.LogFileRouter" active="false">
<Parameters file="../logs/%nodeName%_message_detail.log"
rollOnStart="true" autoFlush="true"
maxFileSize="2M" maxBackupFiles="5"/>
<MetadataProcessorListRef ref="Messaging"/>
<EventFilterRef ref="Message Detail"/>
</EventRouter>

</EventRouters>


I am able to append a node to a nodeset but after saving the XML I am not getting the updated XML I want:

require 'nokogiri'

if File.exist?('a.xml')
data = File.read("a.xml")

end
doc = Nokogiri::XML.parse data
doc2=doc.xpath("//EventRouters/EventRouter")

event_router = Nokogiri::XML::Node.new("EventRouter",doc)
event_router['id'] = "some"
param_node = Nokogiri::XML::Node.new("Parameters",doc)
param_node.content = "some content"
event_router << param_node
doc2<< event_router

File.open('test.xml', 'w') do |file|
file.print doc.to_xml
end

Answer Source

Adding new nodes is simple. The basic idea is similar to parsing XML or HTML:

  1. Find the node where you want to add new content.
  2. Use the appropriate method to append the content, either at the start of the children of the node, or as a sibling or a parent wrapping the node. Those methods are in the Node documentation.
  3. You're done.

Nokogiri makes it easy to add a node using a string, which it will parse and convert to a Node:

require 'nokogiri'
doc = Nokogiri::XML('<xml><node/></xml>')

node = doc.at('node')
node.add_child('<foo>bar</foo>')
doc.to_xml # => "<?xml version=\"1.0\"?>\n<xml>\n  <node>\n    <foo>bar</foo>\n  </node>\n</xml>\n"

You can go the longer route of creating a new Node but I find building a string easier.

Using your code for the basis, here's how I'd have written it:

require 'nokogiri'

data = DATA.read

doc = Nokogiri::XML.parse data
doc.xpath("//EventRouters/EventRouter").each do |event_router|
  event_router.add_next_sibling("<EventRouter id='some'><Parameters>some content</Parameters></EventRouter>")
end

puts doc.to_xml

__END__
<EventRouters>
  <EventRouter>original1</EventRouter>
  <EventRouter>original2</EventRouter>
</EventRouters>

Running that results in:

<?xml version="1.0"?>
<EventRouters>
  <EventRouter>original1</EventRouter><EventRouter id="some"><Parameters>some content</Parameters></EventRouter>
  <EventRouter>original2</EventRouter><EventRouter id="some"><Parameters>some content</Parameters></EventRouter>
</EventRouters>

You're trying to use << to append to a NodeSet, which isn't going to do what you think it will:

doc = Nokogiri::XML.parse data
ers = doc.search('EventRouter') 
ers << Nokogiri::XML::Node.new('<foo/>', doc)

Appending to ers, which is a NodeSet, won't apply the new Node to all the elements in the set:

doc.to_xml # => "<?xml version=\"1.0\"?>\n<EventRouters>\n  <EventRouter>original1</EventRouter>\n  <EventRouter>original2</EventRouter>\n</EventRouters>\n"

Instead it appends it to the end of the set:

ers.to_xml # => "<EventRouter>original1</EventRouter><EventRouter>original2</EventRouter><<foo/>/>"
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download