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

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;

}

 

你可能感兴趣的:(node)