Creating a Twitter Client for OSX in MacRuby - Part 3

Posted in Development on Wednesday, Wednesday, May 06, 2009 by Anthony Burns

Parsing the Xml returned from Twitter

The next step in this series is to parse the Xml data returned from the Twitter API into an array of status objects, then bind them to the table in our main window.

The article I referenced in the first post shows how to use the cocoa class NSXMLDocument and its friends to parse an xml document:
http://cocoawithlove.com/2008/09/cocoa-application-driven-by-http-data.html

Putting that together with the NSXMLElement docs at Apple:
http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSXMLElement_Class/Reference/Reference.html

And some XPath documentation courtesy of W3Schools:
http://www.w3schools.com/XPath/xpath_syntax.asp

We're armed with enough knowledge to start parsing the Twitter Xml response.

In our app we're currently mapping an array of Friend objects (first_name, last_name) to the table in the main window, so we can just as easily map an array of TwitterStatus objects to the table with very little change in code.

First we'll create a TwitterStatus class with accessors for the various fields. Although we'll only be dealing with the users' names and statuses in this article, we'll take the opportunity to grab the users' profile image while we're here for the next article.

class TwitterStatus
	attr_accessor :text, :screen_name, :profile_image_url
end

Then using the example from the Cocoa With Love article and the Apple docs as a starting point, we can write the following method for our TwitterApi class to parse the Xml returned from Twitter into an array of our TwitterStatus objects.

def parseXml(responseData)

	if not responseData then return end
	err = nil
	
	# Create an XMLDocument object from the Xml string
	begin
		document = NSXMLDocument.alloc.initWithData responseData, options:NSXMLDocumentTidyXML, error:err
	rescue StandardError => e
		alert(e.to_s)
		return
	end

	# Create arrays of all the elements we want extracting from the Xml using XPath
	rootNode = document.rootElement
	statusTexts = rootNode.nodesForXPath "//status/text", error:err
	userScreenNames = rootNode.nodesForXPath "//status/user/screen_name", error:err
	userProfileImages = rootNode.nodesForXPath "//status/user/profile_image_url", error:err

	result = []
	statusTexts.length.times do |i|
	
		# Iterate over every status and populate a new TwitterStatus object with them
		status = TwitterStatus.new
		status.text = statusTexts[i].stringValue
		status.screen_name = userScreenNames[i].stringValue
		status.profile_image_url = userProfileImages[i].stringValue
		result << status
	
	end
	
	return result
	
end

Next a quick change to the timeline method so we parse the Xml and hand the resulting array to the callback.

def getTimeline(callback)

	getUrl('http://twitter.com/statuses/friends_timeline.xml', 
            true, lambda {|data| callback.call(self.parseXml(data)) })

end

Which we can then consume from our refresh button like so:

@twitter.getTimeline(lambda do |data|

	@friends = data
	@friendsTableView.reloadData
	
end)

The final thing to do to make the whole thing work is to tweak the tableView method to return the correct properties from the TwitterStatus objects.

def tableView(view, objectValueForTableColumn:column, row:index)
	friend = @friends[index]
	case column.identifier
		when 'screen_name'
			friend.screen_name
		when 'text'
			friend.text
	end
end

Now hitting the refresh button should fill the table with your 20 most recent updates from Twitter. If it doesn't, then you've done something wrong. Don't blame me.

Someone asked in the comments of one of the previous articles if I could post the whole code or upload to github. I'll do that after I post the next article in about a week.

Speaking of which, the next article will show how to download the users' profile images and use them in the table instead of their name.

Tagged as: cocoa, macruby, ruby

Comments