From 3d90aeb9a18e4681a24068f090c14782165b1a1c Mon Sep 17 00:00:00 2001 From: Mike Laurence Date: Fri, 19 Mar 2010 14:12:26 -0500 Subject: [PATCH] added kiss XML library --- .../CoreTest.xcodeproj/mikelaurence.mode1v3 | 26 +- .../CoreTest.xcodeproj/mikelaurence.pbxuser | 36 +- .../CoreTest.xcodeproj/project.pbxproj | 3 +- Libraries/KissXML/DDXML.h | 3 + Libraries/KissXML/DDXMLDocument.h | 67 + Libraries/KissXML/DDXMLDocument.m | 111 + Libraries/KissXML/DDXMLElement.h | 49 + Libraries/KissXML/DDXMLElement.m | 610 ++++++ Libraries/KissXML/DDXMLElementAdditions.h | 21 + Libraries/KissXML/DDXMLElementAdditions.m | 115 ++ Libraries/KissXML/DDXMLNode.h | 170 ++ Libraries/KissXML/DDXMLNode.m | 1808 +++++++++++++++++ Libraries/KissXML/DDXMLPrivate.h | 79 + Libraries/KissXML/NSStringAdditions.h | 14 + Libraries/KissXML/NSStringAdditions.m | 29 + 15 files changed, 3120 insertions(+), 21 deletions(-) create mode 100644 Libraries/KissXML/DDXML.h create mode 100644 Libraries/KissXML/DDXMLDocument.h create mode 100644 Libraries/KissXML/DDXMLDocument.m create mode 100644 Libraries/KissXML/DDXMLElement.h create mode 100644 Libraries/KissXML/DDXMLElement.m create mode 100644 Libraries/KissXML/DDXMLElementAdditions.h create mode 100644 Libraries/KissXML/DDXMLElementAdditions.m create mode 100644 Libraries/KissXML/DDXMLNode.h create mode 100644 Libraries/KissXML/DDXMLNode.m create mode 100644 Libraries/KissXML/DDXMLPrivate.h create mode 100644 Libraries/KissXML/NSStringAdditions.h create mode 100644 Libraries/KissXML/NSStringAdditions.m 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