AS3 to Cocoa touch: XML

For this tutorial were going to discus XML Parsing. I’m not totally fond of the xml functionality’s in objective c. I use XML all the time in actionscript because its so easy and powerfull with E4X.  Although there are some open source initiatives for xml parsing. I find it hard to implement them into my projects so I keep it with the standard XML class provided by the cocoa touch framework. So we NSXMLParser, and.. oh wait that’s the only one. NSXMLDocument, NSXMLElement, .. won’t work on the iphone, I didn’t test it but what I have read on the forums about it will work in the iphone simulator but not on the iphone itself. ( by the way if someone knows a great/simple api for xml parsing on the iphone let me know ;-) )

So for this tutorial we are going to stick with the NSXMLParser class.
Actually the NSXMLParser isn’t so bad at all if you start working with it.
The basic idea behind the NSXMLParser class is that it iterates through every element in your XML.
And it uses a Delegate to invoke the methods needed but that will become more clear when we dive into it.

So our user story will be: we have an XML of a couple of person with there name and an attribute with there age, so you can simple use the classes from my previous examples.

#Actionscript
var xmlLoader : URLLoader = new URLLoader();
xmlLoader.addEventListener(Event.COMPLETE,xmlLoadComplete);
xmlLoader.load(new URLRequest("http://andyj.be/iphone/examples/xml/persons.xml"));
 
private function xmlLoadComplete( e : Event ) : void
{
      var persons : Array = new Array(); // Our Array that we want to fill up with Person Objects
      var xml : XML = XML( e.target.data );
      for each( var xmlPerson : XML in xml.person )
      {
          var person : Person = new Person( xmlPerson.name, xmlPerson.@age );
          persons.push( person );
      }
      trace( persons );
}

So very straight forward, make an URLLoader object, put an addEventListener for Complete on it load the url. And in the function we make a new array where we are going to store our objects in it. Retrieve the xml data from the target of the event (don’t forgot to cast it to XML) and iterate it with a for loop on xml. person which will loop over every element “person”. Make an object of type Person and put the values in it. Push it into the array. Et voila in about 10 lines of code we have our xml stored into objects.

So lets bring that to Objective-c

We want to make use of our Person class early created it has 2 property’s that we need to fill up with the values from the xml so we don’t need to make any changes to that class so if your following. I will be providing code for this example as it may be more readable in XCode than it is on my blog. So if your just interested in my current post you don’t have to worry about the Person class and just download the project at the bottom of my article.

So lets continue shall we.. I’ve mentioned that NSXMLParser uses Delegate to invoke his methods. So we there are 3 methods we should implement to our class.

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string

I will explain them based on the code we shall write for the example.

So for setup we make a new window-based Application project (for the iphone since were learning cocoa touch). So were going to add 2 classes the one is Person (Person.h / Person.m) that we made in a previous example, and we are going to make a new Class called “XMLController” which is going to handle the XML parsing itself so we can separate our business logic. So make a new class that with the NSObject subclass template.

//XMLController.h
#import
@class Person;
 
