iOS App Development Tutorial Part IV: Parsing RSS XML

Last updated on April 11, 2024

In previous tutorial, we learn to make a call to a website and download its content. Since we are dealing with RSS, we need to be able to parse the resulting XML data. Here’s an example article from DZone article at the time of writing this blog.

<item>
<title>In case of emergency, System.exit</title>
<link>http://feeds.dzone.com/~r/dzone/frontpage/~3/iD_9xik5E6E/in_case_of_emergency_systemexit.html</link>
<description>Article covering the System.exit method and why it is advisable to avoid using it unless absolutely necessary.</description>
<category>java</category>
<category>methodology</category>
<category>opinion</category>
<category>usability</category>
<pubDate>Fri, 17 May 2013 10:37:20 GMT</pubDate>
<guid isPermaLink="false">http://www.dzone.com/links/970253.html</guid>
<dc:creator>KieranF</dc:creator>
<dc:date>2013-05-17T10:37:20Z</dc:date>
<content:encoded><![CDATA[<a href='http://www.dzone.com/links/r/in_case_of_emergency_systemexit.html'><img src='http://cdn.dzone.com/images/thumbs/120x90/970253.jpg' style='width:120;height:90;float:left;vertical-align:top;border:1px solid #ccc;' /></a><p style='margin-left: 130px;'>Article covering the System.exit method and why it is advisable to avoid using it unless absolutely necessary.<br/><br/><a href='http://www.dzone.com/links/rss/in_case_of_emergency_systemexit.html'><img src='http://www.dzone.com/links/voteCountImage?linkId=970253' border='0'/></a></p><img src="http://feeds.feedburner.com/~r/dzone/frontpage/~4/iD_9xik5E6E" height="1" width="1"/>]]></content:encoded>
<dz:linkId>970253</dz:linkId>
<dz:submitDate>2013-05-17T10:09:02Z</dz:submitDate>
<dz:promoteDate>2013-05-17T10:37:20Z</dz:promoteDate>
<dz:voteUpCount>7</dz:voteUpCount>
<dz:voteDownCount>0</dz:voteDownCount>
<dz:clickCount>386</dz:clickCount>
<dz:commentCount>1</dz:commentCount>
<dz:thumbnail>http://www.dzone.com/links/images/thumbs/120x90/970253.jpg</dz:thumbnail>
<dz:submitter>
<dz:username>KieranF</dz:username>
<dz:userimage>http://www.dzone.com/links/images/avatars/1087079.gif</dz:userimage>
</dz:submitter>
<feedburner:origLink>http://www.dzone.com/links/r/in_case_of_emergency_systemexit.html</feedburner:origLink></item><item>
<title>In case of emergency, System.exit</title>
<link>http://feeds.dzone.com/~r/dzone/frontpage/~3/iD_9xik5E6E/in_case_of_emergency_systemexit.html</link>
<description>Article covering the System.exit method and why it is advisable to avoid using it unless absolutely necessary.</description>
<category>java</category>
<category>methodology</category>
<category>opinion</category>
<category>usability</category>
<pubDate>Fri, 17 May 2013 10:37:20 GMT</pubDate>
<guid isPermaLink="false">http://www.dzone.com/links/970253.html</guid>
<dc:creator>KieranF</dc:creator>
<dc:date>2013-05-17T10:37:20Z</dc:date>
<content:encoded><![CDATA[<a href='http://www.dzone.com/links/r/in_case_of_emergency_systemexit.html'><img src='http://cdn.dzone.com/images/thumbs/120x90/970253.jpg' style='width:120;height:90;float:left;vertical-align:top;border:1px solid #ccc;' /></a><p style='margin-left: 130px;'>Article covering the System.exit method and why it is advisable to avoid using it unless absolutely necessary.<br/><br/><a href='http://www.dzone.com/links/rss/in_case_of_emergency_systemexit.html'><img src='http://www.dzone.com/links/voteCountImage?linkId=970253' border='0'/></a></p><img src="http://feeds.feedburner.com/~r/dzone/frontpage/~4/iD_9xik5E6E" height="1" width="1"/>]]></content:encoded>
<dz:linkId>970253</dz:linkId>
<dz:submitDate>2013-05-17T10:09:02Z</dz:submitDate>
<dz:promoteDate>2013-05-17T10:37:20Z</dz:promoteDate>
<dz:voteUpCount>7</dz:voteUpCount>
<dz:voteDownCount>0</dz:voteDownCount>
<dz:clickCount>386</dz:clickCount>
<dz:commentCount>1</dz:commentCount>
<dz:thumbnail>http://www.dzone.com/links/images/thumbs/120x90/970253.jpg</dz:thumbnail>
<dz:submitter>
<dz:username>KieranF</dz:username>
<dz:userimage>http://www.dzone.com/links/images/avatars/1087079.gif</dz:userimage>
</dz:submitter>
<feedburner:origLink>http://www.dzone.com/links/r/in_case_of_emergency_systemexit.html</feedburner:origLink>
</item>

