2016-02-24 15:45:24 +01:00

2906 lines
70 KiB
Objective-C
Raw Blame History

#import "DDXMLPrivate.h"
#import "NSString+DDXML.h"
#import <libxml/xpath.h>
#import <libxml/xpathInternals.h>
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
/**
* Welcome to KissXML.
*
* The project page has documentation if you have questions.
* https://github.com/robbiehanson/KissXML
*
* If you're new to the project you may wish to read the "Getting Started" wiki.
* https://github.com/robbiehanson/KissXML/wiki/GettingStarted
*
* KissXML provides a drop-in replacement for Apple's NSXML class cluster.
* The goal is to get the exact same behavior as the NSXML classes.
*
* For API Reference, see Apple's excellent documentation,
* either via Xcode's Mac OS X documentation, or via the web:
*
* https://github.com/robbiehanson/KissXML/wiki/Reference
**/
@implementation DDXMLNode
static void MyErrorHandler(void * userData, xmlErrorPtr error);
#if DDXML_DEBUG_MEMORY_ISSUES
static CFMutableDictionaryRef zombieTracker;
static dispatch_queue_t zombieQueue;
static void RecursiveMarkZombiesFromNode(xmlNodePtr node);
static void RecursiveMarkZombiesFromDoc(xmlDocPtr doc);
static void MarkZombies(void *xmlPtr);
static void MarkBirth(void *xmlPtr, DDXMLNode *wrapper);
static void MarkDeath(void *xmlPtr, DDXMLNode *wrapper);
#endif
/**
* From Apple's Documentation:
*
* The runtime sends initialize to each class in a program exactly one time just before the class,
* or any class that inherits from it, is sent its first message from within the program. (Thus the method may
* never be invoked if the class is not used.) The runtime sends the initialize message to classes
* in a thread-safe manner. Superclasses receive this message before their subclasses.
*
* The method may also be called directly (assumably by accident), hence the safety mechanism.
**/
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Redirect error output to our own function (don't clog up the console)
initGenericErrorDefaultFunc(NULL);
xmlSetStructuredErrorFunc(NULL, MyErrorHandler);
// Tell libxml not to keep ignorable whitespace (such as node indentation, formatting, etc).
// NSXML ignores such whitespace.
// This also has the added benefit of taking up less RAM when parsing formatted XML documents.
xmlKeepBlanksDefault(0);
#if DDXML_DEBUG_MEMORY_ISSUES
{
zombieTracker = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
zombieQueue = dispatch_queue_create("DDXMLZombieQueue", NULL);
}
#endif
});
}
+ (id)elementWithName:(NSString *)name
{
return [[DDXMLElement alloc] initWithName:name];
}
+ (id)elementWithName:(NSString *)name stringValue:(NSString *)string
{
return [[DDXMLElement alloc] initWithName:name stringValue:string];
}
+ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes
{
DDXMLElement *result = [[DDXMLElement alloc] initWithName:name];
[result setChildren:children];
[result setAttributes:attributes];
return result;
}
+ (id)elementWithName:(NSString *)name URI:(NSString *)URI
{
return [[DDXMLElement alloc] initWithName:name URI:URI];
}
+ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue
{
xmlAttrPtr attr = xmlNewProp(NULL, [name xmlChar], [stringValue xmlChar]);
if (attr == NULL) return nil;
return [[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr owner:nil];
}
+ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue
{
xmlAttrPtr attr = xmlNewProp(NULL, [name xmlChar], [stringValue xmlChar]);
if (attr == NULL) return nil;
DDXMLAttributeNode *result = [[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr owner:nil];
[result setURI:URI];
return result;
}
+ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue
{
// If the user passes a nil or empty string name, they are trying to create a default namespace
const xmlChar *xmlName = [name length] > 0 ? [name xmlChar] : NULL;
xmlNsPtr ns = xmlNewNs(NULL, [stringValue xmlChar], xmlName);
if (ns == NULL) return nil;
return [[DDXMLNamespaceNode alloc] initWithNsPrimitive:ns nsParent:NULL owner:nil];
}
+ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue
{
xmlNodePtr procInst = xmlNewPI([name xmlChar], [stringValue xmlChar]);
if (procInst == NULL) return nil;
return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)procInst owner:nil];
}
+ (id)commentWithStringValue:(NSString *)stringValue
{
xmlNodePtr comment = xmlNewComment([stringValue xmlChar]);
if (comment == NULL) return nil;
return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)comment owner:nil];
}
+ (id)textWithStringValue:(NSString *)stringValue
{
xmlNodePtr text = xmlNewText([stringValue xmlChar]);
if (text == NULL) return nil;
return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)text owner:nil];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Init, Dealloc
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
if (kindPtr->type == XML_DOCUMENT_NODE)
{
return [DDXMLDocument nodeWithDocPrimitive:(xmlDocPtr)kindPtr owner:owner];
}
else if (kindPtr->type == XML_ELEMENT_NODE)
{
return [DDXMLElement nodeWithElementPrimitive:(xmlNodePtr)kindPtr owner:owner];
}
else if (kindPtr->type == XML_NAMESPACE_DECL)
{
// Todo: This may be a problem...
return [DDXMLNamespaceNode nodeWithNsPrimitive:(xmlNsPtr)kindPtr nsParent:NULL owner:owner];
}
else if (kindPtr->type == XML_ATTRIBUTE_NODE)
{
return [DDXMLAttributeNode nodeWithAttrPrimitive:(xmlAttrPtr)kindPtr owner:owner];
}
else
{
return [DDXMLNode nodeWithPrimitive:kindPtr owner:owner];
}
}
/**
* Returns a DDXML wrapper object for the given primitive node.
* The given node MUST be non-NULL and of the proper type.
**/
+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
return [[DDXMLNode alloc] initWithPrimitive:kindPtr owner:owner];
}
/**
* Returns a DDXML wrapper object for the given primitive node.
* The given node MUST be non-NULL and of the proper type.
**/
- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)inOwner
{
if ((self = [super init]))
{
genericPtr = kindPtr;
owner = inOwner;
#if DDXML_DEBUG_MEMORY_ISSUES
MarkBirth(genericPtr, self);
#endif
}
return self;
}
/**
* This method shouldn't be used.
* To maintain compatibility with Apple, we return an invalid node.
**/
- (id)init
{
self = [super init];
if ([self isKindOfClass:[DDXMLInvalidNode class]])
{
return self;
}
else
{
return [[DDXMLInvalidNode alloc] init];
}
}
- (void)dealloc
{
#if DDXML_DEBUG_MEMORY_ISSUES
MarkDeath(genericPtr, self);
#endif
// We also check if genericPtr is NULL.
// This may be the case if, e.g., DDXMLElement calls [self release] from it's init method.
if ((owner == nil) && (genericPtr != NULL))
{
if (IsXmlNsPtr(genericPtr))
{
#if DDXML_DEBUG_MEMORY_ISSUES
MarkZombies(genericPtr);
#endif
xmlFreeNs((xmlNsPtr)genericPtr);
}
else if (IsXmlAttrPtr(genericPtr))
{
#if DDXML_DEBUG_MEMORY_ISSUES
MarkZombies(genericPtr);
#endif
xmlFreeProp((xmlAttrPtr)genericPtr);
}
else if (IsXmlDtdPtr(genericPtr))
{
#if DDXML_DEBUG_MEMORY_ISSUES
MarkZombies(genericPtr);
#endif
xmlFreeDtd((xmlDtdPtr)genericPtr);
}
else if (IsXmlDocPtr(genericPtr))
{
xmlDocPtr doc = (xmlDocPtr)genericPtr;
#if DDXML_DEBUG_MEMORY_ISSUES
RecursiveMarkZombiesFromDoc(doc);
#endif
xmlFreeDoc(doc);
}
else if (IsXmlNodePtr(genericPtr))
{
xmlNodePtr node = (xmlNodePtr)genericPtr;
#if DDXML_DEBUG_MEMORY_ISSUES
RecursiveMarkZombiesFromNode(node);
#endif
xmlFreeNode(node);
}
else
{
NSAssert1(NO, @"Cannot free unknown node type: %i", genericPtr->type);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Copying
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)copyWithZone:(NSZone *)zone
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (IsXmlDocPtr(genericPtr))
{
xmlDocPtr copyDocPtr = xmlCopyDoc((xmlDocPtr)genericPtr, 1);
if (copyDocPtr == NULL) return nil;
return [[DDXMLDocument alloc] initWithDocPrimitive:copyDocPtr owner:nil];
}
if (IsXmlNodePtr(genericPtr))
{
xmlNodePtr copyNodePtr = xmlCopyNode((xmlNodePtr)genericPtr, 1);
if (copyNodePtr == NULL) return nil;
if ([self isKindOfClass:[DDXMLElement class]])
return [[DDXMLElement alloc] initWithElementPrimitive:copyNodePtr owner:nil];
else
return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)copyNodePtr owner:nil];
}
if (IsXmlAttrPtr(genericPtr))
{
xmlAttrPtr copyAttrPtr = xmlCopyProp(NULL, (xmlAttrPtr)genericPtr);
if (copyAttrPtr == NULL) return nil;
return [[DDXMLAttributeNode alloc] initWithAttrPrimitive:copyAttrPtr owner:nil];
}
if (IsXmlNsPtr(genericPtr))
{
xmlNsPtr copyNsPtr = xmlCopyNamespace((xmlNsPtr)genericPtr);
if (copyNsPtr == NULL) return nil;
return [[DDXMLNamespaceNode alloc] initWithNsPrimitive:copyNsPtr nsParent:NULL owner:nil];
}
if (IsXmlDtdPtr(genericPtr))
{
xmlDtdPtr copyDtdPtr = xmlCopyDtd((xmlDtdPtr)genericPtr);
if (copyDtdPtr == NULL) return nil;
return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)copyDtdPtr owner:nil];
}
return nil;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Equality
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)isEqual:(id)anObject
{
// DDXMLNode, DDXMLElement, and DDXMLDocument are simply light-weight wrappers atop a libxml structure.
//
// To provide maximum speed and thread-safety,
// multiple DDXML wrapper objects may be created that wrap the same underlying libxml node.
//
// Thus equality is simply a matter of what underlying libxml node DDXML is wrapping.
if ([anObject class] == [self class])
{
DDXMLNode *aNode = (DDXMLNode *)anObject;
return (genericPtr == aNode->genericPtr);
}
return NO;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (DDXMLNodeKind)kind
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (genericPtr != NULL)
return genericPtr->type;
else
return DDXMLInvalidKind;
}
- (void)setName:(NSString *)name
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// The xmlNodeSetName function works for both nodes and attributes
xmlNodeSetName((xmlNodePtr)genericPtr, [name xmlChar]);
}
- (NSString *)name
{
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
const xmlChar *xmlName = ((xmlStdPtr)genericPtr)->name;
if (xmlName == NULL)
{
return nil;
}
NSString *name = [NSString stringWithUTF8String:(const char *)xmlName];
if (IsXmlNodePtr(genericPtr))
{
xmlNodePtr node = (xmlNodePtr)genericPtr;
NSRange range = [name rangeOfString:@":"];
if (range.length == 0)
{
if (node->ns && node->ns->prefix)
{
return [NSString stringWithFormat:@"%s:%@", node->ns->prefix, name];
}
}
}
return name;
}
- (void)setStringValue:(NSString *)string
{
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (IsXmlNodePtr(genericPtr))
{
xmlStdPtr node = (xmlStdPtr)genericPtr;
// Setting the content of a node erases any existing child nodes.
// Therefore, we need to remove them properly first.
[[self class] removeAllChildrenFromNode:(xmlNodePtr)node];
xmlChar *escapedString = xmlEncodeSpecialChars(node->doc, [string xmlChar]);
xmlNodeSetContent((xmlNodePtr)node, escapedString);
xmlFree(escapedString);
}
}
/**
* Returns the content of the receiver as a string value.
*
* If the receiver is a node object of element kind, the content is that of any text-node children.
* This method recursively visits elements nodes and concatenates their text nodes in document order with
* no intervening spaces.
**/
- (NSString *)stringValue
{
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (IsXmlNodePtr(genericPtr))
{
xmlChar *content = xmlNodeGetContent((xmlNodePtr)genericPtr);
NSString *result = [NSString stringWithUTF8String:(const char *)content];
xmlFree(content);
return result;
}
return nil;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Tree Navigation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the index of the receiver identifying its position relative to its sibling nodes.
* The first child node of a parent has an index of zero.
**/
- (NSUInteger)index
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
NSUInteger result = 0;
xmlStdPtr node = ((xmlStdPtr)genericPtr)->prev;
while (node != NULL)
{
result++;
node = node->prev;
}
return result;
}
/**
* Returns the nesting level of the receiver within the tree hierarchy.
* The root element of a document has a nesting level of one.
**/
- (NSUInteger)level
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
NSUInteger result = 0;
xmlNodePtr currentNode = ((xmlStdPtr)genericPtr)->parent;
while (currentNode != NULL)
{
result++;
currentNode = currentNode->parent;
}
return result;
}
/**
* Returns the DDXMLDocument object containing the root element and representing the XML document as a whole.
* If the receiver is a standalone node (that is, a node at the head of a detached branch of the tree), this
* method returns nil.
**/
- (DDXMLDocument *)rootDocument
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlStdPtr node = (xmlStdPtr)genericPtr;
if (node == NULL || node->doc == NULL)
return nil;
else
return [DDXMLDocument nodeWithDocPrimitive:node->doc owner:self];
}
/**
* Returns the parent node of the receiver.
*
* Document nodes and standalone nodes (that is, the root of a detached branch of a tree) have no parent, and
* sending this message to them returns nil. A one-to-one relationship does not always exists between a parent and
* its children; although a namespace or attribute node cannot be a child, it still has a parent element.
**/
- (DDXMLNode *)parent
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlStdPtr node = (xmlStdPtr)genericPtr;
if (node->parent == NULL)
return nil;
else
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent owner:self];
}
/**
* Returns the number of child nodes the receiver has.
* For performance reasons, use this method instead of getting the count from the array returned by children.
**/
- (NSUInteger)childCount
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (!IsXmlDocPtr(genericPtr) && !IsXmlNodePtr(genericPtr) && !IsXmlDtdPtr(genericPtr))
{
return 0;
}
NSUInteger result = 0;
xmlNodePtr child = ((xmlStdPtr)genericPtr)->children;
while (child != NULL)
{
result++;
child = child->next;
}
return result;
}
/**
* Returns an immutable array containing the child nodes of the receiver (as DDXMLNode objects).
**/
- (NSArray *)children
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (!IsXmlDocPtr(genericPtr) && !IsXmlNodePtr(genericPtr) && !IsXmlDtdPtr(genericPtr))
{
return nil;
}
NSMutableArray *result = [NSMutableArray array];
xmlNodePtr child = ((xmlStdPtr)genericPtr)->children;
while (child != NULL)
{
[result addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child owner:self]];
child = child->next;
}
return [result copy];
}
/**
* Returns the child node of the receiver at the specified location.
* Returns a DDXMLNode object or nil if the receiver has no children.
*
* If the receive has children and index is out of bounds, an exception is raised.
*
* The receiver should be a DDXMLNode object representing a document, element, or document type declaration.
* The returned node object can represent an element, comment, text, or processing instruction.
**/
- (DDXMLNode *)childAtIndex:(NSUInteger)index
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (!IsXmlDocPtr(genericPtr) && !IsXmlNodePtr(genericPtr) && !IsXmlDtdPtr(genericPtr))
{
return nil;
}
NSUInteger i = 0;
xmlNodePtr child = ((xmlStdPtr)genericPtr)->children;
if (child == NULL)
{
// NSXML doesn't raise an exception if there are no children
return nil;
}
while (child != NULL)
{
if (i == index)
{
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child owner:self];
}
i++;
child = child->next;
}
// NSXML version uses this same assertion
DDXMLAssert(NO, @"index (%u) beyond bounds (%u)", (unsigned)index, (unsigned)i);
return nil;
}
/**
* Returns the previous DDXMLNode object that is a sibling node to the receiver.
*
* This object will have an index value that is one less than the receiver<65>s.
* If there are no more previous siblings (that is, other child nodes of the receiver<65>s parent) the method returns nil.
**/
- (DDXMLNode *)previousSibling
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlStdPtr node = (xmlStdPtr)genericPtr;
if (node->prev == NULL)
return nil;
else
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->prev owner:self];
}
/**
* Returns the next DDXMLNode object that is a sibling node to the receiver.
*
* This object will have an index value that is one more than the receiver<65>s.
* If there are no more subsequent siblings (that is, other child nodes of the receiver<65>s parent) the
* method returns nil.
**/
- (DDXMLNode *)nextSibling
{
// Note: DDXMLNamespaceNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlStdPtr node = (xmlStdPtr)genericPtr;
if (node->next == NULL)
return nil;
else
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->next owner:self];
}
/**
* Returns the previous DDXMLNode object in document order.
*
* You use this method to <20>walk<6C> backward through the tree structure representing an XML document or document section.
* (Use nextNode to traverse the tree in the opposite direction.) Document order is the natural order that XML
* constructs appear in markup text. If you send this message to the first node in the tree (that is, the root element),
* nil is returned. DDXMLNode bypasses namespace and attribute nodes when it traverses a tree in document order.
**/
- (DDXMLNode *)previousNode
{
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// If the node has a previous sibling,
// then we need the last child of the last child of the last child etc
// Note: Try to accomplish this task without creating dozens of intermediate wrapper objects
xmlStdPtr node = (xmlStdPtr)genericPtr;
xmlStdPtr previousSibling = node->prev;
if (previousSibling != NULL)
{
if (previousSibling->last != NULL)
{
xmlNodePtr lastChild = previousSibling->last;
while (lastChild->last != NULL)
{
lastChild = lastChild->last;
}
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)lastChild owner:self];
}
else
{
// The previous sibling has no children, so the previous node is simply the previous sibling
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)previousSibling owner:self];
}
}
// If there are no previous siblings, then the previous node is simply the parent
// Note: rootNode.parent == docNode
if (node->parent == NULL || node->parent->type == XML_DOCUMENT_NODE)
return nil;
else
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent owner:self];
}
/**
* Returns the next DDXMLNode object in document order.
*
* You use this method to <20>walk<6C> forward through the tree structure representing an XML document or document section.
* (Use previousNode to traverse the tree in the opposite direction.) Document order is the natural order that XML
* constructs appear in markup text. If you send this message to the last node in the tree, nil is returned.
* DDXMLNode bypasses namespace and attribute nodes when it traverses a tree in document order.
**/
- (DDXMLNode *)nextNode
{
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// If the node has children, then next node is the first child
DDXMLNode *firstChild = [self childAtIndex:0];
if (firstChild)
return firstChild;
// If the node has a next sibling, then next node is the same as next sibling
DDXMLNode *nextSibling = [self nextSibling];
if (nextSibling)
return nextSibling;
// There are no children, and no more siblings, so we need to get the next sibling of the parent.
// If that is nil, we need to get the next sibling of the grandparent, etc.
// Note: Try to accomplish this task without creating dozens of intermediate wrapper objects
xmlNodePtr parent = ((xmlStdPtr)genericPtr)->parent;
while (parent != NULL)
{
xmlNodePtr parentNextSibling = parent->next;
if (parentNextSibling != NULL)
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)parentNextSibling owner:self];
else
parent = parent->parent;
}
return nil;
}
/**
* Detaches the receiver from its parent node.
*
* This method is applicable to DDXMLNode objects representing elements, text, comments, processing instructions,
* attributes, and namespaces. Once the node object is detached, you can add it as a child node of another parent.
**/
- (void)detach
{
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlStdPtr node = (xmlStdPtr)genericPtr;
if (node->parent != NULL)
{
if (IsXmlNodePtr(genericPtr))
{
[[self class] detachChild:(xmlNodePtr)node];
owner = nil;
}
}
}
- (xmlStdPtr)_XPathPreProcess:(NSMutableString *)result
{
// This is a private/internal method
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
return (xmlStdPtr)genericPtr;
}
- (NSString *)XPath
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
NSMutableString *result = [NSMutableString stringWithCapacity:25];
// Examples:
// /rootElement[1]/subElement[4]/thisNode[2]
// topElement/thisNode[2]
xmlStdPtr node = [self _XPathPreProcess:result];
// Note: rootNode.parent == docNode
while ((node != NULL) && (node->type != XML_DOCUMENT_NODE))
{
if ((node->parent == NULL) && (node->doc == NULL))
{
// We're at the top of the heirarchy, and there is no xml document.
// Thus we don't use a leading '/', and we don't need an index.
[result insertString:[NSString stringWithFormat:@"%s", node->name] atIndex:0];
}
else
{
// Find out what index this node is.
// If it's the first node with this name, the index is 1.
// If there are previous siblings with the same name, the index is greater than 1.
int index = 1;
xmlStdPtr prevNode = node->prev;
while (prevNode != NULL)
{
if (xmlStrEqual(node->name, prevNode->name))
{
index++;
}
prevNode = prevNode->prev;
}
[result insertString:[NSString stringWithFormat:@"/%s[%i]", node->name, index] atIndex:0];
}
node = (xmlStdPtr)node->parent;
}
return [result copy];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark QNames
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the local name of the receiver.
*
* The local name is the part of a node name that follows a namespace-qualifying colon or the full name if
* there is no colon. For example, <20>chapter<65> is the local name in the qualified name <20>acme:chapter<65>.
**/
- (NSString *)localName
{
// Note: DDXMLNamespaceNode overrides this method
// Zombie test occurs in [self name]
return [[self class] localNameForName:[self name]];
}
/**
* Returns the prefix of the receiver<65>s name.
*
* The prefix is the part of a namespace-qualified name that precedes the colon.
* For example, <20>acme<6D> is the local name in the qualified name <20>acme:chapter<65>.
* This method returns an empty string if the receiver<65>s name is not qualified by a namespace.
**/
- (NSString *)prefix
{
// Note: DDXMLNamespaceNode overrides this method
// Zombie test occurs in [self name]
return [[self class] prefixForName:[self name]];
}
/**
* Sets the URI identifying the source of this document.
* Pass nil to remove the current URI.
**/
- (void)setURI:(NSString *)URI
{
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (IsXmlNodePtr(genericPtr))
{
xmlNodePtr node = (xmlNodePtr)genericPtr;
if (node->ns != NULL)
{
[[self class] removeNamespace:node->ns fromNode:node];
}
if (URI)
{
// Create a new xmlNsPtr, add it to the nsDef list, and make ns point to it
xmlNsPtr ns = xmlNewNs(NULL, [URI xmlChar], NULL);
ns->next = node->nsDef;
node->nsDef = ns;
node->ns = ns;
}
}
}
/**
* Returns the URI associated with the receiver.
*
* A node<64>s URI is derived from its namespace or a document<6E>s URI; for documents, the URI comes either from the
* parsed XML or is explicitly set. You cannot change the URI for a particular node other for than a namespace
* or document node.
**/
- (NSString *)URI
{
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (IsXmlNodePtr(genericPtr))
{
xmlNodePtr node = (xmlNodePtr)genericPtr;
if (node->ns != NULL)
{
return [NSString stringWithUTF8String:((const char *)node->ns->href)];
}
}
return nil;
}
+ (void)getHasPrefix:(BOOL *)hasPrefixPtr localName:(NSString **)localNamePtr forName:(NSString *)name
{
// This is a private/internal method
if (name)
{
NSRange range = [name rangeOfString:@":"];
if (range.length != 0)
{
if (hasPrefixPtr) *hasPrefixPtr = range.location > 0;
if (localNamePtr) *localNamePtr = [name substringFromIndex:(range.location + range.length)];
}
else
{
if (hasPrefixPtr) *hasPrefixPtr = NO;
if (localNamePtr) *localNamePtr = name;
}
}
else
{
if (hasPrefixPtr) *hasPrefixPtr = NO;
if (localNamePtr) *localNamePtr = nil;
}
}
+ (void)getPrefix:(NSString **)prefixPtr localName:(NSString **)localNamePtr forName:(NSString *)name
{
// This is a private/internal method
if (name)
{
NSRange range = [name rangeOfString:@":"];
if (range.length != 0)
{
if (prefixPtr) *prefixPtr = [name substringToIndex:range.location];
if (localNamePtr) *localNamePtr = [name substringFromIndex:(range.location + range.length)];
}
else
{
if (prefixPtr) *prefixPtr = @"";
if (localNamePtr) *localNamePtr = name;
}
}
else
{
if (prefixPtr) *prefixPtr = @"";
if (localNamePtr) *localNamePtr = nil;
}
}
/**
* Returns the local name from the specified qualified name.
*
* Examples:
* "a:node" -> "node"
* "a:a:node" -> "a:node"
* "node" -> "node"
* nil - > nil
**/
+ (NSString *)localNameForName:(NSString *)name
{
// This is a public/API method
NSString *localName;
[self getPrefix:NULL localName:&localName forName:name];
return localName;
}
/**
* Extracts the prefix from the given name.
* If name is nil, or has no prefix, an empty string is returned.
*
* Examples:
* "a:deusty.com" -> "a"
* "a:a:deusty.com" -> "a"
* "node" -> ""
* nil -> ""
**/
+ (NSString *)prefixForName:(NSString *)name
{
// This is a public/API method
NSString *prefix;
[self getPrefix:&prefix localName:NULL forName:name];
return prefix;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Output
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSString *)description
{
// Zombie test occurs in XMLStringWithOptions:
return [self XMLStringWithOptions:0];
}
- (NSString *)XMLString
{
// Zombie test occurs in XMLStringWithOptions:
return [self XMLStringWithOptions:0];
}
- (NSString *)XMLStringWithOptions:(NSUInteger)options
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// xmlSaveNoEmptyTags:
// Global setting, asking the serializer to not output empty tags
// as <empty/> but <empty></empty>. those two forms are undistinguishable
// once parsed.
// Disabled by default
if (options & DDXMLNodeCompactEmptyElement)
xmlSaveNoEmptyTags = 0;
else
xmlSaveNoEmptyTags = 1;
int format = 0;
if (options & DDXMLNodePrettyPrint)
{
format = 1;
xmlIndentTreeOutput = 1;
}
int dumpCnt;
xmlBufferPtr bufferPtr = xmlBufferCreate();
if (IsXmlNsPtr(genericPtr))
dumpCnt = xmlNodeDump(bufferPtr, NULL, (xmlNodePtr)genericPtr, 0, format);
else
dumpCnt = xmlNodeDump(bufferPtr, ((xmlStdPtr)genericPtr)->doc, (xmlNodePtr)genericPtr, 0, format);
if (dumpCnt < 0)
{
return nil;
}
if ([self kind] == DDXMLTextKind)
{
NSString *result = [NSString stringWithUTF8String:(const char *)bufferPtr->content];
xmlBufferFree(bufferPtr);
return result;
}
else
{
NSMutableString *resTmp = [NSMutableString stringWithUTF8String:(const char *)bufferPtr->content];
CFStringTrimWhitespace((__bridge CFMutableStringRef)resTmp);
xmlBufferFree(bufferPtr);
return [resTmp copy];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XPath/XQuery
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlXPathContextPtr xpathCtx;
xmlXPathObjectPtr xpathObj;
BOOL isTempDoc = NO;
xmlDocPtr doc;
if (IsXmlDocPtr(genericPtr))
{
doc = (xmlDocPtr)genericPtr;
}
else if (IsXmlNodePtr(genericPtr))
{
doc = ((xmlNodePtr)genericPtr)->doc;
if(doc == NULL)
{
isTempDoc = YES;
doc = xmlNewDoc(NULL);
xmlDocSetRootElement(doc, (xmlNodePtr)genericPtr);
}
}
else
{
return nil;
}
xpathCtx = xmlXPathNewContext(doc);
xpathCtx->node = (xmlNodePtr)genericPtr;
xmlNodePtr rootNode = (doc)->children;
if(rootNode != NULL)
{
xmlNsPtr ns = rootNode->nsDef;
while(ns != NULL)
{
xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href);
ns = ns->next;
}
}
xpathObj = xmlXPathEvalExpression([xpath xmlChar], xpathCtx);
NSArray *result;
if(xpathObj == NULL)
{
if(error) *error = [[self class] lastError];
result = nil;
}
else
{
if(error) *error = nil;
int count = xmlXPathNodeSetGetLength(xpathObj->nodesetval);
if(count == 0)
{
result = [NSArray array];
}
else
{
NSMutableArray *mResult = [NSMutableArray arrayWithCapacity:count];
int i;
for (i = 0; i < count; i++)
{
xmlNodePtr node = xpathObj->nodesetval->nodeTab[i];
[mResult addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node owner:self]];
}
result = mResult;
}
}
if(xpathObj) xmlXPathFreeObject(xpathObj);
if(xpathCtx) xmlXPathFreeContext(xpathCtx);
if (isTempDoc)
{
xmlUnlinkNode((xmlNodePtr)genericPtr);
xmlFreeDoc(doc);
// xmlUnlinkNode doesn't remove the doc ptr
[[self class] recursiveStripDocPointersFromNode:(xmlNodePtr)genericPtr];
}
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Private API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ---------- MEMORY MANAGEMENT ARCHITECTURE ----------
//
// KissXML is designed to be read-access thread-safe.
// It is not write-access thread-safe as this would require significant overhead.
//
// What exactly does read-access thread-safe mean?
// It means that multiple threads can safely read from the same xml structure,
// so long as none of them attempt to alter the xml structure (add/remove nodes, change attributes, etc).
//
// This read-access thread-safety includes parsed xml structures as well as xml structures created by you.
// Let's walk through a few examples to get a deeper understanding.
//
//
//
// Example #1 - Parallel processing of children
//
// DDXMLElement *root = [[DDXMLElement alloc] initWithXMLString:str error:nil];
// NSArray *children = [root children];
//
// dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// dispatch_apply([children count], q, ^(size_t i) {
// DDXMLElement *child = [children objectAtIndex:i];
// <process child>
// });
//
//
//
// Example #2 - Asynchronous child processing
//
// DDXMLElement *root = [[DDXMLElement alloc] initWithXMLString:str error:nil];
// DDXMLElement *child = [root elementForName:@"starbucks"];
//
// dispatch_async(queue, ^{
// <process child>
// });
//
// [root release];
//
// You may have noticed that we possibly released the root node before the child was processed.
// Is this safe?
//
// The answer is YES.
// The child node retains a reference to the root node,
// so the xml tree heirarchy won't be freed until you're done using all associated nodes.
//
//
//
/**
* Returns whether or not the node has a parent.
* Use this method instead of parent when you only need to ensure parent is nil.
* This prevents the unnecessary creation of a parent node wrapper.
**/
- (BOOL)_hasParent
{
// This is a private/internal method
// Note: DDXMLNamespaceNode overrides this method
xmlStdPtr node = (xmlStdPtr)genericPtr;
return (node->parent != NULL);
}
+ (void)stripDocPointersFromAttr:(xmlAttrPtr)attr
{
xmlNodePtr child = attr->children;
while (child != NULL)
{
child->doc = NULL;
child = child->next;
}
attr->doc = NULL;
}
+ (void)recursiveStripDocPointersFromNode:(xmlNodePtr)node
{
xmlAttrPtr attr = node->properties;
while (attr != NULL)
{
[self stripDocPointersFromAttr:attr];
attr = attr->next;
}
xmlNodePtr child = node->children;
while (child != NULL)
{
[self recursiveStripDocPointersFromNode:child];
child = child->next;
}
node->doc = NULL;
}
/**
* If node->ns is pointing to the given ns, the pointer is nullified.
* The same goes for any attributes and childrend of node.
**/
+ (void)recursiveStripNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node
{
if (node->ns == ns)
{
node->ns = NULL;
}
xmlAttrPtr attr = node->properties;
while (attr)
{
if (attr->ns == ns)
{
attr->ns = NULL;
}
attr = attr->next;
}
xmlNodePtr child = node->children;
while (child)
{
[self recursiveStripNamespace:ns fromNode:child];
child = child->next;
}
}
/**
* If node or any of its attributes or children are referencing the the given old namespace,
* they are migrated to reference the new namespace instead.
*
* If newNs is NULL, and a reference to oldNs is found, then oldNs is copied,
* and the copy is used for the remainder of the recursion.
*
* This method makes copies of oldNs as needed so that oldNs can be cleanly detached from the tree.
**/
+ (void)recursiveMigrateNamespace:(xmlNsPtr)oldNs to:(xmlNsPtr)newNs node:(xmlNodePtr)node
{
// Do we need to copy old namespace?
if (newNs == NULL)
{
// A copy needs to be made if:
// * node->ns == oldNs
// * attr->ns == oldNs
//
// Remember: The namespaces in node->nsDef are owned by node.
// That's not what we're migrating.
BOOL needsCopy = (node->ns == oldNs);
if (!needsCopy)
{
xmlAttrPtr attr = node->properties;
while (attr)
{
if (attr->ns == oldNs)
{
needsCopy = YES;
break;
}
attr = attr->next;
}
}
if (needsCopy)
{
// Copy oldNs, and place at the end of the node's namspace list
newNs = xmlNewNs(NULL, oldNs->href, oldNs->prefix);
if (node->nsDef == NULL)
{
node->nsDef = newNs;
}
else
{
xmlNsPtr lastNs = node->nsDef;
while (lastNs->next)
{
lastNs = lastNs->next;
}
lastNs->next = newNs;
}
}
}
// Migrate node & attributes
if (newNs)
{
if (node->ns == oldNs)
{
node->ns = newNs;
}
xmlAttrPtr attr = node->properties;
while (attr)
{
if (attr->ns == oldNs)
{
attr->ns = newNs;
}
attr = attr->next;
}
}
// Migrate children
xmlNodePtr child = node->children;
while (child)
{
[self recursiveMigrateNamespace:oldNs to:newNs node:child];
child = child->next;
}
}
/**
* If node has a default namespace allocated outside the given root,
* this method copies the namespace so that the given root can be cleanly detached from its tree.
**/
+ (void)recursiveFixDefaultNamespacesInNode:(xmlNodePtr)node withNewRoot:(xmlNodePtr)rootNode
{
NSAssert(rootNode != NULL, @"Must specify rootNode.");
// Step 1 of 3
//
// Copy our namespace.
// It's important to do this first (before the other steps).
// This way attributes and children can reference our copy. (prevents multiple copies throughout tree)
xmlNsPtr nodeNs = node->ns;
if (nodeNs)
{
// Does the namespace reside within the new root somewhere?
// We can find out by searching for it in nsDef lists up to the given root.
BOOL nsResidesWithinNewRoot = NO;
xmlNodePtr treeNode = node;
while (treeNode)
{
xmlNsPtr treeNs = treeNode->nsDef;
while (treeNs)
{
if (treeNs == nodeNs)
{
nsResidesWithinNewRoot = YES;
break;
}
treeNs = treeNs->next;
}
if (nsResidesWithinNewRoot || treeNode == rootNode)
treeNode = NULL;
else
treeNode = treeNode->parent;
}
if (!nsResidesWithinNewRoot)
{
// Create a copy of the namespace, add to nsDef list, and then set as ns
xmlNsPtr nodeNsCopy = xmlNewNs(NULL, nodeNs->href, nodeNs->prefix);
nodeNsCopy->next = node->nsDef;
node->nsDef = nodeNsCopy;
node->ns = nodeNsCopy;
}
}
// Step 2 of 3
//
// If any attributes are referencing namespaces outside the new root,
// copy the namespaces into node, and have the attributes reference the copy.
xmlAttrPtr attr = node->properties;
while (attr)
{
xmlNsPtr attrNs = attr->ns;
while (attrNs)
{
BOOL nsResidesWithinNewRoot = NO;
xmlNodePtr treeNode = node;
while (treeNode)
{
xmlNsPtr treeNs = treeNode->nsDef;
while (treeNs)
{
if (treeNs == attrNs)
{
nsResidesWithinNewRoot = YES;
break;
}
treeNs = treeNs->next;
}
if (nsResidesWithinNewRoot || treeNode == rootNode)
treeNode = NULL;
else
treeNode = treeNode->parent;
}
if (!nsResidesWithinNewRoot)
{
// Create a copy of the namespace, add to node's nsDef list, and then set as attribute's ns
xmlNsPtr attrNsCopy = xmlNewNs(NULL, attrNs->href, attrNs->prefix);
attrNsCopy->next = node->nsDef;
node->nsDef = attrNsCopy;
attr->ns = attrNsCopy;
}
attrNs = attrNs->next;
}
attr = attr->next;
}
// Step 3 of 3
//
// Copy namespaces into children
xmlNodePtr childNode = node->children;
while (childNode)
{
[self recursiveFixDefaultNamespacesInNode:childNode withNewRoot:rootNode];
childNode = childNode->next;
}
}
/**
* Detaches the given namespace from the given node.
* The namespace's surrounding next pointers are properly updated to remove the namespace from the node's nsDef list.
* Then the namespace's parent and next pointers are destroyed.
**/
+ (void)detachNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node
{
// If node, or any of node's attributes are referring to this namespace,
// then we need to nullify those references.
//
// However, if any children are referring to this namespace,
// then we instruct those children to make copies.
if (node->ns == ns)
{
node->ns = NULL;
}
xmlAttrPtr attr = node->properties;
while (attr)
{
if (attr->ns == ns)
{
attr->ns = NULL;
}
attr = attr->next;
}
xmlNodePtr child = node->children;
while (child)
{
[self recursiveMigrateNamespace:ns to:NULL node:child];
child = child->next;
}
// Now detach namespace from the namespace list.
//
// Namespace nodes have no previous pointer, so we have to search for the node
xmlNsPtr previousNs = NULL;
xmlNsPtr currentNs = node->nsDef;
while (currentNs != NULL)
{
if (currentNs == ns)
{
if (previousNs == NULL)
node->nsDef = currentNs->next;
else
previousNs->next = currentNs->next;
break;
}
previousNs = currentNs;
currentNs = currentNs->next;
}
// Nullify pointers
ns->next = NULL;
}
/**
* Removes the given namespace from the given node.
* The namespace's surrounding next pointers are properly updated to remove the namespace from the nsDef list.
* Then the namespace is freed if it's no longer being referenced.
* Otherwise, it's nsParent and next pointers are destroyed.
**/
+ (void)removeNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node
{
#if DDXML_DEBUG_MEMORY_ISSUES
MarkZombies(ns);
#endif
[self detachNamespace:ns fromNode:node];
xmlFreeNs(ns);
}
/**
* Removes all namespaces from the given node.
* All namespaces are either freed, or their nsParent and next pointers are properly destroyed.
* Upon return, the given node's nsDef pointer is NULL.
**/
+ (void)removeAllNamespacesFromNode:(xmlNodePtr)node
{
xmlNsPtr ns = node->nsDef;
while (ns != NULL)
{
xmlNsPtr nextNs = ns->next;
#if DDXML_DEBUG_MEMORY_ISSUES
MarkZombies(ns);
#endif
xmlFreeNs(ns);
ns = nextNs;
}
node->nsDef = NULL;
node->ns = NULL;
}
/**
* Detaches the given attribute from its parent node.
* The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list.
* Then, if the clean flag is YES, the attribute's parent, prev, next and doc pointers are set to null.
**/
+ (void)detachAttribute:(xmlAttrPtr)attr andClean:(BOOL)clean
{
xmlNodePtr parent = attr->parent;
// Update the surrounding prev/next pointers
if (attr->prev == NULL)
{
if (attr->next == NULL)
{
parent->properties = NULL;
}
else
{
parent->properties = attr->next;
attr->next->prev = NULL;
}
}
else
{
if (attr->next == NULL)
{
attr->prev->next = NULL;
}
else
{
attr->prev->next = attr->next;
attr->next->prev = attr->prev;
}
}
if (clean)
{
// Nullify pointers
attr->parent = NULL;
attr->prev = NULL;
attr->next = NULL;
attr->ns = NULL;
if (attr->doc != NULL) [self stripDocPointersFromAttr:attr];
}
}
/**
* Detaches the given attribute from its parent node.
* The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list.
* Then the attribute's parent, prev, next and doc pointers are destroyed.
**/
+ (void)detachAttribute:(xmlAttrPtr)attr
{
[self detachAttribute:attr andClean:YES];
}
/**
* Removes and free's the given attribute from its parent node.
* The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list.
**/
+ (void)removeAttribute:(xmlAttrPtr)attr
{
#if DDXML_DEBUG_MEMORY_ISSUES
MarkZombies(attr);
#endif
// We perform a bit of optimization here.
// No need to bother nullifying pointers since we're about to free the node anyway.
[self detachAttribute:attr andClean:NO];
xmlFreeProp(attr);
}
/**
* Removes and frees all attributes from the given node.
* Upon return, the given node's properties pointer is NULL.
**/
+ (void)removeAllAttributesFromNode:(xmlNodePtr)node
{
xmlAttrPtr attr = node->properties;
while (attr != NULL)
{
xmlAttrPtr nextAttr = attr->next;
#if DDXML_DEBUG_MEMORY_ISSUES
MarkZombies(attr);
#endif
xmlFreeProp(attr);
attr = nextAttr;
}
node->properties = NULL;
}
/**
* Detaches the given child from its parent.
* The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list.
* Then, if the clean flag is YES, the child's parent, prev, next and doc pointers are set to null.
**/
+ (void)detachChild:(xmlNodePtr)child andClean:(BOOL)clean andFixNamespaces:(BOOL)fixNamespaces
{
xmlNodePtr parent = child->parent;
// Update the surrounding prev/next pointers
if (child->prev == NULL)
{
if (child->next == NULL)
{
parent->children = NULL;
parent->last = NULL;
}
else
{
parent->children = child->next;
child->next->prev = NULL;
}
}
else
{
if (child->next == NULL)
{
parent->last = child->prev;
child->prev->next = NULL;
}
else
{
child->prev->next = child->next;
child->next->prev = child->prev;
}
}
if (fixNamespaces)
{
// Fix namesapces (namespace references that now point outside tree)
// Note: This must be done before we nullify pointers so we can search up the tree.
[self recursiveFixDefaultNamespacesInNode:child withNewRoot:child];
}
if (clean)
{
// Nullify pointers
child->parent = NULL;
child->prev = NULL;
child->next = NULL;
if (child->doc != NULL) [self recursiveStripDocPointersFromNode:child];
}
}
/**
* Detaches the given child from its parent.
* The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list.
* Then the child's parent, prev, next and doc pointers are set to null.
**/
+ (void)detachChild:(xmlNodePtr)child
{
[self detachChild:child andClean:YES andFixNamespaces:YES];
}
/**
* Removes the given child from its parent node.
* The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list.
* Then the child is recursively freed if it's no longer being referenced.
* Otherwise, it's parent, prev, next and doc pointers are destroyed.
*
* During the recursive free, subnodes still being referenced are properly handled.
**/
+ (void)removeChild:(xmlNodePtr)child
{
#if DDXML_DEBUG_MEMORY_ISSUES
RecursiveMarkZombiesFromNode(child);
#endif
// We perform a bit of optimization here.
// No need to bother nullifying pointers since we're about to free the node anyway.
[self detachChild:child andClean:NO andFixNamespaces:NO];
xmlFreeNode(child);
}
/**
* Removes all children from the given node.
* All children are either recursively freed, or their parent, prev, next and doc pointers are properly destroyed.
* Upon return, the given node's children pointer is NULL.
*
* During the recursive free, subnodes still being referenced are properly handled.
**/
+ (void)removeAllChildrenFromNode:(xmlNodePtr)node
{
xmlNodePtr child = node->children;
while (child != NULL)
{
xmlNodePtr nextChild = child->next;
#if DDXML_DEBUG_MEMORY_ISSUES
RecursiveMarkZombiesFromNode(child);
#endif
xmlFreeNode(child);
child = nextChild;
}
node->children = NULL;
node->last = NULL;
}
/**
* Returns the last error encountered by libxml.
* Errors are caught in the MyErrorHandler method within DDXMLDocument.
**/
+ (NSError *)lastError
{
NSValue *lastErrorValue = [[[NSThread currentThread] threadDictionary] objectForKey:DDLastErrorKey];
if(lastErrorValue)
{
xmlError lastError;
[lastErrorValue getValue:&lastError];
int errCode = lastError.code;
NSString *errMsg = [[NSString stringWithFormat:@"%s", lastError.message] stringByTrimming];
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:@"DDXMLErrorDomain" code:errCode userInfo:info];
}
else
{
return nil;
}
}
static void MyErrorHandler(void * userData, xmlErrorPtr error)
{
// This method is called by libxml when an error occurs.
// We register for this error in the initialize method below.
// Extract error message and store in the current thread's dictionary.
// This ensure's thread safey, and easy access for all other DDXML classes.
if (error == NULL)
{
[[[NSThread currentThread] threadDictionary] removeObjectForKey:DDLastErrorKey];
}
else
{
NSValue *errorValue = [NSValue valueWithBytes:error objCType:@encode(xmlError)];
[[[NSThread currentThread] threadDictionary] setObject:errorValue forKey:DDLastErrorKey];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Zombie Tracking
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if DDXML_DEBUG_MEMORY_ISSUES
// What is zombie tracking and how does it work?
//
// It is all explained in full detail here:
// https://github.com/robbiehanson/KissXML/wiki/MemoryManagementThreadSafety
//
// But here's a quick overview in case you're on a plane right now
// (and the plane doesn't have internet access, or charges some ridiculous amount and you don't want to pay for it.)
//
// <starbucks>
// <latte/>
// <cappuchino/>
// </starbucks>
//
// You have a reference to the latte node, and you release/dealloc the starbucks node. Uh oh!
// The latte node is now a zombie, since the xmlNode it was pointing to (wrapping) is now gone.
// If you attempt to read info from the latte node, you might get a crash.
// Or you might get junk results.
// And if you attempt to write info to the latte node, you might just wind up with some ugly heap corruption.
// And if this happens, well.. it's a huge P.I.T.A to track down.
//
// But I've been there before. And I feel your pain. That's where this debug option comes in.
//
// The debugging option keeps a dictionary where the keys are the xml pointers (xmlNodePtr, xmlAttrPtr, etc),
// and the values are mutable arrays. Any wrapper objects (DDXMLElement, DDXMLNode, etc) get added to the
// mutable array for which the wrapper is pointing.
//
// If the xml (xmlNodePtr, xmlAttrPtr, etc) is to be freed, it first removes its key from the dictionary,
// and in doing so destroys any associated mutable array.
//
// So a zombie check ensures that the xml structure the wrapper is referring to hasn't been freed.
// If it has an exception is thrown to help track down the problem.
//
// In other words, if you try to read info from the latte node, or attempt to alter the latte node
// (after you release/dealloc starbucks), you'll immediately get a helpful exception.
// (Goodbye junk values and heap corruption.)
//
// This is helpful in debugging, as it is sometimes easy to forget about the memory rules of the xml heirarchy.
// Or simply due to combinations of passing subelements around and using asynchronous operations.
static void RecursiveMarkZombiesFromNode(xmlNodePtr node)
{
// This method only exists if DDXML_DEBUG_MEMORY_ISSUES is enabled.
// Mark attributes
xmlAttrPtr attr = node->properties;
while (attr != NULL)
{
MarkZombies(attr);
attr = attr->next;
}
// Mark namespaces
xmlNsPtr ns = node->nsDef;
while (ns != NULL)
{
MarkZombies(ns);
ns = ns->next;
}
if (node->ns)
{
MarkZombies(node->ns);
}
// Recursively mark children
xmlNodePtr child = node->children;
while (child != NULL)
{
RecursiveMarkZombiesFromNode(child);
child = child->next;
}
MarkZombies(node);
}
static void RecursiveMarkZombiesFromDoc(xmlDocPtr doc)
{
// This method only exists if DDXML_DEBUG_MEMORY_ISSUES is enabled.
xmlNodePtr child = doc->children;
while (child != NULL)
{
RecursiveMarkZombiesFromNode(child);
child = child->next;
}
MarkZombies(doc);
}
static void MarkZombies(void *xmlPtr)
{
// This method only exists if DDXML_DEBUG_MEMORY_ISSUES is enabled.
dispatch_async(zombieQueue, ^{
// NSLog(@"MarkZombies: %p", xmlPtr);
CFDictionaryRemoveValue(zombieTracker, xmlPtr);
});
}
static void MarkBirth(void *xmlPtr, DDXMLNode *wrapper)
{
// This method only exists if DDXML_DEBUG_MEMORY_ISSUES is enabled.
const void *value = (void *)wrapper;
dispatch_async(zombieQueue, ^{
// NSLog(@"MarkBirth: %p, %p", xmlPtr, value);
CFMutableArrayRef values = (CFMutableArrayRef)CFDictionaryGetValue(zombieTracker, xmlPtr);
if (values == NULL)
{
values = CFArrayCreateMutable(NULL, /*MaxCapacity:*/0, /*ValueCallbacks:*/NULL);
CFArrayAppendValue(values, value);
CFDictionarySetValue(zombieTracker, xmlPtr, values);
CFRelease(values);
}
else
{
CFArrayAppendValue(values, value);
}
});
}
static void MarkDeath(void *xmlPtr, DDXMLNode *wrapper)
{
// This method only exists if DDXML_DEBUG_MEMORY_ISSUES is enabled.
const void *value = (void *)wrapper;
dispatch_async(zombieQueue, ^{
// NSLog(@"MarkDeath: %p, %p", xmlPtr, value);
CFMutableArrayRef values = (CFMutableArrayRef)CFDictionaryGetValue(zombieTracker, xmlPtr);
if (values)
{
CFRange range = CFRangeMake(0, CFArrayGetCount(values));
CFIndex index = CFArrayGetFirstIndexOfValue(values, range, value);
if (index >= 0)
{
CFArrayRemoveValueAtIndex(values, index);
}
}
});
}
BOOL DDXMLIsZombie(void *xmlPtr, DDXMLNode *wrapper)
{
// This method only exists if DDXML_DEBUG_MEMORY_ISSUES is enabled.
__block BOOL result;
const void *value = (void *)wrapper;
dispatch_sync(zombieQueue, ^{
CFMutableArrayRef values = (CFMutableArrayRef)CFDictionaryGetValue(zombieTracker, xmlPtr);
if (values)
{
CFRange range = CFRangeMake(0, CFArrayGetCount(values));
CFIndex index = CFArrayGetFirstIndexOfValue(values, range, value);
result = (index < 0);
}
else
{
result = YES;
}
});
return result;
}
#endif
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation DDXMLNamespaceNode
/**
* Returns a DDXML wrapper object for the given primitive node.
* The given node MUST be non-NULL and of the proper type.
**/
+ (id)nodeWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent owner:(DDXMLNode *)owner
{
return [[DDXMLNamespaceNode alloc] initWithNsPrimitive:ns nsParent:parent owner:owner];
}
/**
* Returns a DDXML wrapper object for the given primitive node.
* The given node MUST be non-NULL and of the proper type.
**/
- (id)initWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent owner:(DDXMLNode *)inOwner
{
if ((self = [super initWithPrimitive:(xmlKindPtr)ns owner:inOwner]))
{
nsParentPtr = parent;
}
return self;
}
+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes.
NSAssert(NO, @"Use nodeWithNsPrimitive:nsParent:owner:");
return nil;
}
- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)inOwner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes.
NSAssert(NO, @"Use initWithNsPrimitive:nsParent:owner:");
return nil;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)setName:(NSString *)name
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlNsPtr ns = (xmlNsPtr)genericPtr;
xmlFree((xmlChar *)ns->prefix);
ns->prefix = xmlStrdup([name xmlChar]);
}
- (NSString *)name
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlNsPtr ns = (xmlNsPtr)genericPtr;
if (ns->prefix != NULL)
return [NSString stringWithUTF8String:((const char*)ns->prefix)];
else
return @"";
}
- (void)setStringValue:(NSString *)string
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlNsPtr ns = (xmlNsPtr)genericPtr;
xmlFree((xmlChar *)ns->href);
ns->href = xmlEncodeSpecialChars(NULL, [string xmlChar]);
}
- (NSString *)stringValue
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return [NSString stringWithUTF8String:((const char *)((xmlNsPtr)genericPtr)->href)];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Tree Navigation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSUInteger)index
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlNsPtr ns = (xmlNsPtr)genericPtr;
// The xmlNsPtr has no prev pointer, so we have to search from the parent
if (nsParentPtr == NULL)
{
return 0;
}
NSUInteger result = 0;
xmlNsPtr currentNs = nsParentPtr->nsDef;
while (currentNs != NULL)
{
if (currentNs == ns)
{
return result;
}
result++;
currentNs = currentNs->next;
}
return 0; // Yes 0, not result, because ns wasn't found in list
}
- (NSUInteger)level
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
NSUInteger result = 0;
xmlNodePtr currentNode = nsParentPtr;
while (currentNode != NULL)
{
result++;
currentNode = currentNode->parent;
}
return result;
}
- (DDXMLDocument *)rootDocument
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlStdPtr node = (xmlStdPtr)nsParentPtr;
if (node == NULL || node->doc == NULL)
return nil;
else
return [DDXMLDocument nodeWithDocPrimitive:node->doc owner:self];
}
- (DDXMLNode *)parent
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (nsParentPtr == NULL)
return nil;
else
return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)nsParentPtr owner:self];
}
- (NSUInteger)childCount
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return 0;
}
- (NSArray *)children
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
- (DDXMLNode *)childAtIndex:(NSUInteger)index
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
- (DDXMLNode *)previousSibling
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
- (DDXMLNode *)nextSibling
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
- (DDXMLNode *)previousNode
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
- (DDXMLNode *)nextNode
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
- (void)detach
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (nsParentPtr != NULL)
{
[DDXMLNode detachNamespace:(xmlNsPtr)genericPtr fromNode:nsParentPtr];
owner = nil;
nsParentPtr = NULL;
}
}
- (xmlStdPtr)_XPathPreProcess:(NSMutableString *)result
{
// This is a private/internal method
xmlStdPtr parent = (xmlStdPtr)nsParentPtr;
if (parent == NULL)
[result appendFormat:@"namespace::%@", [self name]];
else
[result appendFormat:@"/namespace::%@", [self name]];
return parent;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark QNames
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSString *)localName
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// Strangely enough, the localName of a namespace is the prefix, and the prefix is an empty string
xmlNsPtr ns = (xmlNsPtr)genericPtr;
if (ns->prefix != NULL)
return [NSString stringWithUTF8String:((const char *)ns->prefix)];
else
return @"";
}
- (NSString *)prefix
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// Strangely enough, the localName of a namespace is the prefix, and the prefix is an empty string
return @"";
}
- (void)setURI:(NSString *)URI
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// Do nothing
}
- (NSString *)URI
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Private API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)_hasParent
{
// This is a private/internal method
return (nsParentPtr != NULL);
}
- (xmlNodePtr)_nsParentPtr
{
// This is a private/internal method
return nsParentPtr;
}
- (void)_setNsParentPtr:(xmlNodePtr)parentPtr
{
// This is a private/internal method
nsParentPtr = parentPtr;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation DDXMLAttributeNode
+ (id)nodeWithAttrPrimitive:(xmlAttrPtr)attr owner:(DDXMLNode *)owner
{
return [[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr owner:owner];
}
- (id)initWithAttrPrimitive:(xmlAttrPtr)attr owner:(DDXMLNode *)inOwner
{
self = [super initWithPrimitive:(xmlKindPtr)attr owner:inOwner];
return self;
}
+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes.
NSAssert(NO, @"Use nodeWithAttrPrimitive:nsParent:owner:");
return nil;
}
- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)inOwner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes.
NSAssert(NO, @"Use initWithAttrPrimitive:nsParent:owner:");
return nil;
}
- (void)dealloc
{
if (attrNsPtr) xmlFreeNs(attrNsPtr);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSString *)name
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
const xmlChar *xmlName = attr->name;
if (xmlName == NULL)
{
return nil;
}
NSString *name = [NSString stringWithUTF8String:(const char *)xmlName];
NSRange range = [name rangeOfString:@":"];
if (range.length == 0)
{
if (attr->ns && attr->ns->prefix)
{
return [NSString stringWithFormat:@"%s:%@", attr->ns->prefix, name];
}
}
return name;
}
- (void)setStringValue:(NSString *)string
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
if (attr->children != NULL)
{
xmlChar *escapedString = xmlEncodeSpecialChars(attr->doc, [string xmlChar]);
xmlNodeSetContent((xmlNodePtr)attr, escapedString);
xmlFree(escapedString);
}
else
{
xmlNodePtr text = xmlNewText([string xmlChar]);
attr->children = text;
}
}
- (NSString *)stringValue
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
if (attr->children != NULL)
{
return [NSString stringWithUTF8String:(const char *)attr->children->content];
}
return nil;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Tree Navigation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (DDXMLNode *)previousNode
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
- (DDXMLNode *)nextNode
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
return nil;
}
- (void)detach
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
if (attr->parent != NULL)
{
// If this attribute is associated with a namespace,
// then we need to copy the namespace in order to maintain the association.
//
// Remember: attr->ns cannot be an owner of an allocated namespaces,
// so we need to use DDXMLAttributeNode's attrNsPtr.
if (attr->ns && (attr->ns != attrNsPtr))
{
attrNsPtr = xmlNewNs(NULL, attr->ns->href, attr->ns->prefix);
}
[[self class] detachAttribute:attr];
if (attrNsPtr)
{
attr->ns = attrNsPtr;
}
owner = nil;
}
}
- (xmlStdPtr)_XPathPreProcess:(NSMutableString *)result
{
// This is a private/internal method
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
xmlStdPtr parent = (xmlStdPtr)attr->parent;
if (parent == NULL)
[result appendFormat:@"@%@", [self name]];
else
[result appendFormat:@"/@%@", [self name]];
return parent;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark QNames
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)setURI:(NSString *)URI
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// An attribute can only have a single namespace attached to it.
// In addition, this namespace can only be accessed via the URI method.
// There is no way, within the API, to get a DDXMLNode wrapper for the attribute's namespace.
// Remember: attr->ns is simply a pointer to a namespace owned by somebody else.
// Unless that points to our attrNsPtr (defined in DDXMLAttributeNode) we cannot free it.
if (attrNsPtr != NULL)
{
xmlFreeNs(attrNsPtr);
attrNsPtr = NULL;
}
xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
attr->ns = NULL;
if (URI)
{
// If there's a namespace defined further up the tree with this URI,
// then we want attr->ns to point to it.
const xmlChar *uri = [URI xmlChar];
xmlNodePtr parent = attr->parent;
while (parent)
{
xmlNsPtr ns = parent->nsDef;
while (ns)
{
if (xmlStrEqual(ns->href, uri))
{
attr->ns = ns;
return;
}
ns = ns->next;
}
parent = parent->parent;
}
// There is no namespace further up the tree with this URI.
// We'll have to create it ourself...
//
// Remember: The attr->ns pointer is not allowed to have direct ownership.
attrNsPtr = xmlNewNs(NULL, uri, NULL);
attr->ns = attrNsPtr;
}
}
- (NSString *)URI
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
if (attr->ns != NULL)
{
if (attr->ns->href != NULL)
{
return [NSString stringWithUTF8String:((const char *)attr->ns->href)];
}
}
// The attribute doesn't explicitly have a namespace.
// But if the attribute is something like animal:duck='quack', then we should look for the URI for 'animal'.
//
// Note: [self prefix] returns an empty string if there is no prefix. (Not nil)
NSString *prefix = [self prefix];
if ([prefix length] > 0)
{
xmlNsPtr ns = xmlSearchNs(attr->doc, attr->parent, [prefix xmlChar]);
if (ns && ns->href)
{
return [NSString stringWithUTF8String:((const char *)ns->href)];
}
}
return nil;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation DDXMLInvalidNode
// #pragma mark Properties
- (DDXMLNodeKind)kind {
return DDXMLInvalidKind;
}
- (void)setName:(NSString *)name { }
- (NSString *)name {
return nil;
}
- (void)setObjectValue:(id)value { }
- (id)objectValue {
return nil;
}
- (void)setStringValue:(NSString *)string { }
- (void)setStringValue:(NSString *)string resolvingEntities:(BOOL)resolve { }
- (NSString *)stringValue {
return nil;
}
// #pragma mark Tree Navigation
- (NSUInteger)index {
return 0;
}
- (NSUInteger)level {
return 0;
}
- (DDXMLDocument *)rootDocument {
return nil;
}
- (DDXMLNode *)parent {
return nil;
}
- (NSUInteger)childCount {
return 0;
}
- (NSArray *)children {
return [NSArray array];
}
- (DDXMLNode *)childAtIndex:(NSUInteger)index {
return nil;
}
- (DDXMLNode *)previousSibling {
return nil;
}
- (DDXMLNode *)nextSibling {
return nil;
}
- (DDXMLNode *)previousNode {
return nil;
}
- (DDXMLNode *)nextNode {
return nil;
}
- (void)detach { }
- (NSString *)XPath {
return @"";
}
// #pragma mark QNames
- (NSString *)localName {
return nil;
}
- (NSString *)prefix {
return @"";
}
- (void)setURI:(NSString *)URI { }
- (NSString *)URI {
return nil;
}
// #pragma mark Output
- (NSString *)description {
return @"";
}
- (NSString *)XMLString {
return @"";
}
- (NSString *)XMLStringWithOptions:(NSUInteger)options {
return @"";
}
- (NSString *)canonicalXMLStringPreservingComments:(BOOL)comments {
return nil;
}
// #pragma mark XPath/XQuery
- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error {
return [NSArray array];
}
- (NSArray *)objectsForXQuery:(NSString *)xquery constants:(NSDictionary *)constants error:(NSError **)error {
return [NSArray array];
}
- (NSArray *)objectsForXQuery:(NSString *)xquery error:(NSError **)error {
return [NSArray array];
}
@end