关于XML解析的blog有很多,我本来不想写的;不过我发现有一些细节他们都没有说,我这里就多说一些细节。
我们在哪些地方用XML:现在json用的这么多,使用XML通讯的已经不多了。我遇到的场景是,我们的服务器有很多个,需要用户去选择。那么我们就需要定期维护一个服务器列表,这个服务器列表的配置文件我需要每次下载。自然我就需要解析这一个文件。
XML解析有两种模式SAX和DOM。我用的是系统的 NSXMLParser 它是SAX解析。
我写了一个工具类,先贴一下代码,下面会有一些说明
.h
1 #import <Foundation/Foundation.h> 2 3 typedef NS_ENUM(NSInteger,xmlModelName){// 这里我对我的XML文件做了区别 因为要解析表情和服务器列表两种 4 xmlModelNameFace, 5 xmlModelNameServer, 6 }; 7 @interface XMLParser : NSObject<NSXMLParserDelegate> 8 // 这个是回调的Block 9 @property(nonatomic,copy)void (^returnParseArray)(NSArray * returnArray); 10 @property(nonatomic,readonly)xmlModelName currentModelName; 11 12 - (instancetype)initWithFilePath:(NSString *)path fileType:(NSString *)fileType modelName:(xmlModelName)modelName; 13 14 - (void)startWithFilePath:(NSString *)path fileType:(NSString *)fileType; 15 @end
.m
1 #import "XMLParser.h" 2 #import "FaceModel.h" 3 #import "ToolClient.h" 4 #import "ServerModel.h" 5 @implementation XMLParser{ 6 NSMutableArray * faceArray; 7 NSMutableArray * serverArray; 8 } 9 10 @synthesize currentModelName; 11 - (instancetype)initWithFilePath:(NSString *)path fileType:(NSString *)fileType modelName:(xmlModelName)modelName{ 12 self = [super init]; 13 if(self){ 14 currentModelName = modelName; 15 } 16 return self; 17 } 18 19 - (void)startWithFilePath:(NSString *)path fileType:(NSString *)fileType { 20 [self parseWithPath:path type:fileType]; 21 } 22 // 23 - (void)parseWithPath:(NSString *)filePath type:(NSString *)fileType{ 24 if (currentModelName == xmlModelNameFace) { 25 faceArray = [[NSMutableArray alloc]init]; 26 }else if(currentModelName == xmlModelNameServer){ 27 serverArray = [[NSMutableArray alloc]init]; 28 } 29 NSData *xmlData = [[NSData alloc] initWithContentsOfFile:filePath]; 30 if(xmlData && xmlData.length > 10){ 31 NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData]; 32 [parser setShouldProcessNamespaces:NO]; 33 [parser setShouldReportNamespacePrefixes:NO]; 34 [parser setShouldResolveExternalEntities:NO]; 35 [parser setDelegate:self]; 36 BOOL success = [parser parse]; 37 if(success) { 38 [self parseSuccess]; 39 }else { 40 [ToolClient activityShowMessage:@"XML解析失败" inView:[UIApplication sharedApplication].windows[0]]; 41 } 42 }else { 43 [ToolClient activityShowMessage:@"XML解析失败" inView:[UIApplication sharedApplication].windows[0]]; 44 } 45 } 46 // 成功后的回调 47 - (void)parseSuccess { 48 if(self.returnParseArray){ 49 if (currentModelName == xmlModelNameFace) { 50 self.returnParseArray(faceArray); 51 }else if(currentModelName == xmlModelNameServer){ 52 self.returnParseArray(serverArray); 53 } 54 } 55 } 56 #pragma mark - NSXMLParserDelegate 57 58 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 59 namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName 60 attributes:(NSDictionary *)attributeDict { 61 // NSLog(@"Name:%@",elementName); 62 63 if([elementName isEqualToString:@"face"] && currentModelName == xmlModelNameFace) { 64 FaceModel * faceModel = [[FaceModel alloc]init]; 65 faceModel.kID = [attributeDict[@"id"]intValue]; 66 faceModel.kName = attributeDict[@"name"]; 67 faceModel.kImage = attributeDict[@"file"]; 68 [faceArray addObject:faceModel]; 69 faceModel = nil; 70 }else if([elementName isEqualToString:@"Server"] && currentModelName == xmlModelNameServer){ 71 ServerModel * sModel = [[ServerModel alloc]init]; 72 sModel.serverName = attributeDict[@"name"]; 73 sModel.serverIP = attributeDict[@"ChatServerIP"]; 74 sModel.chatPort = attributeDict[@"chatPort"]; 75 sModel.fileServerIP = attributeDict[@"fileServerIP"]; 76 sModel.filePort = attributeDict[@"filePort"]; 77 [serverArray addObject:sModel]; 78 sModel = nil; 79 } 80 } 81 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 82 // NSLog(@"value:%@",string); 83 } 84 - (void)parserDidEndDocument:(NSXMLParser *)parser { 85 //86 // NSLog(@"%@",faceArray); 87 88 } 89 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{ 90 // NSLog(@"elementName:%@",elementName); 91 // NSLog(@"qualifiedName:%@",qName); 92 // 93 // NSLog(@"NSXMLParserDone"); 94 // NSLog(@"%@",faceArray); 95 // NSLog(@"%i",(int)faceArray.count); 96 97 } 98 @end
现在我来说一下XML解析的设置里面那3个设置为NO的参数是什么作用
1 [parser setShouldProcessNamespaces:NO]; 2 [parser setShouldReportNamespacePrefixes:NO]; 3 [parser setShouldResolveExternalEntities:NO];
第一个 setShouldProcessNamespaces 这个属性设置为YES的话,这两个方法会有值输出:parser:didStartElement:namespaceURI:qualifiedName:attributes:
和 parser:didEndElement:namespaceURI:qualifiedName: 这两个在解析过程中都是都是可以看到里面的节点或字段的名字的。我觉得调试的时候可以用一下。
第二个 setShouldReportNamespacePrefixes 这个属性设置为YES的话,这两个方法会有值输出:
parser:didStartMappingPrefix:toURI:
和 parser:didEndMappingPrefix: 这个我觉得完全没有必要用它
第三个 setShouldResolveExternalEntities 这个属性设置为YES的话,这个方法会有值输出:parser:foundExternalEntityDeclarationWithName:publicID:systemID: 其中publicID 和systemID 都是XML文档的特有的标识。官方文档对这个两个的变量都是这么说的:
You may access this property once a parsing operation has begun or after an error occurs.
也就是当XML解析已经开始或者出现错误的时候,再去看它。也就是说如果你的XML写的够好 你就忽略它吧。
上面的代码是工具类,下面这段会告诉你这段代码怎么用:
1 // 解析文件 2 - (void)parseFile:(NSString *)filepath{ 3 NSLog(@"filepath%@",filepath);// 文件的路径 4 if(data.length>10){// 简单的长度检测 5 __weak SelectServerViewController * ws = self;// 弱引用 6 // do parse 7 XMLParser * xp = [[XMLParser alloc]initWithFilePath:filepath fileType:@"xml" modelName:xmlModelNameServer]; 8 // 先设置回调 9 xp.returnParseArray = ^(NSArray * array){ 10 // 回调的结果 去给tableView 展示 11 [ws gotDataArray:array]; 12 }; 13 // 再开始解析 14 [xp startWithFilePath:filepath fileType:@"xml"]; 15 } 16 17 } 18 19 - (void)gotDataArray:(NSArray *)array { 20 if(array){ 21 // NSLog(@"array:%@",array); 22 dataArray = [array mutableCopy]; 23 [myTableView reloadData]; 24 } 25 }
XML解析我就写这么多了,给一个建议 解析的时候用一个Model来存储数据,后续的使用会很方便。