diff --git a/Examples/CoreTest/CoreTest.xcodeproj/mikelaurence.mode1v3 b/Examples/CoreTest/CoreTest.xcodeproj/mikelaurence.mode1v3
index 5870078..f2b3ec1 100644
--- a/Examples/CoreTest/CoreTest.xcodeproj/mikelaurence.mode1v3
+++ b/Examples/CoreTest/CoreTest.xcodeproj/mikelaurence.mode1v3
@@ -267,6 +267,7 @@
B568503D1100D03C00A4C816
B56851B41100D66D00A4C816
080E96DDFE201D6D7F000001
+ B5EED674113E2009004DAE9C
B5BAC04410FFE04600D57FCA
B5BAC0D610FFE1EF00D57FCA
29B97315FDCFA39411CA2CEA
@@ -280,13 +281,14 @@
PBXSmartGroupTreeModuleOutlineStateSelectionKey
- 7
- 3
+ 32
+ 27
+ 26
0
PBXSmartGroupTreeModuleOutlineStateVisibleRectKey
- {{0, 0}, {317, 904}}
+ {{0, 274}, {317, 904}}
PBXTopSmartGroupGIDs
@@ -335,7 +337,7 @@
_historyCapacity
0
bookmark
- B55BE55B11536CA20059E974
+ B55D1F8C11540406009C8B4A
history
B56851741100D26100A4C816
@@ -407,7 +409,7 @@
B58014BF1153691D00DA5921
B58014DE115369E700DA5921
B55BE55911536CA20059E974
- B55BE55A11536CA20059E974
+ B55BE55B11536CA20059E974
SplitCount
@@ -419,14 +421,14 @@
GeometryConfiguration
Frame
- {{0, 0}, {1194, 863}}
+ {{0, 0}, {1194, 862}}
RubberWindowFrame
28 60 1533 963 0 0 1680 1028
Module
PBXNavigatorGroup
Proportion
- 863pt
+ 862pt
ContentConfiguration
@@ -439,14 +441,14 @@
GeometryConfiguration
Frame
- {{0, 868}, {1194, 54}}
+ {{0, 867}, {1194, 55}}
RubberWindowFrame
28 60 1533 963 0 0 1680 1028
Module
XCDetailModule
Proportion
- 54pt
+ 55pt
Proportion
@@ -465,9 +467,9 @@
TableOfContents
- B55BE55C11536CA20059E974
+ B55D1F89115403F2009C8B4A
1CE0B1FE06471DED0097A5F4
- B55BE55D11536CA20059E974
+ B55D1F8A115403F2009C8B4A
1CE0B20306471E060097A5F4
1CE0B20506471E060097A5F4
@@ -686,7 +688,7 @@
TableOfContents
B5BAC03F10FFE02400D57FCA
- B55BE55E11536CA20059E974
+ B55D1F8B115403F2009C8B4A
1CD0528F0623707200166675
XCMainBuildResultsModuleGUID
diff --git a/Examples/CoreTest/CoreTest.xcodeproj/mikelaurence.pbxuser b/Examples/CoreTest/CoreTest.xcodeproj/mikelaurence.pbxuser
index a38346f..b545dd1 100644
--- a/Examples/CoreTest/CoreTest.xcodeproj/mikelaurence.pbxuser
+++ b/Examples/CoreTest/CoreTest.xcodeproj/mikelaurence.pbxuser
@@ -101,14 +101,16 @@
PBXFileDataSource_Warnings_ColumnID,
);
};
- PBXPerProjectTemplateStateSaveDate = 290679945;
- PBXWorkspaceStateSaveDate = 290679945;
+ PBXPerProjectTemplateStateSaveDate = 290718696;
+ PBXWorkspaceStateSaveDate = 290718696;
};
perUserProjectItems = {
B52381A11148549900AB02EB = B52381A11148549900AB02EB /* PBXTextBookmark */;
- B55BE55911536CA20059E974 /* PBXTextBookmark */ = B55BE55911536CA20059E974 /* PBXTextBookmark */;
- B55BE55A11536CA20059E974 /* PBXTextBookmark */ = B55BE55A11536CA20059E974 /* PBXTextBookmark */;
- B55BE55B11536CA20059E974 /* PBXTextBookmark */ = B55BE55B11536CA20059E974 /* PBXTextBookmark */;
+ B55BE55911536CA20059E974 = B55BE55911536CA20059E974 /* PBXTextBookmark */;
+ B55BE55A11536CA20059E974 = B55BE55A11536CA20059E974 /* PBXTextBookmark */;
+ B55BE55B11536CA20059E974 = B55BE55B11536CA20059E974 /* PBXTextBookmark */;
+ B55D1F88115403F2009C8B4A /* PBXTextBookmark */ = B55D1F88115403F2009C8B4A /* PBXTextBookmark */;
+ B55D1F8C11540406009C8B4A /* PBXTextBookmark */ = B55D1F8C11540406009C8B4A /* PBXTextBookmark */;
B55D27041151530D004F83F6 = B55D27041151530D004F83F6 /* PBXTextBookmark */;
B55D272D11515553004F83F6 = B55D272D11515553004F83F6 /* PBXTextBookmark */;
B55D272F115155AC004F83F6 = B55D272F115155AC004F83F6 /* PBXTextBookmark */;
@@ -252,6 +254,26 @@
vrLen = 1800;
vrLoc = 2464;
};
+ B55D1F88115403F2009C8B4A /* PBXTextBookmark */ = {
+ isa = PBXTextBookmark;
+ fRef = B56418A311064CBB00D1B038 /* CoreResourceTestCase.m */;
+ name = "CoreResourceTestCase.m: 113";
+ rLen = 0;
+ rLoc = 4186;
+ rType = 0;
+ vrLen = 1801;
+ vrLoc = 2463;
+ };
+ B55D1F8C11540406009C8B4A /* PBXTextBookmark */ = {
+ isa = PBXTextBookmark;
+ fRef = B56418A311064CBB00D1B038 /* CoreResourceTestCase.m */;
+ name = "CoreResourceTestCase.m: 83";
+ rLen = 0;
+ rLoc = 2859;
+ rType = 0;
+ vrLen = 1801;
+ vrLoc = 2463;
+ };
B55D26EB11514F25004F83F6 /* TestScript.sh */ = {
uiCtxt = {
sepNavIntBoundsRect = "{{0, 0}, {1133, 786}}";
@@ -544,8 +566,8 @@
B56418A311064CBB00D1B038 /* CoreResourceTestCase.m */ = {
uiCtxt = {
sepNavIntBoundsRect = "{{0, 0}, {1236, 2318}}";
- sepNavSelRange = "{4186, 0}";
- sepNavVisRange = "{2464, 1800}";
+ sepNavSelRange = "{2859, 0}";
+ sepNavVisRange = "{2463, 1801}";
};
};
B56418A511064CFA00D1B038 /* CoreResourceTestCase.h */ = {
diff --git a/Examples/CoreTest/CoreTest.xcodeproj/project.pbxproj b/Examples/CoreTest/CoreTest.xcodeproj/project.pbxproj
index 76bc88b..0ad4154 100755
--- a/Examples/CoreTest/CoreTest.xcodeproj/project.pbxproj
+++ b/Examples/CoreTest/CoreTest.xcodeproj/project.pbxproj
@@ -315,8 +315,7 @@
B560DF841150088B00BE843A /* NSStringAdditions.h */,
B560DF851150088B00BE843A /* NSStringAdditions.m */,
);
- name = KissXML;
- path = kissxml;
+ path = KissXML;
sourceTree = "";
};
B568503D1100D03C00A4C816 /* Tests */ = {
diff --git a/Libraries/KissXML/DDXML.h b/Libraries/KissXML/DDXML.h
new file mode 100644
index 0000000..2cf21a5
--- /dev/null
+++ b/Libraries/KissXML/DDXML.h
@@ -0,0 +1,3 @@
+#import "DDXMLNode.h"
+#import "DDXMLElement.h"
+#import "DDXMLDocument.h"
diff --git a/Libraries/KissXML/DDXMLDocument.h b/Libraries/KissXML/DDXMLDocument.h
new file mode 100644
index 0000000..68f1b2e
--- /dev/null
+++ b/Libraries/KissXML/DDXMLDocument.h
@@ -0,0 +1,67 @@
+#import
+#import "DDXMLElement.h"
+#import "DDXMLNode.h"
+
+
+enum {
+ DDXMLDocumentXMLKind = 0,
+ DDXMLDocumentXHTMLKind,
+ DDXMLDocumentHTMLKind,
+ DDXMLDocumentTextKind
+};
+typedef NSUInteger DDXMLDocumentContentKind;
+
+@interface DDXMLDocument : DDXMLNode
+{
+}
+
+- (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error;
+//- (id)initWithContentsOfURL:(NSURL *)url options:(NSUInteger)mask error:(NSError **)error;
+- (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error;
+//- (id)initWithRootElement:(DDXMLElement *)element;
+
+//+ (Class)replacementClassForClass:(Class)cls;
+
+//- (void)setCharacterEncoding:(NSString *)encoding; //primitive
+//- (NSString *)characterEncoding; //primitive
+
+//- (void)setVersion:(NSString *)version;
+//- (NSString *)version;
+
+//- (void)setStandalone:(BOOL)standalone;
+//- (BOOL)isStandalone;
+
+//- (void)setDocumentContentKind:(DDXMLDocumentContentKind)kind;
+//- (DDXMLDocumentContentKind)documentContentKind;
+
+//- (void)setMIMEType:(NSString *)MIMEType;
+//- (NSString *)MIMEType;
+
+//- (void)setDTD:(DDXMLDTD *)documentTypeDeclaration;
+//- (DDXMLDTD *)DTD;
+
+//- (void)setRootElement:(DDXMLNode *)root;
+- (DDXMLElement *)rootElement;
+
+//- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index;
+
+//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index;
+
+//- (void)removeChildAtIndex:(NSUInteger)index;
+
+//- (void)setChildren:(NSArray *)children;
+
+//- (void)addChild:(DDXMLNode *)child;
+
+//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node;
+
+- (NSData *)XMLData;
+- (NSData *)XMLDataWithOptions:(NSUInteger)options;
+
+//- (id)objectByApplyingXSLT:(NSData *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error;
+//- (id)objectByApplyingXSLTString:(NSString *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error;
+//- (id)objectByApplyingXSLTAtURL:(NSURL *)xsltURL arguments:(NSDictionary *)argument error:(NSError **)error;
+
+//- (BOOL)validateAndReturnError:(NSError **)error;
+
+@end
diff --git a/Libraries/KissXML/DDXMLDocument.m b/Libraries/KissXML/DDXMLDocument.m
new file mode 100644
index 0000000..b678b5b
--- /dev/null
+++ b/Libraries/KissXML/DDXMLDocument.m
@@ -0,0 +1,111 @@
+#import "DDXMLDocument.h"
+#import "NSStringAdditions.h"
+#import "DDXMLPrivate.h"
+
+
+@implementation DDXMLDocument
+
+/**
+ * Returns a DDXML wrapper object for the given primitive node.
+ * The given node MUST be non-NULL and of the proper type.
+ *
+ * If the wrapper object already exists, it is retained/autoreleased and returned.
+ * Otherwise a new wrapper object is alloc/init/autoreleased and returned.
+**/
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr
+{
+ // If a wrapper object already exists, the _private variable is pointing to it.
+
+ xmlDocPtr doc = (xmlDocPtr)kindPtr;
+ if(doc->_private == NULL)
+ return [[[DDXMLDocument alloc] initWithCheckedPrimitive:kindPtr] autorelease];
+ else
+ return [[((DDXMLDocument *)(doc->_private)) retain] autorelease];
+}
+
+/**
+ * Returns a DDXML wrapper object for the given primitive node.
+ * The given node MUST be non-NULL and of the proper type.
+ *
+ * The given node is checked, meaning a wrapper object for it does not already exist.
+**/
+- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr
+{
+ self = [super initWithCheckedPrimitive:kindPtr];
+ return self;
+}
+
+/**
+ * Initializes and returns a DDXMLDocument object created from an NSData object.
+ *
+ * Returns an initialized DDXMLDocument object, or nil if initialization fails
+ * because of parsing errors or other reasons.
+**/
+- (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error
+{
+ return [self initWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:mask error:error];
+}
+
+/**
+ * Initializes and returns a DDXMLDocument object created from an NSData object.
+ *
+ * Returns an initialized DDXMLDocument object, or nil if initialization fails
+ * because of parsing errors or other reasons.
+**/
+- (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error
+{
+ if(data == nil || [data length] == 0)
+ {
+ if(error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:0 userInfo:nil];
+
+ [self release];
+ return nil;
+ }
+
+ // Even though xmlKeepBlanksDefault(0) is called in DDXMLNode's initialize method,
+ // it has been documented that this call seems to get reset on the iPhone:
+ // http://code.google.com/p/kissxml/issues/detail?id=8
+ //
+ // Therefore, we call it again here just to be safe.
+ xmlKeepBlanksDefault(0);
+
+ xmlDocPtr doc = xmlParseMemory([data bytes], [data length]);
+ if(doc == NULL)
+ {
+ if(error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:1 userInfo:nil];
+
+ [self release];
+ return nil;
+ }
+
+ return [self initWithCheckedPrimitive:(xmlKindPtr)doc];
+}
+
+/**
+ * Returns the root element of the receiver.
+**/
+- (DDXMLElement *)rootElement
+{
+ xmlDocPtr doc = (xmlDocPtr)genericPtr;
+
+ // doc->children is a list containing possibly comments, DTDs, etc...
+
+ xmlNodePtr rootNode = xmlDocGetRootElement(doc);
+
+ if(rootNode != NULL)
+ return [DDXMLElement nodeWithPrimitive:(xmlKindPtr)rootNode];
+ else
+ return nil;
+}
+
+- (NSData *)XMLData
+{
+ return [[self XMLString] dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+- (NSData *)XMLDataWithOptions:(NSUInteger)options
+{
+ return [[self XMLStringWithOptions:options] dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+@end
diff --git a/Libraries/KissXML/DDXMLElement.h b/Libraries/KissXML/DDXMLElement.h
new file mode 100644
index 0000000..8b50789
--- /dev/null
+++ b/Libraries/KissXML/DDXMLElement.h
@@ -0,0 +1,49 @@
+#import
+#import "DDXMLNode.h"
+
+
+@interface DDXMLElement : DDXMLNode
+{
+}
+
+- (id)initWithName:(NSString *)name;
+- (id)initWithName:(NSString *)name URI:(NSString *)URI;
+- (id)initWithName:(NSString *)name stringValue:(NSString *)string;
+- (id)initWithXMLString:(NSString *)string error:(NSError **)error;
+
+#pragma mark --- Elements by name ---
+
+- (NSArray *)elementsForName:(NSString *)name;
+- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI;
+
+#pragma mark --- Attributes ---
+
+- (void)addAttribute:(DDXMLNode *)attribute;
+- (void)removeAttributeForName:(NSString *)name;
+- (void)setAttributes:(NSArray *)attributes;
+//- (void)setAttributesAsDictionary:(NSDictionary *)attributes;
+- (NSArray *)attributes;
+- (DDXMLNode *)attributeForName:(NSString *)name;
+//- (DDXMLNode *)attributeForLocalName:(NSString *)localName URI:(NSString *)URI;
+
+#pragma mark --- Namespaces ---
+
+- (void)addNamespace:(DDXMLNode *)aNamespace;
+- (void)removeNamespaceForPrefix:(NSString *)name;
+- (void)setNamespaces:(NSArray *)namespaces;
+- (NSArray *)namespaces;
+- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix;
+- (DDXMLNode *)resolveNamespaceForName:(NSString *)name;
+- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI;
+
+#pragma mark --- Children ---
+
+- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index;
+//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index;
+- (void)removeChildAtIndex:(NSUInteger)index;
+- (void)setChildren:(NSArray *)children;
+- (void)addChild:(DDXMLNode *)child;
+//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node;
+//- (void)normalizeAdjacentTextNodesPreservingCDATA:(BOOL)preserve;
+
+@end
diff --git a/Libraries/KissXML/DDXMLElement.m b/Libraries/KissXML/DDXMLElement.m
new file mode 100644
index 0000000..591f576
--- /dev/null
+++ b/Libraries/KissXML/DDXMLElement.m
@@ -0,0 +1,610 @@
+#import "DDXMLElement.h"
+#import "NSStringAdditions.h"
+#import "DDXMLPrivate.h"
+
+
+@implementation DDXMLElement
+
+/**
+ * Returns a DDXML wrapper object for the given primitive node.
+ * The given node MUST be non-NULL and of the proper type.
+ *
+ * If the wrapper object already exists, it is retained/autoreleased and returned.
+ * Otherwise a new wrapper object is alloc/init/autoreleased and returned.
+**/
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr
+{
+ // If a wrapper object already exists, the _private variable is pointing to it.
+
+ xmlNodePtr node = (xmlNodePtr)kindPtr;
+ if(node->_private == NULL)
+ return [[[DDXMLElement alloc] initWithCheckedPrimitive:kindPtr] autorelease];
+ else
+ return [[((DDXMLElement *)(node->_private)) retain] autorelease];
+}
+
+/**
+ * Returns a DDXML wrapper object for the given primitive node.
+ * The given node MUST be non-NULL and of the proper type.
+ *
+ * The given node is checked, meaning a wrapper object for it does not already exist.
+**/
+- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr
+{
+ self = [super initWithCheckedPrimitive:kindPtr];
+ return self;
+}
+
+- (id)initWithName:(NSString *)name
+{
+ // Note: Make every guarantee that genericPtr is not null
+
+ xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
+ if(node == NULL)
+ {
+ [self release];
+ return nil;
+ }
+
+ return [self initWithCheckedPrimitive:(xmlKindPtr)node];
+}
+
+- (id)initWithName:(NSString *)name URI:(NSString *)URI
+{
+ // Note: Make every guarantee that genericPtr is not null
+
+ xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
+ if(node == NULL)
+ {
+ [self release];
+ return nil;
+ }
+
+ DDXMLElement *result = [self initWithCheckedPrimitive:(xmlKindPtr)node];
+ [result setURI:URI];
+
+ return result;
+}
+
+- (id)initWithName:(NSString *)name stringValue:(NSString *)string
+{
+ // Note: Make every guarantee that genericPtr is not null
+
+ xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
+ if(node == NULL)
+ {
+ [self release];
+ return nil;
+ }
+
+ DDXMLElement *result = [self initWithCheckedPrimitive:(xmlKindPtr)node];
+ [result setStringValue:string];
+
+ return result;
+}
+
+- (id)initWithXMLString:(NSString *)string error:(NSError **)error
+{
+ DDXMLDocument *doc = [[DDXMLDocument alloc] initWithXMLString:string options:0 error:error];
+ if(doc == nil)
+ {
+ [self release];
+ return nil;
+ }
+
+ DDXMLElement *result = [doc rootElement];
+ [result detach];
+ [doc release];
+
+ [self release];
+ return [result retain];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Elements by name
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the child element nodes (as DDXMLElement objects) of the receiver that have a specified name.
+ *
+ * If name is a qualified name, then this method invokes elementsForLocalName:URI: with the URI parameter set to
+ * the URI associated with the prefix. Otherwise comparison is based on string equality of the qualified or
+ * non-qualified name.
+**/
+- (NSArray *)elementsForName:(NSString *)name
+{
+ if(name == nil) return [NSArray array];
+
+ // We need to check to see if name has a prefix.
+ // If it does have a prefix, we need to figure out what the corresponding URI is for that prefix,
+ // and then search for any elements that have the same name (including prefix) OR have the same URI.
+ // Otherwise we loop through the children as usual and do a string compare on the name
+
+ NSString *prefix = [[self class] prefixForName:name];
+ if([prefix length] > 0)
+ {
+ xmlNodePtr node = (xmlNodePtr)genericPtr;
+ xmlNsPtr ns = xmlSearchNs(node->doc, node, [prefix xmlChar]);
+ if(ns != NULL)
+ {
+ NSString *uri = [NSString stringWithUTF8String:((const char *)ns->href)];
+ return [self elementsWithName:name uri:uri];
+ }
+
+ // Note: We used xmlSearchNs instead of resolveNamespaceForName: - avoid creating wrapper objects when possible
+ }
+
+ return [self elementsWithName:name uri:nil];
+}
+
+- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI
+{
+ if(localName == nil) return [NSArray array];
+
+ // We need to figure out what the prefix is for this URI.
+ // Then we search for elements that are named prefix:localName OR (named localName AND have the given URI).
+
+ NSString *prefix = [self resolvePrefixForNamespaceURI:URI];
+ if(prefix != nil)
+ {
+ NSString *name = [NSString stringWithFormat:@"%@:%@", prefix, localName];
+
+ return [self elementsWithName:name uri:URI];
+ }
+ else
+ {
+ return [self elementsWithName:localName uri:URI];
+ }
+}
+
+/**
+ * Helper method elementsForName and elementsForLocalName:URI: so work isn't duplicated.
+ * The name parameter is required, URI is optional.
+**/
+- (NSArray *)elementsWithName:(NSString *)name uri:(NSString *)uri
+{
+ // Supplied: name, !uri : match: name
+ // Supplied: p:name, uri : match: p:name || (name && uri)
+ // Supplied: name, uri : match: name && uri
+
+ NSMutableArray *result = [NSMutableArray array];
+
+ xmlNodePtr node = (xmlNodePtr)genericPtr;
+
+ BOOL hasPrefix = [[[self class] prefixForName:name] length] > 0;
+ NSString *localName = [[self class] localNameForName:name];
+
+ xmlNodePtr child = node->children;
+ while(child != NULL)
+ {
+ if(child->type == XML_ELEMENT_NODE)
+ {
+ BOOL match = NO;
+ if(uri == nil)
+ {
+ match = xmlStrEqual(child->name, [name xmlChar]);
+ }
+ else
+ {
+ BOOL nameMatch = xmlStrEqual(child->name, [name xmlChar]);
+ BOOL localNameMatch = xmlStrEqual(child->name, [localName xmlChar]);
+
+ BOOL uriMatch = NO;
+ if(child->ns != NULL)
+ {
+ uriMatch = xmlStrEqual(child->ns->href, [uri xmlChar]);
+ }
+
+ if(hasPrefix)
+ match = nameMatch || (localNameMatch && uriMatch);
+ else
+ match = nameMatch && uriMatch;
+ }
+
+ if(match)
+ {
+ [result addObject:[DDXMLElement nodeWithPrimitive:(xmlKindPtr)child]];
+ }
+ }
+
+ child = child->next;
+ }
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Attributes
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)addAttribute:(DDXMLNode *)attribute
+{
+ // NSXML version uses this same assertion
+ DDCheck([attribute hasParent] == NO, @"Cannot add an attribute with a parent; detach or copy first");
+ DDCheck([attribute isXmlAttrPtr], @"Not an attribute");
+
+ // xmlNodePtr xmlAddChild(xmlNodePtr parent, xmlNodePtr cur)
+ // Add a new node to @parent, at the end of the child (or property) list merging
+ // adjacent TEXT nodes (in which case @cur is freed). If the new node is ATTRIBUTE, it is added
+ // into properties instead of children. If there is an attribute with equal name, it is first destroyed.
+
+ [self removeAttributeForName:[attribute name]];
+
+ xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)attribute->genericPtr);
+}
+
+- (void)removeAttribute:(xmlAttrPtr)attr
+{
+ [[self class] removeAttribute:attr fromNode:(xmlNodePtr)genericPtr];
+}
+
+- (void)removeAllAttributes
+{
+ [[self class] removeAllAttributesFromNode:(xmlNodePtr)genericPtr];
+}
+
+- (void)removeAttributeForName:(NSString *)name
+{
+ // If we use xmlUnsetProp, then the attribute will be automatically freed.
+ // We don't want this unless no other wrapper objects have a reference to the property.
+
+ xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
+ while(attr != NULL)
+ {
+ if(xmlStrEqual(attr->name, [name xmlChar]))
+ {
+ [self removeAttribute:attr];
+ return;
+ }
+ attr = attr->next;
+ }
+}
+
+- (NSArray *)attributes
+{
+ NSMutableArray *result = [NSMutableArray array];
+
+ xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
+ while(attr != NULL)
+ {
+ [result addObject:[DDXMLNode nodeWithPrimitive:(xmlKindPtr)attr]];
+
+ attr = attr->next;
+ }
+
+ return result;
+}
+
+- (DDXMLNode *)attributeForName:(NSString *)name
+{
+ xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
+ while(attr != NULL)
+ {
+ if(xmlStrEqual([name xmlChar], attr->name))
+ {
+ return [DDXMLNode nodeWithPrimitive:(xmlKindPtr)attr];
+ }
+ attr = attr->next;
+ }
+ return nil;
+}
+
+/**
+ * Sets the list of attributes for the element.
+ * Any previously set attributes are removed.
+**/
+- (void)setAttributes:(NSArray *)attributes
+{
+ [self removeAllAttributes];
+
+ NSUInteger i;
+ for(i = 0; i < [attributes count]; i++)
+ {
+ DDXMLNode *attribute = [attributes objectAtIndex:i];
+ [self addAttribute:attribute];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Namespaces
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)addNamespace:(DDXMLNode *)namespace
+{
+ // NSXML version uses this same assertion
+ DDCheck([namespace hasParent] == NO, @"Cannot add a namespace with a parent; detach or copy first");
+ DDCheck([namespace isXmlNsPtr], @"Not a namespace");
+
+ // Beware: [namespace prefix] does NOT return what you might expect. Use [namespace name] instead.
+
+ [self removeNamespaceForPrefix:[namespace name]];
+
+ xmlNsPtr currentNs = ((xmlNodePtr)genericPtr)->nsDef;
+ if(currentNs == NULL)
+ {
+ ((xmlNodePtr)genericPtr)->nsDef = (xmlNsPtr)namespace->genericPtr;
+ }
+ else
+ {
+ while(currentNs != NULL)
+ {
+ if(currentNs->next == NULL)
+ {
+ currentNs->next = (xmlNsPtr)namespace->genericPtr;
+ break; // Yes this break is needed
+ }
+ currentNs = currentNs->next;
+ }
+ }
+
+ // The xmlNs structure doesn't contain a reference to the parent, so we manage our own reference
+ namespace->nsParentPtr = (xmlNodePtr)genericPtr;
+
+ // Did we just add a default namespace
+ if([[namespace name] isEqualToString:@""])
+ {
+ ((xmlNodePtr)genericPtr)->ns = (xmlNsPtr)namespace->genericPtr;
+
+ // Note: The removeNamespaceForPrefix method above properly handled removing any previous default namespace
+ }
+}
+
+- (void)removeNamespace:(xmlNsPtr)ns
+{
+ [[self class] removeNamespace:ns fromNode:(xmlNodePtr)genericPtr];
+}
+
+- (void)removeAllNamespaces
+{
+ [[self class] removeAllNamespacesFromNode:(xmlNodePtr)genericPtr];
+}
+
+- (void)removeNamespaceForPrefix:(NSString *)name
+{
+ // If name is nil or the empty string, the user is wishing to remove the default namespace
+ const xmlChar *xmlName = [name length] > 0 ? [name xmlChar] : NULL;
+
+ xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef;
+ while(ns != NULL)
+ {
+ if(xmlStrEqual(ns->prefix, xmlName))
+ {
+ [self removeNamespace:ns];
+ break;
+ }
+ ns = ns->next;
+ }
+
+ // Note: The removeNamespace method properly handles the situation where the namespace is the default namespace
+}
+
+- (NSArray *)namespaces
+{
+ NSMutableArray *result = [NSMutableArray array];
+
+ xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef;
+ while(ns != NULL)
+ {
+ [result addObject:[DDXMLNode nodeWithPrimitive:ns nsParent:(xmlNodePtr)genericPtr]];
+
+ ns = ns->next;
+ }
+
+ return result;
+}
+
+- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix
+{
+ // If the prefix is nil or the empty string, the user is requesting the default namespace
+
+ if([prefix length] == 0)
+ {
+ // Requesting the default namespace
+ xmlNsPtr ns = ((xmlNodePtr)genericPtr)->ns;
+ if(ns != NULL)
+ {
+ return [DDXMLNode nodeWithPrimitive:ns nsParent:(xmlNodePtr)genericPtr];
+ }
+ }
+ else
+ {
+ xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef;
+ while(ns != NULL)
+ {
+ if(xmlStrEqual(ns->prefix, [prefix xmlChar]))
+ {
+ return [DDXMLNode nodeWithPrimitive:ns nsParent:(xmlNodePtr)genericPtr];
+ }
+ ns = ns->next;
+ }
+ }
+
+ return nil;
+}
+
+- (void)setNamespaces:(NSArray *)namespaces
+{
+ [self removeAllNamespaces];
+
+ NSUInteger i;
+ for(i = 0; i < [namespaces count]; i++)
+ {
+ DDXMLNode *namespace = [namespaces objectAtIndex:i];
+ [self addNamespace:namespace];
+ }
+}
+
+/**
+ * Recursively searches the given node for the given namespace
+**/
++ (DDXMLNode *)resolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr
+{
+ if(nodePtr == NULL) return nil;
+
+ xmlNsPtr ns = nodePtr->nsDef;
+ while(ns != NULL)
+ {
+ if(xmlStrEqual(ns->prefix, [prefix xmlChar]))
+ {
+ return [DDXMLNode nodeWithPrimitive:ns nsParent:nodePtr];
+ }
+ ns = ns->next;
+ }
+
+ return [self resolveNamespaceForPrefix:prefix atNode:nodePtr->parent];
+}
+
+/**
+ * Returns the namespace node with the prefix matching the given qualified name.
+ * Eg: You pass it "a:dog", it returns the namespace (defined in this node or parent nodes) that has the "a" prefix.
+**/
+- (DDXMLNode *)resolveNamespaceForName:(NSString *)name
+{
+ // If the user passes nil or an empty string for name, they're looking for the default namespace.
+ if([name length] == 0)
+ {
+ return [[self class] resolveNamespaceForPrefix:nil atNode:(xmlNodePtr)genericPtr];
+ }
+
+ NSString *prefix = [[self class] prefixForName:name];
+
+ if([prefix length] > 0)
+ {
+ // Unfortunately we can't use xmlSearchNs because it returns an xmlNsPtr.
+ // This gives us mostly what we want, except we also need to know the nsParent.
+ // So we do the recursive search ourselves.
+
+ return [[self class] resolveNamespaceForPrefix:prefix atNode:(xmlNodePtr)genericPtr];
+ }
+
+ return nil;
+}
+
+/**
+ * Recursively searches the given node for a namespace with the given URI, and a set prefix.
+**/
++ (NSString *)resolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr
+{
+ if(nodePtr == NULL) return nil;
+
+ xmlNsPtr ns = nodePtr->nsDef;
+ while(ns != NULL)
+ {
+ if(xmlStrEqual(ns->href, [uri xmlChar]))
+ {
+ if(ns->prefix != NULL)
+ {
+ return [NSString stringWithUTF8String:((const char *)ns->prefix)];
+ }
+ }
+ ns = ns->next;
+ }
+
+ return [self resolvePrefixForURI:uri atNode:nodePtr->parent];
+}
+
+/**
+ * Returns the prefix associated with the specified URI.
+ * Returns a string that is the matching prefix or nil if it finds no matching prefix.
+**/
+- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI
+{
+ // We can't use xmlSearchNsByHref because it will return xmlNsPtr's with NULL prefixes.
+ // We're looking for a definitive prefix for the given URI.
+
+ return [[self class] resolvePrefixForURI:namespaceURI atNode:(xmlNodePtr)genericPtr];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Children
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)removeChild:(xmlNodePtr)child
+{
+ [[self class] removeChild:child fromNode:(xmlNodePtr)genericPtr];
+}
+
+- (void)removeAllChildren
+{
+ [[self class] removeAllChildrenFromNode:(xmlNodePtr)genericPtr];
+}
+
+- (void)removeChildAtIndex:(NSUInteger)index
+{
+ NSUInteger i = 0;
+
+ xmlNodePtr child = ((xmlNodePtr)genericPtr)->children;
+ while(child != NULL)
+ {
+ // Ignore all but element, comment, text, or processing instruction nodes
+ if([[self class] isXmlNodePtr:(xmlKindPtr)child])
+ {
+ if(i == index)
+ {
+ [self removeChild:child];
+ return;
+ }
+
+ i++;
+ }
+ child = child->next;
+ }
+}
+
+- (void)addChild:(DDXMLNode *)child
+{
+ // NSXML version uses these same assertions
+ DDCheck([child hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first");
+ DDCheck([child isXmlNodePtr], @"Elements can only have text, elements, processing instructions, and comments as children");
+
+ xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr);
+}
+
+- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index
+{
+ // NSXML version uses these same assertions
+ DDCheck([child hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first");
+ DDCheck([child isXmlNodePtr], @"Elements can only have text, elements, processing instructions, and comments as children");
+
+ NSUInteger i = 0;
+
+ xmlNodePtr childNodePtr = ((xmlNodePtr)genericPtr)->children;
+ while(childNodePtr != NULL)
+ {
+ // Ignore all but element, comment, text, or processing instruction nodes
+ if([[self class] isXmlNodePtr:(xmlKindPtr)childNodePtr])
+ {
+ if(i == index)
+ {
+ xmlAddPrevSibling(childNodePtr, (xmlNodePtr)child->genericPtr);
+ return;
+ }
+
+ i++;
+ }
+ childNodePtr = childNodePtr->next;
+ }
+
+ if(i == index)
+ {
+ xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr);
+ return;
+ }
+
+ // NSXML version uses this same assertion
+ DDCheck(NO, @"index (%u) beyond bounds (%u)", (unsigned)index, (unsigned)++i);
+}
+
+- (void)setChildren:(NSArray *)children
+{
+ [self removeAllChildren];
+
+ NSUInteger i;
+ for(i = 0; i < [children count]; i++)
+ {
+ DDXMLNode *child = [children objectAtIndex:i];
+ [self addChild:child];
+ }
+}
+
+@end
diff --git a/Libraries/KissXML/DDXMLElementAdditions.h b/Libraries/KissXML/DDXMLElementAdditions.h
new file mode 100644
index 0000000..d7972de
--- /dev/null
+++ b/Libraries/KissXML/DDXMLElementAdditions.h
@@ -0,0 +1,21 @@
+#import
+#import "DDXML.h"
+
+// These methods are not part of the standard NSXML API.
+// But any developer working extensively with XML will likely appreciate them.
+
+@interface DDXMLElement (DDAdditions)
+
++ (DDXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns;
+
+- (DDXMLElement *)elementForName:(NSString *)name;
+- (DDXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns;
+
+- (NSString *)xmlns;
+- (void)setXmlns:(NSString *)ns;
+
+- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string;
+
+- (NSDictionary *)attributesAsDictionary;
+
+@end
diff --git a/Libraries/KissXML/DDXMLElementAdditions.m b/Libraries/KissXML/DDXMLElementAdditions.m
new file mode 100644
index 0000000..640d786
--- /dev/null
+++ b/Libraries/KissXML/DDXMLElementAdditions.m
@@ -0,0 +1,115 @@
+#import "DDXMLElementAdditions.h"
+
+@implementation DDXMLElement (DDAdditions)
+
+/**
+ * Quick method to create an element
+**/
++ (DDXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns
+{
+ DDXMLElement *element = [DDXMLElement elementWithName:name];
+ [element setXmlns:ns];
+ return element;
+}
+
+/**
+ * This method returns the first child element for the given name.
+ * If no child element exists for the given name, returns nil.
+**/
+- (DDXMLElement *)elementForName:(NSString *)name
+{
+ NSArray *elements = [self elementsForName:name];
+ if([elements count] > 0)
+ {
+ return [elements objectAtIndex:0];
+ }
+ else
+ {
+ // Note: If you port this code to work with Apple's NSXML, beware of the following:
+ //
+ // There is a bug in the NSXMLElement elementsForName: method.
+ // Consider the following XML fragment:
+ //
+ //
+ //
+ //
+ //
+ // Calling [query elementsForName:@"x"] results in an empty array!
+ //
+ // However, it will work properly if you use the following:
+ // [query elementsForLocalName:@"x" URI:@"some:other:namespace"]
+ //
+ // The trouble with this is that we may not always know the xmlns in advance,
+ // so in this particular case there is no way to access the element without looping through the children.
+ //
+ // This bug was submitted to apple on June 1st, 2007 and was classified as "serious".
+ //
+ // --!!-- This bug does NOT exist in DDXML --!!--
+
+ return nil;
+ }
+}
+
+/**
+ * This method returns the first child element for the given name and given xmlns.
+ * If no child elements exist for the given name and given xmlns, returns nil.
+**/
+- (DDXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns
+{
+ NSArray *elements = [self elementsForLocalName:name URI:xmlns];
+ if([elements count] > 0)
+ {
+ return [elements objectAtIndex:0];
+ }
+ else
+ {
+ return nil;
+ }
+}
+
+/**
+ * Returns the common xmlns "attribute", which is only accessible via the namespace methods.
+ * The xmlns value is often used in jabber elements.
+**/
+- (NSString *)xmlns
+{
+ return [[self namespaceForPrefix:@""] stringValue];
+}
+
+- (void)setXmlns:(NSString *)ns
+{
+ // If you use setURI: then the xmlns won't be displayed in the XMLString.
+ // Adding the namespace this way works properly.
+ //
+ // This applies to both Apple's NSXML and DDXML.
+
+ [self addNamespace:[DDXMLNode namespaceWithName:@"" stringValue:ns]];
+}
+
+/**
+ * Shortcut to avoid having to manually create a DDXMLNode everytime.
+**/
+- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string
+{
+ [self addAttribute:[DDXMLNode attributeWithName:name stringValue:string]];
+}
+
+/**
+ * Returns all the attributes as a dictionary.
+**/
+- (NSDictionary *)attributesAsDictionary
+{
+ NSArray *attributes = [self attributes];
+ NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[attributes count]];
+
+ uint i;
+ for(i = 0; i < [attributes count]; i++)
+ {
+ DDXMLNode *node = [attributes objectAtIndex:i];
+
+ [result setObject:[node stringValue] forKey:[node name]];
+ }
+ return result;
+}
+
+@end
diff --git a/Libraries/KissXML/DDXMLNode.h b/Libraries/KissXML/DDXMLNode.h
new file mode 100644
index 0000000..cb1db24
--- /dev/null
+++ b/Libraries/KissXML/DDXMLNode.h
@@ -0,0 +1,170 @@
+#import
+#import
+
+@class DDXMLDocument;
+
+
+enum {
+ DDXMLInvalidKind = 0,
+ DDXMLDocumentKind = XML_DOCUMENT_NODE,
+ DDXMLElementKind = XML_ELEMENT_NODE,
+ DDXMLAttributeKind = XML_ATTRIBUTE_NODE,
+ DDXMLNamespaceKind = XML_NAMESPACE_DECL,
+ DDXMLProcessingInstructionKind = XML_PI_NODE,
+ DDXMLCommentKind = XML_COMMENT_NODE,
+ DDXMLTextKind = XML_TEXT_NODE,
+ DDXMLDTDKind = XML_DTD_NODE,
+ DDXMLEntityDeclarationKind = XML_ENTITY_DECL,
+ DDXMLAttributeDeclarationKind = XML_ATTRIBUTE_DECL,
+ DDXMLElementDeclarationKind = XML_ELEMENT_DECL,
+ DDXMLNotationDeclarationKind = XML_NOTATION_NODE
+};
+typedef NSUInteger DDXMLNodeKind;
+
+enum {
+ DDXMLNodeOptionsNone = 0,
+ DDXMLNodeExpandEmptyElement = 1 << 1,
+ DDXMLNodeCompactEmptyElement = 1 << 2,
+ DDXMLNodePrettyPrint = 1 << 17,
+};
+
+/**
+ * DDXMLNode can represent several underlying types, such as xmlNodePtr, xmlDocPtr, xmlAttrPtr, xmlNsPtr, etc.
+ * All of these are pointers to structures, and all of those structures start with a pointer, and a type.
+ * The xmlKind struct is used as a generic structure, and a stepping stone.
+ * We use it to check the type of a structure, and then perform the appropriate cast.
+ *
+ * For example:
+ * if(genericPtr->type == XML_ATTRIBUTE_NODE)
+ * {
+ * xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
+ * // Do something with attr
+ * }
+**/
+struct _xmlKind {
+ void * ignore;
+ xmlElementType type;
+};
+typedef struct _xmlKind *xmlKindPtr;
+
+/**
+ * Most xml types all start with this standard structure. In fact, all do except the xmlNsPtr.
+ * We will occasionally take advantage of this to simplify code when the code wouldn't vary from type to type.
+ * Obviously, you cannnot cast a xmlNsPtr to a xmlStdPtr.
+**/
+struct _xmlStd {
+ void * _private;
+ xmlElementType type;
+ const xmlChar *name;
+ struct _xmlNode *children;
+ struct _xmlNode *last;
+ struct _xmlNode *parent;
+ struct _xmlStd *next;
+ struct _xmlStd *prev;
+ struct _xmlDoc *doc;
+};
+typedef struct _xmlStd *xmlStdPtr;
+
+@interface DDXMLNode : NSObject
+{
+ // Every DDXML object is simply a wrapper around an underlying libxml node
+ xmlKindPtr genericPtr;
+
+ // The xmlNsPtr type doesn't store a reference to it's parent
+ // This is here to fix that problem, and make this class more compatible with the NSXML classes
+ xmlNodePtr nsParentPtr;
+}
+
+//- (id)initWithKind:(DDXMLNodeKind)kind;
+
+//- (id)initWithKind:(DDXMLNodeKind)kind options:(NSUInteger)options;
+
+//+ (id)document;
+
+//+ (id)documentWithRootElement:(DDXMLElement *)element;
+
++ (id)elementWithName:(NSString *)name;
+
++ (id)elementWithName:(NSString *)name URI:(NSString *)URI;
+
++ (id)elementWithName:(NSString *)name stringValue:(NSString *)string;
+
++ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes;
+
++ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue;
+
++ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue;
+
++ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue;
+
++ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue;
+
++ (id)commentWithStringValue:(NSString *)stringValue;
+
++ (id)textWithStringValue:(NSString *)stringValue;
+
+//+ (id)DTDNodeWithXMLString:(NSString *)string;
+
+#pragma mark --- Properties ---
+
+- (DDXMLNodeKind)kind;
+
+- (void)setName:(NSString *)name;
+- (NSString *)name;
+
+//- (void)setObjectValue:(id)value;
+//- (id)objectValue;
+
+- (void)setStringValue:(NSString *)string;
+//- (void)setStringValue:(NSString *)string resolvingEntities:(BOOL)resolve;
+- (NSString *)stringValue;
+
+#pragma mark --- Tree Navigation ---
+
+- (NSUInteger)index;
+
+- (NSUInteger)level;
+
+- (DDXMLDocument *)rootDocument;
+
+- (DDXMLNode *)parent;
+- (NSUInteger)childCount;
+- (NSArray *)children;
+- (DDXMLNode *)childAtIndex:(NSUInteger)index;
+
+- (DDXMLNode *)previousSibling;
+- (DDXMLNode *)nextSibling;
+
+- (DDXMLNode *)previousNode;
+- (DDXMLNode *)nextNode;
+
+- (void)detach;
+
+- (NSString *)XPath;
+
+#pragma mark --- QNames ---
+
+- (NSString *)localName;
+- (NSString *)prefix;
+
+- (void)setURI:(NSString *)URI;
+- (NSString *)URI;
+
++ (NSString *)localNameForName:(NSString *)name;
++ (NSString *)prefixForName:(NSString *)name;
+//+ (DDXMLNode *)predefinedNamespaceForPrefix:(NSString *)name;
+
+#pragma mark --- Output ---
+
+- (NSString *)description;
+- (NSString *)XMLString;
+- (NSString *)XMLStringWithOptions:(NSUInteger)options;
+//- (NSString *)canonicalXMLStringPreservingComments:(BOOL)comments;
+
+#pragma mark --- XPath/XQuery ---
+
+- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error;
+//- (NSArray *)objectsForXQuery:(NSString *)xquery constants:(NSDictionary *)constants error:(NSError **)error;
+//- (NSArray *)objectsForXQuery:(NSString *)xquery error:(NSError **)error;
+
+@end
diff --git a/Libraries/KissXML/DDXMLNode.m b/Libraries/KissXML/DDXMLNode.m
new file mode 100644
index 0000000..384b209
--- /dev/null
+++ b/Libraries/KissXML/DDXMLNode.m
@@ -0,0 +1,1808 @@
+#import "DDXMLNode.h"
+#import "DDXMLElement.h"
+#import "DDXMLDocument.h"
+#import "NSStringAdditions.h"
+#import "DDXMLPrivate.h"
+
+#import
+#import
+
+
+@implementation DDXMLNode
+
+static void MyErrorHandler(void * userData, xmlErrorPtr error);
+
++ (void)initialize
+{
+ static BOOL initialized = NO;
+ if(!initialized)
+ {
+ // 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);
+
+ initialized = YES;
+ }
+}
+
++ (id)elementWithName:(NSString *)name
+{
+ return [[[DDXMLElement alloc] initWithName:name] autorelease];
+}
+
++ (id)elementWithName:(NSString *)name stringValue:(NSString *)string
+{
+ return [[[DDXMLElement alloc] initWithName:name stringValue:string] autorelease];
+}
+
++ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes
+{
+ DDXMLElement *result = [[[DDXMLElement alloc] initWithName:name] autorelease];
+ [result setChildren:children];
+ [result setAttributes:attributes];
+
+ return result;
+}
+
++ (id)elementWithName:(NSString *)name URI:(NSString *)URI
+{
+ return [[[DDXMLElement alloc] initWithName:name URI:URI] autorelease];
+}
+
++ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue
+{
+ xmlAttrPtr attr = xmlNewProp(NULL, [name xmlChar], [stringValue xmlChar]);
+
+ if(attr == NULL) return nil;
+
+ return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)attr] autorelease];
+}
+
++ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue
+{
+ xmlAttrPtr attr = xmlNewProp(NULL, [name xmlChar], [stringValue xmlChar]);
+
+ if(attr == NULL) return nil;
+
+ DDXMLNode *result = [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)attr] autorelease];
+ [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 [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)ns] autorelease];
+}
+
++ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue
+{
+ xmlNodePtr procInst = xmlNewPI([name xmlChar], [stringValue xmlChar]);
+
+ if(procInst == NULL) return nil;
+
+ return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)procInst] autorelease];
+}
+
++ (id)commentWithStringValue:(NSString *)stringValue
+{
+ xmlNodePtr comment = xmlNewComment([stringValue xmlChar]);
+
+ if(comment == NULL) return nil;
+
+ return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)comment] autorelease];
+}
+
++ (id)textWithStringValue:(NSString *)stringValue
+{
+ xmlNodePtr text = xmlNewText([stringValue xmlChar]);
+
+ if(text == NULL) return nil;
+
+ return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)text] autorelease];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Init, Dealloc
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr
+{
+ if(kindPtr->type == XML_DOCUMENT_NODE)
+ {
+ return [DDXMLDocument nodeWithPrimitive:kindPtr];
+ }
+ else if(kindPtr->type == XML_ELEMENT_NODE)
+ {
+ return [DDXMLElement nodeWithPrimitive:kindPtr];
+ }
+ else
+ {
+ return [DDXMLNode nodeWithPrimitive:kindPtr];
+ }
+}
+
+/**
+ * Returns a DDXML wrapper object for the given primitive node.
+ * The given node MUST be non-NULL and of the proper type.
+ *
+ * If the wrapper object already exists, it is retained/autoreleased and returned.
+ * Otherwise a new wrapper object is alloc/init/autoreleased and returned.
+**/
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr
+{
+ // If a wrapper object already exists, the _private variable is pointing to it.
+ // Warning: The _private variable is in a different location in the xmlNsPtr
+
+ if([self isXmlNsPtr:kindPtr])
+ {
+ xmlNsPtr ns = (xmlNsPtr)kindPtr;
+ if(ns->_private != NULL)
+ {
+ return [[((DDXMLNode *)(ns->_private)) retain] autorelease];
+ }
+ }
+ else
+ {
+ xmlStdPtr node = (xmlStdPtr)kindPtr;
+ if(node->_private != NULL)
+ {
+ return [[((DDXMLNode *)(node->_private)) retain] autorelease];
+ }
+ }
+
+ return [[[DDXMLNode alloc] initWithCheckedPrimitive:kindPtr] autorelease];
+}
+
+/**
+ * Returns a DDXML wrapper object for the given primitive node.
+ * The given node MUST be non-NULL and of the proper type.
+ *
+ * The given node is checked, meaning a wrapper object for it does not already exist.
+**/
+- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr
+{
+ if((self = [super init]))
+ {
+ genericPtr = kindPtr;
+ nsParentPtr = NULL;
+ [self nodeRetain];
+ }
+ return self;
+}
+
+/**
+ * Returns a DDXML wrapper object for the given primitive node.
+ * The given node MUST be non-NULL and of the proper type.
+ *
+ * If the wrapper object already exists, it is retained/autoreleased and returned.
+ * Otherwise a new wrapper object is alloc/init/autoreleased and returned.
+**/
++ (id)nodeWithPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent
+{
+ // If a wrapper object already exists, the _private variable is pointing to it.
+
+ if(ns->_private == NULL)
+ return [[[DDXMLNode alloc] initWithCheckedPrimitive:ns nsParent:parent] autorelease];
+ else
+ return [[((DDXMLNode *)(ns->_private)) retain] autorelease];
+}
+
+/**
+ * Returns a DDXML wrapper object for the given primitive node.
+ * The given node MUST be non-NULL and of the proper type.
+ *
+ * The given node is checked, meaning a wrapper object for it does not already exist.
+**/
+- (id)initWithCheckedPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent
+{
+ if((self = [super init]))
+ {
+ genericPtr = (xmlKindPtr)ns;
+ nsParentPtr = parent;
+ [self nodeRetain];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ // Check if genericPtr is NULL
+ // This may be the case if, eg, DDXMLElement calls [self release] from it's init method
+ if(genericPtr != NULL)
+ {
+ [self nodeRelease];
+ }
+ [super dealloc];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Copying
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ if([self isXmlDocPtr])
+ {
+ xmlDocPtr copyDocPtr = xmlCopyDoc((xmlDocPtr)genericPtr, 1);
+
+ if(copyDocPtr == NULL) return nil;
+
+ return [[DDXMLDocument alloc] initWithCheckedPrimitive:(xmlKindPtr)copyDocPtr];
+ }
+
+ if([self isXmlNodePtr])
+ {
+ xmlNodePtr copyNodePtr = xmlCopyNode((xmlNodePtr)genericPtr, 1);
+
+ if(copyNodePtr == NULL) return nil;
+
+ if([self isKindOfClass:[DDXMLElement class]])
+ return [[DDXMLElement alloc] initWithCheckedPrimitive:(xmlKindPtr)copyNodePtr];
+ else
+ return [[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)copyNodePtr];
+ }
+
+ if([self isXmlAttrPtr])
+ {
+ xmlAttrPtr copyAttrPtr = xmlCopyProp(NULL, (xmlAttrPtr)genericPtr);
+
+ if(copyAttrPtr == NULL) return nil;
+
+ return [[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)copyAttrPtr];
+ }
+
+ if([self isXmlNsPtr])
+ {
+ xmlNsPtr copyNsPtr = xmlCopyNamespace((xmlNsPtr)genericPtr);
+
+ if(copyNsPtr == NULL) return nil;
+
+ return [[DDXMLNode alloc] initWithCheckedPrimitive:copyNsPtr nsParent:NULL];
+ }
+
+ if([self isXmlDtdPtr])
+ {
+ xmlDtdPtr copyDtdPtr = xmlCopyDtd((xmlDtdPtr)genericPtr);
+
+ if(copyDtdPtr == NULL) return nil;
+
+ return [[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)copyDtdPtr];
+ }
+
+ return nil;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Properties
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (DDXMLNodeKind)kind
+{
+ if(genericPtr != NULL)
+ return genericPtr->type;
+ else
+ return DDXMLInvalidKind;
+}
+
+- (void)setName:(NSString *)name
+{
+ if([self isXmlNsPtr])
+ {
+ xmlNsPtr ns = (xmlNsPtr)genericPtr;
+
+ xmlFree((xmlChar *)ns->prefix);
+ ns->prefix = xmlStrdup([name xmlChar]);
+ }
+ else
+ {
+ // The xmlNodeSetName function works for both nodes and attributes
+ xmlNodeSetName((xmlNodePtr)genericPtr, [name xmlChar]);
+ }
+}
+
+- (NSString *)name
+{
+ if([self isXmlNsPtr])
+ {
+ xmlNsPtr ns = (xmlNsPtr)genericPtr;
+ if(ns->prefix != NULL)
+ return [NSString stringWithUTF8String:((const char*)ns->prefix)];
+ else
+ return @"";
+ }
+ else
+ {
+ const char *name = (const char *)((xmlStdPtr)genericPtr)->name;
+
+ if(name == NULL)
+ return nil;
+ else
+ return [NSString stringWithUTF8String:name];
+ }
+}
+
+- (void)setStringValue:(NSString *)string
+{
+ if([self isXmlNsPtr])
+ {
+ xmlNsPtr ns = (xmlNsPtr)genericPtr;
+
+ xmlFree((xmlChar *)ns->href);
+ ns->href = xmlEncodeSpecialChars(NULL, [string xmlChar]);
+ }
+ else if([self isXmlAttrPtr])
+ {
+ 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;
+ }
+ }
+ else if([self isXmlNodePtr])
+ {
+ 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
+{
+ if([self isXmlNsPtr])
+ {
+ return [NSString stringWithUTF8String:((const char *)((xmlNsPtr)genericPtr)->href)];
+ }
+ else if([self isXmlAttrPtr])
+ {
+ xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
+
+ if(attr->children != NULL)
+ {
+ return [NSString stringWithUTF8String:(const char *)attr->children->content];
+ }
+
+ return nil;
+ }
+ else if([self isXmlNodePtr])
+ {
+ 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
+{
+ if([self isXmlNsPtr])
+ {
+ // The xmlNsPtr has no prev pointer, so we have to search from the parent
+ if(nsParentPtr == NULL) return 0;
+
+ xmlNsPtr currentNs = nsParentPtr->nsDef;
+
+ NSUInteger result = 0;
+ while(currentNs != NULL)
+ {
+ if(currentNs == (xmlNsPtr)genericPtr)
+ {
+ return result;
+ }
+ result++;
+ currentNs = currentNs->next;
+ }
+ return 0;
+ }
+ else
+ {
+ xmlStdPtr node = ((xmlStdPtr)genericPtr)->prev;
+
+ NSUInteger result = 0;
+ 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
+{
+ xmlNodePtr currentNode;
+ if([self isXmlNsPtr])
+ currentNode = nsParentPtr;
+ else
+ currentNode = ((xmlStdPtr)genericPtr)->parent;
+
+ NSUInteger result = 0;
+ 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
+{
+ xmlStdPtr node;
+ if([self isXmlNsPtr])
+ node = (xmlStdPtr)nsParentPtr;
+ else
+ node = (xmlStdPtr)genericPtr;
+
+ if(node == NULL || node->doc == NULL)
+ return nil;
+ else
+ return [DDXMLDocument nodeWithPrimitive:(xmlKindPtr)node->doc];
+}
+
+/**
+ * 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
+{
+ if([self isXmlNsPtr])
+ {
+ if(nsParentPtr == NULL) return nil;
+
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)nsParentPtr];
+ }
+
+ xmlStdPtr node = (xmlStdPtr)genericPtr;
+
+ if(node->parent == NULL) return nil;
+
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent];
+}
+
+/**
+ * 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
+{
+ if(![self isXmlDocPtr] && ![self isXmlNodePtr] && ![self isXmlDtdPtr]) 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
+{
+ if(![self isXmlDocPtr] && ![self isXmlNodePtr] && ![self isXmlDtdPtr]) return nil;
+
+ NSMutableArray *result = [NSMutableArray array];
+
+ xmlNodePtr child = ((xmlStdPtr)genericPtr)->children;
+ while(child != NULL)
+ {
+ [result addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child]];
+
+ child = child->next;
+ }
+
+ return [[result copy] autorelease];
+}
+
+/**
+ * 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
+{
+ if(![self isXmlDocPtr] && ![self isXmlNodePtr] && ![self isXmlDtdPtr]) 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];
+ }
+
+ i++;
+ child = child->next;
+ }
+
+ // NSXML version uses this same assertion
+ DDCheck(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’s.
+ * If there are no more previous siblings (that is, other child nodes of the receiver’s parent) the method returns nil.
+**/
+- (DDXMLNode *)previousSibling
+{
+ if([self isXmlNsPtr]) return nil;
+
+ xmlStdPtr node = (xmlStdPtr)genericPtr;
+
+ if(node->prev == NULL) return nil;
+
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->prev];
+}
+
+/**
+ * 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’s.
+ * If there are no more subsequent siblings (that is, other child nodes of the receiver’s parent) the
+ * method returns nil.
+**/
+- (DDXMLNode *)nextSibling
+{
+ if([self isXmlNsPtr]) return nil;
+
+ xmlStdPtr node = (xmlStdPtr)genericPtr;
+
+ if(node->next == NULL) return nil;
+
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->next];
+}
+
+/**
+ * Returns the previous DDXMLNode object in document order.
+ *
+ * You use this method to “walk” 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
+{
+ if([self isXmlNsPtr] || [self isXmlAttrPtr]) return nil;
+
+ // 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];
+ }
+ else
+ {
+ // The previous sibling has no children, so the previous node is simply the previous sibling
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)previousSibling];
+ }
+ }
+
+ // 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];
+}
+
+/**
+ * Returns the next DDXMLNode object in document order.
+ *
+ * You use this method to “walk” 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
+{
+ if([self isXmlNsPtr] || [self isXmlAttrPtr]) return nil;
+
+ // 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];
+ 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
+{
+ if([self isXmlNsPtr])
+ {
+ if(nsParentPtr != NULL)
+ {
+ [[self class] removeNamespace:(xmlNsPtr)genericPtr fromNode:nsParentPtr];
+ }
+ return;
+ }
+
+ xmlStdPtr node = (xmlStdPtr)genericPtr;
+
+ if(node->parent == NULL) return;
+
+ if([self isXmlAttrPtr])
+ {
+ [[self class] detachAttribute:(xmlAttrPtr)node fromNode:node->parent];
+ }
+ else if([self isXmlNodePtr])
+ {
+ [[self class] detachChild:(xmlNodePtr)node fromNode:node->parent];
+ }
+}
+
+- (NSString *)XPath
+{
+ NSMutableString *result = [NSMutableString stringWithCapacity:25];
+
+ // Examples:
+ // /rootElement[1]/subElement[4]/thisNode[2]
+ // topElement/thisNode[2]
+
+ xmlStdPtr node = NULL;
+
+ if([self isXmlNsPtr])
+ {
+ node = (xmlStdPtr)nsParentPtr;
+
+ if(node == NULL)
+ [result appendFormat:@"namespace::%@", [self name]];
+ else
+ [result appendFormat:@"/namespace::%@", [self name]];
+ }
+ else if([self isXmlAttrPtr])
+ {
+ node = (xmlStdPtr)(((xmlAttrPtr)genericPtr)->parent);
+
+ if(node == NULL)
+ [result appendFormat:@"@%@", [self name]];
+ else
+ [result appendFormat:@"/@%@", [self name]];
+ }
+ else
+ {
+ node = (xmlStdPtr)genericPtr;
+ }
+
+ // 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] autorelease];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#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, “chapter” is the local name in the qualified name “acme:chapter”.
+**/
+- (NSString *)localName
+{
+ if([self isXmlNsPtr])
+ {
+ // 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 @"";
+ }
+
+ return [[self class] localNameForName:[self name]];
+}
+
+/**
+ * Returns the prefix of the receiver’s name.
+ *
+ * The prefix is the part of a namespace-qualified name that precedes the colon.
+ * For example, “acme” is the local name in the qualified name “acme:chapter”.
+ * This method returns an empty string if the receiver’s name is not qualified by a namespace.
+**/
+- (NSString *)prefix
+{
+ if([self isXmlNsPtr])
+ {
+ // Strangely enough, the localName of a namespace is the prefix, and the prefix is an empty string
+ return @"";
+ }
+
+ 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
+{
+ if([self isXmlNodePtr])
+ {
+ 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;
+ }
+ }
+ else if([self isXmlAttrPtr])
+ {
+ xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
+ if(attr->ns != NULL)
+ {
+ // 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.
+ xmlFreeNs(attr->ns);
+ attr->ns = NULL;
+ }
+
+ if(URI)
+ {
+ // Create a new xmlNsPtr, and make ns point to it
+ xmlNsPtr ns = xmlNewNs(NULL, [URI xmlChar], NULL);
+ attr->ns = ns;
+ }
+ }
+}
+
+/**
+ * Returns the URI associated with the receiver.
+ *
+ * A node’s URI is derived from its namespace or a document’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
+{
+ if([self isXmlAttrPtr])
+ {
+ xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
+ if(attr->ns != NULL)
+ {
+ return [NSString stringWithUTF8String:((const char *)attr->ns->href)];
+ }
+ }
+ else if([self isXmlNodePtr])
+ {
+ xmlNodePtr node = (xmlNodePtr)genericPtr;
+ if(node->ns != NULL)
+ {
+ return [NSString stringWithUTF8String:((const char *)node->ns->href)];
+ }
+ }
+
+ return 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
+{
+ if(name)
+ {
+ NSRange range = [name rangeOfString:@":"];
+
+ if(range.length != 0)
+ return [name substringFromIndex:(range.location + range.length)];
+ else
+ return name;
+ }
+ return nil;
+}
+
+/**
+ * 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
+{
+ if(name)
+ {
+ NSRange range = [name rangeOfString:@":"];
+
+ if(range.length != 0)
+ {
+ return [name substringToIndex:range.location];
+ }
+ }
+ return @"";
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Output
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSString *)description
+{
+ return [self XMLStringWithOptions:0];
+}
+
+- (NSString *)XMLString
+{
+ // Todo: Test XMLString for namespace node
+ return [self XMLStringWithOptions:0];
+}
+
+- (NSString *)XMLStringWithOptions:(NSUInteger)options
+{
+ // xmlSaveNoEmptyTags:
+ // Global setting, asking the serializer to not output empty tags
+ // as but . 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;
+ }
+
+ xmlBufferPtr bufferPtr = xmlBufferCreate();
+ if([self isXmlNsPtr])
+ xmlNodeDump(bufferPtr, NULL, (xmlNodePtr)genericPtr, 0, format);
+ else
+ xmlNodeDump(bufferPtr, ((xmlStdPtr)genericPtr)->doc, (xmlNodePtr)genericPtr, 0, format);
+
+ 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];
+ NSString *result = [resTmp stringByTrimming];
+
+ xmlBufferFree(bufferPtr);
+
+ return result;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark XPath/XQuery
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+-(NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error
+{
+ xmlXPathContextPtr xpathCtx;
+ xmlXPathObjectPtr xpathObj;
+
+ BOOL isTempDoc = NO;
+ xmlDocPtr doc;
+
+ if([DDXMLNode isXmlDocPtr:genericPtr])
+ {
+ doc = (xmlDocPtr)genericPtr;
+ }
+ else if([DDXMLNode 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]];
+ }
+
+ 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
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns whether or not the given node is of type xmlAttrPtr.
+**/
++ (BOOL)isXmlAttrPtr:(xmlKindPtr)kindPtr
+{
+ return kindPtr->type == XML_ATTRIBUTE_NODE;
+}
+
+/**
+ * Returns whether or not the genericPtr is of type xmlAttrPtr.
+**/
+- (BOOL)isXmlAttrPtr
+{
+ return [[self class] isXmlAttrPtr:genericPtr];
+}
+
+/**
+ * Returns whether or not the given node is of type xmlNodePtr.
+**/
++ (BOOL)isXmlNodePtr:(xmlKindPtr)kindPtr
+{
+ xmlElementType type = kindPtr->type;
+ switch(type)
+ {
+ case XML_ELEMENT_NODE :
+ case XML_PI_NODE :
+ case XML_COMMENT_NODE :
+ case XML_TEXT_NODE :
+ case XML_CDATA_SECTION_NODE : return YES;
+ default : return NO;
+ }
+}
+
+/**
+ * Returns whether or not the genericPtr is of type xmlNodePtr.
+**/
+- (BOOL)isXmlNodePtr
+{
+ return [[self class] isXmlNodePtr:genericPtr];
+}
+
+/**
+ * Returns whether or not the given node is of type xmlDocPtr.
+**/
++ (BOOL)isXmlDocPtr:(xmlKindPtr)kindPtr
+{
+ return kindPtr->type == XML_DOCUMENT_NODE;
+}
+
+/**
+ * Returns whether or not the genericPtr is of type xmlDocPtr.
+**/
+- (BOOL)isXmlDocPtr
+{
+ return [[self class] isXmlDocPtr:genericPtr];
+}
+
+/**
+ * Returns whether or not the given node is of type xmlDtdPtr.
+**/
++ (BOOL)isXmlDtdPtr:(xmlKindPtr)kindPtr
+{
+ return kindPtr->type == XML_DTD_NODE;
+}
+
+/**
+ * Returns whether or not the genericPtr is of type xmlDtdPtr.
+**/
+- (BOOL)isXmlDtdPtr
+{
+ return [[self class] isXmlDtdPtr:genericPtr];
+}
+
+/**
+ * Returns whether or not the given node is of type xmlNsPtr.
+**/
++ (BOOL)isXmlNsPtr:(xmlKindPtr)kindPtr
+{
+ return kindPtr->type == XML_NAMESPACE_DECL;
+}
+
+/**
+ * Returns whether or not the genericPtr is of type xmlNsPtr.
+**/
+- (BOOL)isXmlNsPtr
+{
+ return [[self class] isXmlNsPtr:genericPtr];
+}
+
+/**
+ * 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
+{
+ if([self isXmlNsPtr])
+ {
+ return (nsParentPtr != NULL);
+ }
+
+ xmlStdPtr node = (xmlStdPtr)genericPtr;
+
+ return (node->parent != NULL);
+}
+
+/**
+ * - - - - - - - - - - R E A D M E - - - - - - - - - -
+ *
+ * The memory management of these wrapper classes is straight-forward, but requires explanation.
+ * To understand the problem, consider the following situation:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Imagine the user has retained two DDXMLElements - one for root, and one for level2.
+ * Then they release the root element, but they want to hold onto the level2 element.
+ * We need to release root, and level1, but keep level2 intact until the user is done with it.
+ * Note that this is also how the NSXML classes work.
+ * The user will no longer be able to traverse up the tree from level2, but will be able to access all the normal
+ * information in level2, as well as any children, if there was any.
+ *
+ * So the first question is, how do we know if a libxml node is being referenced by a cocoa wrapper?
+ * In order to accomplish this, we take advantage of the node's _private variable.
+ * If the private variable is NULL, then the node isn't being directly referenced by any cocoa wrapper objects.
+ * If the private variable is NON-NULL, then the private variable points to the cocoa wrapper object.
+ * When a cocoa wrapper object is created, it points the private variable to itself (via nodeRetain),
+ * and when it's dealloced it sets the private variable back to NULL (via nodeRelease).
+ *
+ * With this simple technique, then given any libxml node, we can easily determine if it's still needed,
+ * or if we can free it:
+ * Is there a cocoa wrapper objects still directly referring to the node?
+ * If so, we can't free the node.
+ * Otherwise, does the node still have a parent?
+ * If so, then the node is still part of a heirarchy, and we can't free the node.
+ *
+ * To fully understand the parent restriction, consider the following scenario:
+ * Imagine the user extracts the level1 DDXMLElement from the root.
+ * The user reads the data, and the level1 DDXMLElement is autoreleased. The root is still retained.
+ * When the level1 DDXMLElement is dealloced, nodeRelease will be called, and the private variable will be set to NULL.
+ * Can we free the level1 node at this point?
+ * Of course not, because it's still within the root heirarchy, and the user is still using the root element.
+ *
+ * The following should be spelled out:
+ * If you call libxml's xmlFreeNode(), this method will free all linked attributes and children.
+ * So you can't blindly call this method, because you can't free nodes that are still being referenced.
+**/
+
+
++ (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;
+}
+
+/**
+ * This method will recursively free the given node, as long as the node is no longer being referenced.
+ * If the node is still being referenced, then it's parent, prev, next and doc pointers are destroyed.
+**/
++ (void)nodeFree:(xmlNodePtr)node
+{
+ NSAssert1([self isXmlNodePtr:(xmlKindPtr)node], @"Wrong kind of node passed to nodeFree: %i", node->type);
+
+ if(node->_private == NULL)
+ {
+ [self removeAllAttributesFromNode:node];
+ [self removeAllNamespacesFromNode:node];
+ [self removeAllChildrenFromNode:node];
+
+ xmlFreeNode(node);
+ }
+ else
+ {
+ node->parent = NULL;
+ node->prev = NULL;
+ node->next = NULL;
+ if(node->doc != NULL) [self recursiveStripDocPointersFromNode:node];
+ }
+}
+
+/**
+ * Detaches the given attribute from the given 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 fromNode:(xmlNodePtr)node
+{
+ // Update the surrounding prev/next pointers
+ if(attr->prev == NULL)
+ {
+ if(attr->next == NULL)
+ {
+ node->properties = NULL;
+ }
+ else
+ {
+ node->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;
+ }
+ }
+
+ // Nullify pointers
+ attr->parent = NULL;
+ attr->prev = NULL;
+ attr->next = NULL;
+ if(attr->doc != NULL) [self stripDocPointersFromAttr:attr];
+}
+
+/**
+ * Removes the given attribute from the given node.
+ * The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list.
+ * Then the attribute is freed if it's no longer being referenced.
+ * Otherwise, it's parent, prev, next and doc pointers are destroyed.
+**/
++ (void)removeAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node
+{
+ [self detachAttribute:attr fromNode:node];
+
+ // Free the attr if it's no longer in use
+ if(attr->_private == NULL)
+ {
+ xmlFreeProp(attr);
+ }
+}
+
+/**
+ * Removes all attributes from the given node.
+ * All attributes are either freed, or their parent, prev, next and doc pointers are properly destroyed.
+ * 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;
+
+ // Free the attr if it's no longer in use
+ if(attr->_private == NULL)
+ {
+ xmlFreeProp(attr);
+ }
+ else
+ {
+ attr->parent = NULL;
+ attr->prev = NULL;
+ attr->next = NULL;
+ if(attr->doc != NULL) [self stripDocPointersFromAttr:attr];
+ }
+
+ attr = nextAttr;
+ }
+
+ node->properties = NULL;
+}
+
+/**
+ * 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
+{
+ // 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;
+
+ if(node->ns == ns)
+ {
+ node->ns = NULL;
+ }
+
+ // We also have to nullify the nsParentPtr, which is in the cocoa wrapper object (if one exists)
+ if(ns->_private != NULL)
+ {
+ DDXMLNode *node = (DDXMLNode *)ns->_private;
+ node->nsParentPtr = 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
+{
+ [self detachNamespace:ns fromNode:node];
+
+ // Free the ns if it's no longer in use
+ if(ns->_private == NULL)
+ {
+ 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;
+
+ // We manage the nsParent pointer, which is in the cocoa wrapper object, so we have to nullify it ourself
+ if(ns->_private != NULL)
+ {
+ DDXMLNode *node = (DDXMLNode *)ns->_private;
+ node->nsParentPtr = NULL;
+ }
+
+ // Free the ns if it's no longer in use
+ if(ns->_private == NULL)
+ {
+ xmlFreeNs(ns);
+ }
+ else
+ {
+ ns->next = NULL;
+ }
+
+ ns = nextNs;
+ }
+
+ node->nsDef = NULL;
+ node->ns = NULL;
+}
+
+/**
+ * Detaches the given child from the given node.
+ * The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list.
+ * Then, if flag is YES, the child's parent, prev, next and doc pointers are destroyed.
+**/
++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node andNullifyPointers:(BOOL)flag
+{
+ // Update the surrounding prev/next pointers
+ if(child->prev == NULL)
+ {
+ if(child->next == NULL)
+ {
+ node->children = NULL;
+ node->last = NULL;
+ }
+ else
+ {
+ node->children = child->next;
+ child->next->prev = NULL;
+ }
+ }
+ else
+ {
+ if(child->next == NULL)
+ {
+ node->last = child->prev;
+ child->prev->next = NULL;
+ }
+ else
+ {
+ child->prev->next = child->next;
+ child->next->prev = child->prev;
+ }
+ }
+
+ if(flag)
+ {
+ // Nullify pointers
+ child->parent = NULL;
+ child->prev = NULL;
+ child->next = NULL;
+ if(child->doc != NULL) [self recursiveStripDocPointersFromNode:child];
+ }
+}
+
+/**
+ * Detaches the given child from the given node.
+ * 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 destroyed.
+**/
++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node
+{
+ [self detachChild:child fromNode:node andNullifyPointers:YES];
+}
+
+/**
+ * Removes the given child from the given 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 fromNode:(xmlNodePtr)node
+{
+ // We perform a wee bit of optimization here.
+ // Imagine that we're removing the root element of a big tree, and none of the elements are retained.
+ // If we simply call detachChild:fromNode:, this will traverse the entire tree, nullifying doc pointers.
+ // Then, when we call nodeFree:, it will again traverse the entire tree, freeing all the nodes.
+ // To avoid this double traversal, we skip the nullification step in the detach method, and let nodeFree do it.
+ [self detachChild:child fromNode:node andNullifyPointers:NO];
+
+ // Free the child recursively if it's no longer in use
+ [self nodeFree: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;
+
+ // Free the child recursively if it's no longer in use
+ [self nodeFree:child];
+
+ child = nextChild;
+ }
+
+ node->children = NULL;
+ node->last = NULL;
+}
+
+/**
+ * Removes the root element from the given document.
+**/
++ (void)removeAllChildrenFromDoc:(xmlDocPtr)doc
+{
+ xmlNodePtr child = doc->children;
+
+ while(child != NULL)
+ {
+ xmlNodePtr nextChild = child->next;
+
+ if(child->type == XML_ELEMENT_NODE)
+ {
+ // Remove child from list of children
+ if(child->prev != NULL)
+ {
+ child->prev->next = child->next;
+ }
+ if(child->next != NULL)
+ {
+ child->next->prev = child->prev;
+ }
+ if(doc->children == child)
+ {
+ doc->children = child->next;
+ }
+ if(doc->last == child)
+ {
+ doc->last = child->prev;
+ }
+
+ // Free the child recursively if it's no longer in use
+ [self nodeFree:child];
+ }
+ else
+ {
+ // Leave comments and DTD's embedded in the doc's child list.
+ // They will get freed in xmlFreeDoc.
+ }
+
+ child = nextChild;
+ }
+}
+
+/**
+ * Adds self to the node's retain list.
+ * This way we know the node is still being referenced, and it won't be improperly freed.
+**/
+- (void)nodeRetain
+{
+ // Warning: The _private variable is in a different location in the xmlNsPtr
+
+ if([self isXmlNsPtr])
+ ((xmlNsPtr)genericPtr)->_private = self;
+ else
+ ((xmlStdPtr)genericPtr)->_private = self;
+}
+
+/**
+ * Removes self from the node's retain list.
+ * If the node is no longer being referenced, and it's not still embedded within a heirarchy above, then
+ * the node is properly freed. This includes element nodes, which are recursively freed, detaching any subnodes
+ * that are still being referenced.
+**/
+- (void)nodeRelease
+{
+ // Check to see if the node can be released.
+ // Did you read the giant readme comment section above?
+
+ // Warning: The _private variable is in a different location in the xmlNsPtr
+
+ if([self isXmlNsPtr])
+ {
+ xmlNsPtr ns = (xmlNsPtr)genericPtr;
+ ns->_private = NULL;
+
+ if(nsParentPtr == NULL)
+ {
+ xmlFreeNs(ns);
+ }
+ else
+ {
+ // The node still has a parent, so it's still in use
+ }
+ }
+ else
+ {
+ xmlStdPtr node = (xmlStdPtr)genericPtr;
+ node->_private = NULL;
+
+ if(node->parent == NULL)
+ {
+ if([self isXmlAttrPtr])
+ {
+ xmlFreeProp((xmlAttrPtr)genericPtr);
+ }
+ else if([self isXmlDtdPtr])
+ {
+ xmlFreeDtd((xmlDtdPtr)genericPtr);
+ }
+ else if([self isXmlDocPtr])
+ {
+ [[self class] removeAllChildrenFromDoc:(xmlDocPtr)genericPtr];
+ xmlFreeDoc((xmlDocPtr)genericPtr);
+ }
+ else
+ {
+ [[self class] nodeFree:(xmlNodePtr)genericPtr];
+ }
+ }
+ else
+ {
+ // The node still has a parent, so it's still in use
+ }
+ }
+}
+
+/**
+ * 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];
+ }
+}
+
+@end
diff --git a/Libraries/KissXML/DDXMLPrivate.h b/Libraries/KissXML/DDXMLPrivate.h
new file mode 100644
index 0000000..cd3f6b6
--- /dev/null
+++ b/Libraries/KissXML/DDXMLPrivate.h
@@ -0,0 +1,79 @@
+#import "DDXMLNode.h"
+#import "DDXMLElement.h"
+#import "DDXMLDocument.h"
+
+// We can't rely solely on NSAssert, because many developers disable them for release builds.
+// Our API contract requires us to keep these assertions intact.
+#define DDCheck(condition, desc, ...) { if(!(condition)) { [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd object:self file:[NSString stringWithUTF8String:__FILE__] lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; } }
+
+#define DDLastErrorKey @"DDXML:LastError"
+
+
+@interface DDXMLNode (PrivateAPI)
+
++ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr;
+
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr;
+- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr;
+
++ (id)nodeWithPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent;
+- (id)initWithCheckedPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent;
+
++ (BOOL)isXmlAttrPtr:(xmlKindPtr)kindPtr;
+- (BOOL)isXmlAttrPtr;
+
++ (BOOL)isXmlNodePtr:(xmlKindPtr)kindPtr;
+- (BOOL)isXmlNodePtr;
+
++ (BOOL)isXmlDocPtr:(xmlKindPtr)kindPtr;
+- (BOOL)isXmlDocPtr;
+
++ (BOOL)isXmlDtdPtr:(xmlKindPtr)kindPtr;
+- (BOOL)isXmlDtdPtr;
+
++ (BOOL)isXmlNsPtr:(xmlKindPtr)kindPtr;
+- (BOOL)isXmlNsPtr;
+
+- (BOOL)hasParent;
+
++ (void)recursiveStripDocPointersFromNode:(xmlNodePtr)node;
+
++ (void)detachAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node;
++ (void)removeAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node;
++ (void)removeAllAttributesFromNode:(xmlNodePtr)node;
+
++ (void)detachNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node;
++ (void)removeNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node;
++ (void)removeAllNamespacesFromNode:(xmlNodePtr)node;
+
++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node;
++ (void)removeChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node;
++ (void)removeAllChildrenFromNode:(xmlNodePtr)node;
+
++ (void)removeAllChildrenFromDoc:(xmlDocPtr)doc;
+
+- (void)nodeRetain;
+- (void)nodeRelease;
+
++ (NSError *)lastError;
+
+@end
+
+@interface DDXMLElement (PrivateAPI)
+
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr;
+- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr;
+
+- (NSArray *)elementsWithName:(NSString *)name uri:(NSString *)URI;
+
++ (DDXMLNode *)resolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr;
++ (NSString *)resolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr;
+
+@end
+
+@interface DDXMLDocument (PrivateAPI)
+
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr;
+- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr;
+
+@end
\ No newline at end of file
diff --git a/Libraries/KissXML/NSStringAdditions.h b/Libraries/KissXML/NSStringAdditions.h
new file mode 100644
index 0000000..a944c23
--- /dev/null
+++ b/Libraries/KissXML/NSStringAdditions.h
@@ -0,0 +1,14 @@
+#import
+#import
+
+
+@interface NSString (NSStringAdditions)
+
+/**
+ * xmlChar - A basic replacement for char, a byte in a UTF-8 encoded string.
+**/
+- (const xmlChar *)xmlChar;
+
+- (NSString *)stringByTrimming;
+
+@end
diff --git a/Libraries/KissXML/NSStringAdditions.m b/Libraries/KissXML/NSStringAdditions.m
new file mode 100644
index 0000000..b73ed2b
--- /dev/null
+++ b/Libraries/KissXML/NSStringAdditions.m
@@ -0,0 +1,29 @@
+#import "NSStringAdditions.h"
+
+
+@implementation NSString (NSStringAdditions)
+
+- (const xmlChar *)xmlChar
+{
+ return (const xmlChar *)[self UTF8String];
+}
+
+#ifdef GNUSTEP
+- (NSString *)stringByTrimming
+{
+ return [self stringByTrimmingSpaces];
+}
+#else
+- (NSString *)stringByTrimming
+{
+ NSMutableString *mStr = [self mutableCopy];
+ CFStringTrimWhitespace((CFMutableStringRef)mStr);
+
+ NSString *result = [mStr copy];
+
+ [mStr release];
+ return [result autorelease];
+}
+#endif
+
+@end