18 March 2010

How To Read and Write XML Documents with GDataXML

 

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 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 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 tutorial!

Our XML Document

We’re going to work with a simple XML document in this 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 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 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>"}

Converting the XML to our Model Objects

Ok now that we see that it’s working so far, let’s continue the code to walk through the DOM tree and create instances of our model objects as we go.

The replace the code in the loadParty method starting with replacing the NSLog statement with the following:

Party *party = [[[Party alloc] init] autorelease];
NSArray *partyMembers = [doc.rootElement elementsForName:@"Player"];
for (GDataXMLElement *partyMember in partyMembers) {
 
    // Let's fill these in!
    NSString *name;
    int level;
    RPGClass rpgClass;
 
    // Name
    NSArray *names = [partyMember elementsForName:@"Name"];
    if (names.count > 0) {
        GDataXMLElement *firstName = (GDataXMLElement *) [names objectAtIndex:0];
        name = firstName.stringValue;
    } else continue;
 
    // Level
    NSArray *levels = [partyMember elementsForName:@"Level"];
    if (levels.count > 0) {
        GDataXMLElement *firstLevel = (GDataXMLElement *) [levels objectAtIndex:0];
        level = firstLevel.stringValue.intValue;
    } else continue;
 
    // Class
    NSArray *classes = [partyMember elementsForName:@"Class"];
    if (classes.count > 0) {
        GDataXMLElement *firstClass = (GDataXMLElement *) [classes objectAtIndex:0];
        if ([firstClass.stringValue caseInsensitiveCompare:@"Fighter"] 
            == NSOrderedSame) {
            rpgClass = RPGClassFighter;
        } else if ([firstClass.stringValue caseInsensitiveCompare:@"Rogue"] 
            == NSOrderedSame) {
            rpgClass = RPGClassRogue;
        } else if ([firstClass.stringValue caseInsensitiveCompare:@"Wizard"] 
            == NSOrderedSame) {
            rpgClass = RPGClassWizard;
        } else {
            continue;
        }            
    } else continue;
 
    Player *player = [[[Player alloc] initWithName:name level:level 
            rpgClass:rpgClass] autorelease];
    [party.players addObject:player];
 
}
 
[doc release];
[xmlData release];
return party;

This is the real meat of our work. We use the elementsForName method on our root element to get all of the elements named “Player” underneath the root “Party” element.

Then, for each “Player” element we look to see what “Name” elements are underneath that. Our code only deals with one name, so we just take the first if there is more than one.

We do similar processing for “Level” and “Class”, but for level we convert the string into an int, and for class we convert the string into an enumeration.

If anything fails, we just skip that Player. Otherwise, we construct a Player model object with the values we read from the XML, and add it to our Party model object, and return that!

So let’s write some code to see if it works. Add the following to XMLTestAppDelegate.m in applicationDidFinishLaunching, after the call to loadParty:

if (_party != nil) {
    for (Player *player in _party.players) {
        NSLog(@"%@", player.name);
    }
}

Compile and run your project, and if all works well you should see the following in the console output:

2010-03-17 12:33:04.301 XMLTest[2531:207] Butch
2010-03-17 12:33:04.303 XMLTest[2531:207] Shadow
2010-03-17 12:33:04.304 XMLTest[2531:207] Crak

Querying with XPath

XPath is a simple syntax you can use to identify portions of an XML document. The easiest way to get a handle on it is by seeing a few examples.

For example, the following XPath expression would identify all of the Player elements in our document:

//Party/Player

And the following would just identify the first Player element in our document:

//Party/Player[1]

And finally, the following would identify the player with the name of Shadow:

//Party/Player[Name="Shadow"]

Let’s see how we could use XPath by slightly modifying our loadParty method. Replace the line that loads the party members as the following:

//NSArray *partyMembers = [doc.rootElement elementsForName:@"Player"];
NSArray *partyMembers = [doc nodesForXPath:@"//Party/Player" error:nil];

If you run the code, you’ll see the exact same results. So there isn’t an advantage of using XPath in this case, since we are interested in reading the entire XML document and constructing a model in memory.

