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:

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>"}

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 XML 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:

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 XML tutorial so far. Note that the app doesn’t show anything in the GUI as that isn’t important for this XML 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?

转载自:http://www.raywenderlich.com/725/xml-tutorial-for-ios-how-to-read-and-write-xml-documents-with-gdataxml

(转载)XML Tutorial for iOS: How To Read and Write XML Documents with GDataXML的更多相关文章

  1. (转载)XML Tutorial for iOS: How To Choose The Best XML Parser for Your iPhone Project

    There are a lot of options when it comes to parsing XML on the iPhone. The iPhone SDK comes with two ...

  2. iOS 应用数据存储方式(XML属性列表-plist)

    iOS 应用数据存储方式(XML属性列表-plist) 一.ios应用常用的数据存储方式 1.plist(XML属性列表归档) 2.偏好设置 3.NSKeydeArchiver归档(存储自定义对象) ...

  3. iOS开发——网络篇——JSON和XML,NSJSONSerialization ,NSXMLParser(XML解析器),NSXMLParserDelegate,MJExtension (字典转模型),GDataXML(三方框架解析XML)

    一.JSON 1.JSON简介什么是JSONJSON是一种轻量级的数据格式,一般用于数据交互服务器返回给客户端的数据,一般都是JSON格式或者XML格式(文件下载除外) JSON的格式很像OC中的字典 ...

  4. iOS开发UI篇—ios应用数据存储方式(XML属性列表-plist)

    iOS开发UI篇—ios应用数据存储方式(XML属性列表-plist) 一.ios应用常用的数据存储方式 1.plist(XML属性列表归档) 2.偏好设置 3.NSKeydeArchiver归档(存 ...

  5. [iOS 多线程 & 网络 - 2.3] - 解析xml

    A.XML基本知识 1.xml概念 什么是XML全称是Extensible Markup Language,译作“可扩展标记语言”跟JSON一样,也是常用的一种用于交互的数据格式一般也叫XML文档(X ...

  6. 转载 Silverlight实用窍门系列:1.Silverlight读取外部XML加载配置---(使用WebClient读取XAP包同目录下的XML文件))

    转载:程兴亮文章,地址;http://www.cnblogs.com/chengxingliang/archive/2011/02/07/1949579.html 使用WebClient读取XAP包同 ...

  7. 关于iOS中几种第三方对XML/JSON数据解析的使用

    Json XML 大数据时代,我们需要从网络中获取海量的新鲜的各种信息,就不免要跟着两个家伙打交道,这是两种结构化的数据交换格式.一般来讲,我们会从网络获取XML或者Json格式的数据,这些数据有着特 ...

  8. iOS SDK中使用NSXMLParser解析XML(iphone网络篇三)

    iOS SDK的NSXMLParser解析XML文档是事件驱动模式的,即采用SAX方式来解析XML格式文档.NSXMLParser在处理XML文档的过程中当遇到一些要素(元素.属性.CDATA块.评论 ...

  9. iOS 详解NSXMLParser方法解析XML数据方法

    前一篇文章已经介绍了如何通过URL从网络上获取xml数据.下面介绍如何将获取到的数据进行解析. 下面先看看xml的数据格式吧! <?xml version="1.0" enc ...

随机推荐

  1. Android源代码之Gallery专题研究(2)

    引言 上一篇文章已经解说了数据载入过程,接下来我们来看一看数据载入后的处理过程.依照正常的思维逻辑.当数据载入之后,接下来就应该考虑数据的显示逻辑. MVC显示逻辑 大家可能对J2EE的MVC架构比較 ...

  2. Android应用开发-小巫CSDN博客client之嵌入有米广告

    Android应用开发-小巫CSDN博客client之嵌入有米广告 上一篇博客给大家介绍怎样集成友盟社会化组件,本篇继续带来干货,教大家怎样嵌入广告到应用中去.小巫自称专业对接30年,熟悉各大渠道SD ...

  3. 【从零学习openCV】IOS7下的人脸检測

    前言: 人脸检測与识别一直是计算机视觉领域一大热门研究方向,并且也从安全监控等工业级的应用扩展到了手机移动端的app,总之随着人脸识别技术获得突破,其应用前景和市场价值都是不可估量的,眼下在学习ope ...

  4. C++中的static成员

    C++中的static 成员永远是我心中的痛,记了好多次了,但是今天在项目中依然忘记了,今天写下来,方便以后不用再去Baidu.google搜索了. 在头文件中声明静态成员 static int i; ...

  5. Android中的创建型模式总结

    共5种,单例模式.工厂方法模式.抽象工厂模式.建造者模式.原型模式 单例模式 定义:确保某一个类的实例只有一个,而且向其他类提供这个实例. 单例模式的使用场景:某个类的创建需要消耗大量资源,new一个 ...

  6. border-radius的用法与技巧总结

    border-radius属性用法重点罗列 border-radius: none | <length>{1,4} [/<length>{1,4}] ? .如果存在反斜杠/,则 ...

  7. POJ 3273 Monthly Expense(二分查找+边界条件)

    POJ 3273 Monthly Expense 此题与POJ3258有点类似,一开始把判断条件写错了,wa了两次,二分查找可以有以下两种: ){ mid=(lb+ub)/; if(C(mid)< ...

  8. $().text() 和 $().html()

    1:性能 stackflow:http://stackoverflow.com/questions/1910794/what-is-the-difference-between-jquery-text ...

  9. 编写自己的javascript功能库之Ajax(仿jquery方式)

    本人学习的是php,所以就用php跟js来演示代码了,主要是锻炼自己写js的能力,练练手而已. 下面这是我编写的操作ajax的代码功能,勉强让我称之为库吧.. js代码实例(tool.ajax.js) ...

  10. PHP学习系列(1)——字符串处理函数(5)

    31.ord() 函数返回字符串第一个字符的 ASCII 值. 语法:ord(string) 例子 <?php echo ord("h"); echo ord("h ...