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;

}

iOS处理XMl提供GDataXMLNode下载的链接的更多相关文章

  1. iOS开发 XML解析和下拉刷新,上拉加载更多

    iOS开发 XML解析和下拉刷新,上拉加载更多 1.XML格式 <?xml version="1.0" encoding="utf-8" ?> 表示 ...

  2. 免费图片存储和图话【提供demo下载】

    我们不管是做博客系统还是其他网站,图片是免不了要使用到的.但是,我们都知道图片的访问是很耗资源的,同时也是很占磁盘空间的,且还特别占带宽. 所以,我们一般都会用到特定的图片服务器.不过,像我等屌丝平时 ...

  3. WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]

    原文:WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载] 我们有两种典型的WCF调用方式:通过SvcUtil.exe(或者添加Web引用)导入发布的服务元数据生成服务代理相关的代码 ...

  4. ssh整合思想初步 struts2与Spring的整合 struts2-spring-plugin-2.3.4.1.jar下载地址 自动加载Spring中的XML配置文件 Struts2下载地址

    首先需要JAR包 Spring整合Structs2的JAR包 struts2-spring-plugin-2.3.4.1.jar 下载地址 链接: https://pan.baidu.com/s/1o ...

  5. 纯前端下载pdf链接文件,而不是打开预览的解决方案

    纯前端下载pdf链接文件,而不是打开预览的解决方案 一,介绍与需求 1.1,介绍 XMLHttpRequest 用于在后台与服务器交换数据.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行 ...

  6. [教程]怎么用百度云观看和下载"磁力链接"无需下载直接观看.

    1, 打开网址 http://okbt.net/  输入你想要看的电影名字, 点搜索,鼠标右键点击拷贝磁力链接.或者 电脑装了迅雷的话.可以直接点击.用迅雷下载. 磁力链接都是这种格式的.例: mag ...

  7. 2016 - 1- 23 iOS中xml解析 (!!!!!!!有坑要解决!!!!!!)

    一: iOS中xml解析的几种方式简介 1.官方原生 NSXMLParser :SAX方式解析,使用起来比较简单 2.第三方框架 libxml2 :纯C 同时支持DOM与SAX GDataXML: D ...

  8. 【IOS】 XML解析和xml转plist文件(GDataXML)

    iOS对于XML的解析有系统自带的SDK--NSXMLParser,鄙人愚拙,只会用GDataXML进行解析,这里仅介绍GData的使用.(以下图为例) 1.对于一个xml文件,先读取出来 NSDat ...

  9. IOS 解析XML文档

    前段时间想找点事做,就是试着看能不能用豆瓣的API做点什么,于是就碰到了这个问题——XML解析. 老师还没讲,只能自己去查. XML文档解析主要有SAX和DOM两种模式,IOS上两种模式都可以用,这里 ...

随机推荐

  1. SQL Server DATEADD() 函数

    SQL Server Date 函数 定义和用法 DATEADD() 函数在日期中添加或减去指定的时间间隔. 语法 DATEADD(datepart,number,date) date 参数是合法的日 ...

  2. 《Power》读书笔记

    原创作品 版权所有 转载请注明出处! 序言 权力是“争”来的,不是“等”来的. 会计.工商管理.营销和销售部门.财务人员(背景).企业咨询小组 在位晋升而竞争的时候,对于公平竞争原则,有些人会采取变通 ...

  3. Extmail maildrop错误

    错误: <liuyb@5teacher.com>: Command died with status 127: "maildrop". Command output: ...

  4. Android ADB 端口占用问题解决方案

    问题描述: The connection to adb is down, and a severe error has occured. You must restart adb and Eclips ...

  5. Visual Studio Code asp.net 5环境搭建技能Get

    启动准备阶段 预热 1 VS Code 官方地址:https://www.visualstudio.com/en-us/products/code-vs.aspx 2 安装Node.js :https ...

  6. word小技巧

    如何将英文和数字替换为times new romans 1,点击替换(ctrl+H) .2,在查找内容中输入”([A-Z0-9])“(不包括引号,但包括小括号),这是替换所有大写字母和数字,如果还想替 ...

  7. 1072: [SCOI2007]排列perm - BZOJ

    Description 给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0).例如123434有90种排列能被2整除,其中末位为2的有30种,末位为4的有60种.Input ...

  8. 1069: [SCOI2007]最大土地面积 - BZOJ

    Description 在某块平面土地上有N个点,你可以选择其中的任意四个点,将这片土地围起来,当然,你希望这四个点围成的多边形面积最大.Input 第1行一个正整数N,接下来N行,每行2个数x,y, ...

  9. could not open XXX permission denied

    http://www.cplusplus.com/forum/beginner/104404/3/ -std=c++11 -std=gnu++11 Then you probably just don ...

  10. contentType,pageEncoding,charset,setCharacterEncoding

    contentType,pageEncoding,charset,setCharacterEncoding HTML中的meta标签,以及HTTP Header. -------------字符集.字 ...