However, you can imagine that this could be pretty useful if we had a big complicated XML document and we wanted to quickly dig down to find a particular element, without having to look through the children of node A, then the children of node B, and so on until we find it.

If you are interested in learning more about XPath, check out a nice tutorial from W2Schools. Also, I’ve found this online XPath expression testbed quite handy when trying to construct XPath expressions.

Saving Back to XML

So far we’ve only done half of the picture: reading data from an XML document. What if we want to add a new player to our party and then save the new document back to disk?

Well, the first thing we need to do is determine where we are going to save the XML document. So far we’ve been loading the XML document from our application’s bundle. We can’t save to the bundle, however, because it is read-only. But we can save to the application’s document directory, so let’s do that.

Modify your dataFilePath method in PartyParser.m to read as follows:

+ (NSString *)dataFilePath:(BOOL)forSave {
 
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, 
        NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *documentsPath = [documentsDirectory
        stringByAppendingPathComponent:@"Party.xml"];
    if (forSave || 
        [[NSFileManager defaultManager] fileExistsAtPath:documentsPath]) {
        return documentsPath;
    } else {
        return [[NSBundle mainBundle] pathForResource:@"Party" ofType:@"xml"];
    }
 
}

Note that when we’re loading the XML, we want to load from the file in the documents directory if it exists, but otherwise fall back to reading the XML file that ships with the app, like we’ve been doing so far.

Now, let’s write a method to construct our XML document from our data model and save it out to disk. Add a new methdo to PartyParser.m as follows:

+ (void)saveParty:(Party *)party {
 
    GDataXMLElement * partyElement = [GDataXMLNode elementWithName:@"Party"];
 
    for(Player *player in party.players) {
 
        GDataXMLElement * playerElement = 
            [GDataXMLNode elementWithName:@"Player"];
        GDataXMLElement * nameElement = 
            [GDataXMLNode elementWithName:@"Name" stringValue:player.name];
        GDataXMLElement * levelElement = 
            [GDataXMLNode elementWithName:@"Level" stringValue:
                [NSString stringWithFormat:@"%d", player.level]];
        NSString *classString;
        if (player.rpgClass == RPGClassFighter) {
            classString = @"Fighter";
        } else if (player.rpgClass == RPGClassRogue) {
            classString = @"Rogue";
        } else if (player.rpgClass == RPGClassWizard) {
            classString = @"Wizard";
        }        
        GDataXMLElement * classElement = 
            [GDataXMLNode elementWithName:@"Class" stringValue:classString];
 
        [playerElement addChild:nameElement];
        [playerElement addChild:levelElement];
        [playerElement addChild:classElement];
        [partyElement addChild:playerElement];
    }
 
    GDataXMLDocument *document = [[[GDataXMLDocument alloc] 
            initWithRootElement:partyElement] autorelease];
    NSData *xmlData = document.XMLData;
 
    NSString *filePath = [self dataFilePath:TRUE];
    NSLog(@"Saving xml data to %@...", filePath);
    [xmlData writeToFile:filePath atomically:YES];
 
}

As you can see, GDataXML makes it quite easy and straightforward to construct our XML document. You simply create elements with elementWithName: or elementWithName:stringValue, connect them to each other with addChild, and then create a GDataXMLDocument specifying the root element. In the end, you get an NSData that you can easily save to disk.

Next declare your new method in PartyParser.h:

+ (void)saveParty:(Party *)party;

Then inside the party != nil check in applicationDidFinishLaunching, go ahead and add a new party member to our list:

[_party.players addObject:[[[Player alloc] initWithName:@"Waldo" level:1 
    rpgClass:RPGClassRogue] autorelease]];

And finally let’s save out the updated party list in applicationWillTerminate:

- (void)applicationWillTerminate:(UIApplication *)application {
    [PartyParser saveParty:_party];   
}

Compile and run your project, and after the app loads go ahead and exit. The app should print out to the console where it saves your XML. It will look something like this:

