Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Updated KissXML source to 5.0 to fix #85

  • Loading branch information...
commit 7ed52f2f31210b2f87a854d68f4ad61500e83116 1 parent b4635b7
@markhewett markhewett authored John Flanagan committed
View
1  KeePassLib/Kdb4Node.h
@@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>
#import "Kdb.h"
#import "DDXML.h"
+#import "DDXMLElementAdditions.h"
#define KDB4_PRE_SIG1 (0x9AA2D903)
#define KDB4_PRE_SIG2 (0xB54BFB66)
View
17 KeePassLib/Kdb4Node.m
@@ -8,6 +8,23 @@
#import "Kdb4Node.h"
+@implementation DDXMLElement (MKPAdditions)
+
+- (void)removeChild:(DDXMLNode *)child {
+ int idx = -1;
+ for (int i = 0; i < [self childCount]; i++) {
+ if ([self childAtIndex:i] == child) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx >= 0) {
+ [self removeChildAtIndex:idx];
+ }
+}
+
+@end
+
@implementation Kdb4Group
@synthesize element;
View
24 KeePassLib/Kdb4Parser.m
@@ -19,6 +19,30 @@
#import "Kdb4Node.h"
#import "Base64.h"
+@implementation DDXMLDocument (MKPDDXMLDocument_Additions)
+
+- (id)initWithReadIO:(xmlInputReadCallback)ioread closeIO:(xmlInputCloseCallback)ioclose context:(void*)ioctx options:(NSUInteger)mask error:(NSError **)error {
+
+ // 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 = xmlReadIO(ioread, ioclose, ioctx, NULL, NULL, mask);
+ if (doc == NULL)
+ {
+ if (error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:1 userInfo:nil];
+
+ return nil;
+ }
+
+ return [self initWithDocPrimitive:doc owner:nil];
+}
+
+@end
+
@interface Kdb4Parser (PrivateMethods)
- (void)decodeProtected:(DDXMLElement*)root;
- (Kdb4Group*)parseGroup:(DDXMLElement*)root;
View
8 KeePassLib/Kdb4Writer.m
@@ -146,12 +146,8 @@ - (void)writeHeader:(OutputStream*)outputStream {
}
- (void)newFile:(NSString*)fileName withPassword:(KdbPassword*)kdbPassword {
- DDXMLElement *docRoot = [DDXMLNode elementWithName:@"KeePassFile"];
-
- DDXMLElement *rootElement = [DDXMLElement elementWithName:@"Root"];
- [docRoot addChild:rootElement];
-
- DDXMLDocument *document = [[DDXMLDocument alloc] initWithRootElement:docRoot];
+ DDXMLDocument *document = [[DDXMLDocument alloc] initWithXMLString:@"<KeePassFile><Root></Root></KeePassFile>" options:0 error:nil];
+ DDXMLElement *rootElement = [[document rootElement] elementForName:@"Root"];
Kdb4Tree *tree = [[Kdb4Tree alloc] initWithDocument:document];
[document release];
View
1  KeePassLib/KdbPassword.m
@@ -11,6 +11,7 @@
#import "KdbPassword.h"
#import "DDXML.h"
+#import "DDXMLElementAdditions.h"
#import "Base64.h"
@interface KdbPassword (PrivateMethods)
View
3  KissXML/Additions/DDXMLElementAdditions.h
@@ -14,6 +14,9 @@
- (NSString *)xmlns;
- (void)setXmlns:(NSString *)ns;
+- (NSString *)prettyXMLString;
+- (NSString *)compactXMLString;
+
- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string;
- (NSDictionary *)attributesAsDictionary;
View
16 KissXML/Additions/DDXMLElementAdditions.m
@@ -87,6 +87,22 @@ - (void)setXmlns:(NSString *)ns
}
/**
+ * Shortcut to get a pretty (formatted) string representation of the element.
+**/
+- (NSString *)prettyXMLString
+{
+ return [self XMLStringWithOptions:(DDXMLNodePrettyPrint | DDXMLNodeCompactEmptyElement)];
+}
+
+/**
+ * Shortcut to get a compact string representation of the element.
+**/
+- (NSString *)compactXMLString
+{
+ return [self XMLStringWithOptions:DDXMLNodeCompactEmptyElement];
+}
+
+/**
* Shortcut to avoid having to manually create a DDXMLNode everytime.
**/
- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string
View
8 KissXML/Categories/NSString+DDXML.m
@@ -1,5 +1,8 @@
#import "NSString+DDXML.h"
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
@implementation NSString (DDXML)
@@ -17,12 +20,11 @@ - (NSString *)stringByTrimming
- (NSString *)stringByTrimming
{
NSMutableString *mStr = [self mutableCopy];
- CFStringTrimWhitespace((CFMutableStringRef)mStr);
+ CFStringTrimWhitespace((__bridge CFMutableStringRef)mStr);
NSString *result = [mStr copy];
- [mStr release];
- return [result autorelease];
+ return result;
}
#endif
View
194 KissXML/DDXML.h
@@ -1,4 +1,196 @@
+/**
+ * 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
+**/
+
#import "DDXMLNode.h"
#import "DDXMLElement.h"
#import "DDXMLDocument.h"
-#import "DDXMLElementAdditions.h"
+
+
+
+#if TARGET_OS_IPHONE && 0 // Disabled by default
+
+// Since KissXML is a drop in replacement for NSXML,
+// it may be desireable (when writing cross-platform code to be used on both Mac OS X and iOS)
+// to use the NSXML prefixes instead of the DDXML prefix.
+//
+// This way, on Mac OS X it uses NSXML, and on iOS it uses KissXML.
+
+#ifndef NSXMLNode
+ #define NSXMLNode DDXMLNode
+#endif
+#ifndef NSXMLElement
+ #define NSXMLElement DDXMLElement
+#endif
+#ifndef NSXMLDocument
+ #define NSXMLDocument DDXMLDocument
+#endif
+
+#ifndef NSXMLInvalidKind
+ #define NSXMLInvalidKind DDXMLInvalidKind
+#endif
+#ifndef NSXMLDocumentKind
+ #define NSXMLDocumentKind DDXMLDocumentKind
+#endif
+#ifndef NSXMLElementKind
+ #define NSXMLElementKind DDXMLElementKind
+#endif
+#ifndef NSXMLAttributeKind
+ #define NSXMLAttributeKind DDXMLAttributeKind
+#endif
+#ifndef NSXMLNamespaceKind
+ #define NSXMLNamespaceKind DDXMLNamespaceKind
+#endif
+#ifndef NSXMLProcessingInstructionKind
+ #define NSXMLProcessingInstructionKind DDXMLProcessingInstructionKind
+#endif
+#ifndef NSXMLCommentKind
+ #define NSXMLCommentKind DDXMLCommentKind
+#endif
+#ifndef NSXMLTextKind
+ #define NSXMLTextKind DDXMLTextKind
+#endif
+#ifndef NSXMLDTDKind
+ #define NSXMLDTDKind DDXMLDTDKind
+#endif
+#ifndef NSXMLEntityDeclarationKind
+ #define NSXMLEntityDeclarationKind DDXMLEntityDeclarationKind
+#endif
+#ifndef NSXMLAttributeDeclarationKind
+ #define NSXMLAttributeDeclarationKind DDXMLAttributeDeclarationKind
+#endif
+#ifndef NSXMLElementDeclarationKind
+ #define NSXMLElementDeclarationKind DDXMLElementDeclarationKind
+#endif
+#ifndef NSXMLNotationDeclarationKind
+ #define NSXMLNotationDeclarationKind DDXMLNotationDeclarationKind
+#endif
+
+#ifndef NSXMLNodeOptionsNone
+ #define NSXMLNodeOptionsNone DDXMLNodeOptionsNone
+#endif
+#ifndef NSXMLNodeExpandEmptyElement
+ #define NSXMLNodeExpandEmptyElement DDXMLNodeExpandEmptyElement
+#endif
+#ifndef NSXMLNodeCompactEmptyElement
+ #define NSXMLNodeCompactEmptyElement DDXMLNodeCompactEmptyElement
+#endif
+#ifndef NSXMLNodePrettyPrint
+ #define NSXMLNodePrettyPrint DDXMLNodePrettyPrint
+#endif
+
+#endif // #if TARGET_OS_IPHONE
+
+
+
+// KissXML has rather straight-forward memory management:
+// https://github.com/robbiehanson/KissXML/wiki/MemoryManagementThreadSafety
+//
+// There are 3 important concepts to keep in mind when working with KissXML:
+//
+//
+// 1.) KissXML provides a light-weight wrapper around libxml.
+//
+// The parsing, creation, storage, etc of the xml tree is all done via libxml.
+// This is a fast low-level C library that's been around for ages, and comes pre-installed on Mac OS X and iOS.
+// KissXML provides an easy-to-use Objective-C library atop libxml.
+// So a DDXMLNode, DDXMLElement, or DDXMLDocument are simply objective-c objects
+// with pointers to the underlying libxml C structure.
+// Then only time you need to be aware of any of this is when it comes to equality.
+// In order to maximize speed and provide read-access thread-safety,
+// the library may create multiple DDXML wrapper objects that point to the same underlying xml node.
+// So don't assume you can test for equality with "==".
+// Instead use the isEqual method (as you should generally do with objects anyway).
+//
+//
+// 2.) XML is implicitly a tree heirarchy, and the XML API's are designed to allow traversal up & down the tree.
+//
+// The tree heirarchy and API contract have an implicit impact concerning memory management.
+//
+// <starbucks>
+// <latte/>
+// </starbucks>
+//
+// Imagine you have a DDXMLNode corresponding to the starbucks node,
+// and you have a DDXMLNode corresponding to the latte node.
+// Now imagine you release the starbucks node, but you retain a reference to the latte node.
+// What happens?
+// Well the latte node is a part of the xml tree heirarchy.
+// So if the latte node is still around, the xml tree heirarchy must stick around as well.
+// So even though the DDXMLNode corresponding to the starbucks node may get deallocated,
+// the underlying xml tree structure won't be freed until the latte node gets dealloacated.
+//
+// In general, this means that KissXML remains thread-safe when reading and processing a tree.
+// If you traverse a tree and fork off asynchronous tasks to process subnodes,
+// the tree will remain properly in place until all your asynchronous tasks have completed.
+// In other words, it just works.
+//
+// However, if you parse a huge document into memory, and retain a single node from the giant xml tree...
+// Well you should see the problem this creates.
+// Instead, in this situation, copy or detach the node if you want to keep it around.
+// Or just extract the info you need from it.
+//
+//
+// 3.) KissXML is read-access thread-safe, but write-access thread-unsafe (designed for speed).
+//
+// <starbucks>
+// <latte/>
+// </starbucks>
+//
+// Imagine you have a DDXMLNode corresponding to the starbucks node,
+// and you have a DDXMLNode corresponding to the latte node.
+// What happens if you invoke [starbucks removeChildAtIndex:0]?
+// Well the undelying xml tree will remove the latte node, and release the associated memory.
+// And what if you still have a reference to the DDXMLNode that corresponds to the latte node?
+// Well the short answer is that you shouldn't use it. At all.
+// This is pretty obvious when you think about it from the context of this simple example.
+// But in the real world, you might have multiple threads running in parallel,
+// and you might accidently modify a node while another thread is processing it.
+//
+// To completely fix this problem, and provide write-access thread-safety, would require extensive overhead.
+// This overhead is completely unwanted in the majority of cases.
+// Most XML usage patterns are heavily read-only.
+// And in the case of xml creation or modification, it is generally done on the same thread.
+// Thus the KissXML library is write-access thread-unsafe, but provides speedier performance.
+//
+// However, when such a bug does creep up, it produces horrible side-effects.
+// Essentially the pointer to the underlying xml structure becomes a dangling pointer,
+// which means that accessing the dangling pointer might give you the correct results, or completely random results.
+// And attempting to make modifications to non-existant xml nodes via the dangling pointer might do nothing,
+// or completely corrupt your heap and cause un-explainable crashes in random parts of your library.
+// Heap corruption is one of the worst problems to track down.
+// So to help out, the library provides a debugging macro to track down these problems.
+// That is, if you invalidate the write-access thread-unsafe rule,
+// this macro will tell you when you're trying to access a now-dangling pointer.
+//
+// How does it work?
+// Well everytime a DDXML wrapper object is created atop a libxml structure,
+// it marks the linkage in a table.
+// And everytime a libxml structure is freed, it destorys all corresponding linkages in the table.
+// So everytime a DDXML wrapper objects is about to dereference it's pointer,
+// it first ensures the linkage still exists in the table.
+//
+// Set to 1 to enable
+// Set to 0 to disable (this is the default)
+//
+// The debugging macro adds a significant amount of overhead, and should NOT be enabled on production builds.
+
+#if DEBUG
+ #define DDXML_DEBUG_MEMORY_ISSUES 0
+#else
+ #define DDXML_DEBUG_MEMORY_ISSUES 0 // Don't change me!
+#endif
View
31 KissXML/DDXMLDocument.h
@@ -2,6 +2,23 @@
#import "DDXMLElement.h"
#import "DDXMLNode.h"
+/**
+ * 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
+**/
enum {
DDXMLDocumentXMLKind = 0,
@@ -14,23 +31,23 @@ typedef NSUInteger DDXMLDocumentContentKind;
@interface DDXMLDocument : DDXMLNode
{
}
+- (id)initWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)inOwner;
- (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)initWithReadIO:(xmlInputReadCallback)ioread closeIO:(xmlInputCloseCallback)ioclose context:(void*)ioctx options:(NSUInteger)mask error:(NSError **)error;
-- (id)initWithRootElement:(DDXMLElement *)element;
+//- (id)initWithRootElement:(DDXMLElement *)element;
//+ (Class)replacementClassForClass:(Class)cls;
//- (void)setCharacterEncoding:(NSString *)encoding; //primitive
//- (NSString *)characterEncoding; //primitive
-- (void)setVersion:(NSString *)version;
-- (NSString *)version;
+//- (void)setVersion:(NSString *)version;
+//- (NSString *)version;
-- (void)setStandalone:(BOOL)standalone;
-- (BOOL)isStandalone;
+//- (void)setStandalone:(BOOL)standalone;
+//- (BOOL)isStandalone;
//- (void)setDocumentContentKind:(DDXMLDocumentContentKind)kind;
//- (DDXMLDocumentContentKind)documentContentKind;
@@ -41,7 +58,7 @@ typedef NSUInteger DDXMLDocumentContentKind;
//- (void)setDTD:(DDXMLDTD *)documentTypeDeclaration;
//- (DDXMLDTD *)DTD;
-- (void)setRootElement:(DDXMLNode *)root;
+//- (void)setRootElement:(DDXMLNode *)root;
- (DDXMLElement *)rootElement;
//- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index;
View
109 KissXML/DDXMLDocument.m
@@ -1,6 +1,27 @@
#import "DDXMLPrivate.h"
#import "NSString+DDXML.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 DDXMLDocument
@@ -8,31 +29,30 @@ @implementation DDXMLDocument
* Returns a DDXML wrapper object for the given primitive node.
* The given node MUST be non-NULL and of the proper type.
**/
-+ (id)nodeWithDocPrimitive:(xmlDocPtr)doc freeOnDealloc:(BOOL)flag
++ (id)nodeWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)owner
{
- return [[[DDXMLDocument alloc] initWithDocPrimitive:doc freeOnDealloc:flag] autorelease];
+ return [[DDXMLDocument alloc] initWithDocPrimitive:doc owner:owner];
}
-- (id)initWithDocPrimitive:(xmlDocPtr)doc freeOnDealloc:(BOOL)flag
+- (id)initWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)inOwner
{
- self = [super initWithPrimitive:(xmlKindPtr)doc freeOnDealloc:flag];
+ self = [super initWithPrimitive:(xmlKindPtr)doc owner:inOwner];
return self;
}
-+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes
- NSAssert(NO, @"Use nodeWithDocPrimitive:freeOnDealloc:");
+ NSAssert(NO, @"Use nodeWithDocPrimitive:owner:");
return nil;
}
-- (id)initWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag
+- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)inOwner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes.
- NSAssert(NO, @"Use initWithDocPrimitive:freeOnDealloc:");
+ NSAssert(NO, @"Use initWithDocPrimitive:owner:");
- [self release];
return nil;
}
@@ -61,7 +81,6 @@ - (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)err
{
if (error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:0 userInfo:nil];
- [self release];
return nil;
}
@@ -77,63 +96,10 @@ - (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)err
{
if (error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:1 userInfo:nil];
- [self release];
- return nil;
- }
-
- return [self initWithDocPrimitive:doc freeOnDealloc:YES];
-}
-
-- (id)initWithReadIO:(xmlInputReadCallback)ioread closeIO:(xmlInputCloseCallback)ioclose context:(void*)ioctx options:(NSUInteger)mask error:(NSError **)error {
-
- // 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 = xmlReadIO(ioread, ioclose, ioctx, NULL, NULL, mask);
- if (doc == NULL)
- {
- if (error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:1 userInfo:nil];
-
return nil;
}
- return [self initWithDocPrimitive:doc freeOnDealloc:YES];
-}
-
-- (id)initWithRootElement:(DDXMLElement *)element {
- xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
- if (self = [self initWithDocPrimitive:doc freeOnDealloc:YES]) {
- if (element) {
- [self setRootElement:element];
- }
- }
-
- return self;
-}
-
-- (void)setVersion:(NSString *)version {
- xmlDocPtr doc = (xmlDocPtr)genericPtr;
- xmlFree((xmlChar *)doc->version);
- doc->version = xmlStrdup([version xmlChar]);
-}
-
-- (NSString *)version {
- xmlDocPtr doc = (xmlDocPtr)genericPtr;
- return [NSString stringWithUTF8String:((const char*)doc->version)];
-}
-
-- (void)setStandalone:(BOOL)standalone {
- xmlDocPtr doc = (xmlDocPtr)genericPtr;
- doc->standalone = standalone;
-}
-
-- (BOOL)isStandalone {
- xmlDocPtr doc = (xmlDocPtr)genericPtr;
- return doc->standalone;
+ return [self initWithDocPrimitive:doc owner:nil];
}
/**
@@ -141,6 +107,10 @@ - (BOOL)isStandalone {
**/
- (DDXMLElement *)rootElement
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
xmlDocPtr doc = (xmlDocPtr)genericPtr;
// doc->children is a list containing possibly comments, DTDs, etc...
@@ -148,23 +118,22 @@ - (DDXMLElement *)rootElement
xmlNodePtr rootNode = xmlDocGetRootElement(doc);
if (rootNode != NULL)
- return [DDXMLElement nodeWithElementPrimitive:rootNode freeOnDealloc:NO];
+ return [DDXMLElement nodeWithElementPrimitive:rootNode owner:self];
else
return nil;
}
-- (void)setRootElement:(DDXMLNode *)root {
- xmlDocPtr doc = (xmlDocPtr)genericPtr;
- xmlDocSetRootElement(doc, (xmlNodePtr)root->genericPtr);
-}
-
- (NSData *)XMLData
{
+ // Zombie test occurs in XMLString
+
return [[self XMLString] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSData *)XMLDataWithOptions:(NSUInteger)options
{
+ // Zombie test occurs in XMLString
+
return [[self XMLStringWithOptions:options] dataUsingEncoding:NSUTF8StringEncoding];
}
View
18 KissXML/DDXMLElement.h
@@ -1,6 +1,23 @@
#import <Foundation/Foundation.h>
#import "DDXMLNode.h"
+/**
+ * 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
+**/
@interface DDXMLElement : DDXMLNode
{
@@ -41,7 +58,6 @@
- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index;
//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index;
- (void)removeChildAtIndex:(NSUInteger)index;
-- (void)removeChild:(DDXMLNode*)child;
- (void)setChildren:(NSArray *)children;
- (void)addChild:(DDXMLNode *)child;
//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node;
View
541 KissXML/DDXMLElement.m
@@ -1,6 +1,27 @@
#import "DDXMLPrivate.h"
#import "NSString+DDXML.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 DDXMLElement
@@ -8,31 +29,30 @@ @implementation DDXMLElement
* Returns a DDXML wrapper object for the given primitive node.
* The given node MUST be non-NULL and of the proper type.
**/
-+ (id)nodeWithElementPrimitive:(xmlNodePtr)node freeOnDealloc:(BOOL)flag
++ (id)nodeWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)owner
{
- return [[[DDXMLElement alloc] initWithElementPrimitive:node freeOnDealloc:flag] autorelease];
+ return [[DDXMLElement alloc] initWithElementPrimitive:node owner:owner];
}
-- (id)initWithElementPrimitive:(xmlNodePtr)node freeOnDealloc:(BOOL)flag
+- (id)initWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)inOwner
{
- self = [super initWithPrimitive:(xmlKindPtr)node freeOnDealloc:flag];
+ self = [super initWithPrimitive:(xmlKindPtr)node owner:inOwner];
return self;
}
-+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes
- NSAssert(NO, @"Use nodeWithElementPrimitive:freeOnDealloc:");
+ NSAssert(NO, @"Use nodeWithElementPrimitive:owner:");
return nil;
}
-- (id)initWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag
+- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)inOwner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes.
- NSAssert(NO, @"Use initWithElementPrimitive:freeOnDealloc:");
+ NSAssert(NO, @"Use initWithElementPrimitive:owner:");
- [self release];
return nil;
}
@@ -43,11 +63,10 @@ - (id)initWithName:(NSString *)name
xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
if (node == NULL)
{
- [self release];
return nil;
}
- return [self initWithElementPrimitive:node freeOnDealloc:YES];
+ return [self initWithElementPrimitive:node owner:nil];
}
- (id)initWithName:(NSString *)name URI:(NSString *)URI
@@ -57,11 +76,10 @@ - (id)initWithName:(NSString *)name URI:(NSString *)URI
xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
if (node == NULL)
{
- [self release];
return nil;
}
- DDXMLElement *result = [self initWithElementPrimitive:node freeOnDealloc:YES];
+ DDXMLElement *result = [self initWithElementPrimitive:node owner:nil];
[result setURI:URI];
return result;
@@ -74,11 +92,10 @@ - (id)initWithName:(NSString *)name stringValue:(NSString *)string
xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
if (node == NULL)
{
- [self release];
return nil;
}
- DDXMLElement *result = [self initWithElementPrimitive:node freeOnDealloc:YES];
+ DDXMLElement *result = [self initWithElementPrimitive:node owner:nil];
[result setStringValue:string];
return result;
@@ -89,16 +106,13 @@ - (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];
+ return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -106,6 +120,71 @@ - (id)initWithXMLString:(NSString *)string error:(NSError **)error
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
+ * Helper method elementsForName and elementsForLocalName:URI: so work isn't duplicated.
+ * The name parameter is required, all others are optional.
+**/
+- (NSArray *)_elementsForName:(NSString *)name
+ localName:(NSString *)localName
+ prefix:(NSString *)prefix
+ uri:(NSString *)uri
+{
+ // This is a private/internal method
+
+ // Rule : !uri => match: name
+ // Rule : uri && hasPrefix => match: name || (localName && uri)
+ // Rule : uri && !hasPefix => match: name && uri
+
+ xmlNodePtr node = (xmlNodePtr)genericPtr;
+
+ NSMutableArray *result = [NSMutableArray array];
+
+ BOOL hasPrefix = [prefix length] > 0;
+
+ const xmlChar *xmlName = [name xmlChar];
+ const xmlChar *xmlLocalName = [localName xmlChar];
+ const xmlChar *xmlUri = [uri xmlChar];
+
+ xmlNodePtr child = node->children;
+ while (child)
+ {
+ if (IsXmlNodePtr(child))
+ {
+ BOOL match = NO;
+
+ if (uri == nil)
+ {
+ match = xmlStrEqual(child->name, xmlName);
+ }
+ else
+ {
+ BOOL nameMatch = xmlStrEqual(child->name, xmlName);
+ BOOL localNameMatch = xmlStrEqual(child->name, xmlLocalName);
+
+ BOOL uriMatch = NO;
+ if (child->ns)
+ {
+ uriMatch = xmlStrEqual(child->ns->href, xmlUri);
+ }
+
+ if (hasPrefix)
+ match = nameMatch || (localNameMatch && uriMatch);
+ else
+ match = nameMatch && uriMatch;
+ }
+
+ if (match)
+ {
+ [result addObject:[DDXMLElement nodeWithElementPrimitive:child owner:self]];
+ }
+ }
+
+ child = child->next;
+ }
+
+ return result;
+}
+
+/**
* 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
@@ -114,6 +193,10 @@ - (id)initWithXMLString:(NSString *)string error:(NSError **)error
**/
- (NSArray *)elementsForName:(NSString *)name
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
if (name == nil) return [NSArray array];
// We need to check to see if name has a prefix.
@@ -121,132 +204,116 @@ - (NSArray *)elementsForName:(NSString *)name
// 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];
+ NSString *prefix;
+ NSString *localName;
+
+ [DDXMLNode getPrefix:&prefix localName:&localName forName:name];
+
if ([prefix length] > 0)
{
xmlNodePtr node = (xmlNodePtr)genericPtr;
+
+ // Note: We use xmlSearchNs instead of resolveNamespaceForName: because
+ // we want to avoid creating wrapper objects when possible.
+
xmlNsPtr ns = xmlSearchNs(node->doc, node, [prefix xmlChar]);
- if (ns != NULL)
+ if (ns)
{
NSString *uri = [NSString stringWithUTF8String:((const char *)ns->href)];
- return [self elementsForName:name uri:uri];
+ return [self _elementsForName:name localName:localName prefix:prefix uri:uri];
}
-
- // Note: We used xmlSearchNs instead of resolveNamespaceForName: because
- // we want to avoid creating wrapper objects when possible.
}
- return [self elementsForName:name uri:nil];
+ return [self _elementsForName:name localName:localName prefix:prefix uri:nil];
}
-- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI
+- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)uri
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
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 *prefix = [self _recursiveResolvePrefixForURI:uri atNode:(xmlNodePtr)genericPtr];
+ if (prefix)
{
NSString *name = [NSString stringWithFormat:@"%@:%@", prefix, localName];
- return [self elementsForName:name uri:URI];
+ return [self _elementsForName:name localName:localName prefix:prefix uri:uri];
}
else
{
- return [self elementsForName:localName uri:URI];
+ NSString *prefix;
+ NSString *realLocalName;
+
+ [DDXMLNode getPrefix:&prefix localName:&realLocalName forName:localName];
+
+ return [self _elementsForName:localName localName:realLocalName prefix:prefix uri:uri];
}
}
-/**
- * Helper method elementsForName and elementsForLocalName:URI: so work isn't duplicated.
- * The name parameter is required, URI is optional.
-**/
-- (NSArray *)elementsForName:(NSString *)name uri:(NSString *)uri
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Attributes
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)_hasAttributeWithName:(NSString *)name
{
- // 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;
+ // This is a private/internal method
- BOOL hasPrefix = [[[self class] prefixForName:name] length] > 0;
- NSString *localName = [[self class] localNameForName:name];
-
- xmlNodePtr child = node->children;
- while (child != NULL)
+ xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
+ if (attr)
{
- if (child->type == XML_ELEMENT_NODE)
+ const xmlChar *xmlName = [name xmlChar];
+ do
{
- BOOL match = NO;
- if (uri == nil)
- {
- match = xmlStrEqual(child->name, [name xmlChar]);
- }
- else
+ if (xmlStrEqual(attr->name, xmlName))
{
- 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;
+ return YES;
}
+ attr = attr->next;
- if (match)
- {
- [result addObject:[DDXMLElement nodeWithElementPrimitive:child freeOnDealloc:NO]];
- }
- }
-
- child = child->next;
+ } while (attr);
}
- return result;
+ return NO;
}
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Attributes
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-- (BOOL)hasAttributeWithName:(NSString *)name
+- (void)_removeAttributeForName:(NSString *)name
{
+ // This is a private/internal method
+
xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
- if (attr != NULL)
+ if (attr)
{
const xmlChar *xmlName = [name xmlChar];
-
do
{
if (xmlStrEqual(attr->name, xmlName))
{
- return YES;
+ [DDXMLNode removeAttribute:attr];
+ return;
}
attr = attr->next;
- } while (attr != NULL);
+ } while(attr);
}
-
- return NO;
}
- (void)addAttribute:(DDXMLNode *)attribute
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
// NSXML version uses this same assertion
- DDXMLAssert([attribute hasParent] == NO, @"Cannot add an attribute with a parent; detach or copy first");
+ DDXMLAssert([attribute _hasParent] == NO, @"Cannot add an attribute with a parent; detach or copy first");
DDXMLAssert(IsXmlAttrPtr(attribute->genericPtr), @"Not an attribute");
- [self removeAttributeForName:[attribute name]];
+ [self _removeAttributeForName:[attribute name]];
// xmlNodePtr xmlAddChild(xmlNodePtr parent, xmlNodePtr cur)
// Add a new node to @parent, at the end of the child (or property) list merging
@@ -256,47 +323,30 @@ - (void)addAttribute:(DDXMLNode *)attribute
xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)attribute->genericPtr);
// The attribute is now part of the xml tree heirarchy
- attribute->freeOnDealloc = NO;
-}
-
-- (void)removeAttribute:(xmlAttrPtr)attr
-{
- [[self class] removeAttribute:attr fromNode:(xmlNodePtr)genericPtr];
-}
-
-- (void)removeAllAttributes
-{
- [[self class] removeAllAttributesFromNode:(xmlNodePtr)genericPtr];
+ attribute->owner = self;
}
- (void)removeAttributeForName:(NSString *)name
{
- xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
- if (attr != NULL)
- {
- const xmlChar *xmlName = [name xmlChar];
-
- do
- {
- if (xmlStrEqual(attr->name, xmlName))
- {
- [self removeAttribute:attr];
- return;
- }
- attr = attr->next;
-
- } while(attr != NULL);
- }
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
+ [self _removeAttributeForName:name];
}
- (NSArray *)attributes
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
NSMutableArray *result = [NSMutableArray array];
xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
while (attr != NULL)
{
- [result addObject:[DDXMLAttributeNode nodeWithAttrPrimitive:attr freeOnDealloc:NO]];
+ [result addObject:[DDXMLAttributeNode nodeWithAttrPrimitive:attr owner:self]];
attr = attr->next;
}
@@ -306,33 +356,40 @@ - (NSArray *)attributes
- (DDXMLNode *)attributeForName:(NSString *)name
{
- const xmlChar *attrName = [name xmlChar];
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
- while (attr != NULL)
+ if (attr)
{
- if (attr->ns && attr->ns->prefix)
+ const xmlChar *xmlName = [name xmlChar];
+ do
{
- // If the attribute name was originally something like "xml:quack",
- // then attr->name is "quack" and attr->ns->prefix is "xml".
- //
- // So if the user is searching for "xml:quack" we need to take the prefix into account.
- // Note that "xml:quack" is what would be printed if we output the attribute.
-
- if (xmlStrQEqual(attr->ns->prefix, attr->name, attrName))
+ if (attr->ns && attr->ns->prefix)
{
- return [DDXMLAttributeNode nodeWithAttrPrimitive:attr freeOnDealloc:NO];
+ // If the attribute name was originally something like "xml:quack",
+ // then attr->name is "quack" and attr->ns->prefix is "xml".
+ //
+ // So if the user is searching for "xml:quack" we need to take the prefix into account.
+ // Note that "xml:quack" is what would be printed if we output the attribute.
+
+ if (xmlStrQEqual(attr->ns->prefix, attr->name, xmlName))
+ {
+ return [DDXMLAttributeNode nodeWithAttrPrimitive:attr owner:self];
+ }
}
- }
- else
- {
- if (xmlStrEqual(attr->name, attrName))
+ else
{
- return [DDXMLAttributeNode nodeWithAttrPrimitive:attr freeOnDealloc:NO];
+ if (xmlStrEqual(attr->name, xmlName))
+ {
+ return [DDXMLAttributeNode nodeWithAttrPrimitive:attr owner:self];
+ }
}
- }
-
- attr = attr->next;
+
+ attr = attr->next;
+
+ } while (attr);
}
return nil;
}
@@ -343,7 +400,11 @@ - (DDXMLNode *)attributeForName:(NSString *)name
**/
- (void)setAttributes:(NSArray *)attributes
{
- [self removeAllAttributes];
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
+ [DDXMLNode removeAllAttributesFromNode:(xmlNodePtr)genericPtr];
NSUInteger i;
for (i = 0; i < [attributes count]; i++)
@@ -359,10 +420,31 @@ - (void)setAttributes:(NSArray *)attributes
#pragma mark Namespaces
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-- (void)addNamespace:(DDXMLNode *)namespace
+- (void)_removeNamespaceForPrefix:(NSString *)name
+{
+ xmlNodePtr node = (xmlNodePtr)genericPtr;
+
+ // 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 = node->nsDef;
+ while (ns != NULL)
+ {
+ if (xmlStrEqual(ns->prefix, xmlName))
+ {
+ [DDXMLNode removeNamespace:ns fromNode:node];
+ break;
+ }
+ ns = ns->next;
+ }
+
+ // Note: The removeNamespace method properly handles the situation where the namespace is the default namespace
+}
+
+- (void)_addNamespace:(DDXMLNode *)namespace
{
// NSXML version uses this same assertion
- DDXMLAssert([namespace hasParent] == NO, @"Cannot add a namespace with a parent; detach or copy first");
+ DDXMLAssert([namespace _hasParent] == NO, @"Cannot add a namespace with a parent; detach or copy first");
DDXMLAssert(IsXmlNsPtr(namespace->genericPtr), @"Not a namespace");
xmlNodePtr node = (xmlNodePtr)genericPtr;
@@ -372,7 +454,7 @@ - (void)addNamespace:(DDXMLNode *)namespace
NSString *namespaceName = [namespace name];
- [self removeNamespaceForPrefix:namespaceName];
+ [self _removeNamespaceForPrefix:namespaceName];
xmlNsPtr currentNs = node->nsDef;
if (currentNs == NULL)
@@ -390,14 +472,14 @@ - (void)addNamespace:(DDXMLNode *)namespace
}
// The namespace is now part of the xml tree heirarchy
- namespace->freeOnDealloc = NO;
+ namespace->owner = self;
if ([namespace isKindOfClass:[DDXMLNamespaceNode class]])
{
DDXMLNamespaceNode *ddNamespace = (DDXMLNamespaceNode *)namespace;
// The xmlNs structure doesn't contain a reference to the parent, so we manage our own reference
- [ddNamespace setNsParentPtr:node];
+ [ddNamespace _setNsParentPtr:node];
}
// Did we just add a default namespace
@@ -409,43 +491,36 @@ - (void)addNamespace:(DDXMLNode *)namespace
}
}
-- (void)removeNamespace:(xmlNsPtr)ns
-{
- [[self class] removeNamespace:ns fromNode:(xmlNodePtr)genericPtr];
-}
-
-- (void)removeAllNamespaces
+- (void)addNamespace:(DDXMLNode *)namespace
{
- [[self class] removeAllNamespacesFromNode:(xmlNodePtr)genericPtr];
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
+ [self _addNamespace:namespace];
}
- (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;
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
- 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
+ [self _removeNamespaceForPrefix:name];
}
- (NSArray *)namespaces
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
NSMutableArray *result = [NSMutableArray array];
xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef;
while (ns != NULL)
{
- [result addObject:[DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr freeOnDealloc:NO]];
+ [result addObject:[DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr owner:self]];
ns = ns->next;
}
@@ -455,6 +530,10 @@ - (NSArray *)namespaces
- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
// If the prefix is nil or the empty string, the user is requesting the default namespace
if ([prefix length] == 0)
@@ -463,19 +542,24 @@ - (DDXMLNode *)namespaceForPrefix:(NSString *)prefix
xmlNsPtr ns = ((xmlNodePtr)genericPtr)->ns;
if (ns != NULL)
{
- return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr freeOnDealloc:NO];
+ return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr owner:self];
}
}
else
{
xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef;
- while (ns != NULL)
+ if (ns)
{
- if (xmlStrEqual(ns->prefix, [prefix xmlChar]))
+ const xmlChar *xmlPrefix = [prefix xmlChar];
+ do
{
- return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr freeOnDealloc:NO];
- }
- ns = ns->next;
+ if (xmlStrEqual(ns->prefix, xmlPrefix))
+ {
+ return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr owner:self];
+ }
+ ns = ns->next;
+
+ } while (ns);
}
}
@@ -484,13 +568,17 @@ - (DDXMLNode *)namespaceForPrefix:(NSString *)prefix
- (void)setNamespaces:(NSArray *)namespaces
{
- [self removeAllNamespaces];
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
+ [DDXMLNode removeAllNamespacesFromNode:(xmlNodePtr)genericPtr];
NSUInteger i;
for (i = 0; i < [namespaces count]; i++)
{
DDXMLNode *namespace = [namespaces objectAtIndex:i];
- [self addNamespace:namespace];
+ [self _addNamespace:namespace];
// Note: The addNamespace method properly sets the freeOnDealloc ivar.
}
@@ -499,21 +587,28 @@ - (void)setNamespaces:(NSArray *)namespaces
/**
* Recursively searches the given node for the given namespace
**/
-+ (DDXMLNode *)resolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr
+- (DDXMLNode *)_recursiveResolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr
{
+ // This is a private/internal method
+
if (nodePtr == NULL) return nil;
xmlNsPtr ns = nodePtr->nsDef;
- while (ns != NULL)
+ if (ns)
{
- if (xmlStrEqual(ns->prefix, [prefix xmlChar]))
+ const xmlChar *xmlPrefix = [prefix xmlChar];
+ do
{
- return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:nodePtr freeOnDealloc:NO];
- }
- ns = ns->next;
+ if (xmlStrEqual(ns->prefix, xmlPrefix))
+ {
+ return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:nodePtr owner:self];
+ }
+ ns = ns->next;
+
+ } while(ns);
}
- return [self resolveNamespaceForPrefix:prefix atNode:nodePtr->parent];
+ return [self _recursiveResolveNamespaceForPrefix:prefix atNode:nodePtr->parent];
}
/**
@@ -522,10 +617,14 @@ + (DDXMLNode *)resolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)n
**/
- (DDXMLNode *)resolveNamespaceForName:(NSString *)name
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
// 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];
+ return [self _recursiveResolveNamespaceForPrefix:nil atNode:(xmlNodePtr)genericPtr];
}
NSString *prefix = [[self class] prefixForName:name];
@@ -536,7 +635,7 @@ - (DDXMLNode *)resolveNamespaceForName:(NSString *)name
// 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 [self _recursiveResolveNamespaceForPrefix:prefix atNode:(xmlNodePtr)genericPtr];
}
return nil;
@@ -545,24 +644,31 @@ - (DDXMLNode *)resolveNamespaceForName:(NSString *)name
/**
* Recursively searches the given node for a namespace with the given URI, and a set prefix.
**/
-+ (NSString *)resolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr
+- (NSString *)_recursiveResolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr
{
+ // This is a private/internal method
+
if (nodePtr == NULL) return nil;
xmlNsPtr ns = nodePtr->nsDef;
- while (ns != NULL)
+ if (ns)
{
- if (xmlStrEqual(ns->href, [uri xmlChar]))
+ const xmlChar *xmlUri = [uri xmlChar];
+ do
{
- if (ns->prefix != NULL)
+ if (xmlStrEqual(ns->href, xmlUri))
{
- return [NSString stringWithUTF8String:((const char *)ns->prefix)];
+ if (ns->prefix != NULL)
+ {
+ return [NSString stringWithUTF8String:((const char *)ns->prefix)];
+ }
}
- }
- ns = ns->next;
+ ns = ns->next;
+
+ } while (ns);
}
- return [self resolvePrefixForURI:uri atNode:nodePtr->parent];
+ return [self _recursiveResolvePrefixForURI:uri atNode:nodePtr->parent];
}
/**
@@ -571,10 +677,14 @@ + (NSString *)resolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr
**/
- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
// 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];
+ return [self _recursiveResolvePrefixForURI:namespaceURI atNode:(xmlNodePtr)genericPtr];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -583,21 +693,29 @@ - (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI
- (void)addChild:(DDXMLNode *)child
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
// NSXML version uses these same assertions
- DDXMLAssert([child hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first");
+ DDXMLAssert([child _hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first");
DDXMLAssert(IsXmlNodePtr(child->genericPtr),
@"Elements can only have text, elements, processing instructions, and comments as children");
xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr);
// The node is now part of the xml tree heirarchy
- child->freeOnDealloc = NO;
+ child->owner = self;
}
- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
// NSXML version uses these same assertions
- DDXMLAssert([child hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first");
+ DDXMLAssert([child _hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first");
DDXMLAssert(IsXmlNodePtr(child->genericPtr),
@"Elements can only have text, elements, processing instructions, and comments as children");
@@ -612,7 +730,9 @@ - (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index
if (i == index)
{
xmlAddPrevSibling(childNodePtr, (xmlNodePtr)child->genericPtr);
- child->freeOnDealloc = NO;
+
+ child->owner = self;
+
return;
}
@@ -624,7 +744,9 @@ - (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index
if (i == index)
{
xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr);
- child->freeOnDealloc = NO;
+
+ child->owner = self;
+
return;
}
@@ -634,6 +756,10 @@ - (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index
- (void)removeChildAtIndex:(NSUInteger)index
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
NSUInteger i = 0;
xmlNodePtr child = ((xmlNodePtr)genericPtr)->children;
@@ -644,7 +770,7 @@ - (void)removeChildAtIndex:(NSUInteger)index
{
if (i == index)
{
- [DDXMLNode removeChild:child fromNode:(xmlNodePtr)genericPtr];
+ [DDXMLNode removeChild:child];
return;
}
@@ -654,27 +780,12 @@ - (void)removeChildAtIndex:(NSUInteger)index
}
}
-- (void)removeChild:(DDXMLNode*)node {
- xmlNodePtr nodePtr = (xmlNodePtr)node->genericPtr;
-
- xmlNodePtr child = ((xmlNodePtr)genericPtr)->children;
- while (child != NULL)
- {
- // Ignore all but element, comment, text, or processing instruction nodes
- if (IsXmlNodePtr(child))
- {
- if (child == nodePtr)
- {
- [DDXMLNode removeChild:child fromNode:(xmlNodePtr)genericPtr];
- return;
- }
- }
- child = child->next;
- }
-}
-
- (void)setChildren:(NSArray *)children
{
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
[DDXMLNode removeAllChildrenFromNode:(xmlNodePtr)genericPtr];
NSUInteger i;
View
24 KissXML/DDXMLNode.h
@@ -3,6 +3,23 @@
@class DDXMLDocument;
+/**
+ * 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
+**/
enum {
DDXMLInvalidKind = 0,
@@ -37,8 +54,11 @@ enum {
// Every DDXML object is simply a wrapper around an underlying libxml node
struct _xmlKind *genericPtr;
- // Root nodes free the underlying libxml node on dealloc.
- BOOL freeOnDealloc;
+ // Every libxml node resides somewhere within an xml tree heirarchy.
+ // We cannot free the tree heirarchy until all referencing nodes have been released.
+ // So all nodes retain a reference to the node that created them,
+ // and when the last reference is released the tree gets freed.
+ DDXMLNode *owner;
}
//- (id)initWithKind:(DDXMLNodeKind)kind;
View
1,591 KissXML/DDXMLNode.m
@@ -4,17 +4,60 @@
#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 BOOL initialized = NO;
- if (!initialized)
- {
- initialized = YES;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
// Redirect error output to our own function (don't clog up the console)
initGenericErrorDefaultFunc(NULL);
@@ -24,22 +67,30 @@ + (void)initialize
// 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] autorelease];
+ return [[DDXMLElement alloc] initWithName:name];
}
+ (id)elementWithName:(NSString *)name stringValue:(NSString *)string
{
- return [[[DDXMLElement alloc] initWithName:name stringValue:string] autorelease];
+ return [[DDXMLElement alloc] initWithName:name stringValue:string];
}
+ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes
{
- DDXMLElement *result = [[[DDXMLElement alloc] initWithName:name] autorelease];
+ DDXMLElement *result = [[DDXMLElement alloc] initWithName:name];
[result setChildren:children];
[result setAttributes:attributes];
@@ -48,7 +99,7 @@ + (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(
+ (id)elementWithName:(NSString *)name URI:(NSString *)URI
{
- return [[[DDXMLElement alloc] initWithName:name URI:URI] autorelease];
+ return [[DDXMLElement alloc] initWithName:name URI:URI];
}
+ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue
@@ -57,7 +108,7 @@ + (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue
if (attr == NULL) return nil;
- return [[[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr freeOnDealloc:YES] autorelease];
+ return [[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr owner:nil];
}
+ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue
@@ -66,10 +117,10 @@ + (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSStri
if (attr == NULL) return nil;
- DDXMLAttributeNode *result = [[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr freeOnDealloc:YES];
+ DDXMLAttributeNode *result = [[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr owner:nil];
[result setURI:URI];
- return [result autorelease];
+ return result;
}
+ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue
@@ -81,7 +132,7 @@ + (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue
if (ns == NULL) return nil;
- return [[[DDXMLNamespaceNode alloc] initWithNsPrimitive:ns nsParent:NULL freeOnDealloc:YES] autorelease];
+ return [[DDXMLNamespaceNode alloc] initWithNsPrimitive:ns nsParent:NULL owner:nil];
}
+ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue
@@ -90,7 +141,7 @@ + (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)str
if (procInst == NULL) return nil;
- return [[[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)procInst freeOnDealloc:YES] autorelease];
+ return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)procInst owner:nil];
}
+ (id)commentWithStringValue:(NSString *)stringValue
@@ -99,7 +150,7 @@ + (id)commentWithStringValue:(NSString *)stringValue
if (comment == NULL) return nil;
- return [[[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)comment freeOnDealloc:YES] autorelease];
+ return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)comment owner:nil];
}
+ (id)textWithStringValue:(NSString *)stringValue
@@ -108,36 +159,36 @@ + (id)textWithStringValue:(NSString *)stringValue
if (text == NULL) return nil;
- return [[[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)text freeOnDealloc:YES] autorelease];
+ return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)text owner:nil];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Init, Dealloc
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-+ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag
++ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
if (kindPtr->type == XML_DOCUMENT_NODE)
{
- return [DDXMLDocument nodeWithDocPrimitive:(xmlDocPtr)kindPtr freeOnDealloc:flag];
+ return [DDXMLDocument nodeWithDocPrimitive:(xmlDocPtr)kindPtr owner:owner];
}
else if (kindPtr->type == XML_ELEMENT_NODE)
{
- return [DDXMLElement nodeWithElementPrimitive:(xmlNodePtr)kindPtr freeOnDealloc:flag];
+ 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 freeOnDealloc:flag];
+ return [DDXMLNamespaceNode nodeWithNsPrimitive:(xmlNsPtr)kindPtr nsParent:NULL owner:owner];
}
else if (kindPtr->type == XML_ATTRIBUTE_NODE)
{
- return [DDXMLAttributeNode nodeWithAttrPrimitive:(xmlAttrPtr)kindPtr freeOnDealloc:flag];
+ return [DDXMLAttributeNode nodeWithAttrPrimitive:(xmlAttrPtr)kindPtr owner:owner];
}
else
{
- return [DDXMLNode nodeWithPrimitive:kindPtr freeOnDealloc:flag];
+ return [DDXMLNode nodeWithPrimitive:kindPtr owner:owner];
}
}
@@ -145,55 +196,102 @@ + (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag
* 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 freeOnDealloc:(BOOL)flag
++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
- return [[[DDXMLNode alloc] initWithPrimitive:kindPtr freeOnDealloc:flag] autorelease];
+ 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 freeOnDealloc:(BOOL)flag
+- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)inOwner
{
if ((self = [super init]))
{
genericPtr = kindPtr;
- freeOnDealloc = flag;
+ 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 (freeOnDealloc)
+#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))
{
- xmlFreeDoc((xmlDocPtr)genericPtr);
+ xmlDocPtr doc = (xmlDocPtr)genericPtr;
+
+ #if DDXML_DEBUG_MEMORY_ISSUES
+ RecursiveMarkZombiesFromDoc(doc);
+ #endif
+ xmlFreeDoc(doc);
}
else if (IsXmlNodePtr(genericPtr))
{
- xmlFreeNode((xmlNodePtr)genericPtr);
+ xmlNodePtr node = (xmlNodePtr)genericPtr;
+
+ #if DDXML_DEBUG_MEMORY_ISSUES
+ RecursiveMarkZombiesFromNode(node);
+ #endif
+ xmlFreeNode(node);
}
else
{
- NSAssert1(NO, @"Cannot free unknown node type: %i", ((xmlKindPtr)genericPtr)->type);
+ NSAssert1(NO, @"Cannot free unknown node type: %i", genericPtr->type);
}
}
- [super dealloc];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -202,13 +300,17 @@ - (void)dealloc
- (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 freeOnDealloc:YES];
+ return [[DDXMLDocument alloc] initWithDocPrimitive:copyDocPtr owner:nil];
}
if (IsXmlNodePtr(genericPtr))
@@ -218,9 +320,9 @@ - (id)copyWithZone:(NSZone *)zone
if (copyNodePtr == NULL) return nil;
if ([self isKindOfClass:[DDXMLElement class]])
- return [[DDXMLElement alloc] initWithElementPrimitive:copyNodePtr freeOnDealloc:YES];
+ return [[DDXMLElement alloc] initWithElementPrimitive:copyNodePtr owner:nil];
else
- return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)copyNodePtr freeOnDealloc:YES];
+ return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)copyNodePtr owner:nil];
}
if (IsXmlAttrPtr(genericPtr))
@@ -229,7 +331,7 @@ - (id)copyWithZone:(NSZone *)zone
if (copyAttrPtr == NULL) return nil;
- return [[DDXMLAttributeNode alloc] initWithAttrPrimitive:copyAttrPtr freeOnDealloc:YES];
+ return [[DDXMLAttributeNode alloc] initWithAttrPrimitive:copyAttrPtr owner:nil];
}
if (IsXmlNsPtr(genericPtr))
@@ -238,7 +340,7 @@ - (id)copyWithZone:(NSZone *)zone
if (copyNsPtr == NULL) return nil;
- return [[DDXMLNamespaceNode alloc] initWithNsPrimitive:copyNsPtr nsParent:NULL freeOnDealloc:YES];
+ return [[DDXMLNamespaceNode alloc] initWithNsPrimitive:copyNsPtr nsParent:NULL owner:nil];
}
if (IsXmlDtdPtr(genericPtr))
@@ -247,18 +349,45 @@ - (id)copyWithZone:(NSZone *)zone
if (copyDtdPtr == NULL) return nil;
- return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)copyDtdPtr freeOnDealloc:YES];
+ 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
@@ -269,6 +398,10 @@ - (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]);
}
@@ -276,13 +409,35 @@ - (void)setName:(NSString *)name
- (NSString *)name
{
// Note: DDXMLNamespaceNode overrides this method
+ // Note: DDXMLAttributeNode overrides this method
- const char *name = (const char *)((xmlStdPtr)genericPtr)->name;
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
- if (name == NULL)
+ const xmlChar *xmlName = ((xmlStdPtr)genericPtr)->name;
+ if (xmlName == NULL)
+ {
return nil;
- else
- return [NSString stringWithUTF8String:name];
+ }
+
+ 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
@@ -290,6 +445,10 @@ - (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;
@@ -316,6 +475,10 @@ - (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);
@@ -341,6 +504,10 @@ - (NSUInteger)index
{
// Note: DDXMLNamespaceNode overrides this method
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
NSUInteger result = 0;
xmlStdPtr node = ((xmlStdPtr)genericPtr)->prev;
@@ -361,6 +528,10 @@ - (NSUInteger)level
{
// Note: DDXMLNamespaceNode overrides this method
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
NSUInteger result = 0;
xmlNodePtr currentNode = ((xmlStdPtr)genericPtr)->parent;
@@ -382,12 +553,16 @@ - (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 freeOnDealloc:NO];
+ return [DDXMLDocument nodeWithDocPrimitive:node->doc owner:self];
}
/**
@@ -401,12 +576,16 @@ - (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 freeOnDealloc:NO];
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent owner:self];
}
/**
@@ -417,6 +596,10 @@ - (NSUInteger)childCount
{
// Note: DDXMLNamespaceNode overrides this method
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
if (!IsXmlDocPtr(genericPtr) && !IsXmlNodePtr(genericPtr) && !IsXmlDtdPtr(genericPtr))
{
return 0;
@@ -441,6 +624,10 @@ - (NSArray *)children
{
// Note: DDXMLNamespaceNode overrides this method
+#if DDXML_DEBUG_MEMORY_ISSUES
+ DDXMLNotZombieAssert();
+#endif
+
if (!IsXmlDocPtr(genericPtr) && !IsXmlNodePtr(genericPtr) && !IsXmlDtdPtr(genericPtr))
{
return nil;
@@ -451,12 +638,12 @@ - (NSArray *)children
xmlNodePtr child = ((xmlStdPtr)genericPtr)->children;
while (child != NULL)
{
- [result addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child freeOnDealloc:NO]];
+ [result addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child owner:self]];
child = child->next;
}
- return [[result copy] autorelease];
+ return [result copy];
}
/**
@@ -472,6 +659,10 @@ - (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;
@@ -491,7 +682,7 @@ - (DDXMLNode *)childAtIndex:(NSUInteger)index
{
if (i == index)
{
- return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child freeOnDealloc:NO];
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child owner:self];
}
i++;
@@ -514,12 +705,16 @@ - (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 freeOnDealloc:NO];
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->prev owner:self];
}
/**
@@ -533,12 +728,16 @@ - (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 freeOnDealloc:NO];
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->next owner:self];
}
/**
@@ -554,6 +753,10 @@ - (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
@@ -572,12 +775,12 @@ - (DDXMLNode *)previousNode
lastChild = lastChild->last;
}
- return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)lastChild freeOnDealloc:NO];
+ 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 freeOnDealloc:NO];
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)previousSibling owner:self];
}
}
@@ -588,7 +791,7 @@ - (DDXMLNode *)previousNode
if (node->parent == NULL || node->parent->type == XML_DOCUMENT_NODE)
return nil;
else
- return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent freeOnDealloc:NO];
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent owner:self];
}
/**
@@ -604,6 +807,10 @@ - (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)
@@ -625,7 +832,7 @@ - (DDXMLNode *)nextNode
{
xmlNodePtr parentNextSibling = parent->next;
if (parentNextSibling != NULL)
- return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)parentNextSibling freeOnDealloc:NO];
+ return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)parentNextSibling owner:self];
else
parent = parent->parent;
}
@@ -644,20 +851,27 @@ - (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 fromNode:node->parent];
- freeOnDealloc = YES;
+ [[self class] detachChild:(xmlNodePtr)node];
+
+ owner = nil;
}
}
}
-- (xmlStdPtr)XPathPreProcess:(NSMutableString *)result
+- (xmlStdPtr)_XPathPreProcess:(NSMutableString *)result
{
+ // This is a private/internal method
+
// Note: DDXMLNamespaceNode overrides this method
// Note: DDXMLAttributeNode overrides this method
@@ -666,13 +880,17 @@ - (xmlStdPtr)XPathPreProcess:(NSMutableString *)result
- (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];
+ xmlStdPtr node = [self _XPathPreProcess:result];
// Note: rootNode.parent == docNode
@@ -709,7 +927,7 @@ - (NSString *)XPath
node = (xmlStdPtr)node->parent;
}
- return [[result copy] autorelease];
+ return [result copy];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -726,6 +944,8 @@ - (NSString *)localName