0

I'm using Ruby to inject new elements around selected XML nodes. Like this:

require 'rexml/document'
include REXML

xml = <<EOF
<fmxmlsnippet type="FMObjectList">
    <Step name="Variable"/>
    <Step name="Comment"/>
    <Step name="Variable"/>
</fmxmlsnippet>
EOF

doc = Document.new xml
el = Element.new 'node'
doc.elements.each( "//Step[@name=\"Variable\"]"){ |e|
  e.previous_sibling = el
  e.next_sibling = el
}
doc.write( $stdout, 2 )

This is the structure that I want:

<fmxmlsnippet type='FMObjectList'>
    <node/>
    <Step name='Variable'/>
    <node/>
    <Step name='Comment'/>
    <node/>
    <Step name='Variable'/>
    <node/>
</fmxmlsnippet>' 

But this is what I'm getting with the code above:

<fmxmlsnippet type='FMObjectList'>
    <Step name='Variable'/>
    <Step name='Comment'/>
    <Step name='Variable'/>
    <node/>
</fmxmlsnippet>' 

What am I doing wrong?

I'm guessing it has to do with my lack of understanding of how the block is getting executed. The path seems to work because it can print the desired elements' attributes just fine.

I'd like to stay with REXML because it's part of the Ruby distribution, but I'd consider Nokogiri if I can get it working that way.

DonovanChan
  • 149
  • 2
  • 10

1 Answers1

2

This is using Nokogiri. I prefer and recommend it because it's very flexible and the defacto standard for Ruby these days.

xml = <<EOT
<fmxmlsnippet type="FMObjectList">
    <Step name="Variable"/>
    <Step name="Comment"/>
    <Step name="Variable"/>
</fmxmlsnippet>
EOT

require 'nokogiri'

doc = Nokogiri::XML(xml)

doc.search('Step[name="Variable"]').each do |s|
  s.add_previous_sibling('<node/>')
  s.add_next_sibling('<node/>')
end

puts doc.to_xml

# >> <?xml version="1.0"?>
# >> <fmxmlsnippet type="FMObjectList">
# >>     <node/><Step name="Variable"/><node/>
# >>     <Step name="Comment"/>
# >>     <node/><Step name="Variable"/><node/>
# >> </fmxmlsnippet>

It's using CSS accessors to find the Step nodes with name="Variable". For each one encountered it adds a previous and next sibling <node>. Nokogiri supports XPath also so '//Step[@name="Variable"]' would work just as well.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • Thanks @theTinMan. This is in a command-line app I plan to share, so I was hoping to avoid the extra dependency, but I haven't found any good reason yet why REXML should be behaving this way. I'll hold out a day to see if anyone can explain it; else, Nokogiri it is! – DonovanChan Jun 21 '11 at 16:49
  • 4 years and there seriously isn't an REXML answer to this? The answer is to include another library? – djsumdog Nov 15 '15 at 20:02