XML Tutorial for iOS: How To Read and Write XML Documents with GDataXML

A XML tutorial for iOS on how to easily read and write XML documents with Google’s open source XML processing library: GDataXML. By Ray Wenderlich.

Leave a rating/review
Save for later
Share

Let's read and write this!

In my recent post on How To Choose the Best XML Parser for Your iPhone Project, Saliom from the comments section suggested writing a post on how to use an XML library to read and write XML documents, create your own objects based on the documents, and perform XPath queries.

This XML tutorial will show you how to do exactly that! We’ll create a project that reads a simple XML document that contains a list of RPG party members, and construct our own objects based on the XML. We’ll then add a new player to the party, and save it back out to disk again.

This XML tutorial uses GDataXML, Google’s XML processing library. I chose GDataXML since it performs well for DOM parsers, supports both reading and writing, and is so easy to integrate. However, if you are using a different DOM parser, it should be almost exactly the same as this but just slightly different API calls.

Special thanks to Saliom for suggesting writing this XML tutorial!

Our XML Document

We’re going to work with a simple XML document in this XML tutorial that looks like the following:

Screenshot of our XML document

As I mentioned, this list represents a list of characters you might have in a RPG dungeon crawl game. I wanted to keep the document pretty small to make the XML tutorial easy to follow, but still show you some interesting things such as using different datatypes and having lists of objects.

Ok, so let’s get started on our project to read and write this thing!

Integrating GDataXML

You can integrate GDataXML into a new project with a few easy steps:

  • Choose Project\New Project, and choose View-based Application, and name the project XMLTest.
  • Download the gdata-objective-c client library.
  • Unzip the file, navigate to Source\XMLSupport, and drag the two files GDataXMLNode.h and GDataXMLNode.m into your project.
  • In XCode, click Project\Edit Project Settings and make sure “All Configurations” are checked.
  • Find the Search Paths\Header Search Paths setting and add /usr/include/libxml2 to the list.
  • Finally, find the Linking\Other Linker Flags section and add -lxml2 to the list.
  • Test out that everything is working by adding the following to the top of XMLTestAppDelegate.h:
#import "GDataXMLNode.h"

If your app compiles and runs GDataXML is integrated successfully!

Creating Model Classses

Next, let’s create a set of model classes that we want to represent our party of XML characters in our game. Basically, we want to make a set of objects that represent how we wish to work with our characters in our game.

First, let’s create our Player class. Click on File\New File, select Cocoa Touch Class\Objective-C class, choose Subclass of NSObject, and click Next. Name the file Player.m and verify that “Also create Player.h” is checked.

Then replace Player.h with the following:

#import <Foundation/Foundation.h>

typedef enum {
    RPGClassFighter,
    RPGClassRogue,
    RPGClassWizard
} RPGClass;
    
