CodeBiker CodeBiker - 4 months ago 12
Ruby Question

Keeping attributes when converting XML to Ruby hash

I have a large XML document I am looking to parse. In this document, many tags have different attributes within them. For example:

<album>
<song-name type="published">Do Re Mi</song-name>
</album>


Currently, I am using Rail's hash-parsing library by requiring
'active_support/core_ext/hash'
.

When I convert it to a hash, it drops the attributes. It returns:

{"album"=>{"song-name"=>"Do Re Mi"}}


How do I maintain those attributes, in this case, the
type="published"
attribute?

This seems to have been previously been asked in "How can I use XML attributes when converting into a hash with from_xml?", which had no conclusive answer, but that was from 2010, and I'm curious if things have changed since then. Or, I wonder if you know of an alternative way of parsing this XML so that I could still have the attribute information included.

Answer

Converting XML to a hash isn't a good solution. You're left with a hash that is more difficult to parse than the original XML. Plus, if the XML is too big, you'll be left with a hash that won't fit into memory, and can't be processed, whereas the original XML could be parsed using a SAX parser.

Assuming the file isn't going to overwhelm your memory when loaded, I'd recommend using Nokogiri to parse it, doing something like:

require 'nokogiri'

class Album

  attr_reader :song_name, :song_type
  def initialize(song_name, song_type)
    @song_name = song_name
    @song_type = song_type
  end
end

xml = <<EOT
<xml>
  <album>
   <song-name type="published">Do Re Mi</song-name>
  </album>
  <album>
    <song-name type="unpublished">Blah blah blah</song-name>
  </album>
</xml>
EOT

albums = []
doc = Nokogiri::XML(xml)
doc.search('album').each do |album|
  song_name = album.at('song-name')
  albums << Album.new(
      song_name.text,
      song_name['type']
    )
end

puts albums.first.song_name
puts albums.last.song_type

Which outputs:

Do Re Mi
unpublished

The code starts by defining a suitable object to be used to hold the data you want. When the XML is parsed into a DOM, the code will loop through all the <album> nodes, and extract the information, defining an instance of the class, and appending it to the albums array.

After running you'd have an array you would walk, and process each item, storing it into a database, or manipulating it however you want. Though, if your goal is to insert that information into a database, you'd be smarter to let the DBM read the XML and import it directly.

Comments