With objective-c foundation library, we can easily parse the above XML by simply implementing the optional methods of NSXMLParserDelegate. We will pick out only the relevant data from each item and store them in dictionary. Each item will then be added to an array to be used in our storyboarding tutorial.

xmlgroup

NSXMLParserDelegate

Like in prior example, let’s create a group call “XML” and associate it to a specific file system folder “XML”. Then, add a new file called “JSTRRSSParser”, a subclass of NSObject, to this group. We need to update our header file to include the NSXMLParserDelegate protocol with necessary instance variables to store  xml content.

#import <Foundation/Foundation.h>

@interface JSTRRSSParser : NSObject
{
  NSMutableArray *_list;
  NSMutableDictionary *_item;

  NSMutableString *_tempStore;
  BOOL _foundItem;
}

- (NSMutableArray *) getParsedList;

@end

The array is used to store a dictionary (a.k.a hashmap) of article details. The dictionary will represent a single article. A mutable string is used to capture xml data within element. Finally, Boolean will be used to track when new article (i.e. <item> tag has appeared).

JSTRRSSParser Implementation

1. Define init method with allocating and initializing our array. As discussed in prior tutorial, we use NSMutableArray since it will be modified. We should also implement our getter method for the parsed list.

#import "JSTRRSSParser.h"

@implementation JSTRRSSParser

- (id) init
{
  self = [super init];
  if (self) {
    // custom initializer
    _list = [[NSMutableArray alloc] init];
  }
  return self;
}

- (NSMutableArray *)getParsedList
{
  return _list;
}

@end

Tip: Add pragma to logically group methods together. It’s a good practice to organize your methods. I personally

xmlparser

prefer to use “#pragma mark -” to add a horizontal bar and comment for Xcode breadcrumb navigation.

2. Implement parser:didStartElement:namespaceURI:qualifiedName:attributes delegate method. Add this method below the pragma mark line.

#pragma mark - implement NSXMLParserDelegate

- (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
   namespaceURI:(NSString *)namespaceURI 
  qualifiedName:(NSString *)qualifiedName 
     attributes:(NSDictionary *)attributeDict
{
  if ([elementName isEqualToString:@"item"]) {
    _item = [[NSMutableDictionary alloc]init];
    [_item setValue:[[NSMutableArray alloc] init] forKey:@"category"];
    [_list addObject:_item];
    _foundItem = YES;
  }

  if([elementName isEqualToString:@"dz:username"] ||
    [elementName isEqualToString:@"dz:userimage"] ||
    [elementName isEqualToString:@"title"] ||
    [elementName isEqualToString:@"link"] ||
    [elementName isEqualToString:@"description"] ||
    [elementName isEqualToString:@"category"] ||
    [elementName isEqualToString:@"guid"] ||
    [elementName isEqualToString:@"linkId"]||
    [elementName isEqualToString:@"pubDate"]||
    [elementName isEqualToString:@"dz:thumbnail"]||
    [elementName isEqualToString:@"dz:voteUpCount"]||
    [elementName isEqualToString:@"dz:voteDownCount"]) {
    _tempStore = [[NSMutableString alloc] init];
  }
}

This method is called by the XMLDataParser when a new tag has been discovered. When the open/start xml element <item> is discovered, we want to initialize a new dictionary to store the content of the article in a new dictionary. We also preset the dictionary value for key “category” as we can have multiple occurrence of xml element <category> (i.e. see example xml at top). Additionally, when the start xml element is any of the elements we want to store in our dictionary, we allocate a new mutable string.

Tip: Use auto complete when available, particularly with delegate methods. You’ll run lower risk of fat-fingering the wrong character and wonder why your method is not being called. Given how simple it is, why not use it and save yourself the headache of banging your head on the wall. Simply, type “-” and first couple of letters of the method you wanted to implement. In our case, it was “- p”. Once you select it and try it again, you’ll notice that implemented method will not re-appear.

Store all content between start xml element and end xml element for item’s child elements we care about by implementing parse:foundCharacters delegate method. In majority of the use cases, this method is called once. However, if content between the start and end element is too large, it will be chunked and this method will be called multiple time. For this reason, we want to append to it until end element is reached.

- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
  if (_foundItem) {
    [_tempStore appendString:string];
  }
}