2010-03-17 13:34:14.447 XMLTest[3118:207] Saving xml data to 
/Users/rwenderlich/Library/Application Support/iPhone Simulator/User/
Applications/BF246A72-7E20-47CF-93FF-AA2CEF50A6B0/Documents/Party.xml..

Go ahead and find that folder with Finder and open up the XML, and if all goes well you should see your new party member in the XML:

Screenshot of our Modified XML

Then run the app again, open up your console log, and see if you can find where Waldo is! :]

And That’s A Wrap!

Here is a sample project with all of the code that we’ve developed in the tutorial so far. Note that the app doesn’t show anything in the GUI as that isn’t important for this tutorial – it just shows a few output lines in the console.

By the way – before you use XML in your projects, you should take a second to consider why you want to use XML in the first place and if that is the best choice. In this example, if all we were using XML for was loading and saving characters for this particular app, XML would probably be overkill, and it would be better to use another serialization format such as property lists, NSCoding, or even Core Data.

However, XML really starts to shine when you start having multiple applications use the same data. If we had a Mac application that generated a list of characters and we wanted our iPhone app to be able to read and modify that list, that’s when it becomes to be particularly useful. This is because XML is a standard format for exchanging data that is quite easy to work with, as you’ve seen.

So any plans to use XML reading and writing in your apps? What are you using it for?


Category: iPhone

Tags: , , ,

