XML文档结构
// An highlighted block
<?xml version="1.0" encoding="UTF-8"?><!--(1)声明-->
<nodes><!--(2)根元素-->
<note id="1"><!--(4)属性-->
<name>名称</name><!--(3)子元素-->
<remark>备注备注备注</remark><!--(3)子元素-->
</note>
<note id="2"><!--(4)属性-->
<name>名称</name><!--(3)子元素-->
<remark>备注备注备注</remark><!--(3)子元素-->
</note>
</nodes><!--(2)根元素-->
XML文档的基本架构可以分为下面几个部分:
(1)声明,上面代码段(1)处就是XML文件的声明,它定义了XML文件的版本和使用的字符集,这里为1.0版,使用中文UTF-8字符。
(2)根元素,上面代码段(2)处是XML文件的根元素,是根元素的开始,是根元素的结束。根元素只有一个,开始标签和结束标签必须一致。
(3)子元素,上面代码段(3)处是XML文件的子元素,name、remark是根元素note的子元素。所有子元素都要有结束标签,开始标签和结束标签必须一致。如果标签中没有内容,可以简写成,这称为“空标签”。
(4)属性,上面代码段(4)处是属性,属性定义在开始标签中。id="1"是note元素的一个属性,id是属性名,1是属性值,属性值必须放在双引号或单引号中。一个元素不能有多个相同名字的属性。
(5)命名空间,用于为XML文档提供名字唯一的元素和属性。下面代码段(1)处。
(6)限定名,它是由命名空间引出的概念,定义了元素和属性的合法标识符。限定名通常在XML文档中用作特定元素或属性引用。下面代码段(2)处。
// An highlighted block
<?xml version="1.0" encoding="UTF-8"?>
<soap:note1 xmlns:xsi="https://www.a.com/XMLSchema-instance"
xmlns:xsd="https://www.b.com/XMLSchema"
xmlns:soap="https://c.com/envelope/"><!--(1)命名空间-->
<soap:Body><!--(2)限定名-->
</soap:Body>
</soap:note1>
XML文档解析
解析XML两种流行模式,SAX和DOM。
SAX模式,优点是解析速度快,弊端是只能读不能写入XML文档,iOS重点推荐使用SAX模式解析。
DOM模式,XML解析器在加载XML文件后,将XML文件的元素视为一个树状结构的节点,一次性的读入内存。如果文档比较大,解析速度会变慢。有点是能够修改文档。
iOS SDK提供了两个XML框架,如下:
(1)NSXML,它是基于ObjectiveC的SAX解析框架,是默认的XML解析框架,不支持DOM模式。
(2)libxml2,他是基于C语言的XML解析器,支持SAX和DOM模式。
此外,还有很多第三方框架可以使用,如下:
(1)TBXML,轻量级DOM解析库,不支持XML文档验证和XPath,只能读取XML文档,不能写入XML文档,但解析速度很快。
(2)TouchXML,基于DOM模式的解析库,与TBXML类似,只能读取XML文档,不能写入。
(3)KissXML,基于DOM模式的解析库,基于TouchXML,不同是可以写入XML文档。
(4)TinyXML,基于C++语言的DOM模式解析库,可以读写XML文档,不支持XPath。
(5)GDataXML,基于DOM模式的解析库,可以读写XML文档,支持XPath查询。
NSXML解析
在SAX解析器从上到下遍历文档的过程中,会遇到开始标签、结束标签、文档开始,文档结束和字符串时,就会触发下面几个常用方法:
**(1)parserDidStartDocument:**在文档开始的时候触发。
**(2)parser:didStartElement:namespaceURI:qualifiedName:attributes:**遇到第一个开始标签时触发,其中namespaceURI部分是命名空间,qualifiedName是限定名,attributes是字典类型的属性集合。
**(3)parser:foundCharacters:**遇到字符串时触发。
**(4)parser:didEndElement:namespaceURI:qualifiedName:**遇到结束标签时触发。
**(5)parserDidEndDocument:**在文档结束时触发。
NSXMLParser时解析类,有三个构造函数:
**(1)initWithContentsOfURL:**可以使用URL对象创建解析对象
**(2)initWithData:**可以使用NSData(Swift是Data)创建解析对象,Swift表示为init(data:Data)。
**(3)initWithStream:**可以使用IO流对象创建解析对象,Swift中表示为init(stream:)。
解析对象创建好后,需要制定委托属性delegate为self,然后发送parse消息,开始解析文档。
例:解析方法
Swift代码
// An highlighted block
import Foundation
class NotesXMLParser:NSObject,NSXMLParserDelegate{
//解析处的数据内部是字典类型
private var listData:NSMutableArray!
//当前标签的名字
private var currentTagName:String!
//开始解析
func start(){
let path=Bundle.main.path(forResource:"Notes",ofType:"xml")!
let url=URL(fileURLWithPath:path)
//开始解析XML
let parser=XMLParser(contentsOf:url)!
parse.delegate=self
parse.parse()
}
//解析开始,只触发一次
//可以在这个方法中初始化解析过程中用到的一些成员变量。
func parserDidStartDocument(parser:NSXMLParser!){
self.listData=NSMutableArray()
}
//此方法一般在调试阶段用,实际发布时意义不大。更不要对用户使用UIAlertView提示,用户会被这些专业的错误吓坏。
func parser(parser:NSXMLParser,parseErrorOccurred parseError:NSError){
print(parserError)
}
//遇到第一个开始标签时触发
func parser(_ parser:XMLParser,didStartElement elementName:String,namespaceURI:String?,qualifiedName qName:String?,attributes attributeDict:[String:String]){
//elementName:正在解析的元素名字
self.currentTagName=elementName
//如果名字是"Note"去除它的id。属性是从attributeDict中传递过来的,它是一个字典类型,键的名字是属性名字,值是属性的值。
if self.currentTagName="Note"{
let id=attributeDict["id"]
let dict=NSMutableDictionary()
dict["id"]=id
self.listData.add(dict)
}
}
//遇到字符串时触发
func parser(_ parser:XMLParser,foundCharacters string:String){
//替换回车符合空格
let s1=string.trimmingCharacters(in:CharacterSet.whitespacesAndNewLines)
if s1=""{
return
}
let dict=self.listData.lastObject as! NSMutableDictionary
if(self.currentTagName=="CDate"){
dict["CDate"]=string
}
if(self.currentTagName=="Content"){
dict["Content"]=string
}
}
//遇到结束标签时触发
func parser(_ parser:XMLParser,didEndElement elementName:String,namespaceURI:String?,qualifiedName qName:String?){
self.currentTagName=nil
}
//遇到结束标签时触发
func parserDidEndDocument(_ parser:XMLParser){
print("NSXML解析完成")
}
}
ObjectiveC代码
// An highlighted block
//NotesXMLParser.h文件代码
@interface NotesXMLParser:NSObject<NSXMLParserDelegate>
//解析处的数据内部是字典类型
@property(strong,nonatomic)NSMutableArray*listData;
//当前标签的名字
@property(strong,nonatomic)NSString*currentTagName;
//开始解析
-(void)start;
@end
//NotesXMLParser.m文件代码
//开始解析
-(void)start{
NSString *path=[[NSbundle mainBundle] pathForResource:@"Notes" ofType:@"xml"];
NSURL *url=[NSURL fileURLWithPath:path];
//开始解析
NSXMLParser *parser=[[NSXMLParser alloc] initWithContentsOfURL:url];
parser.delegate=self;
[parser parse];
}
//解析开始,只触发一次
//可以在这个方法中初始化解析过程中用到的一些成员变量。
-(void)parserDidStartDocument:(NSXMLParser *)parser{
self.listData=[[NSMutableArray alloc] init];
}
//此方法一般在调试阶段用,实际发布时意义不大。更不要对用户使用UIAlertView提示,用户会被这些专业的错误吓坏。
-(void)parser:(NSXMLParser*)parser parserErrorOccurred:(NSError*)parseError{
NSLog(@"%@",parseError);
}
//遇到第一个开始标签时触发
-(void)parser:(NSXMLParser*)parser didStrtElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary*)attributedDict{
self.currentTagName=elementName;
if([self.currentTagName isEqualToString:@"Note"]){
NSString *identifier=[attributeDict objectForKey:@"id"];
NSMutableDictionary *dict=[[NSMutableDictionary alloc] init];
[dict setObject:identifier forKey:@"id"];
[self.listData addObject:dict];
}
}
//遇到字符串时触发
-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString*)string{
//替换回车符合空格
string=[string stringByTrimmingCharactersInSet:[NSCharacterSet WhitespaceAndNewlineCharacterSet]];
if([string isEqualToString:@""]){
return;
}
NSMutableDictionary *dict=[self.listData lastObject];
if([self.currentTagName isEqualToString:@"CDate"] && dict){
[dict setObject:string forKey:@"CDate"];
}
if([self.currentTagName isEqualToString:@"Content"] && dict){
[dict setObject:string forKey:@"Content"];
}
}
//遇到结束标签时触发
-(void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qName{
self.currentTagName=nil;
}
//遇到文档结束时触发
-(void)parserDidEndDocument:(NSXMLParser*)parser{
NSLog(@"解析完成");
}
TBXML解析
使用前先下载TBXML。下载后将TBXML-Headers和TBXML-Code文件夹添加到工程中,然后在工程中添加TBXML所依赖的Framework和库,如下:
CoreGraphics.framework
libz.tbd
然后做一些配置,Objective-C与Swift有区别。
ObjectiveC配置
先在预编译头文件中添加如下代码:
#import <Foundation/Foundation.h>
#define ARC_ENABLED
配置预编译头文件位置,Build Settings->Apple LLVM8.0-Language->Prefix Header。
Swift配置
工程预编译头文件对Swift是不管用的需要在每个头文件中添加如下内容:
#import <Foundation/Foundation.h>
#define ARC_ENABLED
因为要在Swift中调用ObjectiveC,所以要在桥接头文件中引入头文件TBXML.h。
配置桥接头文件位置:Build Settings->Swift Compiler-Code General->Objective-C Bridging Header。
TBXML构造函数3个,如下:
(1)initWithXMLString:error:,通过XML字符串构造TBXML对象。
(2)initWithXMLString:,通过XML字符串构造TBXML对象。
(3)initWithXMLData:error:,通过NSData数据构造TBXML对象,这个方法非常适用于在网络通信下解析。
例:
Swift代码
// An highlighted block
class NotesTBXMLParser:NSObject{
//解析处的数据内部是字典类型
private var listData:NSMutableArray!
//开始解析
func start(){
self.listData=NSMutableArray()
//构造函数创建TBXML对象
let tbxml=TBXML(xmlFile:"Notes.xml")
//获得文档的根元素对象,由于不支持XPath,解析文档需要从根元素开始。
let root=tbxml?.rootXMLElement
if root=tbxml?.rootXMLElement
if root !=nil{
//查找root元素下面的Note元素,childElementNamed:方法值返回第一个Note元素
var noteElement=TBXML.childElementNamed("Note",parentElement:root)
while noteElement!=nil{
let dict=NSMutableDictionary()
let dateElement=TNXML.childElementNamed("CDate",parentElement:noteElement)
if dateElement!=nil{
//获取元素的文本内容
let date=TBXML.text(for:dateElement)
dict["CDate"]=date
}
let contentElement=TBXML.childElementNamed("Content",parentElement:noteElement)
if contentElement !=nil{
let content=TBXML.text(for:contentElement)
dict["Content"]=content
}
//获取属性值
let identifier=TBXML.value(ofAttributeNamed:"id",for:noteElement)
dict["id"]=identifier
self.listData.add(dict)
//获得同层下一个Note元素,Sibling:兄弟的意思
noteElement=TBXML.nextSiblingNamed("Note",searchFrom:noteElement)
}
}
print("TBXML解析完成")
}
}
ObjectiveC代码
// An highlighted block
@implementation NotesTBXMLParser
//开始解析
-(void)start{
self.listData=[[NSMutableArray alloc] init];
//构造函数创建TBXML对象
TBXML*tbxml=[[TBXML alloc] initWithXMLFile:@"Notes.xml" error:nil];
//获得文档的根元素对象,由于不支持XPath,解析文档需要从根元素开始。
TBXMLElement *root=tbxm.rootXMLElement;
//如果root元素有效
if(root){
//查找root元素下面的Note元素,childElementNamed:方法值返回第一个Note元素
TBXMLElement *noteElement=[TBXML childElementNamed:@"Note" parentElement:root];
while(noteElement!=nil){
NSMutableDictionary *dict=[NSMutableDictionary new];
TBXMLElement *dateElement=[TBXML childElementNamed:@"CDate" parentElement:noteElement];
if(dateElement!=nil){
//获取元素的文本内容
NSString *date=[TBXML textForElement:dateElement];
dict[@"CDate"]=date;
}
TBXMLElement *contentElement=[TBXML childElementName:@"Content" parentElement:noteElement];
if(contentElement!=nil){
NSString *content=[TBXML textForElement:contentElement];
dict[@"Content"]=content;
}
//获取属性值,获得id属性
NSString *identifier=[TBXML valueOfAttributeName:@"id" forElement:noteElement error:nil];
dict[@"id"]=identifier;
//获得同层下一个Note元素,Sibling:兄弟的意思
noteElement=[TBXML nextSiblingNamed:@"Note" searchFromElement:noteElement];
}
}
NSLog(@"TBXML解析完成");
}
@end
参考资料
《IOS开发指南 从HELLO WORLD到APP STORE上架 第5版》