Posted in Development on Wednesday, Wednesday, May 06, 2009 by Anthony Burns
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.