Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test if a child node exists (without getting NoMethodError)

Tags:

ruby

nokogiri

<root>
  <channel>
    <one>example</one>
    <two>example2</two>
  </channel>
  <channel>
    <one>example</one>
  </channel>
</root>

In the second node, I don't have a <two> node. If I use this: root.channel.two obviously I get the error "Method missing". How can I check to avoid this error? What is the conditional statement I would use?

like image 584
sparkle Avatar asked Dec 14 '11 21:12

sparkle


2 Answers

Technique 1: Rescue Any Error

require 'nokogiri'
d = Nokogiri.XML("<foo><bar /></foo>")
bad = d.root.bar            #=> undefined method `bar' for #<...> (NoMethodError)
d.slop!           
yay = d.root.bar            #=> #<... name="bar">
bad = d.root.xxx            #=> undefined method `xxx' for #<...> (NoMethodError)
yay = d.root.xxx rescue nil #=> nil

Technique 2: Look Before Leaping (aka Don't Use Slop)

%w[ bar xxx ].each do |node_name|
  if n = d.root.at_xpath(node_name)
    puts "Yay! #{n}"
  else
    puts "No node named #{node_name}"
  end
end
#=> Yay! <bar/>
#=> No node named xxx

The (no-slop) code some_node.at_xpath("foo") is identical to some_node.foo when using slop, except that it returns nil when no child node with that name exists. Indeed, the implementation of Slop just calls xpath for the element name: if it finds many elements, you get that Nodeset; if it finds only one element, it gives you that; if it finds no elements, it raises the NoMethodError. The important bits look like this:

def method_missing( name )
  list = xpath(name)
  if list.empty?
    super                 # NoMethodError unless someone else handles this
  elsif list.length == 1
    list.first            # Since we only found one element, return that
  else
    list                  # ...otherwise return the whole list
  end
end

Here's what the Nokogiri documents say about Slop (in the footnotes):

Don’t use this.
No, really, don’t use this. If you use it, don’t report bugs.
You’ve been warned!

In general, XPath is way more powerful and faster than slop traversal. For example, if you want to iterate over every <two> node, you can do:

d.xpath('/root/channel/two').each do |two|
  # This will only find nodes that exist
end

If you describe what you really need to do in the end, we can help you craft better code. In my personal opinion Slop is generally a less-effective way to traverse a document.

like image 83
Phrogz Avatar answered Nov 13 '22 17:11

Phrogz


Here is a simple way of doing this:

  xml = Nokogiri::XML(open("http://www.google.com/ig/api?weather=Auckland+New+Zealand"))

  @current_conditions = xml.xpath("//current_conditions")

  if @current_conditions.empty?
    @display_weather = 0
  else
    @display_weather = 1
  end
like image 45
Oleg B Avatar answered Nov 13 '22 17:11

Oleg B