Detect end xml element and store the final relevant data within xml element to dictionary. If end element is , reset our Boolean flag to detect the next article.

- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
   namespaceURI:(NSString *)namespaceURI 
   qualifiedName:(NSString *) qName {
  if (_foundItem) {
    if([elementName isEqualToString:@"item"]) {
      _foundItem = NO;
       return; // don't preceed
    }

    // trim whitespace from left and right and replace commonly encoded html string
    NSString *content = [_tempStore stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
    content = [content stringByReplacingOccurrencesOfString:@" " withString:@""];
    content = [content stringByReplacingOccurrencesOfString:@"&" withString:@"&"];
    content = [content stringByReplacingOccurrencesOfString:@""" withString:@"\""];
    content = [content stringByReplacingOccurrencesOfString:@"­" withString:@"-"];
    content = [content stringByReplacingOccurrencesOfString:@"»" withString:@">>"];
    content = [content stringByReplacingOccurrencesOfString:@"·" withString:@"-"];
    content = [content stringByReplacingOccurrencesOfString:@"<" withString:@"<"]; 
    content = [content stringByReplacingOccurrencesOfString:@">" withString:@">"];

    if( [elementName isEqualToString:@"dz:username"] ) {
      [_item setObject:content forKey:@"username"];
    } else if( [elementName isEqualToString:@"dz:userimage"] ) {
      [_item setObject:content forKey:@"userimage"];
    } else if( [elementName isEqualToString:@"title"] ) {
      [_item setObject:content forKey:@"title"];
    } else if ( [elementName isEqualToString:@"link"]) {
      [_item setObject:content forKey:@"link"];
    } else if ( [elementName isEqualToString:@"description"]) {
      [_item setObject:content forKey:@"description"];
    } else if ( [elementName isEqualToString:@"category"]) {
      NSMutableArray *temp = (NSMutableArray *)[_item objectForKey:@"category"];
      [temp addObject:content];
    } else if ( [elementName isEqualToString:@"guid"]) {
      [_item setObject:content forKey:@"guid"];
    } else if ( [elementName isEqualToString:@"linkId"]) {
      [_item setObject:content forKey:@"linkId"];
    } else if ( [elementName isEqualToString:@"pubDate"]) {
      [_item setObject:content forKey:@"pubDate"];
    } else if ( [elementName isEqualToString:@"dz:voteUpCount"]) {
      [_item setObject:content forKey:@"voteUpCountr"];
    } else if ( [elementName isEqualToString:@"dz:voteDownCount"]) {
      [_item setObject:content forKey:@"voteDownCount"];
    } else if ( [elementName isEqualToString:@"dz:thumbnail"]) {
      [_item setObject:content forKey:@"thumbnail"];
    }
  }
}

JSTRRSSReader

Go back to our code from prior tutorial and import our new class “JSTRRSSParser.h” to “JSTRRSSReader.m”. Then, update the connectionDidFinishLoading method.

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
  NSXMLParser *parser = [[NSXMLParser alloc] initWithData:_receivedData];
  JSTRRSSParser *rssParser = [[JSTRRSSParser alloc] init];
  parser.delegate = rssParser;
  [parser parse];

  NSMutableArray *articleList = [rssParser getParsedList];
  NSLog(@"%@", articleList);
}

The revision will instantiate a new instance of NSXMLParser with the completed data from the RSS URL response. The delegate for this parser is the class we just wrote (JSTRRSSParser). When our XMLParser parses this response body, it will call our custom class. Once completed, we now have an array list of all the article with their relevant pieces from the article item.

Run Latest Change

If all works out, when you run your program again, you’ll now see only the relevant parsed data in your console.

console2

If the format looks familiar, that’s because it’s JSON format. Congrats! You have successfully parsed an xml content.