您的位置:首页 > Web前端 > Node.js

iOS处理XMl提供GDataXMLNode下载的链接

2014-09-13 17:25 267 查看

GDataXMLNode 。好东西,处理xml 在iOS 中使用。可以编辑和读取Xml文档。支持Xpath.这个很好。

GDataXMLNode.h

GDataXMLNode.m 文件很不好找啊。

/* Copyright (c) 2008 Google Inc.

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

* http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

// These node, element, and document classes implement a subset of the methods

// provided by NSXML. While NSXML behavior is mimicked as much as possible,

// there are important differences.

//

// The biggest difference is that, since this is based on libxml2, there

// is no retain model for the underlying node data. Rather than copy every

// node obtained from a parse tree (which would have a substantial memory

// impact), we rely on weak references, and it is up to the code that

// created a document to retain it for as long as any

// references rely on nodes inside that document tree.

#import <Foundation/Foundation.h>

// libxml includes require that the target Header Search Paths contain

//

// /usr/include/libxml2

//

// and Other Linker Flags contain

//

// -lxml2

#import <libxml/tree.h>

#import <libxml/parser.h>

#import <libxml/xmlstring.h>

#import <libxml/xpath.h>

#import <libxml/xpathInternals.h>

#if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4) || defined(GDATA_TARGET_NAMESPACE)

// we need NSInteger for the 10.4 SDK, or we're using target namespace macros

#import "GDataDefines.h"

#endif

#undef _EXTERN

#undef _INITIALIZE_AS

#ifdef GDATAXMLNODE_DEFINE_GLOBALS

#define _EXTERN

#define _INITIALIZE_AS(x) =x

#else

#if defined(__cplusplus)

#define _EXTERN extern "C"

#else

#define _EXTERN extern

#endif

#define _INITIALIZE_AS(x)

#endif

// when no namespace dictionary is supplied for XPath, the default namespace

// for the evaluated tree is registered with the prefix _def_ns

_EXTERN const char* kGDataXMLXPathDefaultNamespacePrefix _INITIALIZE_AS("_def_ns");

// Nomenclature for method names:

//

// Node = GData node

// XMLNode = xmlNodePtr

//

// So, for example:

// + (id)nodeConsumingXMLNode:(xmlNodePtr)theXMLNode;

@class NSArray, NSDictionary, NSError, NSString, NSURL;

@class GDataXMLElement, GDataXMLDocument;

enum {

GDataXMLInvalidKind = 0,

GDataXMLDocumentKind,

GDataXMLElementKind,

GDataXMLAttributeKind,

GDataXMLNamespaceKind,

GDataXMLProcessingInstructionKind,

GDataXMLCommentKind,

GDataXMLTextKind,

GDataXMLDTDKind,

GDataXMLEntityDeclarationKind,

GDataXMLAttributeDeclarationKind,

GDataXMLElementDeclarationKind,

GDataXMLNotationDeclarationKind

};

typedef NSUInteger GDataXMLNodeKind;

@interface GDataXMLNode : NSObject <NSCopying> {

@protected

// NSXMLNodes can have a namespace URI or prefix even if not part

// of a tree; xmlNodes cannot. When we create nodes apart from

// a tree, we'll store the dangling prefix or URI in the xmlNode's name,

// like

// "prefix:name"

// or

// "{http://uri}:name"

//

// We will fix up the node's namespace and name (and those of any children)

// later when adding the node to a tree with addChild: or addAttribute:.

// See fixUpNamespacesForNode:.

xmlNodePtr xmlNode_; // may also be an xmlAttrPtr or xmlNsPtr

BOOL shouldFreeXMLNode_; // if yes, xmlNode_ will be free'd in dealloc

// cached values

NSString *cachedName_;

NSArray *cachedChildren_;

NSArray *cachedAttributes_;

}

+ (GDataXMLElement *)elementWithName:(NSString *)name;

+ (GDataXMLElement *)elementWithName:(NSString *)name stringValue:(NSString *)value;

+ (GDataXMLElement *)elementWithName:(NSString *)name URI:(NSString *)value;

+ (id)attributeWithName:(NSString *)name stringValue:(NSString *)value;

+ (id)attributeWithName:(NSString *)name URI:(NSString *)attributeURI stringValue:(NSString *)value;

+ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)value;

+ (id)textWithStringValue:(NSString *)value;

- (NSString *)stringValue;

- (void)setStringValue:(NSString *)str;

- (NSUInteger)childCount;

- (NSArray *)children;

- (GDataXMLNode *)childAtIndex:(unsigned)index;

- (NSString *)localName;

- (NSString *)name;

- (NSString *)prefix;

- (NSString *)URI;

- (GDataXMLNodeKind)kind;

- (NSString *)XMLString;

+ (NSString *)localNameForName:(NSString *)name;

+ (NSString *)prefixForName:(NSString *)name;

// This is the preferred entry point for nodesForXPath. This takes an explicit

// namespace dictionary (keys are prefixes, values are URIs).

- (NSArray *)nodesForXPath:(NSString *)xpath namespaces:(NSDictionary *)namespaces error:(NSError **)error;

// This implementation of nodesForXPath registers namespaces only from the

// document's root node. _def_ns may be used as a prefix for the default

// namespace, though there's no guarantee that the default namespace will

// be consistenly the same namespace in server responses.

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error;

// access to the underlying libxml node; be sure to release the cached values

// if you change the underlying tree at all

- (xmlNodePtr)XMLNode;

- (void)releaseCachedValues;

@end

@interface GDataXMLElement : GDataXMLNode

- (id)initWithXMLString:(NSString *)str error:(NSError **)error;

- (NSArray *)namespaces;

- (void)setNamespaces:(NSArray *)namespaces;

- (void)addNamespace:(GDataXMLNode *)aNamespace;

// addChild adds a copy of the child node to the element

- (void)addChild:(GDataXMLNode *)child;

- (void)removeChild:(GDataXMLNode *)child;

- (NSArray *)elementsForName:(NSString *)name;

- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI;

- (NSArray *)attributes;

- (GDataXMLNode *)attributeForName:(NSString *)name;

- (GDataXMLNode *)attributeForLocalName:(NSString *)name URI:(NSString *)attributeURI;

- (void)addAttribute:(GDataXMLNode *)attribute;

- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI;

@end

@interface GDataXMLDocument : NSObject {

@protected

xmlDoc* xmlDoc_; // strong; always free'd in dealloc

}

- (id)initWithXMLString:(NSString *)str options:(unsigned int)mask error:(NSError **)error;

- (id)initWithData:(NSData *)data options:(unsigned int)mask error:(NSError **)error;

// initWithRootElement uses a copy of the argument as the new document's root

- (id)initWithRootElement:(GDataXMLElement *)element;

- (GDataXMLElement *)rootElement;

- (NSData *)XMLData;

- (void)setVersion:(NSString *)version;

- (void)setCharacterEncoding:(NSString *)encoding;

// This is the preferred entry point for nodesForXPath. This takes an explicit

// namespace dictionary (keys are prefixes, values are URIs).

- (NSArray *)nodesForXPath:(NSString *)xpath namespaces:(NSDictionary *)namespaces error:(NSError **)error;

// This implementation of nodesForXPath registers namespaces only from the

// document's root node. _def_ns may be used as a prefix for the default

// namespace, though there's no guarantee that the default namespace will

// be consistenly the same namespace in server responses.

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error;

- (NSString *)description;

@end

/* Copyright (c) 2008 Google Inc.

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

* http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

#define GDATAXMLNODE_DEFINE_GLOBALS 1

#import "GDataXMLNode.h"

@class NSArray, NSDictionary, NSError, NSString, NSURL;

@class GDataXMLElement, GDataXMLDocument;

static const int kGDataXMLParseOptions = (XML_PARSE_NOCDATA | XML_PARSE_NOBLANKS);

// dictionary key callbacks for string cache

static const void *StringCacheKeyRetainCallBack(CFAllocatorRef allocator, const void *str);

static void StringCacheKeyReleaseCallBack(CFAllocatorRef allocator, const void *str);

static CFStringRef StringCacheKeyCopyDescriptionCallBack(const void *str);

static Boolean StringCacheKeyEqualCallBack(const void *str1, const void *str2);

static CFHashCode StringCacheKeyHashCallBack(const void *str);

// isEqual: has the fatal flaw that it doesn't deal well with the received

// being nil. We'll use this utility instead.

// Static copy of AreEqualOrBothNil from GDataObject.m, so that using

// GDataXMLNode does not require pulling in all of GData.

static BOOL AreEqualOrBothNilPrivate(id obj1, id obj2) {

if (obj1 == obj2) {

return YES;

}

if (obj1 && obj2) {

return [obj1 isEqual:obj2];

}

return NO;

}

// convert NSString* to xmlChar*

//

// the "Get" part implies that ownership remains with str

static xmlChar* GDataGetXMLString(NSString *str) {

xmlChar* result = (xmlChar *)[str UTF8String];

return result;

}

// Make a fake qualified name we use as local name internally in libxml

// data structures when there's no actual namespace node available to point to

// from an element or attribute node

//

// Returns an autoreleased NSString*

static NSString *GDataFakeQNameForURIAndName(NSString *theURI, NSString *name) {

NSString *localName = [GDataXMLNode localNameForName:name];

NSString *fakeQName = [NSString stringWithFormat:@"{%@}:%@",

theURI, localName];

return fakeQName;

}

// libxml2 offers xmlSplitQName2, but that searches forwards. Since we may

// be searching for a whole URI shoved in as a prefix, like

// {http://foo}:name

// we'll search for the prefix in backwards from the end of the qualified name

//

// returns a copy of qname as the local name if there's no prefix

static xmlChar *SplitQNameReverse(const xmlChar *qname, xmlChar **prefix) {

// search backwards for a colon

int qnameLen = xmlStrlen(qname);

for (int idx = qnameLen - 1; idx >= 0; idx--) {

if (qname[idx] == ':') {

// found the prefix; copy the prefix, if requested

if (prefix != NULL) {

if (idx > 0) {

*prefix = xmlStrsub(qname, 0, idx);

} else {

*prefix = NULL;

}

}

if (idx < qnameLen - 1) {

// return a copy of the local name

xmlChar *localName = xmlStrsub(qname, idx + 1, qnameLen - idx - 1);

return localName;

} else {

return NULL;

}

}

}

// no colon found, so the qualified name is the local name

xmlChar *qnameCopy = xmlStrdup(qname);

return qnameCopy;

}

@interface GDataXMLNode (PrivateMethods)

// consuming a node implies it will later be freed when the instance is

// dealloc'd; borrowing it implies that ownership and disposal remain the

// job of the supplier of the node

+ (id)nodeConsumingXMLNode:(xmlNodePtr)theXMLNode;

- (id)initConsumingXMLNode:(xmlNodePtr)theXMLNode;

+ (id)nodeBorrowingXMLNode:(xmlNodePtr)theXMLNode;

- (id)initBorrowingXMLNode:(xmlNodePtr)theXMLNode;

// getters of the underlying node

- (xmlNodePtr)XMLNode;

- (xmlNodePtr)XMLNodeCopy;

// search for an underlying attribute

- (GDataXMLNode *)attributeForXMLNode:(xmlAttrPtr)theXMLNode;

// return an NSString for an xmlChar*, using our strings cache in the

// document

- (NSString *)stringFromXMLString:(const xmlChar *)chars;

// setter/getter of the dealloc flag for the underlying node

- (BOOL)shouldFreeXMLNode;

- (void)setShouldFreeXMLNode:(BOOL)flag;

@end

@interface GDataXMLElement (PrivateMethods)

+ (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode;

@end

@implementation GDataXMLNode

+ (void)load {

xmlInitParser();

}

// Note on convenience methods for making stand-alone element and

// attribute nodes:

//

// Since we're making a node from scratch, we don't

// have any namespace info. So the namespace prefix, if

// any, will just be slammed into the node name.

// We'll rely on the -addChild method below to remove

// the namespace prefix and replace it with a proper ns

// pointer.

+ (GDataXMLElement *)elementWithName:(NSString *)name {

xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace

GDataGetXMLString(name));

if (theNewNode) {

// succeeded

return [self nodeConsumingXMLNode:theNewNode];

}

return nil;

}

+ (GDataXMLElement *)elementWithName:(NSString *)name stringValue:(NSString *)value {

xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace

GDataGetXMLString(name));

if (theNewNode) {

xmlNodePtr textNode = xmlNewText(GDataGetXMLString(value));

if (textNode) {

xmlNodePtr temp = xmlAddChild(theNewNode, textNode);

if (temp) {

// succeeded

return [self nodeConsumingXMLNode:theNewNode];

}

}

// failed; free the node and any children

xmlFreeNode(theNewNode);

}

return nil;

}

+ (GDataXMLElement *)elementWithName:(NSString *)name URI:(NSString *)theURI {

// since we don't know a prefix yet, shove in the whole URI; we'll look for

// a proper namespace ptr later when addChild calls fixUpNamespacesForNode

NSString *fakeQName = GDataFakeQNameForURIAndName(theURI, name);

xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace

GDataGetXMLString(fakeQName));

if (theNewNode) {

return [self nodeConsumingXMLNode:theNewNode];

}

return nil;

}

+ (id)attributeWithName:(NSString *)name stringValue:(NSString *)value {

xmlChar *xmlName = GDataGetXMLString(name);

xmlChar *xmlValue = GDataGetXMLString(value);

xmlAttrPtr theNewAttr = xmlNewProp(NULL, // parent node for the attr

xmlName, xmlValue);

if (theNewAttr) {

return [self nodeConsumingXMLNode:(xmlNodePtr) theNewAttr];

}

return nil;

}

+ (id)attributeWithName:(NSString *)name URI:(NSString *)attributeURI stringValue:(NSString *)value {

// since we don't know a prefix yet, shove in the whole URI; we'll look for

// a proper namespace ptr later when addChild calls fixUpNamespacesForNode

NSString *fakeQName = GDataFakeQNameForURIAndName(attributeURI, name);

xmlChar *xmlName = GDataGetXMLString(fakeQName);

xmlChar *xmlValue = GDataGetXMLString(value);

xmlAttrPtr theNewAttr = xmlNewProp(NULL, // parent node for the attr

xmlName, xmlValue);

if (theNewAttr) {

return [self nodeConsumingXMLNode:(xmlNodePtr) theNewAttr];

}

return nil;

}

+ (id)textWithStringValue:(NSString *)value {

xmlNodePtr theNewText = xmlNewText(GDataGetXMLString(value));

if (theNewText) {

return [self nodeConsumingXMLNode:theNewText];

}

return nil;

}

+ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)value {

xmlChar *href = GDataGetXMLString(value);

xmlChar *prefix;

if ([name length] > 0) {

prefix = GDataGetXMLString(name);

} else {

// default namespace is represented by a nil prefix

prefix = nil;

}

xmlNsPtr theNewNs = xmlNewNs(NULL, // parent node

href, prefix);

if (theNewNs) {

return [self nodeConsumingXMLNode:(xmlNodePtr) theNewNs];

}

return nil;

}

+ (id)nodeConsumingXMLNode:(xmlNodePtr)theXMLNode {

Class theClass;

if (theXMLNode->type == XML_ELEMENT_NODE) {

theClass = [GDataXMLElement class];

} else {

theClass = [GDataXMLNode class];

}

return [[[theClass alloc] initConsumingXMLNode:theXMLNode] autorelease];

}

- (id)initConsumingXMLNode:(xmlNodePtr)theXMLNode {

self = [super init];

if (self) {

xmlNode_ = theXMLNode;

shouldFreeXMLNode_ = YES;

}

return self;

}

+ (id)nodeBorrowingXMLNode:(xmlNodePtr)theXMLNode {

Class theClass;

if (theXMLNode->type == XML_ELEMENT_NODE) {

theClass = [GDataXMLElement class];

} else {

theClass = [GDataXMLNode class];

}

return [[[theClass alloc] initBorrowingXMLNode:theXMLNode] autorelease];

}

- (id)initBorrowingXMLNode:(xmlNodePtr)theXMLNode {

self = [super init];

if (self) {

xmlNode_ = theXMLNode;

shouldFreeXMLNode_ = NO;

}

return self;

}

- (void)releaseCachedValues {

[cachedName_ release];

cachedName_ = nil;

[cachedChildren_ release];

cachedChildren_ = nil;

[cachedAttributes_ release];

cachedAttributes_ = nil;

}

// convert xmlChar* to NSString*

//

// returns an autoreleased NSString*, from the current node's document strings

// cache if possible

- (NSString *)stringFromXMLString:(const xmlChar *)chars {

#if DEBUG

NSCAssert(chars != NULL, @"GDataXMLNode sees an unexpected empty string");

#endif

if (chars == NULL) return nil;

CFMutableDictionaryRef cacheDict = NULL;

NSString *result = nil;

if (xmlNode_ != NULL

&& (xmlNode_->type == XML_ELEMENT_NODE

|| xmlNode_->type == XML_ATTRIBUTE_NODE

|| xmlNode_->type == XML_TEXT_NODE)) {

// there is no xmlDocPtr in XML_NAMESPACE_DECL nodes,

// so we can't cache the text of those

// look for a strings cache in the document

//

// the cache is in the document's user-defined _private field

if (xmlNode_->doc != NULL) {

cacheDict = xmlNode_->doc->_private;

if (cacheDict) {

// this document has a strings cache

result = (NSString *) CFDictionaryGetValue(cacheDict, chars);

if (result) {

// we found the xmlChar string in the cache; return the previously

// allocated NSString, rather than allocate a new one

return result;

}

}

}

}

// allocate a new NSString for this xmlChar*

result = [NSString stringWithUTF8String:(const char *) chars];

if (cacheDict) {

// save the string in the document's string cache

CFDictionarySetValue(cacheDict, chars, result);

}

return result;

}

- (void)dealloc {

if (xmlNode_ && shouldFreeXMLNode_) {

xmlFreeNode(xmlNode_);

xmlNode_ = NULL;

}

[self releaseCachedValues];

[super dealloc];

}

#pragma mark -

- (void)setStringValue:(NSString *)str {

if (xmlNode_ != NULL && str != nil) {

if (xmlNode_->type == XML_NAMESPACE_DECL) {

// for a namespace node, the value is the namespace URI

xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;

if (nsNode->href != NULL) xmlFree((char *)nsNode->href);

nsNode->href = xmlStrdup(GDataGetXMLString(str));

} else {

// attribute or element node

// do we need to call xmlEncodeSpecialChars?

xmlNodeSetContent(xmlNode_, GDataGetXMLString(str));

}

}

}

- (NSString *)stringValue {

NSString *str = nil;

if (xmlNode_ != NULL) {

if (xmlNode_->type == XML_NAMESPACE_DECL) {

// for a namespace node, the value is the namespace URI

xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;

str = [self stringFromXMLString:(nsNode->href)];

} else {

// attribute or element node

xmlChar* chars = xmlNodeGetContent(xmlNode_);

if (chars) {

str = [self stringFromXMLString:chars];

xmlFree(chars);

}

}

}

return str;

}

- (NSString *)XMLString {

NSString *str = nil;

if (xmlNode_ != NULL) {

xmlBufferPtr buff = xmlBufferCreate();

if (buff) {

xmlDocPtr doc = NULL;

int level = 0;

int format = 0;

int result = xmlNodeDump(buff, doc, xmlNode_, level, format);

if (result > -1) {

str = [[[NSString alloc] initWithBytes:(xmlBufferContent(buff))

length:(xmlBufferLength(buff))

encoding:NSUTF8StringEncoding] autorelease];

}

xmlBufferFree(buff);

}

}

// remove leading and trailing whitespace

NSCharacterSet *ws = [NSCharacterSet whitespaceAndNewlineCharacterSet];

NSString *trimmed = [str stringByTrimmingCharactersInSet:ws];

return trimmed;

}

- (NSString *)localName {

NSString *str = nil;

if (xmlNode_ != NULL) {

str = [self stringFromXMLString:(xmlNode_->name)];

// if this is part of a detached subtree, str may have a prefix in it

str = [[self class] localNameForName:str];

}

return str;

}

- (NSString *)prefix {

NSString *str = nil;

if (xmlNode_ != NULL) {

// the default namespace's prefix is an empty string, though libxml

// represents it as NULL for ns->prefix

str = @"";

if (xmlNode_->ns != NULL && xmlNode_->ns->prefix != NULL) {

str = [self stringFromXMLString:(xmlNode_->ns->prefix)];

}

}

return str;

}

- (NSString *)URI {

NSString *str = nil;

if (xmlNode_ != NULL) {

if (xmlNode_->ns != NULL && xmlNode_->ns->href != NULL) {

str = [self stringFromXMLString:(xmlNode_->ns->href)];

}

}

return str;

}

- (NSString *)qualifiedName {

// internal utility

NSString *str = nil;

if (xmlNode_ != NULL) {

if (xmlNode_->type == XML_NAMESPACE_DECL) {

// name of a namespace node

xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;

// null is the default namespace; one is the loneliest number

if (nsNode->prefix == NULL) {

str = @"";

}

else {

str = [self stringFromXMLString:(nsNode->prefix)];

}

} else if (xmlNode_->ns != NULL && xmlNode_->ns->prefix != NULL) {

// name of a non-namespace node

// has a prefix

char *qname;

if (asprintf(&qname, "%s:%s", (const char *)xmlNode_->ns->prefix,

xmlNode_->name) != -1) {

str = [self stringFromXMLString:(const xmlChar *)qname];

free(qname);

}

} else {

// lacks a prefix

str = [self stringFromXMLString:(xmlNode_->name)];

}

}

return str;

}

- (NSString *)name {

if (cachedName_ != nil) {

return cachedName_;

}

NSString *str = [self qualifiedName];

cachedName_ = [str retain];

return str;

}

+ (NSString *)localNameForName:(NSString *)name {

if (name != nil) {

NSRange range = [name rangeOfString:@":"];

if (range.location != NSNotFound) {

// found a colon

if (range.location + 1 < [name length]) {

NSString *localName = [name substringFromIndex:(range.location + 1)];

return localName;

}

}

}

return name;

}

+ (NSString *)prefixForName:(NSString *)name {

if (name != nil) {

NSRange range = [name rangeOfString:@":"];

if (range.location != NSNotFound) {

NSString *prefix = [name substringToIndex:(range.location)];

return prefix;

}

}

return nil;

}

- (NSUInteger)childCount {

if (cachedChildren_ != nil) {

return [cachedChildren_ count];

}

if (xmlNode_ != NULL) {

unsigned int count = 0;

xmlNodePtr currChild = xmlNode_->children;

while (currChild != NULL) {

++count;

currChild = currChild->next;

}

return count;

}

return 0;

}

- (NSArray *)children {

if (cachedChildren_ != nil) {

return cachedChildren_;

}

NSMutableArray *array = nil;

if (xmlNode_ != NULL) {

xmlNodePtr currChild = xmlNode_->children;

while (currChild != NULL) {

GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:currChild];

if (array == nil) {

array = [NSMutableArray arrayWithObject:node];

} else {

[array addObject:node];

}

currChild = currChild->next;

}

cachedChildren_ = [array retain];

}

return array;

}

- (GDataXMLNode *)childAtIndex:(unsigned)index {

NSArray *children = [self children];

if ([children count] > index) {

return [children objectAtIndex:index];

}

return nil;

}

- (GDataXMLNodeKind)kind {

if (xmlNode_ != NULL) {

xmlElementType nodeType = xmlNode_->type;

switch (nodeType) {

case XML_ELEMENT_NODE: return GDataXMLElementKind;

case XML_ATTRIBUTE_NODE: return GDataXMLAttributeKind;

case XML_TEXT_NODE: return GDataXMLTextKind;

case XML_CDATA_SECTION_NODE: return GDataXMLTextKind;

case XML_ENTITY_REF_NODE: return GDataXMLEntityDeclarationKind;

case XML_ENTITY_NODE: return GDataXMLEntityDeclarationKind;

case XML_PI_NODE: return GDataXMLProcessingInstructionKind;

case XML_COMMENT_NODE: return GDataXMLCommentKind;

case XML_DOCUMENT_NODE: return GDataXMLDocumentKind;

case XML_DOCUMENT_TYPE_NODE: return GDataXMLDocumentKind;

case XML_DOCUMENT_FRAG_NODE: return GDataXMLDocumentKind;

case XML_NOTATION_NODE: return GDataXMLNotationDeclarationKind;

case XML_HTML_DOCUMENT_NODE: return GDataXMLDocumentKind;

case XML_DTD_NODE: return GDataXMLDTDKind;

case XML_ELEMENT_DECL: return GDataXMLElementDeclarationKind;

case XML_ATTRIBUTE_DECL: return GDataXMLAttributeDeclarationKind;

case XML_ENTITY_DECL: return GDataXMLEntityDeclarationKind;

case XML_NAMESPACE_DECL: return GDataXMLNamespaceKind;

case XML_XINCLUDE_START: return GDataXMLProcessingInstructionKind;

case XML_XINCLUDE_END: return GDataXMLProcessingInstructionKind;

case XML_DOCB_DOCUMENT_NODE: return GDataXMLDocumentKind;

}

}

return GDataXMLInvalidKind;

}

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error {

// call through with no explicit namespace dictionary; that will register the

// root node's namespaces

return [self nodesForXPath:xpath namespaces:nil error:error];

}

- (NSArray *)nodesForXPath:(NSString *)xpath

namespaces:(NSDictionary *)namespaces

error:(NSError **)error {

NSMutableArray *array = nil;

NSInteger errorCode = -1;

NSDictionary *errorInfo = nil;

// xmlXPathNewContext requires a doc for its context, but if our elements

// are created from GDataXMLElement's initWithXMLString there may not be

// a document. (We may later decide that we want to stuff the doc used

// there into a GDataXMLDocument and retain it, but we don't do that now.)

//

// We'll temporarily make a document to use for the xpath context.

xmlDocPtr tempDoc = NULL;

xmlNodePtr topParent = NULL;

if (xmlNode_->doc == NULL) {

tempDoc = xmlNewDoc(NULL);

if (tempDoc) {

// find the topmost node of the current tree to make the root of

// our temporary document

topParent = xmlNode_;

while (topParent->parent != NULL) {

topParent = topParent->parent;

}

xmlDocSetRootElement(tempDoc, topParent);

}

}

if (xmlNode_ != NULL && xmlNode_->doc != NULL) {

xmlXPathContextPtr xpathCtx = xmlXPathNewContext(xmlNode_->doc);

if (xpathCtx) {

// anchor at our current node

xpathCtx->node = xmlNode_;

// if a namespace dictionary was provided, register its contents

if (namespaces) {

// the dictionary keys are prefixes; the values are URIs

for (NSString *prefix in namespaces) {

NSString *uri = [namespaces objectForKey:prefix];

xmlChar *prefixChars = (xmlChar *) [prefix UTF8String];

xmlChar *uriChars = (xmlChar *) [uri UTF8String];

int result = xmlXPathRegisterNs(xpathCtx, prefixChars, uriChars);

if (result != 0) {

#if DEBUG

NSCAssert1(result == 0, @"GDataXMLNode XPath namespace %@ issue",

prefix);

#endif

}

}

} else {

// no namespace dictionary was provided

//

// register the namespaces of this node, if it's an element, or of

// this node's root element, if it's a document

xmlNodePtr nsNodePtr = xmlNode_;

if (xmlNode_->type == XML_DOCUMENT_NODE) {

nsNodePtr = xmlDocGetRootElement((xmlDocPtr) xmlNode_);

}

// step through the namespaces, if any, and register each with the

// xpath context

if (nsNodePtr != NULL) {

for (xmlNsPtr nsPtr = nsNodePtr->ns; nsPtr != NULL; nsPtr = nsPtr->next) {

// default namespace is nil in the tree, but there's no way to

// register a default namespace, so we'll register a fake one,

// _def_ns

const xmlChar* prefix = nsPtr->prefix;

if (prefix == NULL) {

prefix = (xmlChar*) kGDataXMLXPathDefaultNamespacePrefix;

}

int result = xmlXPathRegisterNs(xpathCtx, prefix, nsPtr->href);

if (result != 0) {

#if DEBUG

NSCAssert1(result == 0, @"GDataXMLNode XPath namespace %@ issue",

prefix);

#endif

}

}

}

}

// now evaluate the path

xmlXPathObjectPtr xpathObj;

xpathObj = xmlXPathEval(GDataGetXMLString(xpath), xpathCtx);

if (xpathObj) {

// we have some result from the search

array = [NSMutableArray array];

xmlNodeSetPtr nodeSet = xpathObj->nodesetval;

if (nodeSet) {

// add each node in the result set to our array

for (int index = 0; index < nodeSet->nodeNr; index++) {

xmlNodePtr currNode = nodeSet->nodeTab[index];

GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:currNode];

if (node) {

[array addObject:node];

}

}

}

xmlXPathFreeObject(xpathObj);

} else {

// provide an error for failed evaluation

const char *msg = xpathCtx->lastError.str1;

errorCode = xpathCtx->lastError.code;

if (msg) {

NSString *errStr = [NSString stringWithUTF8String:msg];

errorInfo = [NSDictionary dictionaryWithObject:errStr

forKey:@"error"];

}

}

xmlXPathFreeContext(xpathCtx);

}

} else {

// not a valid node for using XPath

errorInfo = [NSDictionary dictionaryWithObject:@"invalid node"

forKey:@"error"];

}

if (array == nil && error != nil) {

*error = [NSError errorWithDomain:@"com.google.GDataXML"

code:errorCode

userInfo:errorInfo];

}

if (tempDoc != NULL) {

xmlUnlinkNode(topParent);

xmlSetTreeDoc(topParent, NULL);

xmlFreeDoc(tempDoc);

}

return array;

}

- (NSString *)description {

int nodeType = (xmlNode_ ? (int)xmlNode_->type : -1);

return [NSString stringWithFormat:@"%@ %p: {type:%d name:%@ xml:\"%@\"}",

[self class], self, nodeType, [self name], [self XMLString]];

}

- (id)copyWithZone:(NSZone *)zone {

xmlNodePtr nodeCopy = [self XMLNodeCopy];

if (nodeCopy != NULL) {

return [[[self class] alloc] initConsumingXMLNode:nodeCopy];

}

return nil;

}

- (BOOL)isEqual:(GDataXMLNode *)other {

if (self == other) return YES;

if (![other isKindOfClass:[GDataXMLNode class]]) return NO;

return [self XMLNode] == [other XMLNode]

|| ([self kind] == [other kind]

&& AreEqualOrBothNilPrivate([self name], [other name])

&& [[self children] count] == [[other children] count]);

}

- (NSUInteger)hash {

return (NSUInteger) (void *) [GDataXMLNode class];

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {

return [super methodSignatureForSelector:selector];

}

#pragma mark -

- (xmlNodePtr)XMLNodeCopy {

if (xmlNode_ != NULL) {

// Note: libxml will create a new copy of namespace nodes (xmlNs records)

// and attach them to this copy in order to keep namespaces within this

// node subtree copy value.

xmlNodePtr nodeCopy = xmlCopyNode(xmlNode_, 1); // 1 = recursive

return nodeCopy;

}

return NULL;

}

- (xmlNodePtr)XMLNode {

return xmlNode_;

}

- (BOOL)shouldFreeXMLNode {

return shouldFreeXMLNode_;

}

- (void)setShouldFreeXMLNode:(BOOL)flag {

shouldFreeXMLNode_ = flag;

}

@end

@implementation GDataXMLElement

- (id)initWithXMLString:(NSString *)str error:(NSError **)error {

self = [super init];

if (self) {

const char *utf8Str = [str UTF8String];

// NOTE: We are assuming a string length that fits into an int

xmlDocPtr doc = xmlReadMemory(utf8Str, (int)strlen(utf8Str), NULL, // URL

NULL, // encoding

kGDataXMLParseOptions);

if (doc == NULL) {

if (error) {

// TODO(grobbins) use xmlSetGenericErrorFunc to capture error

}

} else {

// copy the root node from the doc

xmlNodePtr root = xmlDocGetRootElement(doc);

if (root) {

xmlNode_ = xmlCopyNode(root, 1); // 1: recursive

shouldFreeXMLNode_ = YES;

}

xmlFreeDoc(doc);

}

if (xmlNode_ == NULL) {

// failure

if (error) {

*error = [NSError errorWithDomain:@"com.google.GDataXML"

code:-1

userInfo:nil];

}

[self release];

return nil;

}

}

return self;

}

- (NSArray *)namespaces {

NSMutableArray *array = nil;

if (xmlNode_ != NULL && xmlNode_->nsDef != NULL) {

xmlNsPtr currNS = xmlNode_->nsDef;

while (currNS != NULL) {

// add this prefix/URI to the list, unless it's the implicit xml prefix

if (!xmlStrEqual(currNS->prefix, (const xmlChar *) "xml")) {

GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:(xmlNodePtr) currNS];

if (array == nil) {

array = [NSMutableArray arrayWithObject:node];

} else {

[array addObject:node];

}

}

currNS = currNS->next;

}

}

return array;

}

- (void)setNamespaces:(NSArray *)namespaces {

if (xmlNode_ != NULL) {

[self releaseCachedValues];

// remove previous namespaces

if (xmlNode_->nsDef) {

xmlFreeNsList(xmlNode_->nsDef);

xmlNode_->nsDef = NULL;

}

// add a namespace for each object in the array

NSEnumerator *enumerator = [namespaces objectEnumerator];

GDataXMLNode *namespaceNode;

while ((namespaceNode = [enumerator nextObject]) != nil) {

xmlNsPtr ns = (xmlNsPtr) [namespaceNode XMLNode];

if (ns) {

(void)xmlNewNs(xmlNode_, ns->href, ns->prefix);

}

}

// we may need to fix this node's own name; the graft point is where

// the namespace search starts, so that points to this node too

[[self class] fixUpNamespacesForNode:xmlNode_

graftingToTreeNode:xmlNode_];

}

}

- (void)addNamespace:(GDataXMLNode *)aNamespace {

if (xmlNode_ != NULL) {

[self releaseCachedValues];

xmlNsPtr ns = (xmlNsPtr) [aNamespace XMLNode];

if (ns) {

(void)xmlNewNs(xmlNode_, ns->href, ns->prefix);

// we may need to fix this node's own name; the graft point is where

// the namespace search starts, so that points to this node too

[[self class] fixUpNamespacesForNode:xmlNode_

graftingToTreeNode:xmlNode_];

}

}

}

- (void)addChild:(GDataXMLNode *)child {

if ([child kind] == GDataXMLAttributeKind) {

[self addAttribute:child];

return;

}

if (xmlNode_ != NULL) {

[self releaseCachedValues];

xmlNodePtr childNodeCopy = [child XMLNodeCopy];

if (childNodeCopy) {

xmlNodePtr resultNode = xmlAddChild(xmlNode_, childNodeCopy);

if (resultNode == NULL) {

// failed to add

xmlFreeNode(childNodeCopy);

} else {

// added this child subtree successfully; see if it has

// previously-unresolved namespace prefixes that can now be fixed up

[[self class] fixUpNamespacesForNode:childNodeCopy

graftingToTreeNode:xmlNode_];

}

}

}

}

- (void)removeChild:(GDataXMLNode *)child {

// this is safe for attributes too

if (xmlNode_ != NULL) {

[self releaseCachedValues];

xmlNodePtr node = [child XMLNode];

xmlUnlinkNode(node);

// if the child node was borrowing its xmlNodePtr, then we need to

// explicitly free it, since there is probably no owning object that will

// free it on dealloc

if (![child shouldFreeXMLNode]) {

xmlFreeNode(node);

}

}

}

- (NSArray *)elementsForName:(NSString *)name {

NSString *desiredName = name;

if (xmlNode_ != NULL) {

NSString *prefix = [[self class] prefixForName:desiredName];

if (prefix) {

xmlChar* desiredPrefix = GDataGetXMLString(prefix);

xmlNsPtr foundNS = xmlSearchNs(xmlNode_->doc, xmlNode_, desiredPrefix);

if (foundNS) {

// we found a namespace; fall back on elementsForLocalName:URI:

// to get the elements

NSString *desiredURI = [self stringFromXMLString:(foundNS->href)];

NSString *localName = [[self class] localNameForName:desiredName];

NSArray *nsArray = [self elementsForLocalName:localName URI:desiredURI];

return nsArray;

}

}

// no namespace found for the node's prefix; try an exact match

// for the name argument, including any prefix

NSMutableArray *array = nil;

// walk our list of cached child nodes

NSArray *children = [self children];

for (GDataXMLNode *child in children) {

xmlNodePtr currNode = [child XMLNode];

// find all children which are elements with the desired name

if (currNode->type == XML_ELEMENT_NODE) {

NSString *qName = [child name];

if ([qName isEqual:name]) {

if (array == nil) {

array = [NSMutableArray arrayWithObject:child];

} else {

[array addObject:child];

}

}

}

}

return array;

}

return nil;

}

- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI {

NSMutableArray *array = nil;

if (xmlNode_ != NULL && xmlNode_->children != NULL) {

xmlChar* desiredNSHref = GDataGetXMLString(URI);

xmlChar* requestedLocalName = GDataGetXMLString(localName);

xmlChar* expectedLocalName = requestedLocalName;

// resolve the URI at the parent level, since usually children won't

// have their own namespace definitions, and we don't want to try to

// resolve it once for every child

xmlNsPtr foundParentNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, desiredNSHref);

if (foundParentNS == NULL) {

NSString *fakeQName = GDataFakeQNameForURIAndName(URI, localName);

expectedLocalName = GDataGetXMLString(fakeQName);

}

NSArray *children = [self children];

for (GDataXMLNode *child in children) {

xmlNodePtr currChildPtr = [child XMLNode];

// find all children which are elements with the desired name and

// namespace, or with the prefixed name and a null namespace

if (currChildPtr->type == XML_ELEMENT_NODE) {

// normally, we can assume the resolution done for the parent will apply

// to the child, as most children do not define their own namespaces

xmlNsPtr childLocalNS = foundParentNS;

xmlChar* childDesiredLocalName = expectedLocalName;

if (currChildPtr->nsDef != NULL) {

// this child has its own namespace definitons; do a fresh resolve

// of the namespace starting from the child, and see if it differs

// from the resolve done starting from the parent. If the resolve

// finds a different namespace, then override the desired local

// name just for this child.

childLocalNS = xmlSearchNsByHref(xmlNode_->doc, currChildPtr, desiredNSHref);

if (childLocalNS != foundParentNS) {

// this child does indeed have a different namespace resolution

// result than was found for its parent

if (childLocalNS == NULL) {

// no namespace found

NSString *fakeQName = GDataFakeQNameForURIAndName(URI, localName);

childDesiredLocalName = GDataGetXMLString(fakeQName);

} else {

// a namespace was found; use the original local name requested,

// not a faked one expected from resolving the parent

childDesiredLocalName = requestedLocalName;

}

}

}

// check if this child's namespace and local name are what we're

// seeking

if (currChildPtr->ns == childLocalNS

&& currChildPtr->name != NULL

&& xmlStrEqual(currChildPtr->name, childDesiredLocalName)) {

if (array == nil) {

array = [NSMutableArray arrayWithObject:child];

} else {

[array addObject:child];

}

}

}

}

// we return nil, not an empty array, according to docs

}

return array;

}

- (NSArray *)attributes {

if (cachedAttributes_ != nil) {

return cachedAttributes_;

}

NSMutableArray *array = nil;

if (xmlNode_ != NULL && xmlNode_->properties != NULL) {

xmlAttrPtr prop = xmlNode_->properties;

while (prop != NULL) {

GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:(xmlNodePtr) prop];

if (array == nil) {

array = [NSMutableArray arrayWithObject:node];

} else {

[array addObject:node];

}

prop = prop->next;

}

cachedAttributes_ = [array retain];

}

return array;

}

- (void)addAttribute:(GDataXMLNode *)attribute {

if (xmlNode_ != NULL) {

[self releaseCachedValues];

xmlAttrPtr attrPtr = (xmlAttrPtr) [attribute XMLNode];

if (attrPtr) {

// ignore this if an attribute with the name is already present,

// similar to NSXMLNode's addAttribute

xmlAttrPtr oldAttr;

if (attrPtr->ns == NULL) {

oldAttr = xmlHasProp(xmlNode_, attrPtr->name);

} else {

oldAttr = xmlHasNsProp(xmlNode_, attrPtr->name, attrPtr->ns->href);

}

if (oldAttr == NULL) {

xmlNsPtr newPropNS = NULL;

// if this attribute has a namespace, search for a matching namespace

// on the node we're adding to

if (attrPtr->ns != NULL) {

newPropNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, attrPtr->ns->href);

if (newPropNS == NULL) {

// make a new namespace on the parent node, and use that for the

// new attribute

newPropNS = xmlNewNs(xmlNode_, attrPtr->ns->href, attrPtr->ns->prefix);

}

}

// copy the attribute onto this node

xmlChar *value = xmlNodeGetContent((xmlNodePtr) attrPtr);

xmlAttrPtr newProp = xmlNewNsProp(xmlNode_, newPropNS, attrPtr->name, value);

if (newProp != NULL) {

// we made the property, so clean up the property's namespace

[[self class] fixUpNamespacesForNode:(xmlNodePtr)newProp

graftingToTreeNode:xmlNode_];

}

if (value != NULL) {

xmlFree(value);

}

}

}

}

}

- (GDataXMLNode *)attributeForXMLNode:(xmlAttrPtr)theXMLNode {

// search the cached attributes list for the GDataXMLNode with

// the underlying xmlAttrPtr

NSArray *attributes = [self attributes];

for (GDataXMLNode *attr in attributes) {

if (theXMLNode == (xmlAttrPtr) [attr XMLNode]) {

return attr;

}

}

return nil;

}

- (GDataXMLNode *)attributeForName:(NSString *)name {

if (xmlNode_ != NULL) {

xmlAttrPtr attrPtr = xmlHasProp(xmlNode_, GDataGetXMLString(name));

if (attrPtr == NULL) {

// can we guarantee that xmlAttrPtrs always have the ns ptr and never

// a namespace as part of the actual attribute name?

// split the name and its prefix, if any

xmlNsPtr ns = NULL;

NSString *prefix = [[self class] prefixForName:name];

if (prefix) {

// find the namespace for this prefix, and search on its URI to find

// the xmlNsPtr

name = [[self class] localNameForName:name];

ns = xmlSearchNs(xmlNode_->doc, xmlNode_, GDataGetXMLString(prefix));

}

const xmlChar* nsURI = ((ns != NULL) ? ns->href : NULL);

attrPtr = xmlHasNsProp(xmlNode_, GDataGetXMLString(name), nsURI);

}

if (attrPtr) {

GDataXMLNode *attr = [self attributeForXMLNode:attrPtr];

return attr;

}

}

return nil;

}

- (GDataXMLNode *)attributeForLocalName:(NSString *)localName

URI:(NSString *)attributeURI {

if (xmlNode_ != NULL) {

const xmlChar* name = GDataGetXMLString(localName);

const xmlChar* nsURI = GDataGetXMLString(attributeURI);

xmlAttrPtr attrPtr = xmlHasNsProp(xmlNode_, name, nsURI);

if (attrPtr == NULL) {

// if the attribute is in a tree lacking the proper namespace,

// the local name may include the full URI as a prefix

NSString *fakeQName = GDataFakeQNameForURIAndName(attributeURI, localName);

const xmlChar* xmlFakeQName = GDataGetXMLString(fakeQName);

attrPtr = xmlHasProp(xmlNode_, xmlFakeQName);

}

if (attrPtr) {

GDataXMLNode *attr = [self attributeForXMLNode:attrPtr];

return attr;

}

}

return nil;

}

- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI {

if (xmlNode_ != NULL) {

xmlChar* desiredNSHref = GDataGetXMLString(namespaceURI);

xmlNsPtr foundNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, desiredNSHref);

if (foundNS) {

// we found the namespace

if (foundNS->prefix != NULL) {

NSString *prefix = [self stringFromXMLString:(foundNS->prefix)];

return prefix;

} else {

// empty prefix is default namespace

return @"";

}

}

}

return nil;

}

#pragma mark Namespace fixup routines

+ (void)deleteNamespacePtr:(xmlNsPtr)namespaceToDelete

fromXMLNode:(xmlNodePtr)node {

// utilty routine to remove a namespace pointer from an element's

// namespace definition list. This is just removing the nsPtr

// from the singly-linked list, the node's namespace definitions.

xmlNsPtr currNS = node->nsDef;

xmlNsPtr prevNS = NULL;

while (currNS != NULL) {

xmlNsPtr nextNS = currNS->next;

if (namespaceToDelete == currNS) {

// found it; delete it from the head of the node's ns definition list

// or from the next field of the previous namespace

if (prevNS != NULL) prevNS->next = nextNS;

else node->nsDef = nextNS;

xmlFreeNs(currNS);

return;

}

prevNS = currNS;

currNS = nextNS;

}

}

+ (void)fixQualifiedNamesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode {

// Replace prefix-in-name with proper namespace pointers

//

// This is an inner routine for fixUpNamespacesForNode:

//

// see if this node's name lacks a namespace and is qualified, and if so,

// see if we can resolve the prefix against the parent

//

// The prefix may either be normal, "gd:foo", or a URI

// "{http://blah.com/}:foo"

if (nodeToFix->ns == NULL) {

xmlNsPtr foundNS = NULL;

xmlChar* prefix = NULL;

xmlChar* localName = SplitQNameReverse(nodeToFix->name, &prefix);

if (localName != NULL) {

if (prefix != NULL) {

// if the prefix is wrapped by { and } then it's a URI

int prefixLen = xmlStrlen(prefix);

if (prefixLen > 2

&& prefix[0] == '{'

&& prefix[prefixLen - 1] == '}') {

// search for the namespace by URI

xmlChar* uri = xmlStrsub(prefix, 1, prefixLen - 2);

if (uri != NULL) {

foundNS = xmlSearchNsByHref(graftPointNode->doc, graftPointNode, uri);

xmlFree(uri);

}

}

}

if (foundNS == NULL) {

// search for the namespace by prefix, even if the prefix is nil

// (nil prefix means to search for the default namespace)

foundNS = xmlSearchNs(graftPointNode->doc, graftPointNode, prefix);

}

if (foundNS != NULL) {

// we found a namespace, so fix the ns pointer and the local name

xmlSetNs(nodeToFix, foundNS);

xmlNodeSetName(nodeToFix, localName);

}

if (prefix != NULL) {

xmlFree(prefix);

prefix = NULL;

}

xmlFree(localName);

}

}

}

+ (void)fixDuplicateNamespacesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode

namespaceSubstitutionMap:(NSMutableDictionary *)nsMap {

// Duplicate namespace removal

//

// This is an inner routine for fixUpNamespacesForNode:

//

// If any of this node's namespaces are already defined at the graft point

// level, add that namespace to the map of namespace substitutions

// so it will be replaced in the children below the nodeToFix, and

// delete the namespace record

if (nodeToFix->type == XML_ELEMENT_NODE) {

// step through the namespaces defined on this node

xmlNsPtr definedNS = nodeToFix->nsDef;

while (definedNS != NULL) {

// see if this namespace is already defined higher in the tree,

// with both the same URI and the same prefix; if so, add a mapping for

// it

xmlNsPtr foundNS = xmlSearchNsByHref(graftPointNode->doc, graftPointNode,

definedNS->href);

if (foundNS != NULL

&& foundNS != definedNS

&& xmlStrEqual(definedNS->prefix, foundNS->prefix)) {

// store a mapping from this defined nsPtr to the one found higher

// in the tree

[nsMap setObject:[NSValue valueWithPointer:foundNS]

forKey:[NSValue valueWithPointer:definedNS]];

// remove this namespace from the ns definition list of this node;

// all child elements and attributes referencing this namespace

// now have a dangling pointer and must be updated (that is done later

// in this method)

//

// before we delete this namespace, move our pointer to the

// next one

xmlNsPtr nsToDelete = definedNS;

definedNS = definedNS->next;

[self deleteNamespacePtr:nsToDelete fromXMLNode:nodeToFix];

} else {

// this namespace wasn't a duplicate; move to the next

definedNS = definedNS->next;

}

}

}

// if this node's namespace is one we deleted, update it to point

// to someplace better

if (nodeToFix->ns != NULL) {

NSValue *currNS = [NSValue valueWithPointer:nodeToFix->ns];

NSValue *replacementNS = [nsMap objectForKey:currNS];

if (replacementNS != nil) {

xmlNsPtr replaceNSPtr = (xmlNsPtr)[replacementNS pointerValue];

xmlSetNs(nodeToFix, replaceNSPtr);

}

}

}

+ (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode

namespaceSubstitutionMap:(NSMutableDictionary *)nsMap {

// This is the inner routine for fixUpNamespacesForNode:graftingToTreeNode:

//

// This routine fixes two issues:

//

// Because we can create nodes with qualified names before adding

// them to the tree that declares the namespace for the prefix,

// we need to set the node namespaces after adding them to the tree.

//

// Because libxml adds namespaces to nodes when it copies them,

// we want to remove redundant namespaces after adding them to

// a tree.

//

// If only the Mac's libxml had xmlDOMWrapReconcileNamespaces, it could do

// namespace cleanup for us

// We only care about fixing names of elements and attributes

if (nodeToFix->type != XML_ELEMENT_NODE

&& nodeToFix->type != XML_ATTRIBUTE_NODE) return;

// Do the fixes

[self fixQualifiedNamesForNode:nodeToFix

graftingToTreeNode:graftPointNode];

[self fixDuplicateNamespacesForNode:nodeToFix

graftingToTreeNode:graftPointNode

namespaceSubstitutionMap:nsMap];

if (nodeToFix->type == XML_ELEMENT_NODE) {

// when fixing element nodes, recurse for each child element and

// for each attribute

xmlNodePtr currChild = nodeToFix->children;

while (currChild != NULL) {

[self fixUpNamespacesForNode:currChild

graftingToTreeNode:graftPointNode

namespaceSubstitutionMap:nsMap];

currChild = currChild->next;

}

xmlAttrPtr currProp = nodeToFix->properties;

while (currProp != NULL) {

[self fixUpNamespacesForNode:(xmlNodePtr)currProp

graftingToTreeNode:graftPointNode

namespaceSubstitutionMap:nsMap];

currProp = currProp->next;

}

}

}

+ (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode {

// allocate the namespace map that will be passed

// down on recursive calls

NSMutableDictionary *nsMap = [NSMutableDictionary dictionary];

[self fixUpNamespacesForNode:nodeToFix

graftingToTreeNode:graftPointNode

namespaceSubstitutionMap:nsMap];

}

@end

@interface GDataXMLDocument (PrivateMethods)

- (void)addStringsCacheToDoc;

@end

@implementation GDataXMLDocument

- (id)initWithXMLString:(NSString *)str options:(unsigned int)mask error:(NSError **)error {

NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];

GDataXMLDocument *doc = [self initWithData:data options:mask error:error];

return doc;

}

- (id)initWithData:(NSData *)data options:(unsigned int)mask error:(NSError **)error {

self = [super init];

if (self) {

const char *baseURL = NULL;

const char *encoding = NULL;

// NOTE: We are assuming [data length] fits into an int.

xmlDoc_ = xmlReadMemory((const char*)[data bytes], (int)[data length], baseURL, encoding,

kGDataXMLParseOptions); // TODO(grobbins) map option values

if (xmlDoc_ == NULL) {

if (error) {

*error = [NSError errorWithDomain:@"com.google.GDataXML"

code:-1

userInfo:nil];

// TODO(grobbins) use xmlSetGenericErrorFunc to capture error

}

[self release];

return nil;

} else {

if (error) *error = NULL;

[self addStringsCacheToDoc];

}

}

return self;

}

- (id)initWithRootElement:(GDataXMLElement *)element {

self = [super init];

if (self) {

xmlDoc_ = xmlNewDoc(NULL);

(void) xmlDocSetRootElement(xmlDoc_, [element XMLNodeCopy]);

[self addStringsCacheToDoc];

}

return self;

}

- (void)addStringsCacheToDoc {

// utility routine for init methods

#if DEBUG

NSCAssert(xmlDoc_ != NULL && xmlDoc_->_private == NULL,

@"GDataXMLDocument cache creation problem");

#endif

// add a strings cache as private data for the document

//

// we'll use plain C pointers (xmlChar*) as the keys, and NSStrings

// as the values

CFIndex capacity = 0; // no limit

CFDictionaryKeyCallBacks keyCallBacks = {

0, // version

StringCacheKeyRetainCallBack,

StringCacheKeyReleaseCallBack,

StringCacheKeyCopyDescriptionCallBack,

StringCacheKeyEqualCallBack,

StringCacheKeyHashCallBack

};

CFMutableDictionaryRef dict = CFDictionaryCreateMutable(

kCFAllocatorDefault, capacity,

&keyCallBacks, &kCFTypeDictionaryValueCallBacks);

// we'll use the user-defined _private field for our cache

xmlDoc_->_private = dict;

}

- (NSString *)description {

return [NSString stringWithFormat:@"%@ %p", [self class], self];

}

- (void)dealloc {

if (xmlDoc_ != NULL) {

// release the strings cache

//

// since it's a CF object, were anyone to use this in a GC environment,

// this would need to be released in a finalize method, too

if (xmlDoc_->_private != NULL) {

CFRelease(xmlDoc_->_private);

}

xmlFreeDoc(xmlDoc_);

}

[super dealloc];

}

#pragma mark -

- (GDataXMLElement *)rootElement {

GDataXMLElement *element = nil;

if (xmlDoc_ != NULL) {

xmlNodePtr rootNode = xmlDocGetRootElement(xmlDoc_);

if (rootNode) {

element = [GDataXMLElement nodeBorrowingXMLNode:rootNode];

}

}

return element;

}

- (NSData *)XMLData {

if (xmlDoc_ != NULL) {

xmlChar *buffer = NULL;

int bufferSize = 0;

xmlDocDumpMemory(xmlDoc_, &buffer, &bufferSize);

if (buffer) {

NSData *data = [NSData dataWithBytes:buffer

length:bufferSize];

xmlFree(buffer);

return data;

}

}

return nil;

}

- (void)setVersion:(NSString *)version {

if (xmlDoc_ != NULL) {

if (xmlDoc_->version != NULL) {

// version is a const char* so we must cast

xmlFree((char *) xmlDoc_->version);

xmlDoc_->version = NULL;

}

if (version != nil) {

xmlDoc_->version = xmlStrdup(GDataGetXMLString(version));

}

}

}

- (void)setCharacterEncoding:(NSString *)encoding {

if (xmlDoc_ != NULL) {

if (xmlDoc_->encoding != NULL) {

// version is a const char* so we must cast

xmlFree((char *) xmlDoc_->encoding);

xmlDoc_->encoding = NULL;

}

if (encoding != nil) {

xmlDoc_->encoding = xmlStrdup(GDataGetXMLString(encoding));

}

}

}

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error {

return [self nodesForXPath:xpath namespaces:nil error:error];

}

- (NSArray *)nodesForXPath:(NSString *)xpath

namespaces:(NSDictionary *)namespaces

error:(NSError **)error {

if (xmlDoc_ != NULL) {

GDataXMLNode *docNode = [GDataXMLElement nodeBorrowingXMLNode:(xmlNodePtr)xmlDoc_];

NSArray *array = [docNode nodesForXPath:xpath

namespaces:namespaces

error:error];

return array;

}

return nil;

}

@end

//

// Dictionary key callbacks for our C-string to NSString cache dictionary

//

static const void *StringCacheKeyRetainCallBack(CFAllocatorRef allocator, const void *str) {

// copy the key

xmlChar* key = xmlStrdup(str);

return key;

}

static void StringCacheKeyReleaseCallBack(CFAllocatorRef allocator, const void *str) {

// free the key

char *chars = (char *)str;

xmlFree((char *) chars);

}

static CFStringRef StringCacheKeyCopyDescriptionCallBack(const void *str) {

// make a CFString from the key

CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorDefault,

(const char *)str,

kCFStringEncodingUTF8);

return cfStr;

}

static Boolean StringCacheKeyEqualCallBack(const void *str1, const void *str2) {

// compare the key strings

if (str1 == str2) return true;

int result = xmlStrcmp(str1, str2);

return (result == 0);

}

static CFHashCode StringCacheKeyHashCallBack(const void *str) {

// dhb hash, per http://www.cse.yorku.ca/~oz/hash.html

CFHashCode hash = 5381;

int c;

const char *chars = (const char *)str;

while ((c = *chars++) != 0) {

hash = ((hash << 5) + hash) + c;

}

return hash;

}

/* Copyright (c) 2008 Google Inc.

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

* http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

#define GDATAXMLNODE_DEFINE_GLOBALS 1

#import "GDataXMLNode.h"

@class NSArray, NSDictionary, NSError, NSString, NSURL;

@class GDataXMLElement, GDataXMLDocument;

static const int kGDataXMLParseOptions = (XML_PARSE_NOCDATA | XML_PARSE_NOBLANKS);

// dictionary key callbacks for string cache

static const void *StringCacheKeyRetainCallBack(CFAllocatorRef allocator, const void *str);

static void StringCacheKeyReleaseCallBack(CFAllocatorRef allocator, const void *str);

static CFStringRef StringCacheKeyCopyDescriptionCallBack(const void *str);

static Boolean StringCacheKeyEqualCallBack(const void *str1, const void *str2);

static CFHashCode StringCacheKeyHashCallBack(const void *str);

// isEqual: has the fatal flaw that it doesn't deal well with the received

// being nil. We'll use this utility instead.

// Static copy of AreEqualOrBothNil from GDataObject.m, so that using

// GDataXMLNode does not require pulling in all of GData.

static BOOL AreEqualOrBothNilPrivate(id obj1, id obj2) {

if (obj1 == obj2) {

return YES;

}

if (obj1 && obj2) {

return [obj1 isEqual:obj2];

}

return NO;

}

// convert NSString* to xmlChar*

//

// the "Get" part implies that ownership remains with str

static xmlChar* GDataGetXMLString(NSString *str) {

xmlChar* result = (xmlChar *)[str UTF8String];

return result;

}

// Make a fake qualified name we use as local name internally in libxml

// data structures when there's no actual namespace node available to point to

// from an element or attribute node

//

// Returns an autoreleased NSString*

static NSString *GDataFakeQNameForURIAndName(NSString *theURI, NSString *name) {

NSString *localName = [GDataXMLNode localNameForName:name];

NSString *fakeQName = [NSString stringWithFormat:@"{%@}:%@",

theURI, localName];

return fakeQName;

}

// libxml2 offers xmlSplitQName2, but that searches forwards. Since we may

// be searching for a whole URI shoved in as a prefix, like

// {http://foo}:name

// we'll search for the prefix in backwards from the end of the qualified name

//

// returns a copy of qname as the local name if there's no prefix

static xmlChar *SplitQNameReverse(const xmlChar *qname, xmlChar **prefix) {

// search backwards for a colon

int qnameLen = xmlStrlen(qname);

for (int idx = qnameLen - 1; idx >= 0; idx--) {

if (qname[idx] == ':') {

// found the prefix; copy the prefix, if requested

if (prefix != NULL) {

if (idx > 0) {

*prefix = xmlStrsub(qname, 0, idx);

} else {

*prefix = NULL;

}

}

if (idx < qnameLen - 1) {

// return a copy of the local name

xmlChar *localName = xmlStrsub(qname, idx + 1, qnameLen - idx - 1);

return localName;

} else {

return NULL;

}

}

}

// no colon found, so the qualified name is the local name

xmlChar *qnameCopy = xmlStrdup(qname);

return qnameCopy;

}

@interface GDataXMLNode (PrivateMethods)

// consuming a node implies it will later be freed when the instance is

// dealloc'd; borrowing it implies that ownership and disposal remain the

// job of the supplier of the node

+ (id)nodeConsumingXMLNode:(xmlNodePtr)theXMLNode;

- (id)initConsumingXMLNode:(xmlNodePtr)theXMLNode;

+ (id)nodeBorrowingXMLNode:(xmlNodePtr)theXMLNode;

- (id)initBorrowingXMLNode:(xmlNodePtr)theXMLNode;

// getters of the underlying node

- (xmlNodePtr)XMLNode;

- (xmlNodePtr)XMLNodeCopy;

// search for an underlying attribute

- (GDataXMLNode *)attributeForXMLNode:(xmlAttrPtr)theXMLNode;

// return an NSString for an xmlChar*, using our strings cache in the

// document

- (NSString *)stringFromXMLString:(const xmlChar *)chars;

// setter/getter of the dealloc flag for the underlying node

- (BOOL)shouldFreeXMLNode;

- (void)setShouldFreeXMLNode:(BOOL)flag;

@end

@interface GDataXMLElement (PrivateMethods)

+ (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode;

@end

@implementation GDataXMLNode

+ (void)load {

xmlInitParser();

}

// Note on convenience methods for making stand-alone element and

// attribute nodes:

//

// Since we're making a node from scratch, we don't

// have any namespace info. So the namespace prefix, if

// any, will just be slammed into the node name.

// We'll rely on the -addChild method below to remove

// the namespace prefix and replace it with a proper ns

// pointer.

+ (GDataXMLElement *)elementWithName:(NSString *)name {

xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace

GDataGetXMLString(name));

if (theNewNode) {

// succeeded

return [self nodeConsumingXMLNode:theNewNode];

}

return nil;

}

+ (GDataXMLElement *)elementWithName:(NSString *)name stringValue:(NSString *)value {

xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace

GDataGetXMLString(name));

if (theNewNode) {

xmlNodePtr textNode = xmlNewText(GDataGetXMLString(value));

if (textNode) {

xmlNodePtr temp = xmlAddChild(theNewNode, textNode);

if (temp) {

// succeeded

return [self nodeConsumingXMLNode:theNewNode];

}

}

// failed; free the node and any children

xmlFreeNode(theNewNode);

}

return nil;

}

+ (GDataXMLElement *)elementWithName:(NSString *)name URI:(NSString *)theURI {

// since we don't know a prefix yet, shove in the whole URI; we'll look for

// a proper namespace ptr later when addChild calls fixUpNamespacesForNode

NSString *fakeQName = GDataFakeQNameForURIAndName(theURI, name);

xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace

GDataGetXMLString(fakeQName));

if (theNewNode) {

return [self nodeConsumingXMLNode:theNewNode];

}

return nil;

}

+ (id)attributeWithName:(NSString *)name stringValue:(NSString *)value {

xmlChar *xmlName = GDataGetXMLString(name);

xmlChar *xmlValue = GDataGetXMLString(value);

xmlAttrPtr theNewAttr = xmlNewProp(NULL, // parent node for the attr

xmlName, xmlValue);

if (theNewAttr) {

return [self nodeConsumingXMLNode:(xmlNodePtr) theNewAttr];

}

return nil;

}

+ (id)attributeWithName:(NSString *)name URI:(NSString *)attributeURI stringValue:(NSString *)value {

// since we don't know a prefix yet, shove in the whole URI; we'll look for

// a proper namespace ptr later when addChild calls fixUpNamespacesForNode

NSString *fakeQName = GDataFakeQNameForURIAndName(attributeURI, name);

xmlChar *xmlName = GDataGetXMLString(fakeQName);

xmlChar *xmlValue = GDataGetXMLString(value);

xmlAttrPtr theNewAttr = xmlNewProp(NULL, // parent node for the attr

xmlName, xmlValue);

if (theNewAttr) {

return [self nodeConsumingXMLNode:(xmlNodePtr) theNewAttr];

}

return nil;

}

+ (id)textWithStringValue:(NSString *)value {

xmlNodePtr theNewText = xmlNewText(GDataGetXMLString(value));

if (theNewText) {

return [self nodeConsumingXMLNode:theNewText];

}

return nil;

}

+ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)value {

xmlChar *href = GDataGetXMLString(value);

xmlChar *prefix;

if ([name length] > 0) {

prefix = GDataGetXMLString(name);

} else {

// default namespace is represented by a nil prefix

prefix = nil;

}

xmlNsPtr theNewNs = xmlNewNs(NULL, // parent node

href, prefix);

if (theNewNs) {

return [self nodeConsumingXMLNode:(xmlNodePtr) theNewNs];

}

return nil;

}

+ (id)nodeConsumingXMLNode:(xmlNodePtr)theXMLNode {

Class theClass;

if (theXMLNode->type == XML_ELEMENT_NODE) {

theClass = [GDataXMLElement class];

} else {

theClass = [GDataXMLNode class];

}

return [[[theClass alloc] initConsumingXMLNode:theXMLNode] autorelease];

}

- (id)initConsumingXMLNode:(xmlNodePtr)theXMLNode {

self = [super init];

if (self) {

xmlNode_ = theXMLNode;

shouldFreeXMLNode_ = YES;

}

return self;

}

+ (id)nodeBorrowingXMLNode:(xmlNodePtr)theXMLNode {

Class theClass;

if (theXMLNode->type == XML_ELEMENT_NODE) {

theClass = [GDataXMLElement class];

} else {

theClass = [GDataXMLNode class];

}

return [[[theClass alloc] initBorrowingXMLNode:theXMLNode] autorelease];

}

- (id)initBorrowingXMLNode:(xmlNodePtr)theXMLNode {

self = [super init];

if (self) {

xmlNode_ = theXMLNode;

shouldFreeXMLNode_ = NO;

}

return self;

}

- (void)releaseCachedValues {

[cachedName_ release];

cachedName_ = nil;

[cachedChildren_ release];

cachedChildren_ = nil;

[cachedAttributes_ release];

cachedAttributes_ = nil;

}

// convert xmlChar* to NSString*

//

// returns an autoreleased NSString*, from the current node's document strings

// cache if possible

- (NSString *)stringFromXMLString:(const xmlChar *)chars {

#if DEBUG

NSCAssert(chars != NULL, @"GDataXMLNode sees an unexpected empty string");

#endif

if (chars == NULL) return nil;

CFMutableDictionaryRef cacheDict = NULL;

NSString *result = nil;

if (xmlNode_ != NULL

&& (xmlNode_->type == XML_ELEMENT_NODE

|| xmlNode_->type == XML_ATTRIBUTE_NODE

|| xmlNode_->type == XML_TEXT_NODE)) {

// there is no xmlDocPtr in XML_NAMESPACE_DECL nodes,

// so we can't cache the text of those

// look for a strings cache in the document

//

// the cache is in the document's user-defined _private field

if (xmlNode_->doc != NULL) {

cacheDict = xmlNode_->doc->_private;

if (cacheDict) {

// this document has a strings cache

result = (NSString *) CFDictionaryGetValue(cacheDict, chars);

if (result) {

// we found the xmlChar string in the cache; return the previously

// allocated NSString, rather than allocate a new one

return result;

}

}

}

}

// allocate a new NSString for this xmlChar*

result = [NSString stringWithUTF8String:(const char *) chars];

if (cacheDict) {

// save the string in the document's string cache

CFDictionarySetValue(cacheDict, chars, result);

}

return result;

}

- (void)dealloc {

if (xmlNode_ && shouldFreeXMLNode_) {

xmlFreeNode(xmlNode_);

xmlNode_ = NULL;

}

[self releaseCachedValues];

[super dealloc];

}

#pragma mark -

- (void)setStringValue:(NSString *)str {

if (xmlNode_ != NULL && str != nil) {

if (xmlNode_->type == XML_NAMESPACE_DECL) {

// for a namespace node, the value is the namespace URI

xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;

if (nsNode->href != NULL) xmlFree((char *)nsNode->href);

nsNode->href = xmlStrdup(GDataGetXMLString(str));

} else {

// attribute or element node

// do we need to call xmlEncodeSpecialChars?

xmlNodeSetContent(xmlNode_, GDataGetXMLString(str));

}

}

}

- (NSString *)stringValue {

NSString *str = nil;

if (xmlNode_ != NULL) {

if (xmlNode_->type == XML_NAMESPACE_DECL) {

// for a namespace node, the value is the namespace URI

xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;

str = [self stringFromXMLString:(nsNode->href)];

} else {

// attribute or element node

xmlChar* chars = xmlNodeGetContent(xmlNode_);

if (chars) {

str = [self stringFromXMLString:chars];

xmlFree(chars);

}

}

}

return str;

}

- (NSString *)XMLString {

NSString *str = nil;

if (xmlNode_ != NULL) {

xmlBufferPtr buff = xmlBufferCreate();

if (buff) {

xmlDocPtr doc = NULL;

int level = 0;

int format = 0;

int result = xmlNodeDump(buff, doc, xmlNode_, level, format);

if (result > -1) {

str = [[[NSString alloc] initWithBytes:(xmlBufferContent(buff))

length:(xmlBufferLength(buff))

encoding:NSUTF8StringEncoding] autorelease];

}

xmlBufferFree(buff);

}

}

// remove leading and trailing whitespace

NSCharacterSet *ws = [NSCharacterSet whitespaceAndNewlineCharacterSet];

NSString *trimmed = [str stringByTrimmingCharactersInSet:ws];

return trimmed;

}

- (NSString *)localName {

NSString *str = nil;

if (xmlNode_ != NULL) {

str = [self stringFromXMLString:(xmlNode_->name)];

// if this is part of a detached subtree, str may have a prefix in it

str = [[self class] localNameForName:str];

}

return str;

}

- (NSString *)prefix {

NSString *str = nil;

if (xmlNode_ != NULL) {

// the default namespace's prefix is an empty string, though libxml

// represents it as NULL for ns->prefix

str = @"";

if (xmlNode_->ns != NULL && xmlNode_->ns->prefix != NULL) {

str = [self stringFromXMLString:(xmlNode_->ns->prefix)];

}

}

return str;

}

- (NSString *)URI {

NSString *str = nil;

if (xmlNode_ != NULL) {

if (xmlNode_->ns != NULL && xmlNode_->ns->href != NULL) {

str = [self stringFromXMLString:(xmlNode_->ns->href)];

}

}

return str;

}

- (NSString *)qualifiedName {

// internal utility

NSString *str = nil;

if (xmlNode_ != NULL) {

if (xmlNode_->type == XML_NAMESPACE_DECL) {

// name of a namespace node

xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;

// null is the default namespace; one is the loneliest number

if (nsNode->prefix == NULL) {

str = @"";

}

else {

str = [self stringFromXMLString:(nsNode->prefix)];

}

} else if (xmlNode_->ns != NULL && xmlNode_->ns->prefix != NULL) {

// name of a non-namespace node

// has a prefix

char *qname;

if (asprintf(&qname, "%s:%s", (const char *)xmlNode_->ns->prefix,

xmlNode_->name) != -1) {

str = [self stringFromXMLString:(const xmlChar *)qname];

free(qname);

}

} else {

// lacks a prefix

str = [self stringFromXMLString:(xmlNode_->name)];

}

}

return str;

}

- (NSString *)name {

if (cachedName_ != nil) {

return cachedName_;

}

NSString *str = [self qualifiedName];

cachedName_ = [str retain];

return str;

}

+ (NSString *)localNameForName:(NSString *)name {

if (name != nil) {

NSRange range = [name rangeOfString:@":"];

if (range.location != NSNotFound) {

// found a colon

if (range.location + 1 < [name length]) {

NSString *localName = [name substringFromIndex:(range.location + 1)];

return localName;

}

}

}

return name;

}

+ (NSString *)prefixForName:(NSString *)name {

if (name != nil) {

NSRange range = [name rangeOfString:@":"];

if (range.location != NSNotFound) {

NSString *prefix = [name substringToIndex:(range.location)];

return prefix;

}

}

return nil;

}

- (NSUInteger)childCount {

if (cachedChildren_ != nil) {

return [cachedChildren_ count];

}

if (xmlNode_ != NULL) {

unsigned int count = 0;

xmlNodePtr currChild = xmlNode_->children;

while (currChild != NULL) {

++count;

currChild = currChild->next;

}

return count;

}

return 0;

}

- (NSArray *)children {

if (cachedChildren_ != nil) {

return cachedChildren_;

}

NSMutableArray *array = nil;

if (xmlNode_ != NULL) {

xmlNodePtr currChild = xmlNode_->children;

while (currChild != NULL) {

GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:currChild];

if (array == nil) {

array = [NSMutableArray arrayWithObject:node];

} else {

[array addObject:node];

}

currChild = currChild->next;

}

cachedChildren_ = [array retain];

}

return array;

}

- (GDataXMLNode *)childAtIndex:(unsigned)index {

NSArray *children = [self children];

if ([children count] > index) {

return [children objectAtIndex:index];

}

return nil;

}

- (GDataXMLNodeKind)kind {

if (xmlNode_ != NULL) {

xmlElementType nodeType = xmlNode_->type;

switch (nodeType) {

case XML_ELEMENT_NODE: return GDataXMLElementKind;

case XML_ATTRIBUTE_NODE: return GDataXMLAttributeKind;

case XML_TEXT_NODE: return GDataXMLTextKind;

case XML_CDATA_SECTION_NODE: return GDataXMLTextKind;

case XML_ENTITY_REF_NODE: return GDataXMLEntityDeclarationKind;

case XML_ENTITY_NODE: return GDataXMLEntityDeclarationKind;

case XML_PI_NODE: return GDataXMLProcessingInstructionKind;

case XML_COMMENT_NODE: return GDataXMLCommentKind;

case XML_DOCUMENT_NODE: return GDataXMLDocumentKind;

case XML_DOCUMENT_TYPE_NODE: return GDataXMLDocumentKind;

case XML_DOCUMENT_FRAG_NODE: return GDataXMLDocumentKind;

case XML_NOTATION_NODE: return GDataXMLNotationDeclarationKind;

case XML_HTML_DOCUMENT_NODE: return GDataXMLDocumentKind;

case XML_DTD_NODE: return GDataXMLDTDKind;

case XML_ELEMENT_DECL: return GDataXMLElementDeclarationKind;

case XML_ATTRIBUTE_DECL: return GDataXMLAttributeDeclarationKind;

case XML_ENTITY_DECL: return GDataXMLEntityDeclarationKind;

case XML_NAMESPACE_DECL: return GDataXMLNamespaceKind;

case XML_XINCLUDE_START: return GDataXMLProcessingInstructionKind;

case XML_XINCLUDE_END: return GDataXMLProcessingInstructionKind;

case XML_DOCB_DOCUMENT_NODE: return GDataXMLDocumentKind;

}

}

return GDataXMLInvalidKind;

}

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error {

// call through with no explicit namespace dictionary; that will register the

// root node's namespaces

return [self nodesForXPath:xpath namespaces:nil error:error];

}

- (NSArray *)nodesForXPath:(NSString *)xpath

namespaces:(NSDictionary *)namespaces

error:(NSError **)error {

NSMutableArray *array = nil;

NSInteger errorCode = -1;

NSDictionary *errorInfo = nil;

// xmlXPathNewContext requires a doc for its context, but if our elements

// are created from GDataXMLElement's initWithXMLString there may not be

// a document. (We may later decide that we want to stuff the doc used

// there into a GDataXMLDocument and retain it, but we don't do that now.)

//

// We'll temporarily make a document to use for the xpath context.

xmlDocPtr tempDoc = NULL;

xmlNodePtr topParent = NULL;

if (xmlNode_->doc == NULL) {

tempDoc = xmlNewDoc(NULL);

if (tempDoc) {

// find the topmost node of the current tree to make the root of

// our temporary document

topParent = xmlNode_;

while (topParent->parent != NULL) {

topParent = topParent->parent;

}

xmlDocSetRootElement(tempDoc, topParent);

}

}

if (xmlNode_ != NULL && xmlNode_->doc != NULL) {

xmlXPathContextPtr xpathCtx = xmlXPathNewContext(xmlNode_->doc);

if (xpathCtx) {

// anchor at our current node

xpathCtx->node = xmlNode_;

// if a namespace dictionary was provided, register its contents

if (namespaces) {

// the dictionary keys are prefixes; the values are URIs

for (NSString *prefix in namespaces) {

NSString *uri = [namespaces objectForKey:prefix];

xmlChar *prefixChars = (xmlChar *) [prefix UTF8String];

xmlChar *uriChars = (xmlChar *) [uri UTF8String];

int result = xmlXPathRegisterNs(xpathCtx, prefixChars, uriChars);

if (result != 0) {

#if DEBUG

NSCAssert1(result == 0, @"GDataXMLNode XPath namespace %@ issue",

prefix);

#endif

}

}

} else {

// no namespace dictionary was provided

//

// register the namespaces of this node, if it's an element, or of

// this node's root element, if it's a document

xmlNodePtr nsNodePtr = xmlNode_;

if (xmlNode_->type == XML_DOCUMENT_NODE) {

nsNodePtr = xmlDocGetRootElement((xmlDocPtr) xmlNode_);

}

// step through the namespaces, if any, and register each with the

// xpath context

if (nsNodePtr != NULL) {

for (xmlNsPtr nsPtr = nsNodePtr->ns; nsPtr != NULL; nsPtr = nsPtr->next) {

// default namespace is nil in the tree, but there's no way to

// register a default namespace, so we'll register a fake one,

// _def_ns

const xmlChar* prefix = nsPtr->prefix;

if (prefix == NULL) {

prefix = (xmlChar*) kGDataXMLXPathDefaultNamespacePrefix;

}

int result = xmlXPathRegisterNs(xpathCtx, prefix, nsPtr->href);

if (result != 0) {

#if DEBUG

NSCAssert1(result == 0, @"GDataXMLNode XPath namespace %@ issue",

prefix);

#endif

}

}

}

}

// now evaluate the path

xmlXPathObjectPtr xpathObj;

xpathObj = xmlXPathEval(GDataGetXMLString(xpath), xpathCtx);

if (xpathObj) {

// we have some result from the search

array = [NSMutableArray array];

xmlNodeSetPtr nodeSet = xpathObj->nodesetval;

if (nodeSet) {

// add each node in the result set to our array

for (int index = 0; index < nodeSet->nodeNr; index++) {

xmlNodePtr currNode = nodeSet->nodeTab[index];

GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:currNode];

if (node) {

[array addObject:node];

}

}

}

xmlXPathFreeObject(xpathObj);

} else {

// provide an error for failed evaluation

const char *msg = xpathCtx->lastError.str1;

errorCode = xpathCtx->lastError.code;

if (msg) {

NSString *errStr = [NSString stringWithUTF8String:msg];

errorInfo = [NSDictionary dictionaryWithObject:errStr

forKey:@"error"];

}

}

xmlXPathFreeContext(xpathCtx);

}

} else {

// not a valid node for using XPath

errorInfo = [NSDictionary dictionaryWithObject:@"invalid node"

forKey:@"error"];

}

if (array == nil && error != nil) {

*error = [NSError errorWithDomain:@"com.google.GDataXML"

code:errorCode

userInfo:errorInfo];

}

if (tempDoc != NULL) {

xmlUnlinkNode(topParent);

xmlSetTreeDoc(topParent, NULL);

xmlFreeDoc(tempDoc);

}

return array;

}

- (NSString *)description {

int nodeType = (xmlNode_ ? (int)xmlNode_->type : -1);

return [NSString stringWithFormat:@"%@ %p: {type:%d name:%@ xml:\"%@\"}",

[self class], self, nodeType, [self name], [self XMLString]];

}

- (id)copyWithZone:(NSZone *)zone {

xmlNodePtr nodeCopy = [self XMLNodeCopy];

if (nodeCopy != NULL) {

return [[[self class] alloc] initConsumingXMLNode:nodeCopy];

}

return nil;

}

- (BOOL)isEqual:(GDataXMLNode *)other {

if (self == other) return YES;

if (![other isKindOfClass:[GDataXMLNode class]]) return NO;

return [self XMLNode] == [other XMLNode]

|| ([self kind] == [other kind]

&& AreEqualOrBothNilPrivate([self name], [other name])

&& [[self children] count] == [[other children] count]);

}

- (NSUInteger)hash {

return (NSUInteger) (void *) [GDataXMLNode class];

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {

return [super methodSignatureForSelector:selector];

}

#pragma mark -

- (xmlNodePtr)XMLNodeCopy {

if (xmlNode_ != NULL) {

// Note: libxml will create a new copy of namespace nodes (xmlNs records)

// and attach them to this copy in order to keep namespaces within this

// node subtree copy value.

xmlNodePtr nodeCopy = xmlCopyNode(xmlNode_, 1); // 1 = recursive

return nodeCopy;

}

return NULL;

}

- (xmlNodePtr)XMLNode {

return xmlNode_;

}

- (BOOL)shouldFreeXMLNode {

return shouldFreeXMLNode_;

}

- (void)setShouldFreeXMLNode:(BOOL)flag {

shouldFreeXMLNode_ = flag;

}

@end

@implementation GDataXMLElement

- (id)initWithXMLString:(NSString *)str error:(NSError **)error {

self = [super init];

if (self) {

const char *utf8Str = [str UTF8String];

// NOTE: We are assuming a string length that fits into an int

xmlDocPtr doc = xmlReadMemory(utf8Str, (int)strlen(utf8Str), NULL, // URL

NULL, // encoding

kGDataXMLParseOptions);

if (doc == NULL) {

if (error) {

// TODO(grobbins) use xmlSetGenericErrorFunc to capture error

}

} else {

// copy the root node from the doc

xmlNodePtr root = xmlDocGetRootElement(doc);

if (root) {

xmlNode_ = xmlCopyNode(root, 1); // 1: recursive

shouldFreeXMLNode_ = YES;

}

xmlFreeDoc(doc);

}

if (xmlNode_ == NULL) {

// failure

if (error) {

*error = [NSError errorWithDomain:@"com.google.GDataXML"

code:-1

userInfo:nil];

}

[self release];

return nil;

}

}

return self;

}

- (NSArray *)namespaces {

NSMutableArray *array = nil;

if (xmlNode_ != NULL && xmlNode_->nsDef != NULL) {

xmlNsPtr currNS = xmlNode_->nsDef;

while (currNS != NULL) {

// add this prefix/URI to the list, unless it's the implicit xml prefix

if (!xmlStrEqual(currNS->prefix, (const xmlChar *) "xml")) {

GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:(xmlNodePtr) currNS];

if (array == nil) {

array = [NSMutableArray arrayWithObject:node];

} else {

[array addObject:node];

}

}

currNS = currNS->next;

}

}

return array;

}

- (void)setNamespaces:(NSArray *)namespaces {

if (xmlNode_ != NULL) {

[self releaseCachedValues];

// remove previous namespaces

if (xmlNode_->nsDef) {

xmlFreeNsList(xmlNode_->nsDef);

xmlNode_->nsDef = NULL;

}

// add a namespace for each object in the array

NSEnumerator *enumerator = [namespaces objectEnumerator];

GDataXMLNode *namespaceNode;

while ((namespaceNode = [enumerator nextObject]) != nil) {

xmlNsPtr ns = (xmlNsPtr) [namespaceNode XMLNode];

if (ns) {

(void)xmlNewNs(xmlNode_, ns->href, ns->prefix);

}

}

// we may need to fix this node's own name; the graft point is where

// the namespace search starts, so that points to this node too

[[self class] fixUpNamespacesForNode:xmlNode_

graftingToTreeNode:xmlNode_];

}

}

- (void)addNamespace:(GDataXMLNode *)aNamespace {

if (xmlNode_ != NULL) {

[self releaseCachedValues];

xmlNsPtr ns = (xmlNsPtr) [aNamespace XMLNode];

if (ns) {

(void)xmlNewNs(xmlNode_, ns->href, ns->prefix);

// we may need to fix this node's own name; the graft point is where

// the namespace search starts, so that points to this node too

[[self class] fixUpNamespacesForNode:xmlNode_

graftingToTreeNode:xmlNode_];

}

}

}

- (void)addChild:(GDataXMLNode *)child {

if ([child kind] == GDataXMLAttributeKind) {

[self addAttribute:child];

return;

}

if (xmlNode_ != NULL) {

[self releaseCachedValues];

xmlNodePtr childNodeCopy = [child XMLNodeCopy];

if (childNodeCopy) {

xmlNodePtr resultNode = xmlAddChild(xmlNode_, childNodeCopy);

if (resultNode == NULL) {

// failed to add

xmlFreeNode(childNodeCopy);

} else {

// added this child subtree successfully; see if it has

// previously-unresolved namespace prefixes that can now be fixed up

[[self class] fixUpNamespacesForNode:childNodeCopy

graftingToTreeNode:xmlNode_];

}

}

}

}

- (void)removeChild:(GDataXMLNode *)child {

// this is safe for attributes too

if (xmlNode_ != NULL) {

[self releaseCachedValues];

xmlNodePtr node = [child XMLNode];

xmlUnlinkNode(node);

// if the child node was borrowing its xmlNodePtr, then we need to

// explicitly free it, since there is probably no owning object that will

// free it on dealloc

if (![child shouldFreeXMLNode]) {

xmlFreeNode(node);

}

}

}

- (NSArray *)elementsForName:(NSString *)name {

NSString *desiredName = name;

if (xmlNode_ != NULL) {

NSString *prefix = [[self class] prefixForName:desiredName];

if (prefix) {

xmlChar* desiredPrefix = GDataGetXMLString(prefix);

xmlNsPtr foundNS = xmlSearchNs(xmlNode_->doc, xmlNode_, desiredPrefix);

if (foundNS) {

// we found a namespace; fall back on elementsForLocalName:URI:

// to get the elements

NSString *desiredURI = [self stringFromXMLString:(foundNS->href)];

NSString *localName = [[self class] localNameForName:desiredName];

NSArray *nsArray = [self elementsForLocalName:localName URI:desiredURI];

return nsArray;

}

}

// no namespace found for the node's prefix; try an exact match

// for the name argument, including any prefix

NSMutableArray *array = nil;

// walk our list of cached child nodes

NSArray *children = [self children];

for (GDataXMLNode *child in children) {

xmlNodePtr currNode = [child XMLNode];

// find all children which are elements with the desired name

if (currNode->type == XML_ELEMENT_NODE) {

NSString *qName = [child name];

if ([qName isEqual:name]) {

if (array == nil) {

array = [NSMutableArray arrayWithObject:child];

} else {

[array addObject:child];

}

}

}

}

return array;

}

return nil;

}

- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI {

NSMutableArray *array = nil;

if (xmlNode_ != NULL && xmlNode_->children != NULL) {

xmlChar* desiredNSHref = GDataGetXMLString(URI);

xmlChar* requestedLocalName = GDataGetXMLString(localName);

xmlChar* expectedLocalName = requestedLocalName;

// resolve the URI at the parent level, since usually children won't

// have their own namespace definitions, and we don't want to try to

// resolve it once for every child

xmlNsPtr foundParentNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, desiredNSHref);

if (foundParentNS == NULL) {

NSString *fakeQName = GDataFakeQNameForURIAndName(URI, localName);

expectedLocalName = GDataGetXMLString(fakeQName);

}

NSArray *children = [self children];

for (GDataXMLNode *child in children) {

xmlNodePtr currChildPtr = [child XMLNode];

// find all children which are elements with the desired name and

// namespace, or with the prefixed name and a null namespace

if (currChildPtr->type == XML_ELEMENT_NODE) {

// normally, we can assume the resolution done for the parent will apply

// to the child, as most children do not define their own namespaces

xmlNsPtr childLocalNS = foundParentNS;

xmlChar* childDesiredLocalName = expectedLocalName;

if (currChildPtr->nsDef != NULL) {

// this child has its own namespace definitons; do a fresh resolve

// of the namespace starting from the child, and see if it differs

// from the resolve done starting from the parent. If the resolve

// finds a different namespace, then override the desired local

// name just for this child.

childLocalNS = xmlSearchNsByHref(xmlNode_->doc, currChildPtr, desiredNSHref);

if (childLocalNS != foundParentNS) {

// this child does indeed have a different namespace resolution

// result than was found for its parent

if (childLocalNS == NULL) {

// no namespace found

NSString *fakeQName = GDataFakeQNameForURIAndName(URI, localName);

childDesiredLocalName = GDataGetXMLString(fakeQName);

} else {

// a namespace was found; use the original local name requested,

// not a faked one expected from resolving the parent

childDesiredLocalName = requestedLocalName;

}

}

}

// check if this child's namespace and local name are what we're

// seeking

if (currChildPtr->ns == childLocalNS

&& currChildPtr->name != NULL

&& xmlStrEqual(currChildPtr->name, childDesiredLocalName)) {

if (array == nil) {

array = [NSMutableArray arrayWithObject:child];

} else {

[array addObject:child];

}

}

}

}

// we return nil, not an empty array, according to docs

}

return array;

}

- (NSArray *)attributes {

if (cachedAttributes_ != nil) {

return cachedAttributes_;

}

NSMutableArray *array = nil;

if (xmlNode_ != NULL && xmlNode_->properties != NULL) {

xmlAttrPtr prop = xmlNode_->properties;

while (prop != NULL) {

GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:(xmlNodePtr) prop];

if (array == nil) {

array = [NSMutableArray arrayWithObject:node];

} else {

[array addObject:node];

}

prop = prop->next;

}

cachedAttributes_ = [array retain];

}

return array;

}

- (void)addAttribute:(GDataXMLNode *)attribute {

if (xmlNode_ != NULL) {

[self releaseCachedValues];

xmlAttrPtr attrPtr = (xmlAttrPtr) [attribute XMLNode];

if (attrPtr) {

// ignore this if an attribute with the name is already present,

// similar to NSXMLNode's addAttribute

xmlAttrPtr oldAttr;

if (attrPtr->ns == NULL) {

oldAttr = xmlHasProp(xmlNode_, attrPtr->name);

} else {

oldAttr = xmlHasNsProp(xmlNode_, attrPtr->name, attrPtr->ns->href);

}

if (oldAttr == NULL) {

xmlNsPtr newPropNS = NULL;

// if this attribute has a namespace, search for a matching namespace

// on the node we're adding to

if (attrPtr->ns != NULL) {

newPropNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, attrPtr->ns->href);

if (newPropNS == NULL) {

// make a new namespace on the parent node, and use that for the

// new attribute

newPropNS = xmlNewNs(xmlNode_, attrPtr->ns->href, attrPtr->ns->prefix);

}

}

// copy the attribute onto this node

xmlChar *value = xmlNodeGetContent((xmlNodePtr) attrPtr);

xmlAttrPtr newProp = xmlNewNsProp(xmlNode_, newPropNS, attrPtr->name, value);

if (newProp != NULL) {

// we made the property, so clean up the property's namespace

[[self class] fixUpNamespacesForNode:(xmlNodePtr)newProp

graftingToTreeNode:xmlNode_];

}

if (value != NULL) {

xmlFree(value);

}

}

}

}

}

- (GDataXMLNode *)attributeForXMLNode:(xmlAttrPtr)theXMLNode {

// search the cached attributes list for the GDataXMLNode with

// the underlying xmlAttrPtr

NSArray *attributes = [self attributes];

for (GDataXMLNode *attr in attributes) {

if (theXMLNode == (xmlAttrPtr) [attr XMLNode]) {

return attr;

}

}

return nil;

}

- (GDataXMLNode *)attributeForName:(NSString *)name {

if (xmlNode_ != NULL) {

xmlAttrPtr attrPtr = xmlHasProp(xmlNode_, GDataGetXMLString(name));

if (attrPtr == NULL) {

// can we guarantee that xmlAttrPtrs always have the ns ptr and never

// a namespace as part of the actual attribute name?

// split the name and its prefix, if any

xmlNsPtr ns = NULL;

NSString *prefix = [[self class] prefixForName:name];

if (prefix) {

// find the namespace for this prefix, and search on its URI to find

// the xmlNsPtr

name = [[self class] localNameForName:name];

ns = xmlSearchNs(xmlNode_->doc, xmlNode_, GDataGetXMLString(prefix));

}

const xmlChar* nsURI = ((ns != NULL) ? ns->href : NULL);

attrPtr = xmlHasNsProp(xmlNode_, GDataGetXMLString(name), nsURI);

}

if (attrPtr) {

GDataXMLNode *attr = [self attributeForXMLNode:attrPtr];

return attr;

}

}

return nil;

}

- (GDataXMLNode *)attributeForLocalName:(NSString *)localName

URI:(NSString *)attributeURI {

if (xmlNode_ != NULL) {

const xmlChar* name = GDataGetXMLString(localName);

const xmlChar* nsURI = GDataGetXMLString(attributeURI);

xmlAttrPtr attrPtr = xmlHasNsProp(xmlNode_, name, nsURI);

if (attrPtr == NULL) {

// if the attribute is in a tree lacking the proper namespace,

// the local name may include the full URI as a prefix

NSString *fakeQName = GDataFakeQNameForURIAndName(attributeURI, localName);

const xmlChar* xmlFakeQName = GDataGetXMLString(fakeQName);

attrPtr = xmlHasProp(xmlNode_, xmlFakeQName);

}

if (attrPtr) {

GDataXMLNode *attr = [self attributeForXMLNode:attrPtr];

return attr;

}

}

return nil;

}

- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI {

if (xmlNode_ != NULL) {

xmlChar* desiredNSHref = GDataGetXMLString(namespaceURI);

xmlNsPtr foundNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, desiredNSHref);

if (foundNS) {

// we found the namespace

if (foundNS->prefix != NULL) {

NSString *prefix = [self stringFromXMLString:(foundNS->prefix)];

return prefix;

} else {

// empty prefix is default namespace

return @"";

}

}

}

return nil;

}

#pragma mark Namespace fixup routines

+ (void)deleteNamespacePtr:(xmlNsPtr)namespaceToDelete

fromXMLNode:(xmlNodePtr)node {

// utilty routine to remove a namespace pointer from an element's

// namespace definition list. This is just removing the nsPtr

// from the singly-linked list, the node's namespace definitions.

xmlNsPtr currNS = node->nsDef;

xmlNsPtr prevNS = NULL;

while (currNS != NULL) {

xmlNsPtr nextNS = currNS->next;

if (namespaceToDelete == currNS) {

// found it; delete it from the head of the node's ns definition list

// or from the next field of the previous namespace

if (prevNS != NULL) prevNS->next = nextNS;

else node->nsDef = nextNS;

xmlFreeNs(currNS);

return;

}

prevNS = currNS;

currNS = nextNS;

}

}

+ (void)fixQualifiedNamesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode {

// Replace prefix-in-name with proper namespace pointers

//

// This is an inner routine for fixUpNamespacesForNode:

//

// see if this node's name lacks a namespace and is qualified, and if so,

// see if we can resolve the prefix against the parent

//

// The prefix may either be normal, "gd:foo", or a URI

// "{http://blah.com/}:foo"

if (nodeToFix->ns == NULL) {

xmlNsPtr foundNS = NULL;

xmlChar* prefix = NULL;

xmlChar* localName = SplitQNameReverse(nodeToFix->name, &prefix);

if (localName != NULL) {

if (prefix != NULL) {

// if the prefix is wrapped by { and } then it's a URI

int prefixLen = xmlStrlen(prefix);

if (prefixLen > 2

&& prefix[0] == '{'

&& prefix[prefixLen - 1] == '}') {

// search for the namespace by URI

xmlChar* uri = xmlStrsub(prefix, 1, prefixLen - 2);

if (uri != NULL) {

foundNS = xmlSearchNsByHref(graftPointNode->doc, graftPointNode, uri);

xmlFree(uri);

}

}

}

if (foundNS == NULL) {

// search for the namespace by prefix, even if the prefix is nil

// (nil prefix means to search for the default namespace)

foundNS = xmlSearchNs(graftPointNode->doc, graftPointNode, prefix);

}

if (foundNS != NULL) {

// we found a namespace, so fix the ns pointer and the local name

xmlSetNs(nodeToFix, foundNS);

xmlNodeSetName(nodeToFix, localName);

}

if (prefix != NULL) {

xmlFree(prefix);

prefix = NULL;

}

xmlFree(localName);

}

}

}

+ (void)fixDuplicateNamespacesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode

namespaceSubstitutionMap:(NSMutableDictionary *)nsMap {

// Duplicate namespace removal

//

// This is an inner routine for fixUpNamespacesForNode:

//

// If any of this node's namespaces are already defined at the graft point

// level, add that namespace to the map of namespace substitutions

// so it will be replaced in the children below the nodeToFix, and

// delete the namespace record

if (nodeToFix->type == XML_ELEMENT_NODE) {

// step through the namespaces defined on this node

xmlNsPtr definedNS = nodeToFix->nsDef;

while (definedNS != NULL) {

// see if this namespace is already defined higher in the tree,

// with both the same URI and the same prefix; if so, add a mapping for

// it

xmlNsPtr foundNS = xmlSearchNsByHref(graftPointNode->doc, graftPointNode,

definedNS->href);

if (foundNS != NULL

&& foundNS != definedNS

&& xmlStrEqual(definedNS->prefix, foundNS->prefix)) {

// store a mapping from this defined nsPtr to the one found higher

// in the tree

[nsMap setObject:[NSValue valueWithPointer:foundNS]

forKey:[NSValue valueWithPointer:definedNS]];

// remove this namespace from the ns definition list of this node;

// all child elements and attributes referencing this namespace

// now have a dangling pointer and must be updated (that is done later

// in this method)

//

// before we delete this namespace, move our pointer to the

// next one

xmlNsPtr nsToDelete = definedNS;

definedNS = definedNS->next;

[self deleteNamespacePtr:nsToDelete fromXMLNode:nodeToFix];

} else {

// this namespace wasn't a duplicate; move to the next

definedNS = definedNS->next;

}

}

}

// if this node's namespace is one we deleted, update it to point

// to someplace better

if (nodeToFix->ns != NULL) {

NSValue *currNS = [NSValue valueWithPointer:nodeToFix->ns];

NSValue *replacementNS = [nsMap objectForKey:currNS];

if (replacementNS != nil) {

xmlNsPtr replaceNSPtr = (xmlNsPtr)[replacementNS pointerValue];

xmlSetNs(nodeToFix, replaceNSPtr);

}

}

}

+ (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode

namespaceSubstitutionMap:(NSMutableDictionary *)nsMap {

// This is the inner routine for fixUpNamespacesForNode:graftingToTreeNode:

//

// This routine fixes two issues:

//

// Because we can create nodes with qualified names before adding

// them to the tree that declares the namespace for the prefix,

// we need to set the node namespaces after adding them to the tree.

//

// Because libxml adds namespaces to nodes when it copies them,

// we want to remove redundant namespaces after adding them to

// a tree.

//

// If only the Mac's libxml had xmlDOMWrapReconcileNamespaces, it could do

// namespace cleanup for us

// We only care about fixing names of elements and attributes

if (nodeToFix->type != XML_ELEMENT_NODE

&& nodeToFix->type != XML_ATTRIBUTE_NODE) return;

// Do the fixes

[self fixQualifiedNamesForNode:nodeToFix

graftingToTreeNode:graftPointNode];

[self fixDuplicateNamespacesForNode:nodeToFix

graftingToTreeNode:graftPointNode

namespaceSubstitutionMap:nsMap];

if (nodeToFix->type == XML_ELEMENT_NODE) {

// when fixing element nodes, recurse for each child element and

// for each attribute

xmlNodePtr currChild = nodeToFix->children;

while (currChild != NULL) {

[self fixUpNamespacesForNode:currChild

graftingToTreeNode:graftPointNode

namespaceSubstitutionMap:nsMap];

currChild = currChild->next;

}

xmlAttrPtr currProp = nodeToFix->properties;

while (currProp != NULL) {

[self fixUpNamespacesForNode:(xmlNodePtr)currProp

graftingToTreeNode:graftPointNode

namespaceSubstitutionMap:nsMap];

currProp = currProp->next;

}

}

}

+ (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix

graftingToTreeNode:(xmlNodePtr)graftPointNode {

// allocate the namespace map that will be passed

// down on recursive calls

NSMutableDictionary *nsMap = [NSMutableDictionary dictionary];

[self fixUpNamespacesForNode:nodeToFix

graftingToTreeNode:graftPointNode

namespaceSubstitutionMap:nsMap];

}

@end

@interface GDataXMLDocument (PrivateMethods)

- (void)addStringsCacheToDoc;

@end

@implementation GDataXMLDocument

- (id)initWithXMLString:(NSString *)str options:(unsigned int)mask error:(NSError **)error {

NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];

GDataXMLDocument *doc = [self initWithData:data options:mask error:error];

return doc;

}

- (id)initWithData:(NSData *)data options:(unsigned int)mask error:(NSError **)error {

self = [super init];

if (self) {

const char *baseURL = NULL;

const char *encoding = NULL;

// NOTE: We are assuming [data length] fits into an int.

xmlDoc_ = xmlReadMemory((const char*)[data bytes], (int)[data length], baseURL, encoding,

kGDataXMLParseOptions); // TODO(grobbins) map option values

if (xmlDoc_ == NULL) {

if (error) {

*error = [NSError errorWithDomain:@"com.google.GDataXML"

code:-1

userInfo:nil];

// TODO(grobbins) use xmlSetGenericErrorFunc to capture error

}

[self release];

return nil;

} else {

if (error) *error = NULL;

[self addStringsCacheToDoc];

}

}

return self;

}

- (id)initWithRootElement:(GDataXMLElement *)element {

self = [super init];

if (self) {

xmlDoc_ = xmlNewDoc(NULL);

(void) xmlDocSetRootElement(xmlDoc_, [element XMLNodeCopy]);

[self addStringsCacheToDoc];

}

return self;

}

- (void)addStringsCacheToDoc {

// utility routine for init methods

#if DEBUG

NSCAssert(xmlDoc_ != NULL && xmlDoc_->_private == NULL,

@"GDataXMLDocument cache creation problem");

#endif

// add a strings cache as private data for the document

//

// we'll use plain C pointers (xmlChar*) as the keys, and NSStrings

// as the values

CFIndex capacity = 0; // no limit

CFDictionaryKeyCallBacks keyCallBacks = {

0, // version

StringCacheKeyRetainCallBack,

StringCacheKeyReleaseCallBack,

StringCacheKeyCopyDescriptionCallBack,

StringCacheKeyEqualCallBack,

StringCacheKeyHashCallBack

};

CFMutableDictionaryRef dict = CFDictionaryCreateMutable(

kCFAllocatorDefault, capacity,

&keyCallBacks, &kCFTypeDictionaryValueCallBacks);

// we'll use the user-defined _private field for our cache

xmlDoc_->_private = dict;

}

- (NSString *)description {

return [NSString stringWithFormat:@"%@ %p", [self class], self];

}

- (void)dealloc {

if (xmlDoc_ != NULL) {

// release the strings cache

//

// since it's a CF object, were anyone to use this in a GC environment,

// this would need to be released in a finalize method, too

if (xmlDoc_->_private != NULL) {

CFRelease(xmlDoc_->_private);

}

xmlFreeDoc(xmlDoc_);

}

[super dealloc];

}

#pragma mark -

- (GDataXMLElement *)rootElement {

GDataXMLElement *element = nil;

if (xmlDoc_ != NULL) {

xmlNodePtr rootNode = xmlDocGetRootElement(xmlDoc_);

if (rootNode) {

element = [GDataXMLElement nodeBorrowingXMLNode:rootNode];

}

}

return element;

}

- (NSData *)XMLData {

if (xmlDoc_ != NULL) {

xmlChar *buffer = NULL;

int bufferSize = 0;

xmlDocDumpMemory(xmlDoc_, &buffer, &bufferSize);

if (buffer) {

NSData *data = [NSData dataWithBytes:buffer

length:bufferSize];

xmlFree(buffer);

return data;

}

}

return nil;

}

- (void)setVersion:(NSString *)version {

if (xmlDoc_ != NULL) {

if (xmlDoc_->version != NULL) {

// version is a const char* so we must cast

xmlFree((char *) xmlDoc_->version);

xmlDoc_->version = NULL;

}

if (version != nil) {

xmlDoc_->version = xmlStrdup(GDataGetXMLString(version));

}

}

}

- (void)setCharacterEncoding:(NSString *)encoding {

if (xmlDoc_ != NULL) {

if (xmlDoc_->encoding != NULL) {

// version is a const char* so we must cast

xmlFree((char *) xmlDoc_->encoding);

xmlDoc_->encoding = NULL;

}

if (encoding != nil) {

xmlDoc_->encoding = xmlStrdup(GDataGetXMLString(encoding));

}

}

}

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error {

return [self nodesForXPath:xpath namespaces:nil error:error];

}

- (NSArray *)nodesForXPath:(NSString *)xpath

namespaces:(NSDictionary *)namespaces

error:(NSError **)error {

if (xmlDoc_ != NULL) {

GDataXMLNode *docNode = [GDataXMLElement nodeBorrowingXMLNode:(xmlNodePtr)xmlDoc_];

NSArray *array = [docNode nodesForXPath:xpath

namespaces:namespaces

error:error];

return array;

}

return nil;

}

@end

//

// Dictionary key callbacks for our C-string to NSString cache dictionary

//

static const void *StringCacheKeyRetainCallBack(CFAllocatorRef allocator, const void *str) {

// copy the key

xmlChar* key = xmlStrdup(str);

return key;

}

static void StringCacheKeyReleaseCallBack(CFAllocatorRef allocator, const void *str) {

// free the key

char *chars = (char *)str;

xmlFree((char *) chars);

}

static CFStringRef StringCacheKeyCopyDescriptionCallBack(const void *str) {

// make a CFString from the key

CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorDefault,

(const char *)str,

kCFStringEncodingUTF8);

return cfStr;

}

static Boolean StringCacheKeyEqualCallBack(const void *str1, const void *str2) {

// compare the key strings

if (str1 == str2) return true;

int result = xmlStrcmp(str1, str2);

return (result == 0);

}

static CFHashCode StringCacheKeyHashCallBack(const void *str) {

// dhb hash, per http://www.cse.yorku.ca/~oz/hash.html

CFHashCode hash = 5381;

int c;

const char *chars = (const char *)str;

while ((c = *chars++) != 0) {

hash = ((hash << 5) + hash) + c;

}

return hash;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: