Last Updated: May 11, 2020
·
884
· brendanjcaffrey

Generic XML Parsing in RubyMotion With NSXMLParser

I've been working a lot with NSXMLParser objects returned by AFMotion lately, and I couldn't find too much about how to use its delegate style of parsing. I figured I'd share the love with this generic parser. It doesn't take into account namespaces or anything, but this is a good simple base if you need one.

class XMLParserDelegate
  attr_reader :root

  def initialize
    @root = nil
    @queue = []
  end

  def parser(parser, didStartElement:elementName, namespaceURI:namespaceURI, qualifiedName:qualifiedName, attributes:attributeDict)
    el = XMLElement.new(elementName, attributeDict)

    if @queue.empty?
      @root = el 
    else
      @queue[-1].add_child(el)
    end

    @queue << el
  end

  def parser(parser, didEndElement:elementName, namespaceURI:namespaceURI, qualifiedName:qName)
    @queue.pop
  end
end

class XMLElement
  attr_accessor :name, :attributes, :children

  def initialize(name, attributes)
    @el = name
    @attributes = attributes
    @children = []
  end

  def add_child(el)
    @children << el
  end

  def to_xml
    if @children.empty?
      '<' + @el + attrs_as_xml + '/>'
    else
      '<' + @el + attrs_as_xml + '>' + @children.map(&:to_xml).join + '</' + @el + '>'
    end
  end

  private
  def attrs_as_xml
    @attributes.map { |k, v| ' ' + k.to_s + '="' + v.to_s + '"' }.join
  end
end

Used like this (for example on the REPL):

(main)> AFMotion::XML.get('http://example.com/test.xml') do |result|
  parser = result.object
end
=> #<AFXMLRequestOperation:0xa0d6490>
(main)> parser.delegate = XMLParserDelegate.new
=> #<XMLParserDelegate:0xa0d8900 @root=nil @queue=[]>
(main)> parser.parse
=> 1
(main)> parser.delegate.root
=> #<XMLElement:....>

There are plenty of other NSXMLParserDelegate callbacks to check out, so take a look.