62

I have a simple task of accessing the values of some attributes. This is a simple script that uses Nokogiri::XML::Builder to create a simple XML doc.

require 'nokogiri'

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  xml.Placement(:messageId => "392847-039820-938777", :system => "MOD", :version => "2.0") {
    xml.objects {
        xml.object(:myattribute => "99", :anotherattrib => "333")
        xml.nextobject_ '9387toot'
        xml.Entertainment "Last Man Standing"
    }
  }
end

puts builder.to_xml
puts builder.root.attributes["messageId"]

The results are:

<?xml version="1.0" encoding="UTF-8"?>
<Placement messageId="392847-039820-938777" version="2.0" system="MOD">
  <objects>
    <object anotherattrib="333" myattribute="99"/>
    <nextobject>9387toot</nextobject>
    <Entertainment>Last Man Standing</Entertainment>
  </objects>
</Placement>
C:/Ruby/lib/ruby/gems/1.8/gems/nokogiri-1.4.2-x86-mingw32/lib/nokogiri/xml/document.rb:178:in `add_child': Document already has a root node (RuntimeError)
    from C:/Ruby/lib/ruby/gems/1.8/gems/nokogiri-1.4.2-x86-mingw32/lib/nokogiri/xml/node.rb:455:in `parent='
    from C:/Ruby/lib/ruby/gems/1.8/gems/nokogiri-1.4.2-x86-mingw32/lib/nokogiri/xml/builder.rb:358:in `insert'
    from C:/Ruby/lib/ruby/gems/1.8/gems/nokogiri-1.4.2-x86-mingw32/lib/nokogiri/xml/builder.rb:350:in `method_missing'
    from C:/Documents and Settings/etrojan/workspace/Lads/tryXPATH2.rb:15

The XML that is generated looks fine. However, my attempts to access attributes cause an error to be generated:

Document already has a root node

I don't understand why puts would cause this error.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Liz
  • 621
  • 1
  • 5
  • 3

2 Answers2

69

Using Nokogiri::XML::Reader works for your example, but probably isn't the full answer you are looking for (Note that there is no attributes method for Builder).

reader = Nokogiri::XML::Reader(builder.to_xml)
reader.read #Moves to next node in document
reader.attribute("messageId")

Note that if you issued reader.read again and then tried reader.attribute("messageId") the result will be nil since the current node will not have this attribute.

What you probably want to do is use Nokogiri::XML::Document if you want to search an XML document by attribute.

doc = Nokogiri::XML(builder.to_xml)
elems = doc.xpath("//*[@messageId]") #get all elements with an attribute of 'messageId'
elems[0].attr('messageId') #gets value of attribute of first elem 
atomicules
  • 2,155
  • 25
  • 24
  • 5
    Great stuff, this helped me out at 2:22am during an all weekend codefest. Gracias. – Chuck Bergeron Jun 26 '11 at 00:22
  • I looked @ this code http://www.dzone.com/snippets/finding-elements-attributes and was breaking my head because I was using the @ outside [. This has been a real saviour. I have wasted 2 days to figure out a way to parse xml attributes and this is a true saviour. It will be nice if this link added to nokogiri –  Aug 03 '12 at 01:47
  • you can use xpath to get attributes value `nokogiri_element.xpath("@id").text()` – stopanko Apr 16 '15 at 10:32
  • Using [`doc.at_xpath`](https://nokogiri.org/rdoc/Nokogiri/XML/Searchable.html#at_xpath-instance_method) would achieve the same thing as `elems[0]`. `doc.at_xpath("//*[@messageId]")['messageId']` would be simpler. – the Tin Man Apr 08 '19 at 21:14
54

Here is a slightly more succinct way to access attributes using Nokogiri (assuming you already have your xml stored in a variable called xml, as covered by @atomicules' answer):

xml.xpath("//Placement").attr("messageId")
user664833
  • 18,397
  • 19
  • 91
  • 140
ben.snape
  • 1,495
  • 10
  • 21