DOM based XML Parser for Objective-C

当前Objective-C可用的XML解析器比较多(可以到我的文章《介绍两种常用的XML解析方式(NSXMLParser & GDataXMLNode)》《如何选择你的iPhone XML解析器》中了解)。想了解DOM相关知识可参看W3School

其实Google的GDataXMLNode 其实也是一种基于DOM结构的XML解析器,GDataXMLNode只是是对libxml XML解析库进行了Objective-C的封装。在大多是情况下GDataXMLNode完全可以满足我们解析XML格式文件的需求,也能够对文档XML文件结构进行修改(可能需要自己实现一些东西)。但是GDataXMLNode存在一个问题,就是GDataXMLNode中的每一个Node节点对象对应一个libxml中的xmlNodePtr的结构体,但这种关系并不是一一对应的,也就是你不能通过xmlNodePtr的结构体对应到响应的Node对象,GDataXMLNode中你可以发现每次取相同位子的一个节点,都是对应不同的Node对象,虽然这些Node对象内容是相同的。以为GDataXMLNode中每次都是通过xmlNodePtr去创建一个新的Node对象,这对于只关注文档内容的解析是没有影响的,但是如果涉及到大量节点的操作、遍历,可能会有些麻烦。

之前我也做过一些改造工作,希望让GDataXMLNode实现一种Node对象与xmlNodePtr结构体一一对应的方法,尝试过通过字典Dictionary映射表,将xmlNodePtr结构体的地址作为key值,对应一个Node对象,在每次要找到一个对象时,先查找字典里是否已经存在xmlNodePtr对应的Node。这种方式也存在问题,如每次对Node节点进行操作(增、删、改等)时,都得更新一下映射表,同时映射表因为存在每个Node对象的一份拷贝,占用额外的内存。(如果各位大牛有更好的方法能够将C里面的结构体与对象语言中的对象一一对应,可以分享一下)

因此我就开始着手自己开发XML解析器了,libxml不能用了,只能从NSXMLParser找方法了,NSXMLParser本身是基于SAX方式的解析,因此DOM树需要自己去创建,具体实现细节可直接看我的源码ESXML (欢迎大家到我的Github(github.com/tracy-e))。下面贴出ESXML的头文件:

ESXMLNode.h

//
//  ESXMLNode.h
//  ESXML
//
//  Created by Tracy E on 12-9-8.
//  Copyright (c) 2012年 ChinaMWorld Inc. All rights reserved.
//

#import <Foundation/Foundation.h>

enum {
    ES_ELEMENT_NODE = 1,
    ES_ATTRIBUTE_NODE = 2,
    ES_TEXT_NODE = 3,
    ES_COMMENT_NODE = 8,
    ES_DOCUMENT_NODE = 9,
};

@class ESXMLDocument;
@interface ESXMLNode : NSObject{
    NSMutableDictionary *_attributes;
    NSMutableArray *_childNodes;
}

@property (readonly, copy) NSString *nodeName;
@property (nonatomic, copy) NSString *nodeValue;
@property (readonly) unsigned short nodeType;

@property (nonatomic, assign) ESXMLDocument *ownerDocument;
@property (nonatomic, assign) ESXMLNode *parentNode;
@property (nonatomic, assign) ESXMLNode *firstChild;
@property (nonatomic, assign) ESXMLNode *lastChild;
@property (nonatomic, assign) ESXMLNode *previousSibling;
@property (nonatomic, assign) ESXMLNode *nextSibling;

@property (nonatomic, retain) NSMutableArray *childNodes;
@property (nonatomic, retain) NSMutableDictionary *attributes;

- (ESXMLNode *)insertBefore:(ESXMLNode *)newChild refChild:(ESXMLNode *)refChild;
- (ESXMLNode *)replaceChild:(ESXMLNode *)newChild oldChild:(ESXMLNode *)oldChild;
- (ESXMLNode *)removeChild:(ESXMLNode *)oldChild;
- (ESXMLNode *)appendChild:(ESXMLNode *)newChild;

- (BOOL)hasChildNodes;
- (ESXMLNode *)cloneNode:(BOOL)deep;
- (BOOL)isSameNode:(ESXMLNode *)other;
- (BOOL)isEqualNode:(ESXMLNode *)other;

- (void)visitTree;

@end

ESXMLDocument.h

//
//  ESXMLDoument.h
//  ESXML
//
//  Created by Tracy E on 12-9-8.
//  Copyright (c) 2012年 ChinaMWorld Inc. All rights reserved.
//

#import "ESXMLNode.h"

@class ESXMLElement;
@class ESXMLText;
@interface ESXMLDocument : ESXMLNode<NSXMLParserDelegate>{
  @private
    ESXMLElement *_topElement;
    ESXMLNode *_lastNode;
    
    NSMutableString *_chars;
    NSMutableArray *_stack;
}

@property (nonatomic, retain) ESXMLElement *documentElement;
@property (nonatomic, copy) NSString *URL;

@property (nonatomic, copy) NSString *title;
@property (nonatomic, readonly) ESXMLElement *head;
@property (nonatomic, readonly) ESXMLElement *body;

@property (nonatomic, retain) NSMutableArray *styleSheets;

- (id)initWithXMLString:(NSString *)string baseURL:(NSString *)url;

- (ESXMLElement *)createElement:(NSString *)tagName;
- (ESXMLText *)createTextNode:(NSString *)text;

- (ESXMLElement *)getElementById:(NSString *)elementId;
- (NSArray *)getElementsByTagName:(NSString *)tagName;

@end

ESXMLElement.h

//
//  ESXMLElement.h
//  ESXML
//
//  Created by Tracy E on 12-9-8.
//  Copyright (c) 2012年 ChinaMWorld Inc. All rights reserved.
//

#import "ESXMLNode.h"

@interface ESXMLElement : ESXMLNode

@property (nonatomic, retain) NSString *tagName;


- (id)initWithName:(NSString *)tagName attributes:(NSDictionary *)attributes;

- (NSString *)innerText;
- (void)setInnerText:(NSString *)text;
- (NSString *)innerHTML;
- (void)setInnerHTML:(NSString *)html;

- (ESXMLElement *)getElementById:(NSString *)elementId;
- (NSArray *)getElementsByTagName:(NSString *)tagName;

- (NSString *)getAttribute:(NSString *)attributeName;
- (void)setAttribute:(NSString *)attributeName value:(NSString *)attributeValue;
- (void)removeAttribute:(NSString *)attributeName;

@end

ESXMLText.h

//
//  ESXMLText.h
//  ESXML
//
//  Created by Tracy E on 12-9-8.
//  Copyright (c) 2012年 ChinaMWorld Inc. All rights reserved.
//

#import "ESXMLNode.h"

@interface ESXMLText : ESXMLNode

@property (nonatomic, readonly) unsigned length;
@property (nonatomic, copy) NSString *wholeText;

- (id)initWithText:(NSString *)text;

@end