motion/rss_parser.rb
# Asynchronous and non blocking RSS Parser based on NSXMLParser.
# The parser will progressively parse a feed and yield each item to the provided block.
#
# The parser can also trigger delegate methods, you can define the following delegate method on the receiving object:
# def when_parser_initializes; end
# def when_parser_parses; end
# def when_parser_is_done; end
# def when_parser_errors; end
#
# @see https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSXMLParser_Class/Reference/Reference.html
#
# @usage example:
# feed = RSSParser.new(URL)
# feed.delegate = self
# feed.parse do |item|
# print item.link
# end
# def when_parser_is_done
# App.alert('parsing complete')
# end
#
module BubbleWrap
class RSSParser
attr_accessor :parser, :source, :doc, :debug, :delegate, :parser_error
attr_reader :state
# RSSItem is a simple class that holds all of RSS items.
# Extend this class to display/process the item differently.
class RSSItem
attr_accessor :title, :description, :link, :guid, :pubDate, :creator, :category, :encoded, :enclosure
def initialize
@title, @description, @link, @pubDate, @guid, @creator, @category, @encoded = '', '', '', '', ''
end
def to_hash
{
:title => title,
:description => description,
:link => link,
:pubDate => pubDate,
:guid => guid,
:enclosure => enclosure,
:category => category,
:encoded => encoded,
}
end
end
def initialize(input, data=false)
if data
data_to_parse = input.respond_to?(:to_data) ? input.to_data : input
@source = data_to_parse
@source_type = :data
else
url = input.is_a?(NSURL) ? input : NSURL.alloc.initWithString(input)
@source = url
@source_type = :url
end
self
end
def state=(new_state)
@state = new_state
callback_meth = "when_parser_#{new_state}"
if self.delegate && self.delegate.respond_to?(callback_meth)
self.delegate.send(callback_meth)
end
end
# Starts the parsing and send each parsed item through its block.
#
# Usage:
# feed.parse do |item|
# puts item.link
# end
def parse(&block)
@block = block
if @source_type == :url
@parser = NSXMLParser.alloc.initWithContentsOfURL(@source)
else
@parser = NSXMLParser.alloc.initWithData(@source)
end
@parser.shouldProcessNamespaces = true
@parser.delegate ||= self
self.state = :initializes
@parser.parse
end
# Delegate getting called when parsing starts
def parserDidStartDocument(parser)
puts "starting parsing.." if debug
self.state = :parses
end
# Delegate being called when an element starts being processed
def parser(parser, didStartElement:element, namespaceURI:uri, qualifiedName:name, attributes:attrs)
if element == 'item'
@current_item = RSSItem.new
elsif element == 'enclosure'
@current_item.enclosure = attrs
end
@current_element = element
end
# as the parser finds characters, this method is being called
def parser(parser, foundCharacters:string)
if @current_element && @current_item && @current_item.respond_to?(@current_element)
el = @current_item.send(@current_element)
el << string if el.respond_to?(:<<)
end
end
# method called when an element is done being parsed
def parser(parser, didEndElement:element, namespaceURI:uri, qualifiedName:name)
if element == 'item'
@block.call(@current_item) if @block
else
@current_element = nil
end
end
# method called when the parser encounters an error
# error can be retrieved with parserError
def parser(parser, parseErrorOccurred:parse_error)
puts "parseErrorOccurred" if debug
@parser_error = parse_error
self.state = :errors
end
# delegate getting called when the parsing is done
# If a block was set, it will be called on each parsed items
def parserDidEndDocument(parser)
puts "done parsing" if debug
self.state = :is_done unless self.state == :errors
end
def parserError
@parser_error || @parser.parserError
end
# TODO: implement
# parser:validationErrorOccurred:
# parser:foundCDATA:
end
end