@interface XMLController : NSObject {
      NSMutableString *currentNodeContent;
      NSMutableArray *persons;
      NSXMLParser *parser;
      Person *currentPerson;
}
@property (readonly,retain) NSMutableArray *persons;
- (id)loadXMLByURL:(NSString *)urlString;
@end
//XMLController.m
#import "XMLController.h"
#import "Person.h"
@implementation XMLController
@synthesize persons;
- (id)loadXMLByURL:(NSString *)urlString{
      persons = [[NSMutableArray alloc] init];
      NSURL *url = [NSURL URLWithString: urlString];
      parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
      parser.delegate = self;
      [parser parse];
      return self;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
      if([elementName isEqualToString:@"person"]){
            currentPerson = [Person alloc];
            currentPerson.age = [(NSString *)[attributeDict valueForKey:@"age"] intValue];
            currentNodeContent = [[NSMutableString alloc] init];
      }
}
 
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
      if([elementName isEqualToString:@"name"]){
            [currentPerson setValue:currentNodeContent forKey:@"name"];
 
            [persons addObject:currentPerson];
            [currentPerson release];
            currentPerson = nil;
 
            [currentNodeContent release];
            currentNodeContent = nil;
      }
}
 
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
      [currentNodeContent appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
 
@end

So in the XMLController.h file we specify our variables and methods.

  • currentNodeContent is the variable we are going to store our node values into it, it is of type NSMutableString because we want the string to by dynamic so we can append strings to it rather than a simple static string.
  • persons is an mutableArray where we are going to add our different person objects into it. It’s the only variable we are going to define as a property (readonly,retain) because we only want to read the array outside our class, not modify it.
  • parser is our XMLParser object that is going to handle the delegates for parsing the xml
  • currentPerson of type Person is going to hold the current Person values for that xml node. It will be released when its complete and added to the persons array.

- (id)loadXMLByURL:(NSString *)urlString;
is the method were adding to let our XMLController class initialze itself (like a constructor in actionscript).

On to the implementation file. We synthesize our person variable so our getter is automatically generated. Then on to the implementation for our loadXMLByURL method whe are going to allocate the persons array just like we would say in actionscript new Array();

We make a NSURL object with the string provided by the methods argument( wich contains the url ) it is the same as making an URLRequest object in actionscript.

We allocate the NSXMLParser and we instantly use the initWithContentsOfURL method which is going to load the xml from a URL. We assign our delegation class by telling the parser our delgate = self ( self = this in actionscript) which means our delegation methods will be invoked in the this class. And we return the class itself.

Now we are going to setup our 3 delegation methods mentioned earlier.
So what the NSXMLParser is going to do is iterate through every element of your xml so in our case first persons, then person, then name, then person.

The didStartElement is going to be invoked whenever a element is opened, and the didEndElement will be invoked whenever it finds a closing element in the xml.

So for our case it will be:

  1. persons -> didStartElement
  2. person -> didStartElement
  3. name -> didStartElement
  4. name -> didEndElement
  5. person -> didEndElement
  6. person -> didStartElement
    and so on..

So in our function we are just going to search for the elementName we want.
Notice that I don’t use Comparison operator to check if the elementName is the same as “person” like I would do in actionscript. People who have a background in java know that you cannot compare strings using the comparison operator, instead we are using the isEqualToString method.

Once we found our person element we are going to allocate our currentPerson variable because we now know that every element that follows will be a needed to store in our Person object.
Every element that has attributes in our xml will be stored in the attributeDict. The only thing we have to do now is use the method valueForKey because NSXMLParser will store the values of the attributes with the keys that are defined in the xml, but because our age property is of type int we use the NSString method intValue to convert our string to an int.

We could search for the next element we want data from that would be the element name, but instead were just going to allocate currentNodeContent (wich will collect the data between the nodes) in the same if structure. Note that it our currentNodeContent will collect all the whitespaces and newlines but we will erase that later on.

So in our didEndElement method we are going to search that elementName is equal to name .. so what happens behind the scene.

  1. person -> didStartElement
  2. we instantiate our currentPerson object
  3. name -> didStartElement
  4. foundCharacters method is invoked and will append all values it finds between the nodes to our currentNodeContent string*
  5. name -> didEndElement

*note that we use stringByTrimmingCharactersInSet using the whitespaceAndNewlineCharacterSet NSCharacterSet object so that will automaticly erase whitespace and newlines.

So when our didEndElement method for element name is invoked we know that our currentNodeValue contains the data between the name nodes. So we are going to assign it to our name property of our currentPerson object. ( I could use currentPerson.name = currentNodeContent; for assigning it I use a different approach so you can see that you can assign your values in very different ways )

We add our object to our persons array and we release both currentPerson and currentNodeContent and assign it to nil. So its cleared completely and released from the memory.

Ok that’s for the parsing part of our array. Now we still have to do something with our data.

Since we made a window based Application XCode will generate an AppDelegate class in my case PersonXMLAppDelegate.h and PersonXMLAppDelegate.m

So the only thing we must do now is to edit the applicationDidFinishLaunching method so we can do something with our data ( the only thing we are going to do with it, is tracing it to the output (or Debugger console called in XCode ( shift + apple + R ))

//PersonXMLAppDelegat.m
XMLController *xmlcontr = [[XMLController alloc] loadXMLByURL:@"http://andyj.be/iphone/examples/xml/persons.xml"];
NSLog(@"%@",[xmlcontr persons]);
for (Person *p in [xmlcontr persons])
      NSLog(@"%@ is %i years old.", [p name], [p age]);

*note: don’t forget to import XMLController.h and Person.h

We allocate our XMLController class. And use the loadXMLByURL earlier created and pass it the url to our xml. In our XMLController class we defined 1 property called person that is readonly, so we can now just acces that property and trace it with NSLog. For the sake of it, I’m going to trace a string with the values of each person object separately in a for loop.

So that’s the very basics of parsing XML. If you want a more advanced tutorial you should definitely check out this article

If you want a more automated way of parsing your xml to objects you should definitely check this blog post which can save you allot of time!

download the source files for this example here

9 Comments

  1. [...] tutorial: AS3 to Cocoa touch: XML For this tutorial were going to discus XML Parsing. I

  2. Eric Loubignac says:

    For this kind of really simple XML, i guess you’d better use XML property list (persons.plist for example) :

    name
    Ted
    age
    32

    name
    Barney
    age
    33

    name
    Lily
    age
    30

    The property list editor in XCode is very easy to use.

    And your code could be something like :

    // load the content of persons.plist
    NSString *path = [[NSBundle mainBundle] pathForResource:@”persons” ofType:@”plist”];
    NSArray *array = [[NSArray alloc] initWithContentsOfFile:path];

    //fast enumeration of dictionaries inside the array
    for (NSDictionary *dict in array) {
    Person *person = [[Person alloc] init];
    NSNumber *age = [dict valueForKey:@"age"];
    person.age = [age intValue];
    person.name = [dict valueForKey:@"name"];
    [persons addObject:person];
    [person release];
    }
    NSLog([NSString stringWithFormat:@"%@", persons]);

    And to better see the content of your person, you could override the description instance method of your Person class and Person.m could look like :

    #import “Person.h”

    @implementation Person
    @synthesize name,age;

    -(NSString *) description {
    NSString *desc = [NSString stringWithFormat:@"Name: %@, age: %i", [self name], [self age]];
    return desc;
    }
    @end

    Depending on the complexity of your XML structures, you should probably have a look to property list. It could very simpler !

    I’m also coming from AS3 world and it’s nice to find that kind of blogs.

  3. Eric Loubignac says:

    Oups, i forgot about XML, it looks quite ugly. Hope, it’s better like that :
    <?xml version=”1.0″ encoding=”UTF-8″?>
    <!DOCTYPE plist PUBLIC “-//Apple//DTD PLIST 1.0//EN” “http://www.apple.com/DTDs/PropertyList-1.0.dtd”>
    <plist version=”1.0″>
    <array>
    <dict>
    <key>name</key>
    <string>Ted</string>
    <key>age</key>
    <integer>32</integer>
    </dict>
    <dict>
    <key>name</key>
    <string>Barney</string>
    <key>age</key>
    <integer>33</integer>
    </dict>
    <dict>
    <key>name</key>
    <string>Lily</string>
    <key>age</key>
    <integer>30</integer>
    </dict>
    </array>
    </plist>

  4. alok says:

    Very nice tutorials andy! Thanks a lot.
    I’d like to hear somthing related to views and NavBar from you. We can compate flex views woth it or simple loading i flash whatever u feel most prominent in the comtext.
    Thanks again
    Alok

  5. holms says:

    at the end of didStartElement after if statement it must be:

    [currentNodeContent setString:@""];

    or else it will give output like ALL XML TAGS VALUES IN ONE BIG LINE

    and I got 3 hours of fcking my self with this strange issue thnx to #macdev who helped me in it, btw the same goes to official apple manual!!!!!!!!!

    That line clears string everytime when new tag parsed!!

  6. Tony says:

    Any ideas how to use this code to retrieve xml from a secure https site that is using a self-signed certificate? If I simply use https in the URL, I don’t get any response. Normally a browser would pop up with a dialog asking if I want to use a self-signed cert. iPhone doesn’t have such a mechanism.

    Thanks for your help

  7. Rinkart says:

    Hello, please how do i get the XML values and assign them into string and integer variables?

Leave a Reply