79 Comments

  1. Saliom (16 comments) says:

    wouhou thx for that tutorial :) i will read it now :)

  2. Saliom (16 comments) says:

    héhé just finished it, in fact it seems all so easy when it’s that good explained :D

    again thanks you very much for this tutorial :) you did like always a very good job :D

    i’m going to use XML parser to build lvl for a game (can’t use tiledMap for that kind of game) and also make an lvl editor so i will allow players to make their own levels and save them in a XML file :D then if i have the time (all of this is for my internship) i’ll make players able to exchange their levels trhough a server or something like that :)
    this is what’s planned so far :D

    by the way, another suggestion for a tutorial more likely for the How To Make A Simple iPhone Game with Cocos2D Tutorial line, save the stat of the game so if anything comes to disturb the game ( like a phone call on the iphone) you can exit the game and then come back and push the continue button to get where at the same point that you left the game.

  3. dRine (4 comments) says:

    Hello,

    Thanks for this, but have you really tested it with XPath queries ? In my application, some are working and some not. If I want to have a node with a specified name, it doesn’t work (“//Party/Player” would be an example in your document).

    Have you the same problem or do I miss something ?
    Thanks in advice

  4. Eric (15 comments) says:

    @dRine I’ve just made some tests using Ray project by replacing xpath expression at line 39 of PartyParser.m with some test:
    1) conditional predicate
    //Party/Player[Level = '1' or Name = 'Crak']
    (it should return Butch and Crak)
    2) function
    //Party/Player[string-length(Name)< 5]
    (it should return Crak, the only one with a name with less than 5 characters)
    3) attributes by slightly modifying the xml of Ray like :
    //Party/Player[@id='butch' and ./Class/@id='fighter']
    4) axis
    descendant::Name[. != 'Butch']/parent::node()
    (it should return Crak and Shadow)

    With the XML given by Ray, if you want to access all the players, you can directly use //Player instead of //Party/Player or using axis notation descendant::Player, the result will be the same.

    Do you have some examples of xpath expressions that didn't work for you ? if you give the expressions and the awaited result, maybe i could help you.

  5. Eric (15 comments) says:

    And thank you very much Ray for this demo of GDataXML, i’m a big fan of xml and xpath queries but on iPhone OS, i’ve limited so far my use of xml to xml property list format but it’s sometimes really frustrating to have to conform to this approach.

    I forgot to say that for my tests, I’ve forced the function :
    + (void)saveParty:(Party *)party to return [[NSBundle mainBundle] pathForResource:@”Party” ofType:@”xml”] so I work only with the original XML file.

  6. dRine (4 comments) says:

    @Eric
    In fact, my non working xpath requests are just on axes, like descendant::Player or //Party/Player .
    The only difference between this XML file and mine are namespaces. There are some xmlns in my XML file, and maybe it is the problem ?
    If I do things like : //*[attribute1='val1'] , I’ve got the right result, but simple xpaths like //Player don’t work. Weird.

  7. Eric (15 comments) says:

    You should use your namespaces in your xpath query. Say, Ray has used a namespace rw … your query should be //rw:Player and not //Player.

    Is it your problem ? or can you show an example of your xml and your query ?

  8. Saliom (16 comments) says:

    i’m having trouble when i try to had GDataXML to my cocos2D project… any idea why??

  9. Saliom (16 comments) says:

    nevermind my bad i found the problem thx to lil help from a colleague :p

  10. dRine (4 comments) says:

    @Eric
    Maybe you can contact me on my email address : dRineuh at gmail dot com

    This is one of the xml I have to work with :

    with a lot of in the and and can be nested.

    What if I want to get the element of a ? Even “child::text” doesn’t seem to work whan I call it on a element.
    Could you help me ?
    Thank you very much !

  11. dRine (4 comments) says:

    @Eric : sorry but it seems that I cannot set xml in comments ! Email me if you have some time ;)

  12. Jason (19 comments) says:

    Thanks for another well written tutorial.

    I’m currently having to parse an immense amount of XML data for a clients iPhone app and your XML posts have been helpful.

    Still, I wish everyone would just use JSON which I find much simpler to parse in Objective-C.

  13. tota (1 comments) says:

    thank you man ! thank you v.much.
    I have 2 Question..
    1\ I want to use GDateXML but to save in Database of my website (send to server),not to disk as your explain..can you help me with some informations about this?

    2\ how I can retrieve the data from xml file to tableview in my app?

  14. Ray Wenderlich (874 comments) says:

    @Jason – Great glad they came in handy! Yeah JSON is nice too :]

    @tota – You’ll have to send the XML to your web site, most likely by passing it in a POST with NSURLRequest to a PHP script or some such. See this thread for more info:

    http://stackoverflow.com/questions/319463/can-i-make-post-or-get-requests-from-an-iphone-application

    Once you have the data parsed into a set of your own objects and put into an array, you can display them in a table view in the normal manner you’d display information in a table view, it has nothing to do with XML at that point anymore.

  15. Billy Coover (2 comments) says:

    Awesome tutorial, just one minor correction if you don’t mind. In Partyparser.m add a #import “Player.h”

    The code sample has this correct, but the its missing from the tutorial.

    Regards

  16. Ray Wenderlich (874 comments) says:

    @Billy: Nice catch, updated! Thanks!

  17. shishir (1 comments) says:

    HI,thanks for such a useful tutorial..

    i was wondering if u can post another tutorials on dynamic XML parsing.

    i was working on dynamic xml parsing for more than 2 weeks,using JSon,but didnt tasted success yet.

    I am able call data using dynamic xml paarsing,even i set data into an array,but i am not able to set that data into table view,
    whole data is coming out in a single row.

    I hope i m clear with my problem,hope u can suggest me something,to taste sweet moment of success.

    thanks again for such a nice tutorial.

    regards
    shishir

  18. Ray Wenderlich (874 comments) says:

    @shishir: I’m not sure what you mean with “dynamic XML parsing.”

  19. Julio Cezar (1 comments) says:

    Outstanding! This tutorial was really great. Thx and congratulatior for it!

  20. aye (3 comments) says:

    Thanks Ray, for this great tutorial.
    I try to implement your tutorial in my project and I have problem using GDataXML with cocos2d, it won’t run and shows error :
    Library not found for -lcocos2d libraries
    I read above, that @saliom had same problem with me and he already solved it, unfortunately he didn’t share how he solved this problem :(. Could you please help me?

  21. Ray Wenderlich (874 comments) says:

    @aye: Yeah the common problem here is you need to make sure that the properties are set up right on a per-target basis when you have multiple targets in your project (like you would if you’re building cocos2d).

    So just open up the info window on each target, and make sure the paths/library settings are set up correctly.

  22. Sam Huang (2 comments) says:

    I find that it could not parse the content with ‘&’. Do you happen with it ?

  23. Bzytan (1 comments) says:

    Hey Ray,

    thanks for the tutorial, seems easy enough to follow. Unfortunately I’m getting the same error as aye (Library not found for -lcocos2d libraries) and I’m not sure how exactly to remedy the situation.

    If i understand what you were saying correctly, i opened up the info for each target (cocos2d libraries and my app’s target) and made sure that the Search Paths\Header Search Paths setting had /usr/include/libxml2. Are there more paths to set than that?

    Thanks for your help!

  24. Ray Wenderlich (874 comments) says:

    @Sam: The ampersand is a reserved character in XML, you must escape it by replacing it with the following:

    &amp;
    

    @Bzytan: Actually you only need to put it in your app’s target (not the cocos2d lib target). Also make sure you didn’t set the “Other Linker Flags” in the cocos2d target at all.

  25. Kayvan (2 comments) says:

    Hi Ray,

    Thanks a lot for sharing this. It is really great.

    Just want to know that if it is possible to save updated nodes to the same XML file we loaded.

    Regards,
    k1

  26. KoOL4u (2 comments) says:

    great tutorial…

  27. Ray Wenderlich (874 comments) says:

    @Kayvan: Yep, see the “Saving Back to XML” section. But keep in mind you can only save to the documents directory on the iPhone (i.e. not the app bundle).

  28. Kayvan (2 comments) says:

    Ray, thank you for the reply.

  29. Sander B. (2 comments) says:

    Thanks for the writeup and samples, it was useful.

    I think you have a typo at the start of the ‘Converting the XML to our Model Objects’ section:

    IS: ‘Then replace the code in logParty starting with the log statement with the following: …’

    SHOULD BE: ‘The replace the code in the *loadParty* method starting with *replacing* the *NSLog* statement with the following: …’

  30. Ray Wenderlich (874 comments) says:

    @Sander: You are absolutely right! I have updated the tutorial with this fix, thanks!

  31. Sander B. (2 comments) says:

    Thanks Ray! One last question if you don’t mind? I cannot find a class reference document for GDataXMLDocument, or any of the GDataXML classes for that matter. Any clue/recommendations where I can get a human readable copy for this? Where did you look? Thanks again.

  32. Ray Wenderlich (874 comments) says:

    @Sander: Ah, yes AFAIK the only documentation for GDataXML is the header file for GDataXMLNode.h itself :] Luckily, it is quite short so you can pretty quickly look through it to see what’s available.

  33. Tuyen Nguyen (1 comments) says:

    Outstanding!
    This tutorial was really great!

    Thank you so much Ray.

  34. Sebastien Phaneuf (1 comments) says:

    I tried to use GDataXML from a cocos2d project… But I got the following errors:

    - Include in Header Search Path won’t work. You need to put your include in User Header Search paths (which is strange…).

    - Adding -lxml2 in Other flags doesn’t work at all! It creates a linking error…

    Does somebody has an idea on this?

  35. Ray Wenderlich (874 comments) says:

    @Sebastien: In cocos2d projects, try setting your properties by right clicking on your target and selecting “Get Info” to set the target properties, rather than the project properties. Let me know if that takes care of it.

  36. Mtran003 (3 comments) says:

    Ray,

    Like Sebastien, aye and others, I’ve not been able to get GDataXML integrated into my cocos project without the dreaded “Library not found for -lcocos2d libraries” errors.

    I’ve scoured this forum for fixes and tried and retried setting and resetting the Project and Target settings.

    Still no luck. Can anyone please help a noob out and provide some details on how to fix this?

    This is my first XCode project so please be kind.

    Regards,
    Manny

  37. Mtran003 (3 comments) says:

    OK. I think I got it now. My mistake for my cocos2d project was to follow the instructions for implementation at the top of this article when it speaks to *Project level* settings and then, later when reading in the comments, trying to apply those changes to the Target level settings as well.

    I backed everything out and just tried to apply the changes only to Target level settings and leaving project level alone and it worked.

    In summary, for cocos2d projects the steps for add “/usr/include/libxml2″ and adding link flag “-lxml2″ should only be applied in XCode menu option “Project/Edit Active Target Settings “” where is the name of the cocos2 project — NOT cocos2d libraries.

    Do not make the changes under XCode menu option “Project/Edit Project Settings”.

    Manny

  38. Ray Wenderlich (874 comments) says:

    @Manny: Awesome, thanks for sharing the info. As you know it’s a common question so that’s good info to have for others running into the same thing.

  39. Ronald Situmorang (1 comments) says:

    Thanks for this great tutorial

  40. Piyush Purohit (2 comments) says:

    Hi Ray,

    I wanna save this XML file on my desktop for back up purpose … is it possible ???.. if yes then plz tell me what i do change in the above code..???
    thanks in advance.

  41. Piyush Purohit (2 comments) says:

    in other words i want to say that, how i retrieve XML file (Party.xml) from: /var/mobile/Applications/C144B217-C7DA-4431-BADF-392A5C4D0160/Documents/Party.xml……. to my imac desktop and than read again desktop to my iphone.

    please help me … thanks in advance.

  42. Roland (1 comments) says:

    Nice tutorial, thanks a lot !!!

    but my questions is, who can i change the code to load the XML File from a web resource?

    thanks in advance

  43. Sebastian (7 comments) says:

    Cool tutorial, having 2 question :

    #1 – Can GDataXML also handle loading data from the web directly instead of the document directory ?

    #2 – How or where would you start if you want to store the data with CoreData ? Pass the context to the PartyParser ?

  44. Ray Wenderlich (874 comments) says:

    @Ronald: Glad it was helpful!

    @Plyush: You can’t save an XML file to your Mac desktop from an iPhone app to your Mac desktop. However, you could use File Sharing to allow users a method to easily transfer files from your iPhone to their PC if you would like. See this tutorial for more information:

    http://www.raywenderlich.com/1948/how-integrate-itunes-file-sharing-with-your-ios-app

    @Roland: To load a file from a web resource, you would want to use the NSURLConnection/NSURLRequest APIs. Also, you may want to look into ASIHTTPRequest, an open source wrapper around these APIs that is easier to work with.

    @Sebastian: GDataXML doesn’t contain any network code – so you’d need to retrieve the XML document from the network first (using one of the APIs I mentioned to @Roland), then parse it.

    As for your second question, yeah – the only difference is to create the managed object you’d insert a new object into the managed object context, set all the properties for the object, then save the context.

  45. Tom (3 comments) says:

    Thanks for the tutorial. We are writing an ipad app to interface with our web application – oakfrog. Oakfrog is a small business information system: order processing, customer management, inventory, etc.

    Thanks again.

    Tom

  46. Andy (2 comments) says:

    Ray,

    Thanks for the tutorial!

    Andy

  47. Ray Wenderlich (874 comments) says:

    @Tom/Andy: Glad the article was helpful! :]

  48. suchita (15 comments) says:

    hi.. Ray
    I download ur code & i build&run ur project then its build successfully but when it runs it shows only gray screen. whats the region behind that how can I come out from it

  49. suchita (15 comments) says:

    Hi.. Ray
    whats meaning of the line?(in quotes)
    ” In XCode, click Project\Edit Project Settings and make sure “All Configurations” are checked.”
    becoz in Project\Edit Project Settings “All Configrations” aren’t present.

  50. Ray Wenderlich (874 comments) says:

    @suchita: This app has no UI – it’s just demonstrating how to use GDataXML. If you look at the console output, you’ll see where it saves the XML.

    As far as your second question goes, if you go to Project\Edit Project Settings, and then choose the Build tab, you should see a line that says Configuration – set this to “All Configurations.”

  51. S.A. (2 comments) says:

    Hi Ray,

    When i try to run your sample project with my xcode simulator, it show me a blank screen. Any idea ? Did i miss something here ? Thx

  52. kohsli (1 comments) says:

    Hihi,

    This is a great tutorial!

    Question: Is there a way to remove the xml version declaration? (i.e. )

  53. suchita (15 comments) says:

    Hi.. Ray
    I used ur code in my app its works but one problem occur that is application’s document directory shows only those data which we write but in ur app application’s document directory shows first application’s bundle then those data which we write(means
    Waldo1FighterAlex1FighterJeni2Rogue
    instead of urs path show.
    where i’m wrong

  54. Manoj (3 comments) says:

    How to create native app iPhone? please help me.

  55. Ray Wenderlich (874 comments) says:

    @SA: This app has no UI, it is supposed to be just a blank screen. Check the console output and the tutorial for more details.

    @kohsli: Hm good question. GDataXML is based on libxml2 – you can see the API at the link below. I took a quick look and didn’t see an obvious way to do it – if you figure it out please post here so others can benefit!

    http://xmlsoft.org/html/libxml-tree.html

    @suchita, @Manoj: I’m sorry, I’m not quite sure what you guys are asking, lol…

  56. S.A. (2 comments) says:

    Thanks alot.

  57. suchita (15 comments) says:

    Hi… ray
    I caught my problem.
    Now its pefectly run.
    ur code is awesome…….

  58. deepesh jain (1 comments) says:

    Hi Ray,

    Can i have the same tutorial explained for iphone app with a interface builder to display each node and then user can delete that node or add new or edit the existing one. Any suggestions or links i can refer to.

    Thanks
    Deepesh

  59. suchita (15 comments) says:

    Hi Ray…..
    how can i delete a node from xml or editing perform on it. provide some sort of code through which out come from stuck.

  60. Ray Wenderlich (874 comments) says:

    @deepesh: I’d suggest going through the How To Create a Simple iPhone App tutorial series on this site. It shows how to make table views to add/delete rows etc. and you can combine that with the info here to have the data come from XML.

    @suchita: To delete an element from an XML tree, call removeChild. Usually, I just look through GDataXMLNode.h to see what’s available.

  61. suchita (15 comments) says:

    Hi.. Ray,
    how I can retrieve the data from xml file to tableview in my app?
    in my app MainScreen shows two buttons first one for writing data into xml means(it have textfields & submitbutton when i clicked on submitbutton it save(write) data into xml perfectly) & second one for read data from xml into table view.
    I read upper comments(solution for that ques.) but i can’t understand that so suggest me where i parsed data & how can i put into an array explain in detail for that i read ur scaryBugs and try to do same as but it holds 0 object.

    thanks in advance

  62. suchita (15 comments) says:

    Hi.. Ray,
    how can i’ve the data parsed into a set of own object & how to put into an array please provide such of code for that.

  63. marcelo (4 comments) says:

    Hi Ray, thanks for this tutorial!
    I have a trouble, I just wanted print all player data, I mean name, level and rpgClass then I added two lines on this part:
    if (_party != nil) {
    for (Player *player in _party.players) {
    NSLog(@”%@”, player.name);
    NSLog(@”%@”, player.level);
    NSLog(@”%@”, player.rpgClass);
    }
    but I didn’t have success. Which is my fault?
    Thanks in advance.

  64. marcelo (4 comments) says:

    Hi Ray.I found my mistake. I am not sure if this is the best way but it works:

    -(void)applicationDidFinishLaunching:(UIApplication *)application{
    NSString * const FormatType_toString[] = {
    @”Fighter”,
    @”Rogue”,
    @”Wizard”
    };
    self.party = [PartyParser loadParty];
    if (_party != nil) {
    NSString *levelString;
    for (Player *player in _party.players) {
    NSLog(@”%@”, player.name);
    levelString = [NSString stringWithFormat:@"%d", player.level];
    NSLog(@”%@”,levelString);
    NSLog(@”%@”, FormatType_toString[player.rpgClass]);
    }
    }

    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
    }

    Please let me know if there is another way to get all values for player. Thanks.

  65. marcelo (4 comments) says:

    … sorry, I forgot one question. How can I read the attributes of an element, for example de id for the following snippet:

    Gambardella, Matthew
    XML Developer’s Guide
    Computer

    Ralls, Kim

  66. marcelo (4 comments) says:

    I can’t post the xml snippet :S

  67. suchita (15 comments) says:

    Hi.. Ray,
    how I can retrieve the data from xml file to tableview in my app?
    in my app MainScreen shows two buttons first one for writing data into xml means(it have textfields & submitbutton when i clicked on submitbutton it save(write) data into xml perfectly) & second one for read data from xml into table view.
    for this when i clicked on second button
    -(IBAction)review:(id)sender{
    //[self testing];
    rrController = [[ReviewReferrals alloc]init];
    [self.navigationController pushViewController:rrController animated:YES];

    [self tested];
    }
    -(void)tested{
    self.party = [PartyParser loadParty];
    }
    in the loadParty method definition is same as your are done but little difference is that at begining

    Party *party = [[[Party alloc] init] autorelease];
    ReviewReferrals *rr = [[ReviewReferrals alloc]init];
    NSMutableArray *muArray = [[NSMutableArray alloc]init];

    and end of the method

    [muArray addObject:player];
    //Player *doc = [[Player alloc]init];
    //doc = [muArray objectAtIndex:0];
    //NSLog(@”%@”, doc.name);

    }
    rr.data = muArray;
    //ff.arr = muArray;
    [doc release];
    [xmlData release];

    in this rr.data shows with the help of breakpoint 3 objects but
    in ReviewReferrals class in cellForRowAtIndexPath method

    Player *doc = [[Player alloc]init];
    doc = [_data objectAtIndex:indexPath.row];
    // cell.textLabel.text = doc.data.title;
    cell.textLabel.text = doc.name;
    NSLog(@”name %@”, doc.name);

    _data shows zero object where i’m wrong.

    here _data is NSMutableArray object and its property declarations is
    @property(nonatomic,retain)NSMutableArray *data;
    & @synthesize data = _data .

    its complies without any error & warring.
    thanks in advance

  68. MJ (1 comments) says:

    In your header files you have something like
    Party *_party

    and then in your main file you have
    @synthesize party=_party;

    What does that do?

  69. suchita (15 comments) says:

    I solved it perfectly.

  70. Ray Wenderlich (874 comments) says:

    @suchita: Glad you got it working :]

    @marcelo: To get an attribute for a node, you can use the attributeForName method on GDataXMLNode.

    @MJ: This is a personal coding preference. See my response to @Jason in this tutorial for more details:

    http://www.raywenderlich.com/1797/how-to-create-a-simple-iphone-app-tutorial-part-1

  71. leo (1 comments) says:

    very useful! thanks very much !

  72. Shiun (1 comments) says:

    Thank you! This tutorial is just what I’m looking for. Great articles. Keep up the great work!

  73. Mark (9 comments) says:

    Ray, thanks for these tutorials – the two I’ve followed along with so far have been great.

    I have two questions about Player.m:
    1) Is there a reason why you have double parentheses for “if((self = [super init])){” within the init: method? I’ve seen this in multiple places.

    2) In Player.m’s dealloc: method, I noticed you set self.name = nil. How come you don’t have to call [_name release] before that? I’m thinking that the name variable’s retain count goes to 1 when copy gets called within its setter.

  74. Ray Wenderlich (874 comments) says:

    @leo, @Shium: Thanks guys!

    @Mark: 1) Yeah, the double parenthesis are because the inner parenthesis set self to [super init], and the outer parenthesis checks if the result (self) is nil or not. Saves a line of code doing it this way.

    2) By calling self.name = nil, that goes through the property, and the property is auto-genrerated by @synthesize to release any existing value before overwriting it, so we’re OK.

  75. Rohan (2 comments) says:

    hi,
    can sm1 plz tell me how to modify an existing element?
    eg:update the name butch to butcher

  76. rohan (2 comments) says:

    hey i worked it ut…so please ignore my previous question..thnks

  77. Patrick (2 comments) says:

    Ray, I noticed that Google doesn’t have docs / an api reference listed for the gdata objective-c library like they do for their other client libraries.

    Do you know of one?

  78. rafiq (4 comments) says:

    Hi ray.
    Thanks for the tutorial.But the xml data is to be stored in the console cant we do this in table view.So that the data is to be seen in table instead of console and also save the data in the table itself.

    Thank u

I'd love to hear your thoughts!