@interface Player : NSObject {
    NSString *_name;
    int _level;
    RPGClass _rpgClass;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int level;
@property (nonatomic, assign) RPGClass rpgClass;

- (id)initWithName:(NSString *)name level:(int)level rpgClass:(RPGClass)rpgClass;

@end

And replace Player.m with the following:

#import "Player.h"

@implementation Player
@synthesize name = _name;
@synthesize level = _level;
@synthesize rpgClass = _rpgClass;

- (id)initWithName:(NSString *)name level:(int)level rpgClass:(RPGClass)rpgClass {

    if ((self = [super init])) {
        self.name = name;
        self.level = level;
        self.rpgClass = rpgClass;
    }    
    return self;
    
}

- (void) dealloc {
    self.name = nil;    
    [super dealloc];
}

@end

Again, all of this is straight Objective-C, nothing really to do with XML at this point. However, I’m still including it for completeness sake.

Follow the same steps as above to create a new subclass of NSObject named Party. Replace Party.h with the following:

#import <Foundation/Foundation.h>

@interface Party : NSObject {
    NSMutableArray *_players;
}

@property (nonatomic, retain) NSMutableArray *players;

@end

And Party.m with the following:

#import "Party.h"

@implementation Party
@synthesize players = _players;

- (id)init {
 
    if ((self = [super init])) {
        self.players = [[[NSMutableArray alloc] init] autorelease];
    }
    return self;
    
}

- (void) dealloc {
    self.players = nil;    
    [super dealloc];
}

@end

Ok, so far so good! Now let’s get to parsing our XML file and creating instances of these objects based on the XML.

Parsing the XML with GDataXML

Let’s get together some code quickly to use GDataXML to read the XML document and create a DOM tree in memory.

First, download Party.xml and add it to your project.

Next, we’re going to create a new class to hold all of our parsing code. Add a new subclass of NSObject to your project named PartyParser.

Replace PartyParser.h with the following:

#import <Foundation/Foundation.h>

@class Party;

@interface PartyParser : NSObject {

}

+ (Party *)loadParty;

@end

And then replace PartyParser.m with the following:

#import "PartyParser.h"
#import "Party.h"
#import "GDataXMLNode.h"
#import "Player.h"

@implementation PartyParser

+ (NSString *)dataFilePath:(BOOL)forSave {
    return [[NSBundle mainBundle] pathForResource:@"Party" ofType:@"xml"];
}

+ (Party *)loadParty {
 
    NSString *filePath = [self dataFilePath:FALSE];
    NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
    NSError *error;
    GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData 
        options:0 error:&error];
    if (doc == nil) { return nil; }
    
    NSLog(@"%@", doc.rootElement);
    
    [doc release];
    [xmlData release];
    return nil;
    
}

@end

Ok, finally some semi-interesting code here. First we create a static method called dataFilePath that returns the path to the Party.xml that we included in our project (and hence becomes part of our application’s bundle). Note that we have a boolean that indicates whether we want the path of the file for loading or saving – this isn’t used right now, but will be later.

Next, we create another static method called loadParty. This loads the raw bytes for the file off the disk with NSMutableData’s initWithContentsOfFile method, and then passes the data to GDataXML’s parser with the GDataXMLDocument initWithData method.

If GDataXML has an error parsing the file (such as a missing close tag), it will return nil and return an NSError with more information. For this XML tutorial, we’ll just return nil if this occurs.

However, if we receive a success, we just print out a description of the root element of the document (which should be the tag in our case).

Ok, let’s put this to use. Modify your XMLTestAppDelegate.h so it looks like the following:

#import <UIKit/UIKit.h>

@class XMLTestViewController;
@class Party;

@interface XMLTestAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    XMLTestViewController *viewController;
    Party *_party;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet XMLTestViewController *viewController;
@property (nonatomic, retain) Party *party;

@end

Note all we did here was declare a Party member variable.

Then modify your XMLTestAppDelegate.m so it looks like the following:

#import "XMLTestAppDelegate.h"
#import "XMLTestViewController.h"
#import "PartyParser.h"
#import "Party.h"
#import "Player.h"

@implementation XMLTestAppDelegate

@synthesize window;
@synthesize viewController;
@synthesize party = _party;

- (void)applicationDidFinishLaunching:(UIApplication *)application {    
    
    self.party = [PartyParser loadParty];    
    
    // Override point for customization after app launch    
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
}


- (void)dealloc {
    self.party = nil;
    [viewController release];
    [window release];
    [super dealloc];
}

@end

Here we just add some include files and call the static method to load the party in our applicationDidFinishLaunching, and do some cleanup in our dealloc method.

Ok let’s see if it works so far! Compile and run your application, and if you go to Run\Console and scroll to the bottom, you should see something like the following print out:

2010-03-17 11:31:21.467 XMLTest[1568:207] GDataXMLElement 0x3b02530: 
{type:1 name:Party xml:"<Party><Player><Name>Butch</Name>
<Level>1</Level><Class>Fighter</Class></Player><Player>
<Name>Shadow</Name><Level>2</Level><Class>Rogue</Class>
</Player><Player><Name>Crak</Name><Level>3</Level>
<Class>Wizard</Class></Player></Party>"}