Search code examples
ruby-on-railsrubyxmlxml-binding

how to handle xml elements that may or may not be repeated using xml-object


I'm using the xml-object gem to process XML. Certain elements in the XML we receive may or may not be repeated. When they only appear once, xml-object binds them as a non-array property, but when they appear more than once they're bound as an array. I would prefer to treat them as if they were always bound as an array. This should explain what I'm talking about:

!/usr/bin/env ruby
require 'rubygems'
require 'xml-object'

XML_ONE = <<END
<foo>
  <bar baz="123">abc</bar>
</foo>
END

foo = XMLObject.new(XML_ONE)
puts foo.bar
puts foo.bar.baz

XML_TWO = <<END
<foo>
  <bar baz="123">abc</bar>
  <bar baz="456">def</bar>
</foo>
END

foo = XMLObject.new(XML_TWO)
puts foo.bar[0]
puts foo.bar[0].baz

What I would like to do is process xml in the form of XML_ONE identically to xml of the form XML_TWO. I tried doing this:

puts [*foo.bar][0]
puts foo.bar.to_a[0]
puts [*foo.bar][0].baz
puts foo.bar.to_a[0].baz

The first two lines output "abc". The second two fail, complaining that there's no method "baz" on "abc". From what I can tell, when the result of "foo.bar" is coerced into an array, that array contains a "plain" string and not the instrumented string returned by "foo.bar", which has the method "baz".

Any thoughts?


Solution

  • I would try to use something different to parse the XML. After a bit of playing with the xml-object gem, it seems to lack consistency with the type of objects that it returns. I would use Nokogiri instead. I was able to get the behavior you were trying for with this code:

    #!/usr/bin/env ruby
    require 'rubygems'
    require 'xml-object'
    require 'nokogiri'
    
    XML_ONE = <<END
    <foo>
      <bar baz="123">abc</bar>
    </foo>
    END
    
    XML_TWO = <<END
    <foo>
      <bar baz="123">abc</bar>
      <bar baz="456">def</bar>
    </foo>
    END
    
    xml1 = Nokogiri::XML(XML_ONE)
    xml2 = Nokogiri::XML(XML_TWO)
    
    puts xml1.xpath('/foo/bar/@baz')[0]
    puts xml2.xpath('/foo/bar/@baz')[1]
    

    Results:

    123
    456
    

    There is similar functionality to XMLObject in Nokogiri as well, via Nokogiri::Slop, but it was just as inconsistent as XMLObject, which is why I opted to use Nokogiri::XML().xpath instead.