| Revision | 274 (tree) |
|---|---|
| Time | 2011-05-06 19:07:02 |
| Author | poi-poi-capsule |
kissxmlアップデート
| @@ -0,0 +1,114 @@ | ||
| 1 | +#import "DDXMLPrivate.h" | |
| 2 | +#import "NSString+DDXML.h" | |
| 3 | + | |
| 4 | + | |
| 5 | +@implementation DDXMLDocument | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Returns a DDXML wrapper object for the given primitive node. | |
| 9 | + * The given node MUST be non-NULL and of the proper type. | |
| 10 | +**/ | |
| 11 | ++ (id)nodeWithDocPrimitive:(xmlDocPtr)doc freeOnDealloc:(BOOL)flag | |
| 12 | +{ | |
| 13 | + return [[[DDXMLDocument alloc] initWithDocPrimitive:doc freeOnDealloc:flag] autorelease]; | |
| 14 | +} | |
| 15 | + | |
| 16 | +- (id)initWithDocPrimitive:(xmlDocPtr)doc freeOnDealloc:(BOOL)flag | |
| 17 | +{ | |
| 18 | + self = [super initWithPrimitive:(xmlKindPtr)doc freeOnDealloc:flag]; | |
| 19 | + return self; | |
| 20 | +} | |
| 21 | + | |
| 22 | ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 23 | +{ | |
| 24 | + // Promote initializers which use proper parameter types to enable compiler to catch more mistakes | |
| 25 | + NSAssert(NO, @"Use nodeWithDocPrimitive:freeOnDealloc:"); | |
| 26 | + | |
| 27 | + return nil; | |
| 28 | +} | |
| 29 | + | |
| 30 | +- (id)initWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 31 | +{ | |
| 32 | + // Promote initializers which use proper parameter types to enable compiler to catch more mistakes. | |
| 33 | + NSAssert(NO, @"Use initWithDocPrimitive:freeOnDealloc:"); | |
| 34 | + | |
| 35 | + [self release]; | |
| 36 | + return nil; | |
| 37 | +} | |
| 38 | + | |
| 39 | +/** | |
| 40 | + * Initializes and returns a DDXMLDocument object created from an NSData object. | |
| 41 | + * | |
| 42 | + * Returns an initialized DDXMLDocument object, or nil if initialization fails | |
| 43 | + * because of parsing errors or other reasons. | |
| 44 | +**/ | |
| 45 | +- (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error | |
| 46 | +{ | |
| 47 | + return [self initWithData:[string dataUsingEncoding:NSUTF8StringEncoding] | |
| 48 | + options:mask | |
| 49 | + error:error]; | |
| 50 | +} | |
| 51 | + | |
| 52 | +/** | |
| 53 | + * Initializes and returns a DDXMLDocument object created from an NSData object. | |
| 54 | + * | |
| 55 | + * Returns an initialized DDXMLDocument object, or nil if initialization fails | |
| 56 | + * because of parsing errors or other reasons. | |
| 57 | +**/ | |
| 58 | +- (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error | |
| 59 | +{ | |
| 60 | + if (data == nil || [data length] == 0) | |
| 61 | + { | |
| 62 | + if (error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:0 userInfo:nil]; | |
| 63 | + | |
| 64 | + [self release]; | |
| 65 | + return nil; | |
| 66 | + } | |
| 67 | + | |
| 68 | + // Even though xmlKeepBlanksDefault(0) is called in DDXMLNode's initialize method, | |
| 69 | + // it has been documented that this call seems to get reset on the iPhone: | |
| 70 | + // http://code.google.com/p/kissxml/issues/detail?id=8 | |
| 71 | + // | |
| 72 | + // Therefore, we call it again here just to be safe. | |
| 73 | + xmlKeepBlanksDefault(0); | |
| 74 | + | |
| 75 | + xmlDocPtr doc = xmlParseMemory([data bytes], [data length]); | |
| 76 | + if (doc == NULL) | |
| 77 | + { | |
| 78 | + if (error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:1 userInfo:nil]; | |
| 79 | + | |
| 80 | + [self release]; | |
| 81 | + return nil; | |
| 82 | + } | |
| 83 | + | |
| 84 | + return [self initWithDocPrimitive:doc freeOnDealloc:YES]; | |
| 85 | +} | |
| 86 | + | |
| 87 | +/** | |
| 88 | + * Returns the root element of the receiver. | |
| 89 | +**/ | |
| 90 | +- (DDXMLElement *)rootElement | |
| 91 | +{ | |
| 92 | + xmlDocPtr doc = (xmlDocPtr)genericPtr; | |
| 93 | + | |
| 94 | + // doc->children is a list containing possibly comments, DTDs, etc... | |
| 95 | + | |
| 96 | + xmlNodePtr rootNode = xmlDocGetRootElement(doc); | |
| 97 | + | |
| 98 | + if (rootNode != NULL) | |
| 99 | + return [DDXMLElement nodeWithElementPrimitive:rootNode freeOnDealloc:NO]; | |
| 100 | + else | |
| 101 | + return nil; | |
| 102 | +} | |
| 103 | + | |
| 104 | +- (NSData *)XMLData | |
| 105 | +{ | |
| 106 | + return [[self XMLString] dataUsingEncoding:NSUTF8StringEncoding]; | |
| 107 | +} | |
| 108 | + | |
| 109 | +- (NSData *)XMLDataWithOptions:(NSUInteger)options | |
| 110 | +{ | |
| 111 | + return [[self XMLStringWithOptions:options] dataUsingEncoding:NSUTF8StringEncoding]; | |
| 112 | +} | |
| 113 | + | |
| 114 | +@end |
| @@ -0,0 +1,136 @@ | ||
| 1 | +#import <Foundation/Foundation.h> | |
| 2 | +#import <libxml/tree.h> | |
| 3 | + | |
| 4 | +@class DDXMLDocument; | |
| 5 | + | |
| 6 | + | |
| 7 | +enum { | |
| 8 | + DDXMLInvalidKind = 0, | |
| 9 | + DDXMLDocumentKind = XML_DOCUMENT_NODE, | |
| 10 | + DDXMLElementKind = XML_ELEMENT_NODE, | |
| 11 | + DDXMLAttributeKind = XML_ATTRIBUTE_NODE, | |
| 12 | + DDXMLNamespaceKind = XML_NAMESPACE_DECL, | |
| 13 | + DDXMLProcessingInstructionKind = XML_PI_NODE, | |
| 14 | + DDXMLCommentKind = XML_COMMENT_NODE, | |
| 15 | + DDXMLTextKind = XML_TEXT_NODE, | |
| 16 | + DDXMLDTDKind = XML_DTD_NODE, | |
| 17 | + DDXMLEntityDeclarationKind = XML_ENTITY_DECL, | |
| 18 | + DDXMLAttributeDeclarationKind = XML_ATTRIBUTE_DECL, | |
| 19 | + DDXMLElementDeclarationKind = XML_ELEMENT_DECL, | |
| 20 | + DDXMLNotationDeclarationKind = XML_NOTATION_NODE | |
| 21 | +}; | |
| 22 | +typedef NSUInteger DDXMLNodeKind; | |
| 23 | + | |
| 24 | +enum { | |
| 25 | + DDXMLNodeOptionsNone = 0, | |
| 26 | + DDXMLNodeExpandEmptyElement = 1 << 1, | |
| 27 | + DDXMLNodeCompactEmptyElement = 1 << 2, | |
| 28 | + DDXMLNodePrettyPrint = 1 << 17, | |
| 29 | +}; | |
| 30 | + | |
| 31 | + | |
| 32 | +//extern struct _xmlKind; | |
| 33 | + | |
| 34 | + | |
| 35 | +@interface DDXMLNode : NSObject <NSCopying> | |
| 36 | +{ | |
| 37 | + // Every DDXML object is simply a wrapper around an underlying libxml node | |
| 38 | + struct _xmlKind *genericPtr; | |
| 39 | + | |
| 40 | + // Root nodes free the underlying libxml node on dealloc. | |
| 41 | + BOOL freeOnDealloc; | |
| 42 | +} | |
| 43 | + | |
| 44 | +//- (id)initWithKind:(DDXMLNodeKind)kind; | |
| 45 | + | |
| 46 | +//- (id)initWithKind:(DDXMLNodeKind)kind options:(NSUInteger)options; | |
| 47 | + | |
| 48 | +//+ (id)document; | |
| 49 | + | |
| 50 | +//+ (id)documentWithRootElement:(DDXMLElement *)element; | |
| 51 | + | |
| 52 | ++ (id)elementWithName:(NSString *)name; | |
| 53 | + | |
| 54 | ++ (id)elementWithName:(NSString *)name URI:(NSString *)URI; | |
| 55 | + | |
| 56 | ++ (id)elementWithName:(NSString *)name stringValue:(NSString *)string; | |
| 57 | + | |
| 58 | ++ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes; | |
| 59 | + | |
| 60 | ++ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue; | |
| 61 | + | |
| 62 | ++ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue; | |
| 63 | + | |
| 64 | ++ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue; | |
| 65 | + | |
| 66 | ++ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue; | |
| 67 | + | |
| 68 | ++ (id)commentWithStringValue:(NSString *)stringValue; | |
| 69 | + | |
| 70 | ++ (id)textWithStringValue:(NSString *)stringValue; | |
| 71 | + | |
| 72 | +//+ (id)DTDNodeWithXMLString:(NSString *)string; | |
| 73 | + | |
| 74 | +#pragma mark --- Properties --- | |
| 75 | + | |
| 76 | +- (DDXMLNodeKind)kind; | |
| 77 | + | |
| 78 | +- (void)setName:(NSString *)name; | |
| 79 | +- (NSString *)name; | |
| 80 | + | |
| 81 | +//- (void)setObjectValue:(id)value; | |
| 82 | +//- (id)objectValue; | |
| 83 | + | |
| 84 | +- (void)setStringValue:(NSString *)string; | |
| 85 | +//- (void)setStringValue:(NSString *)string resolvingEntities:(BOOL)resolve; | |
| 86 | +- (NSString *)stringValue; | |
| 87 | + | |
| 88 | +#pragma mark --- Tree Navigation --- | |
| 89 | + | |
| 90 | +- (NSUInteger)index; | |
| 91 | + | |
| 92 | +- (NSUInteger)level; | |
| 93 | + | |
| 94 | +- (DDXMLDocument *)rootDocument; | |
| 95 | + | |
| 96 | +- (DDXMLNode *)parent; | |
| 97 | +- (NSUInteger)childCount; | |
| 98 | +- (NSArray *)children; | |
| 99 | +- (DDXMLNode *)childAtIndex:(NSUInteger)index; | |
| 100 | + | |
| 101 | +- (DDXMLNode *)previousSibling; | |
| 102 | +- (DDXMLNode *)nextSibling; | |
| 103 | + | |
| 104 | +- (DDXMLNode *)previousNode; | |
| 105 | +- (DDXMLNode *)nextNode; | |
| 106 | + | |
| 107 | +- (void)detach; | |
| 108 | + | |
| 109 | +- (NSString *)XPath; | |
| 110 | + | |
| 111 | +#pragma mark --- QNames --- | |
| 112 | + | |
| 113 | +- (NSString *)localName; | |
| 114 | +- (NSString *)prefix; | |
| 115 | + | |
| 116 | +- (void)setURI:(NSString *)URI; | |
| 117 | +- (NSString *)URI; | |
| 118 | + | |
| 119 | ++ (NSString *)localNameForName:(NSString *)name; | |
| 120 | ++ (NSString *)prefixForName:(NSString *)name; | |
| 121 | +//+ (DDXMLNode *)predefinedNamespaceForPrefix:(NSString *)name; | |
| 122 | + | |
| 123 | +#pragma mark --- Output --- | |
| 124 | + | |
| 125 | +- (NSString *)description; | |
| 126 | +- (NSString *)XMLString; | |
| 127 | +- (NSString *)XMLStringWithOptions:(NSUInteger)options; | |
| 128 | +//- (NSString *)canonicalXMLStringPreservingComments:(BOOL)comments; | |
| 129 | + | |
| 130 | +#pragma mark --- XPath/XQuery --- | |
| 131 | + | |
| 132 | +- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error; | |
| 133 | +//- (NSArray *)objectsForXQuery:(NSString *)xquery constants:(NSDictionary *)constants error:(NSError **)error; | |
| 134 | +//- (NSArray *)objectsForXQuery:(NSString *)xquery error:(NSError **)error; | |
| 135 | + | |
| 136 | +@end |
| @@ -0,0 +1,14 @@ | ||
| 1 | +#import <Foundation/Foundation.h> | |
| 2 | +#import <libxml/tree.h> | |
| 3 | + | |
| 4 | + | |
| 5 | +@interface NSString (DDXML) | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * xmlChar - A basic replacement for char, a byte in a UTF-8 encoded string. | |
| 9 | +**/ | |
| 10 | +- (const xmlChar *)xmlChar; | |
| 11 | + | |
| 12 | +- (NSString *)stringByTrimming; | |
| 13 | + | |
| 14 | +@end |
| @@ -0,0 +1,29 @@ | ||
| 1 | +#import "NSString+DDXML.h" | |
| 2 | + | |
| 3 | + | |
| 4 | +@implementation NSString (DDXML) | |
| 5 | + | |
| 6 | +- (const xmlChar *)xmlChar | |
| 7 | +{ | |
| 8 | + return (const xmlChar *)[self UTF8String]; | |
| 9 | +} | |
| 10 | + | |
| 11 | +#ifdef GNUSTEP | |
| 12 | +- (NSString *)stringByTrimming | |
| 13 | +{ | |
| 14 | + return [self stringByTrimmingSpaces]; | |
| 15 | +} | |
| 16 | +#else | |
| 17 | +- (NSString *)stringByTrimming | |
| 18 | +{ | |
| 19 | + NSMutableString *mStr = [self mutableCopy]; | |
| 20 | + CFStringTrimWhitespace((CFMutableStringRef)mStr); | |
| 21 | + | |
| 22 | + NSString *result = [mStr copy]; | |
| 23 | + | |
| 24 | + [mStr release]; | |
| 25 | + return [result autorelease]; | |
| 26 | +} | |
| 27 | +#endif | |
| 28 | + | |
| 29 | +@end |
| @@ -0,0 +1,49 @@ | ||
| 1 | +#import <Foundation/Foundation.h> | |
| 2 | +#import "DDXMLNode.h" | |
| 3 | + | |
| 4 | + | |
| 5 | +@interface DDXMLElement : DDXMLNode | |
| 6 | +{ | |
| 7 | +} | |
| 8 | + | |
| 9 | +- (id)initWithName:(NSString *)name; | |
| 10 | +- (id)initWithName:(NSString *)name URI:(NSString *)URI; | |
| 11 | +- (id)initWithName:(NSString *)name stringValue:(NSString *)string; | |
| 12 | +- (id)initWithXMLString:(NSString *)string error:(NSError **)error; | |
| 13 | + | |
| 14 | +#pragma mark --- Elements by name --- | |
| 15 | + | |
| 16 | +- (NSArray *)elementsForName:(NSString *)name; | |
| 17 | +- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI; | |
| 18 | + | |
| 19 | +#pragma mark --- Attributes --- | |
| 20 | + | |
| 21 | +- (void)addAttribute:(DDXMLNode *)attribute; | |
| 22 | +- (void)removeAttributeForName:(NSString *)name; | |
| 23 | +- (void)setAttributes:(NSArray *)attributes; | |
| 24 | +//- (void)setAttributesAsDictionary:(NSDictionary *)attributes; | |
| 25 | +- (NSArray *)attributes; | |
| 26 | +- (DDXMLNode *)attributeForName:(NSString *)name; | |
| 27 | +//- (DDXMLNode *)attributeForLocalName:(NSString *)localName URI:(NSString *)URI; | |
| 28 | + | |
| 29 | +#pragma mark --- Namespaces --- | |
| 30 | + | |
| 31 | +- (void)addNamespace:(DDXMLNode *)aNamespace; | |
| 32 | +- (void)removeNamespaceForPrefix:(NSString *)name; | |
| 33 | +- (void)setNamespaces:(NSArray *)namespaces; | |
| 34 | +- (NSArray *)namespaces; | |
| 35 | +- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix; | |
| 36 | +- (DDXMLNode *)resolveNamespaceForName:(NSString *)name; | |
| 37 | +- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI; | |
| 38 | + | |
| 39 | +#pragma mark --- Children --- | |
| 40 | + | |
| 41 | +- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index; | |
| 42 | +//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index; | |
| 43 | +- (void)removeChildAtIndex:(NSUInteger)index; | |
| 44 | +- (void)setChildren:(NSArray *)children; | |
| 45 | +- (void)addChild:(DDXMLNode *)child; | |
| 46 | +//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node; | |
| 47 | +//- (void)normalizeAdjacentTextNodesPreservingCDATA:(BOOL)preserve; | |
| 48 | + | |
| 49 | +@end |
| @@ -0,0 +1,1858 @@ | ||
| 1 | +#import "DDXMLPrivate.h" | |
| 2 | +#import "NSString+DDXML.h" | |
| 3 | + | |
| 4 | +#import <libxml/xpath.h> | |
| 5 | +#import <libxml/xpathInternals.h> | |
| 6 | + | |
| 7 | + | |
| 8 | +@implementation DDXMLNode | |
| 9 | + | |
| 10 | +static void MyErrorHandler(void * userData, xmlErrorPtr error); | |
| 11 | + | |
| 12 | ++ (void)initialize | |
| 13 | +{ | |
| 14 | + static BOOL initialized = NO; | |
| 15 | + if (!initialized) | |
| 16 | + { | |
| 17 | + initialized = YES; | |
| 18 | + | |
| 19 | + // Redirect error output to our own function (don't clog up the console) | |
| 20 | + initGenericErrorDefaultFunc(NULL); | |
| 21 | + xmlSetStructuredErrorFunc(NULL, MyErrorHandler); | |
| 22 | + | |
| 23 | + // Tell libxml not to keep ignorable whitespace (such as node indentation, formatting, etc). | |
| 24 | + // NSXML ignores such whitespace. | |
| 25 | + // This also has the added benefit of taking up less RAM when parsing formatted XML documents. | |
| 26 | + xmlKeepBlanksDefault(0); | |
| 27 | + } | |
| 28 | +} | |
| 29 | + | |
| 30 | ++ (id)elementWithName:(NSString *)name | |
| 31 | +{ | |
| 32 | + return [[[DDXMLElement alloc] initWithName:name] autorelease]; | |
| 33 | +} | |
| 34 | + | |
| 35 | ++ (id)elementWithName:(NSString *)name stringValue:(NSString *)string | |
| 36 | +{ | |
| 37 | + return [[[DDXMLElement alloc] initWithName:name stringValue:string] autorelease]; | |
| 38 | +} | |
| 39 | + | |
| 40 | ++ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes | |
| 41 | +{ | |
| 42 | + DDXMLElement *result = [[[DDXMLElement alloc] initWithName:name] autorelease]; | |
| 43 | + [result setChildren:children]; | |
| 44 | + [result setAttributes:attributes]; | |
| 45 | + | |
| 46 | + return result; | |
| 47 | +} | |
| 48 | + | |
| 49 | ++ (id)elementWithName:(NSString *)name URI:(NSString *)URI | |
| 50 | +{ | |
| 51 | + return [[[DDXMLElement alloc] initWithName:name URI:URI] autorelease]; | |
| 52 | +} | |
| 53 | + | |
| 54 | ++ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue | |
| 55 | +{ | |
| 56 | + xmlAttrPtr attr = xmlNewProp(NULL, [name xmlChar], [stringValue xmlChar]); | |
| 57 | + | |
| 58 | + if (attr == NULL) return nil; | |
| 59 | + | |
| 60 | + return [[[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr freeOnDealloc:YES] autorelease]; | |
| 61 | +} | |
| 62 | + | |
| 63 | ++ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue | |
| 64 | +{ | |
| 65 | + xmlAttrPtr attr = xmlNewProp(NULL, [name xmlChar], [stringValue xmlChar]); | |
| 66 | + | |
| 67 | + if (attr == NULL) return nil; | |
| 68 | + | |
| 69 | + DDXMLAttributeNode *result = [[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr freeOnDealloc:YES]; | |
| 70 | + [result setURI:URI]; | |
| 71 | + | |
| 72 | + return [result autorelease]; | |
| 73 | +} | |
| 74 | + | |
| 75 | ++ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue | |
| 76 | +{ | |
| 77 | + // If the user passes a nil or empty string name, they are trying to create a default namespace | |
| 78 | + const xmlChar *xmlName = [name length] > 0 ? [name xmlChar] : NULL; | |
| 79 | + | |
| 80 | + xmlNsPtr ns = xmlNewNs(NULL, [stringValue xmlChar], xmlName); | |
| 81 | + | |
| 82 | + if (ns == NULL) return nil; | |
| 83 | + | |
| 84 | + return [[[DDXMLNamespaceNode alloc] initWithNsPrimitive:ns nsParent:NULL freeOnDealloc:YES] autorelease]; | |
| 85 | +} | |
| 86 | + | |
| 87 | ++ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue | |
| 88 | +{ | |
| 89 | + xmlNodePtr procInst = xmlNewPI([name xmlChar], [stringValue xmlChar]); | |
| 90 | + | |
| 91 | + if (procInst == NULL) return nil; | |
| 92 | + | |
| 93 | + return [[[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)procInst freeOnDealloc:YES] autorelease]; | |
| 94 | +} | |
| 95 | + | |
| 96 | ++ (id)commentWithStringValue:(NSString *)stringValue | |
| 97 | +{ | |
| 98 | + xmlNodePtr comment = xmlNewComment([stringValue xmlChar]); | |
| 99 | + | |
| 100 | + if (comment == NULL) return nil; | |
| 101 | + | |
| 102 | + return [[[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)comment freeOnDealloc:YES] autorelease]; | |
| 103 | +} | |
| 104 | + | |
| 105 | ++ (id)textWithStringValue:(NSString *)stringValue | |
| 106 | +{ | |
| 107 | + xmlNodePtr text = xmlNewText([stringValue xmlChar]); | |
| 108 | + | |
| 109 | + if (text == NULL) return nil; | |
| 110 | + | |
| 111 | + return [[[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)text freeOnDealloc:YES] autorelease]; | |
| 112 | +} | |
| 113 | + | |
| 114 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 115 | +#pragma mark Init, Dealloc | |
| 116 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 117 | + | |
| 118 | ++ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 119 | +{ | |
| 120 | + if (kindPtr->type == XML_DOCUMENT_NODE) | |
| 121 | + { | |
| 122 | + return [DDXMLDocument nodeWithDocPrimitive:(xmlDocPtr)kindPtr freeOnDealloc:flag]; | |
| 123 | + } | |
| 124 | + else if (kindPtr->type == XML_ELEMENT_NODE) | |
| 125 | + { | |
| 126 | + return [DDXMLElement nodeWithElementPrimitive:(xmlNodePtr)kindPtr freeOnDealloc:flag]; | |
| 127 | + } | |
| 128 | + else if (kindPtr->type == XML_NAMESPACE_DECL) | |
| 129 | + { | |
| 130 | + // Todo: This may be a problem... | |
| 131 | + | |
| 132 | + return [DDXMLNamespaceNode nodeWithNsPrimitive:(xmlNsPtr)kindPtr nsParent:NULL freeOnDealloc:flag]; | |
| 133 | + } | |
| 134 | + else if (kindPtr->type == XML_ATTRIBUTE_NODE) | |
| 135 | + { | |
| 136 | + return [DDXMLAttributeNode nodeWithAttrPrimitive:(xmlAttrPtr)kindPtr freeOnDealloc:flag]; | |
| 137 | + } | |
| 138 | + else | |
| 139 | + { | |
| 140 | + return [DDXMLNode nodeWithPrimitive:kindPtr freeOnDealloc:flag]; | |
| 141 | + } | |
| 142 | +} | |
| 143 | + | |
| 144 | +/** | |
| 145 | + * Returns a DDXML wrapper object for the given primitive node. | |
| 146 | + * The given node MUST be non-NULL and of the proper type. | |
| 147 | +**/ | |
| 148 | ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 149 | +{ | |
| 150 | + return [[[DDXMLNode alloc] initWithPrimitive:kindPtr freeOnDealloc:flag] autorelease]; | |
| 151 | +} | |
| 152 | + | |
| 153 | +/** | |
| 154 | + * Returns a DDXML wrapper object for the given primitive node. | |
| 155 | + * The given node MUST be non-NULL and of the proper type. | |
| 156 | +**/ | |
| 157 | +- (id)initWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 158 | +{ | |
| 159 | + if ((self = [super init])) | |
| 160 | + { | |
| 161 | + genericPtr = kindPtr; | |
| 162 | + freeOnDealloc = flag; | |
| 163 | + } | |
| 164 | + return self; | |
| 165 | +} | |
| 166 | + | |
| 167 | +- (void)dealloc | |
| 168 | +{ | |
| 169 | + if (freeOnDealloc) | |
| 170 | + { | |
| 171 | + if (IsXmlNsPtr(genericPtr)) | |
| 172 | + { | |
| 173 | + xmlFreeNs((xmlNsPtr)genericPtr); | |
| 174 | + } | |
| 175 | + else if (IsXmlAttrPtr(genericPtr)) | |
| 176 | + { | |
| 177 | + xmlFreeProp((xmlAttrPtr)genericPtr); | |
| 178 | + } | |
| 179 | + else if (IsXmlDtdPtr(genericPtr)) | |
| 180 | + { | |
| 181 | + xmlFreeDtd((xmlDtdPtr)genericPtr); | |
| 182 | + } | |
| 183 | + else if (IsXmlDocPtr(genericPtr)) | |
| 184 | + { | |
| 185 | + xmlFreeDoc((xmlDocPtr)genericPtr); | |
| 186 | + } | |
| 187 | + else if (IsXmlNodePtr(genericPtr)) | |
| 188 | + { | |
| 189 | + xmlFreeNode((xmlNodePtr)genericPtr); | |
| 190 | + } | |
| 191 | + else | |
| 192 | + { | |
| 193 | + NSAssert1(NO, @"Cannot free unknown node type: %i", ((xmlKindPtr)genericPtr)->type); | |
| 194 | + } | |
| 195 | + } | |
| 196 | + [super dealloc]; | |
| 197 | +} | |
| 198 | + | |
| 199 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 200 | +#pragma mark Copying | |
| 201 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 202 | + | |
| 203 | +- (id)copyWithZone:(NSZone *)zone | |
| 204 | +{ | |
| 205 | + if (IsXmlDocPtr(genericPtr)) | |
| 206 | + { | |
| 207 | + xmlDocPtr copyDocPtr = xmlCopyDoc((xmlDocPtr)genericPtr, 1); | |
| 208 | + | |
| 209 | + if (copyDocPtr == NULL) return nil; | |
| 210 | + | |
| 211 | + return [[DDXMLDocument alloc] initWithDocPrimitive:copyDocPtr freeOnDealloc:YES]; | |
| 212 | + } | |
| 213 | + | |
| 214 | + if (IsXmlNodePtr(genericPtr)) | |
| 215 | + { | |
| 216 | + xmlNodePtr copyNodePtr = xmlCopyNode((xmlNodePtr)genericPtr, 1); | |
| 217 | + | |
| 218 | + if (copyNodePtr == NULL) return nil; | |
| 219 | + | |
| 220 | + if ([self isKindOfClass:[DDXMLElement class]]) | |
| 221 | + return [[DDXMLElement alloc] initWithElementPrimitive:copyNodePtr freeOnDealloc:YES]; | |
| 222 | + else | |
| 223 | + return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)copyNodePtr freeOnDealloc:YES]; | |
| 224 | + } | |
| 225 | + | |
| 226 | + if (IsXmlAttrPtr(genericPtr)) | |
| 227 | + { | |
| 228 | + xmlAttrPtr copyAttrPtr = xmlCopyProp(NULL, (xmlAttrPtr)genericPtr); | |
| 229 | + | |
| 230 | + if (copyAttrPtr == NULL) return nil; | |
| 231 | + | |
| 232 | + return [[DDXMLAttributeNode alloc] initWithAttrPrimitive:copyAttrPtr freeOnDealloc:YES]; | |
| 233 | + } | |
| 234 | + | |
| 235 | + if (IsXmlNsPtr(genericPtr)) | |
| 236 | + { | |
| 237 | + xmlNsPtr copyNsPtr = xmlCopyNamespace((xmlNsPtr)genericPtr); | |
| 238 | + | |
| 239 | + if (copyNsPtr == NULL) return nil; | |
| 240 | + | |
| 241 | + return [[DDXMLNamespaceNode alloc] initWithNsPrimitive:copyNsPtr nsParent:NULL freeOnDealloc:YES]; | |
| 242 | + } | |
| 243 | + | |
| 244 | + if (IsXmlDtdPtr(genericPtr)) | |
| 245 | + { | |
| 246 | + xmlDtdPtr copyDtdPtr = xmlCopyDtd((xmlDtdPtr)genericPtr); | |
| 247 | + | |
| 248 | + if (copyDtdPtr == NULL) return nil; | |
| 249 | + | |
| 250 | + return [[DDXMLNode alloc] initWithPrimitive:(xmlKindPtr)copyDtdPtr freeOnDealloc:YES]; | |
| 251 | + } | |
| 252 | + | |
| 253 | + return nil; | |
| 254 | +} | |
| 255 | + | |
| 256 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 257 | +#pragma mark Properties | |
| 258 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 259 | + | |
| 260 | +- (DDXMLNodeKind)kind | |
| 261 | +{ | |
| 262 | + if (genericPtr != NULL) | |
| 263 | + return genericPtr->type; | |
| 264 | + else | |
| 265 | + return DDXMLInvalidKind; | |
| 266 | +} | |
| 267 | + | |
| 268 | +- (void)setName:(NSString *)name | |
| 269 | +{ | |
| 270 | + // Note: DDXMLNamespaceNode overrides this method | |
| 271 | + | |
| 272 | + // The xmlNodeSetName function works for both nodes and attributes | |
| 273 | + xmlNodeSetName((xmlNodePtr)genericPtr, [name xmlChar]); | |
| 274 | +} | |
| 275 | + | |
| 276 | +- (NSString *)name | |
| 277 | +{ | |
| 278 | + // Note: DDXMLNamespaceNode overrides this method | |
| 279 | + | |
| 280 | + const char *name = (const char *)((xmlStdPtr)genericPtr)->name; | |
| 281 | + | |
| 282 | + if (name == NULL) | |
| 283 | + return nil; | |
| 284 | + else | |
| 285 | + return [NSString stringWithUTF8String:name]; | |
| 286 | +} | |
| 287 | + | |
| 288 | +- (void)setStringValue:(NSString *)string | |
| 289 | +{ | |
| 290 | + // Note: DDXMLNamespaceNode overrides this method | |
| 291 | + // Note: DDXMLAttributeNode overrides this method | |
| 292 | + | |
| 293 | + if (IsXmlNodePtr(genericPtr)) | |
| 294 | + { | |
| 295 | + xmlStdPtr node = (xmlStdPtr)genericPtr; | |
| 296 | + | |
| 297 | + // Setting the content of a node erases any existing child nodes. | |
| 298 | + // Therefore, we need to remove them properly first. | |
| 299 | + [[self class] removeAllChildrenFromNode:(xmlNodePtr)node]; | |
| 300 | + | |
| 301 | + xmlChar *escapedString = xmlEncodeSpecialChars(node->doc, [string xmlChar]); | |
| 302 | + xmlNodeSetContent((xmlNodePtr)node, escapedString); | |
| 303 | + xmlFree(escapedString); | |
| 304 | + } | |
| 305 | +} | |
| 306 | + | |
| 307 | +/** | |
| 308 | + * Returns the content of the receiver as a string value. | |
| 309 | + * | |
| 310 | + * If the receiver is a node object of element kind, the content is that of any text-node children. | |
| 311 | + * This method recursively visits elements nodes and concatenates their text nodes in document order with | |
| 312 | + * no intervening spaces. | |
| 313 | +**/ | |
| 314 | +- (NSString *)stringValue | |
| 315 | +{ | |
| 316 | + // Note: DDXMLNamespaceNode overrides this method | |
| 317 | + // Note: DDXMLAttributeNode overrides this method | |
| 318 | + | |
| 319 | + if (IsXmlNodePtr(genericPtr)) | |
| 320 | + { | |
| 321 | + xmlChar *content = xmlNodeGetContent((xmlNodePtr)genericPtr); | |
| 322 | + | |
| 323 | + NSString *result = [NSString stringWithUTF8String:(const char *)content]; | |
| 324 | + | |
| 325 | + xmlFree(content); | |
| 326 | + return result; | |
| 327 | + } | |
| 328 | + | |
| 329 | + return nil; | |
| 330 | +} | |
| 331 | + | |
| 332 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 333 | +#pragma mark Tree Navigation | |
| 334 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 335 | + | |
| 336 | +/** | |
| 337 | + * Returns the index of the receiver identifying its position relative to its sibling nodes. | |
| 338 | + * The first child node of a parent has an index of zero. | |
| 339 | +**/ | |
| 340 | +- (NSUInteger)index | |
| 341 | +{ | |
| 342 | + // Note: DDXMLNamespaceNode overrides this method | |
| 343 | + | |
| 344 | + NSUInteger result = 0; | |
| 345 | + | |
| 346 | + xmlStdPtr node = ((xmlStdPtr)genericPtr)->prev; | |
| 347 | + while (node != NULL) | |
| 348 | + { | |
| 349 | + result++; | |
| 350 | + node = node->prev; | |
| 351 | + } | |
| 352 | + | |
| 353 | + return result; | |
| 354 | +} | |
| 355 | + | |
| 356 | +/** | |
| 357 | + * Returns the nesting level of the receiver within the tree hierarchy. | |
| 358 | + * The root element of a document has a nesting level of one. | |
| 359 | +**/ | |
| 360 | +- (NSUInteger)level | |
| 361 | +{ | |
| 362 | + // Note: DDXMLNamespaceNode overrides this method | |
| 363 | + | |
| 364 | + NSUInteger result = 0; | |
| 365 | + | |
| 366 | + xmlNodePtr currentNode = ((xmlStdPtr)genericPtr)->parent; | |
| 367 | + while (currentNode != NULL) | |
| 368 | + { | |
| 369 | + result++; | |
| 370 | + currentNode = currentNode->parent; | |
| 371 | + } | |
| 372 | + | |
| 373 | + return result; | |
| 374 | +} | |
| 375 | + | |
| 376 | +/** | |
| 377 | + * Returns the DDXMLDocument object containing the root element and representing the XML document as a whole. | |
| 378 | + * If the receiver is a standalone node (that is, a node at the head of a detached branch of the tree), this | |
| 379 | + * method returns nil. | |
| 380 | +**/ | |
| 381 | +- (DDXMLDocument *)rootDocument | |
| 382 | +{ | |
| 383 | + // Note: DDXMLNamespaceNode overrides this method | |
| 384 | + | |
| 385 | + xmlStdPtr node = (xmlStdPtr)genericPtr; | |
| 386 | + | |
| 387 | + if (node == NULL || node->doc == NULL) | |
| 388 | + return nil; | |
| 389 | + else | |
| 390 | + return [DDXMLDocument nodeWithDocPrimitive:node->doc freeOnDealloc:NO]; | |
| 391 | +} | |
| 392 | + | |
| 393 | +/** | |
| 394 | + * Returns the parent node of the receiver. | |
| 395 | + * | |
| 396 | + * Document nodes and standalone nodes (that is, the root of a detached branch of a tree) have no parent, and | |
| 397 | + * sending this message to them returns nil. A one-to-one relationship does not always exists between a parent and | |
| 398 | + * its children; although a namespace or attribute node cannot be a child, it still has a parent element. | |
| 399 | +**/ | |
| 400 | +- (DDXMLNode *)parent | |
| 401 | +{ | |
| 402 | + // Note: DDXMLNamespaceNode overrides this method | |
| 403 | + | |
| 404 | + xmlStdPtr node = (xmlStdPtr)genericPtr; | |
| 405 | + | |
| 406 | + if (node->parent == NULL) | |
| 407 | + return nil; | |
| 408 | + else | |
| 409 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent freeOnDealloc:NO]; | |
| 410 | +} | |
| 411 | + | |
| 412 | +/** | |
| 413 | + * Returns the number of child nodes the receiver has. | |
| 414 | + * For performance reasons, use this method instead of getting the count from the array returned by children. | |
| 415 | +**/ | |
| 416 | +- (NSUInteger)childCount | |
| 417 | +{ | |
| 418 | + // Note: DDXMLNamespaceNode overrides this method | |
| 419 | + | |
| 420 | + if (!IsXmlDocPtr(genericPtr) && !IsXmlNodePtr(genericPtr) && !IsXmlDtdPtr(genericPtr)) | |
| 421 | + { | |
| 422 | + return 0; | |
| 423 | + } | |
| 424 | + | |
| 425 | + NSUInteger result = 0; | |
| 426 | + | |
| 427 | + xmlNodePtr child = ((xmlStdPtr)genericPtr)->children; | |
| 428 | + while (child != NULL) | |
| 429 | + { | |
| 430 | + result++; | |
| 431 | + child = child->next; | |
| 432 | + } | |
| 433 | + | |
| 434 | + return result; | |
| 435 | +} | |
| 436 | + | |
| 437 | +/** | |
| 438 | + * Returns an immutable array containing the child nodes of the receiver (as DDXMLNode objects). | |
| 439 | +**/ | |
| 440 | +- (NSArray *)children | |
| 441 | +{ | |
| 442 | + // Note: DDXMLNamespaceNode overrides this method | |
| 443 | + | |
| 444 | + if (!IsXmlDocPtr(genericPtr) && !IsXmlNodePtr(genericPtr) && !IsXmlDtdPtr(genericPtr)) | |
| 445 | + { | |
| 446 | + return nil; | |
| 447 | + } | |
| 448 | + | |
| 449 | + NSMutableArray *result = [NSMutableArray array]; | |
| 450 | + | |
| 451 | + xmlNodePtr child = ((xmlStdPtr)genericPtr)->children; | |
| 452 | + while (child != NULL) | |
| 453 | + { | |
| 454 | + [result addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child freeOnDealloc:NO]]; | |
| 455 | + | |
| 456 | + child = child->next; | |
| 457 | + } | |
| 458 | + | |
| 459 | + return [[result copy] autorelease]; | |
| 460 | +} | |
| 461 | + | |
| 462 | +/** | |
| 463 | + * Returns the child node of the receiver at the specified location. | |
| 464 | + * Returns a DDXMLNode object or nil if the receiver has no children. | |
| 465 | + * | |
| 466 | + * If the receive has children and index is out of bounds, an exception is raised. | |
| 467 | + * | |
| 468 | + * The receiver should be a DDXMLNode object representing a document, element, or document type declaration. | |
| 469 | + * The returned node object can represent an element, comment, text, or processing instruction. | |
| 470 | +**/ | |
| 471 | +- (DDXMLNode *)childAtIndex:(NSUInteger)index | |
| 472 | +{ | |
| 473 | + // Note: DDXMLNamespaceNode overrides this method | |
| 474 | + | |
| 475 | + if (!IsXmlDocPtr(genericPtr) && !IsXmlNodePtr(genericPtr) && !IsXmlDtdPtr(genericPtr)) | |
| 476 | + { | |
| 477 | + return nil; | |
| 478 | + } | |
| 479 | + | |
| 480 | + NSUInteger i = 0; | |
| 481 | + | |
| 482 | + xmlNodePtr child = ((xmlStdPtr)genericPtr)->children; | |
| 483 | + | |
| 484 | + if (child == NULL) | |
| 485 | + { | |
| 486 | + // NSXML doesn't raise an exception if there are no children | |
| 487 | + return nil; | |
| 488 | + } | |
| 489 | + | |
| 490 | + while (child != NULL) | |
| 491 | + { | |
| 492 | + if (i == index) | |
| 493 | + { | |
| 494 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child freeOnDealloc:NO]; | |
| 495 | + } | |
| 496 | + | |
| 497 | + i++; | |
| 498 | + child = child->next; | |
| 499 | + } | |
| 500 | + | |
| 501 | + // NSXML version uses this same assertion | |
| 502 | + DDXMLAssert(NO, @"index (%u) beyond bounds (%u)", (unsigned)index, (unsigned)i); | |
| 503 | + | |
| 504 | + return nil; | |
| 505 | +} | |
| 506 | + | |
| 507 | +/** | |
| 508 | + * Returns the previous DDXMLNode object that is a sibling node to the receiver. | |
| 509 | + * | |
| 510 | + * This object will have an index value that is one less than the receiverユs. | |
| 511 | + * If there are no more previous siblings (that is, other child nodes of the receiverユs parent) the method returns nil. | |
| 512 | +**/ | |
| 513 | +- (DDXMLNode *)previousSibling | |
| 514 | +{ | |
| 515 | + // Note: DDXMLNamespaceNode overrides this method | |
| 516 | + | |
| 517 | + xmlStdPtr node = (xmlStdPtr)genericPtr; | |
| 518 | + | |
| 519 | + if (node->prev == NULL) | |
| 520 | + return nil; | |
| 521 | + else | |
| 522 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->prev freeOnDealloc:NO]; | |
| 523 | +} | |
| 524 | + | |
| 525 | +/** | |
| 526 | + * Returns the next DDXMLNode object that is a sibling node to the receiver. | |
| 527 | + * | |
| 528 | + * This object will have an index value that is one more than the receiverユs. | |
| 529 | + * If there are no more subsequent siblings (that is, other child nodes of the receiverユs parent) the | |
| 530 | + * method returns nil. | |
| 531 | +**/ | |
| 532 | +- (DDXMLNode *)nextSibling | |
| 533 | +{ | |
| 534 | + // Note: DDXMLNamespaceNode overrides this method | |
| 535 | + | |
| 536 | + xmlStdPtr node = (xmlStdPtr)genericPtr; | |
| 537 | + | |
| 538 | + if (node->next == NULL) | |
| 539 | + return nil; | |
| 540 | + else | |
| 541 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->next freeOnDealloc:NO]; | |
| 542 | +} | |
| 543 | + | |
| 544 | +/** | |
| 545 | + * Returns the previous DDXMLNode object in document order. | |
| 546 | + * | |
| 547 | + * You use this method to メwalkモ backward through the tree structure representing an XML document or document section. | |
| 548 | + * (Use nextNode to traverse the tree in the opposite direction.) Document order is the natural order that XML | |
| 549 | + * constructs appear in markup text. If you send this message to the first node in the tree (that is, the root element), | |
| 550 | + * nil is returned. DDXMLNode bypasses namespace and attribute nodes when it traverses a tree in document order. | |
| 551 | +**/ | |
| 552 | +- (DDXMLNode *)previousNode | |
| 553 | +{ | |
| 554 | + // Note: DDXMLNamespaceNode overrides this method | |
| 555 | + // Note: DDXMLAttributeNode overrides this method | |
| 556 | + | |
| 557 | + // If the node has a previous sibling, | |
| 558 | + // then we need the last child of the last child of the last child etc | |
| 559 | + | |
| 560 | + // Note: Try to accomplish this task without creating dozens of intermediate wrapper objects | |
| 561 | + | |
| 562 | + xmlStdPtr node = (xmlStdPtr)genericPtr; | |
| 563 | + xmlStdPtr previousSibling = node->prev; | |
| 564 | + | |
| 565 | + if (previousSibling != NULL) | |
| 566 | + { | |
| 567 | + if (previousSibling->last != NULL) | |
| 568 | + { | |
| 569 | + xmlNodePtr lastChild = previousSibling->last; | |
| 570 | + while (lastChild->last != NULL) | |
| 571 | + { | |
| 572 | + lastChild = lastChild->last; | |
| 573 | + } | |
| 574 | + | |
| 575 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)lastChild freeOnDealloc:NO]; | |
| 576 | + } | |
| 577 | + else | |
| 578 | + { | |
| 579 | + // The previous sibling has no children, so the previous node is simply the previous sibling | |
| 580 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)previousSibling freeOnDealloc:NO]; | |
| 581 | + } | |
| 582 | + } | |
| 583 | + | |
| 584 | + // If there are no previous siblings, then the previous node is simply the parent | |
| 585 | + | |
| 586 | + // Note: rootNode.parent == docNode | |
| 587 | + | |
| 588 | + if (node->parent == NULL || node->parent->type == XML_DOCUMENT_NODE) | |
| 589 | + return nil; | |
| 590 | + else | |
| 591 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent freeOnDealloc:NO]; | |
| 592 | +} | |
| 593 | + | |
| 594 | +/** | |
| 595 | + * Returns the next DDXMLNode object in document order. | |
| 596 | + * | |
| 597 | + * You use this method to メwalkモ forward through the tree structure representing an XML document or document section. | |
| 598 | + * (Use previousNode to traverse the tree in the opposite direction.) Document order is the natural order that XML | |
| 599 | + * constructs appear in markup text. If you send this message to the last node in the tree, nil is returned. | |
| 600 | + * DDXMLNode bypasses namespace and attribute nodes when it traverses a tree in document order. | |
| 601 | +**/ | |
| 602 | +- (DDXMLNode *)nextNode | |
| 603 | +{ | |
| 604 | + // Note: DDXMLNamespaceNode overrides this method | |
| 605 | + // Note: DDXMLAttributeNode overrides this method | |
| 606 | + | |
| 607 | + // If the node has children, then next node is the first child | |
| 608 | + DDXMLNode *firstChild = [self childAtIndex:0]; | |
| 609 | + if (firstChild) | |
| 610 | + return firstChild; | |
| 611 | + | |
| 612 | + // If the node has a next sibling, then next node is the same as next sibling | |
| 613 | + | |
| 614 | + DDXMLNode *nextSibling = [self nextSibling]; | |
| 615 | + if (nextSibling) | |
| 616 | + return nextSibling; | |
| 617 | + | |
| 618 | + // There are no children, and no more siblings, so we need to get the next sibling of the parent. | |
| 619 | + // If that is nil, we need to get the next sibling of the grandparent, etc. | |
| 620 | + | |
| 621 | + // Note: Try to accomplish this task without creating dozens of intermediate wrapper objects | |
| 622 | + | |
| 623 | + xmlNodePtr parent = ((xmlStdPtr)genericPtr)->parent; | |
| 624 | + while (parent != NULL) | |
| 625 | + { | |
| 626 | + xmlNodePtr parentNextSibling = parent->next; | |
| 627 | + if (parentNextSibling != NULL) | |
| 628 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)parentNextSibling freeOnDealloc:NO]; | |
| 629 | + else | |
| 630 | + parent = parent->parent; | |
| 631 | + } | |
| 632 | + | |
| 633 | + return nil; | |
| 634 | +} | |
| 635 | + | |
| 636 | +/** | |
| 637 | + * Detaches the receiver from its parent node. | |
| 638 | + * | |
| 639 | + * This method is applicable to DDXMLNode objects representing elements, text, comments, processing instructions, | |
| 640 | + * attributes, and namespaces. Once the node object is detached, you can add it as a child node of another parent. | |
| 641 | +**/ | |
| 642 | +- (void)detach | |
| 643 | +{ | |
| 644 | + // Note: DDXMLNamespaceNode overrides this method | |
| 645 | + // Note: DDXMLAttributeNode overrides this method | |
| 646 | + | |
| 647 | + xmlStdPtr node = (xmlStdPtr)genericPtr; | |
| 648 | + | |
| 649 | + if (node->parent != NULL) | |
| 650 | + { | |
| 651 | + if (IsXmlNodePtr(genericPtr)) | |
| 652 | + { | |
| 653 | + [[self class] detachChild:(xmlNodePtr)node fromNode:node->parent]; | |
| 654 | + freeOnDealloc = YES; | |
| 655 | + } | |
| 656 | + } | |
| 657 | +} | |
| 658 | + | |
| 659 | +- (xmlStdPtr)XPathPreProcess:(NSMutableString *)result | |
| 660 | +{ | |
| 661 | + // Note: DDXMLNamespaceNode overrides this method | |
| 662 | + // Note: DDXMLAttributeNode overrides this method | |
| 663 | + | |
| 664 | + return (xmlStdPtr)genericPtr; | |
| 665 | +} | |
| 666 | + | |
| 667 | +- (NSString *)XPath | |
| 668 | +{ | |
| 669 | + NSMutableString *result = [NSMutableString stringWithCapacity:25]; | |
| 670 | + | |
| 671 | + // Examples: | |
| 672 | + // /rootElement[1]/subElement[4]/thisNode[2] | |
| 673 | + // topElement/thisNode[2] | |
| 674 | + | |
| 675 | + xmlStdPtr node = [self XPathPreProcess:result]; | |
| 676 | + | |
| 677 | + // Note: rootNode.parent == docNode | |
| 678 | + | |
| 679 | + while ((node != NULL) && (node->type != XML_DOCUMENT_NODE)) | |
| 680 | + { | |
| 681 | + if ((node->parent == NULL) && (node->doc == NULL)) | |
| 682 | + { | |
| 683 | + // We're at the top of the heirarchy, and there is no xml document. | |
| 684 | + // Thus we don't use a leading '/', and we don't need an index. | |
| 685 | + | |
| 686 | + [result insertString:[NSString stringWithFormat:@"%s", node->name] atIndex:0]; | |
| 687 | + } | |
| 688 | + else | |
| 689 | + { | |
| 690 | + // Find out what index this node is. | |
| 691 | + // If it's the first node with this name, the index is 1. | |
| 692 | + // If there are previous siblings with the same name, the index is greater than 1. | |
| 693 | + | |
| 694 | + int index = 1; | |
| 695 | + xmlStdPtr prevNode = node->prev; | |
| 696 | + | |
| 697 | + while (prevNode != NULL) | |
| 698 | + { | |
| 699 | + if (xmlStrEqual(node->name, prevNode->name)) | |
| 700 | + { | |
| 701 | + index++; | |
| 702 | + } | |
| 703 | + prevNode = prevNode->prev; | |
| 704 | + } | |
| 705 | + | |
| 706 | + [result insertString:[NSString stringWithFormat:@"/%s[%i]", node->name, index] atIndex:0]; | |
| 707 | + } | |
| 708 | + | |
| 709 | + node = (xmlStdPtr)node->parent; | |
| 710 | + } | |
| 711 | + | |
| 712 | + return [[result copy] autorelease]; | |
| 713 | +} | |
| 714 | + | |
| 715 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 716 | +#pragma mark QNames | |
| 717 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 718 | + | |
| 719 | +/** | |
| 720 | + * Returns the local name of the receiver. | |
| 721 | + * | |
| 722 | + * The local name is the part of a node name that follows a namespace-qualifying colon or the full name if | |
| 723 | + * there is no colon. For example, メchapterモ is the local name in the qualified name メacme:chapterモ. | |
| 724 | +**/ | |
| 725 | +- (NSString *)localName | |
| 726 | +{ | |
| 727 | + // Note: DDXMLNamespaceNode overrides this method | |
| 728 | + | |
| 729 | + return [[self class] localNameForName:[self name]]; | |
| 730 | +} | |
| 731 | + | |
| 732 | +/** | |
| 733 | + * Returns the prefix of the receiverユs name. | |
| 734 | + * | |
| 735 | + * The prefix is the part of a namespace-qualified name that precedes the colon. | |
| 736 | + * For example, メacmeモ is the local name in the qualified name メacme:chapterモ. | |
| 737 | + * This method returns an empty string if the receiverユs name is not qualified by a namespace. | |
| 738 | +**/ | |
| 739 | +- (NSString *)prefix | |
| 740 | +{ | |
| 741 | + // Note: DDXMLNamespaceNode overrides this method | |
| 742 | + | |
| 743 | + return [[self class] prefixForName:[self name]]; | |
| 744 | +} | |
| 745 | + | |
| 746 | +/** | |
| 747 | + * Sets the URI identifying the source of this document. | |
| 748 | + * Pass nil to remove the current URI. | |
| 749 | +**/ | |
| 750 | +- (void)setURI:(NSString *)URI | |
| 751 | +{ | |
| 752 | + // Note: DDXMLNamespaceNode overrides this method | |
| 753 | + // Note: DDXMLAttributeNode overrides this method | |
| 754 | + | |
| 755 | + if (IsXmlNodePtr(genericPtr)) | |
| 756 | + { | |
| 757 | + xmlNodePtr node = (xmlNodePtr)genericPtr; | |
| 758 | + if (node->ns != NULL) | |
| 759 | + { | |
| 760 | + [[self class] removeNamespace:node->ns fromNode:node]; | |
| 761 | + } | |
| 762 | + | |
| 763 | + if (URI) | |
| 764 | + { | |
| 765 | + // Create a new xmlNsPtr, add it to the nsDef list, and make ns point to it | |
| 766 | + xmlNsPtr ns = xmlNewNs(NULL, [URI xmlChar], NULL); | |
| 767 | + ns->next = node->nsDef; | |
| 768 | + node->nsDef = ns; | |
| 769 | + node->ns = ns; | |
| 770 | + } | |
| 771 | + } | |
| 772 | +} | |
| 773 | + | |
| 774 | +/** | |
| 775 | + * Returns the URI associated with the receiver. | |
| 776 | + * | |
| 777 | + * A nodeユs URI is derived from its namespace or a documentユs URI; for documents, the URI comes either from the | |
| 778 | + * parsed XML or is explicitly set. You cannot change the URI for a particular node other for than a namespace | |
| 779 | + * or document node. | |
| 780 | +**/ | |
| 781 | +- (NSString *)URI | |
| 782 | +{ | |
| 783 | + // Note: DDXMLNamespaceNode overrides this method | |
| 784 | + // Note: DDXMLAttributeNode overrides this method | |
| 785 | + | |
| 786 | + if (IsXmlNodePtr(genericPtr)) | |
| 787 | + { | |
| 788 | + xmlNodePtr node = (xmlNodePtr)genericPtr; | |
| 789 | + if (node->ns != NULL) | |
| 790 | + { | |
| 791 | + return [NSString stringWithUTF8String:((const char *)node->ns->href)]; | |
| 792 | + } | |
| 793 | + } | |
| 794 | + | |
| 795 | + return nil; | |
| 796 | +} | |
| 797 | + | |
| 798 | +/** | |
| 799 | + * Returns the local name from the specified qualified name. | |
| 800 | + * | |
| 801 | + * Examples: | |
| 802 | + * "a:node" -> "node" | |
| 803 | + * "a:a:node" -> "a:node" | |
| 804 | + * "node" -> "node" | |
| 805 | + * nil - > nil | |
| 806 | +**/ | |
| 807 | ++ (NSString *)localNameForName:(NSString *)name | |
| 808 | +{ | |
| 809 | + if (name) | |
| 810 | + { | |
| 811 | + NSRange range = [name rangeOfString:@":"]; | |
| 812 | + | |
| 813 | + if (range.length != 0) | |
| 814 | + return [name substringFromIndex:(range.location + range.length)]; | |
| 815 | + else | |
| 816 | + return name; | |
| 817 | + } | |
| 818 | + return nil; | |
| 819 | +} | |
| 820 | + | |
| 821 | +/** | |
| 822 | + * Extracts the prefix from the given name. | |
| 823 | + * If name is nil, or has no prefix, an empty string is returned. | |
| 824 | + * | |
| 825 | + * Examples: | |
| 826 | + * "a:deusty.com" -> "a" | |
| 827 | + * "a:a:deusty.com" -> "a" | |
| 828 | + * "node" -> "" | |
| 829 | + * nil -> "" | |
| 830 | +**/ | |
| 831 | ++ (NSString *)prefixForName:(NSString *)name | |
| 832 | +{ | |
| 833 | + if (name) | |
| 834 | + { | |
| 835 | + NSRange range = [name rangeOfString:@":"]; | |
| 836 | + | |
| 837 | + if (range.length != 0) | |
| 838 | + { | |
| 839 | + return [name substringToIndex:range.location]; | |
| 840 | + } | |
| 841 | + } | |
| 842 | + return @""; | |
| 843 | +} | |
| 844 | + | |
| 845 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 846 | +#pragma mark Output | |
| 847 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 848 | + | |
| 849 | +- (NSString *)description | |
| 850 | +{ | |
| 851 | + return [self XMLStringWithOptions:0]; | |
| 852 | +} | |
| 853 | + | |
| 854 | +- (NSString *)XMLString | |
| 855 | +{ | |
| 856 | + // Todo: Test XMLString for namespace node | |
| 857 | + return [self XMLStringWithOptions:0]; | |
| 858 | +} | |
| 859 | + | |
| 860 | +- (NSString *)XMLStringWithOptions:(NSUInteger)options | |
| 861 | +{ | |
| 862 | + // xmlSaveNoEmptyTags: | |
| 863 | + // Global setting, asking the serializer to not output empty tags | |
| 864 | + // as <empty/> but <empty></empty>. those two forms are undistinguishable | |
| 865 | + // once parsed. | |
| 866 | + // Disabled by default | |
| 867 | + | |
| 868 | + if (options & DDXMLNodeCompactEmptyElement) | |
| 869 | + xmlSaveNoEmptyTags = 0; | |
| 870 | + else | |
| 871 | + xmlSaveNoEmptyTags = 1; | |
| 872 | + | |
| 873 | + int format = 0; | |
| 874 | + if (options & DDXMLNodePrettyPrint) | |
| 875 | + { | |
| 876 | + format = 1; | |
| 877 | + xmlIndentTreeOutput = 1; | |
| 878 | + } | |
| 879 | + | |
| 880 | + xmlBufferPtr bufferPtr = xmlBufferCreate(); | |
| 881 | + if (IsXmlNsPtr(genericPtr)) | |
| 882 | + xmlNodeDump(bufferPtr, NULL, (xmlNodePtr)genericPtr, 0, format); | |
| 883 | + else | |
| 884 | + xmlNodeDump(bufferPtr, ((xmlStdPtr)genericPtr)->doc, (xmlNodePtr)genericPtr, 0, format); | |
| 885 | + | |
| 886 | + if ([self kind] == DDXMLTextKind) | |
| 887 | + { | |
| 888 | + NSString *result = [NSString stringWithUTF8String:(const char *)bufferPtr->content]; | |
| 889 | + | |
| 890 | + xmlBufferFree(bufferPtr); | |
| 891 | + | |
| 892 | + return result; | |
| 893 | + } | |
| 894 | + else | |
| 895 | + { | |
| 896 | + NSMutableString *resTmp = [NSMutableString stringWithUTF8String:(const char *)bufferPtr->content]; | |
| 897 | + CFStringTrimWhitespace((CFMutableStringRef)resTmp); | |
| 898 | + | |
| 899 | + xmlBufferFree(bufferPtr); | |
| 900 | + | |
| 901 | + return [[resTmp copy] autorelease]; | |
| 902 | + } | |
| 903 | +} | |
| 904 | + | |
| 905 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 906 | +#pragma mark XPath/XQuery | |
| 907 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 908 | + | |
| 909 | +- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error | |
| 910 | +{ | |
| 911 | + xmlXPathContextPtr xpathCtx; | |
| 912 | + xmlXPathObjectPtr xpathObj; | |
| 913 | + | |
| 914 | + BOOL isTempDoc = NO; | |
| 915 | + xmlDocPtr doc; | |
| 916 | + | |
| 917 | + if (IsXmlDocPtr(genericPtr)) | |
| 918 | + { | |
| 919 | + doc = (xmlDocPtr)genericPtr; | |
| 920 | + } | |
| 921 | + else if (IsXmlNodePtr(genericPtr)) | |
| 922 | + { | |
| 923 | + doc = ((xmlNodePtr)genericPtr)->doc; | |
| 924 | + | |
| 925 | + if(doc == NULL) | |
| 926 | + { | |
| 927 | + isTempDoc = YES; | |
| 928 | + | |
| 929 | + doc = xmlNewDoc(NULL); | |
| 930 | + xmlDocSetRootElement(doc, (xmlNodePtr)genericPtr); | |
| 931 | + } | |
| 932 | + } | |
| 933 | + else | |
| 934 | + { | |
| 935 | + return nil; | |
| 936 | + } | |
| 937 | + | |
| 938 | + xpathCtx = xmlXPathNewContext(doc); | |
| 939 | + xpathCtx->node = (xmlNodePtr)genericPtr; | |
| 940 | + | |
| 941 | + xmlNodePtr rootNode = (doc)->children; | |
| 942 | + if(rootNode != NULL) | |
| 943 | + { | |
| 944 | + xmlNsPtr ns = rootNode->nsDef; | |
| 945 | + while(ns != NULL) | |
| 946 | + { | |
| 947 | + xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href); | |
| 948 | + | |
| 949 | + ns = ns->next; | |
| 950 | + } | |
| 951 | + } | |
| 952 | + | |
| 953 | + xpathObj = xmlXPathEvalExpression([xpath xmlChar], xpathCtx); | |
| 954 | + | |
| 955 | + NSArray *result; | |
| 956 | + | |
| 957 | + if(xpathObj == NULL) | |
| 958 | + { | |
| 959 | + if(error) *error = [[self class] lastError]; | |
| 960 | + result = nil; | |
| 961 | + } | |
| 962 | + else | |
| 963 | + { | |
| 964 | + if(error) *error = nil; | |
| 965 | + | |
| 966 | + int count = xmlXPathNodeSetGetLength(xpathObj->nodesetval); | |
| 967 | + | |
| 968 | + if(count == 0) | |
| 969 | + { | |
| 970 | + result = [NSArray array]; | |
| 971 | + } | |
| 972 | + else | |
| 973 | + { | |
| 974 | + NSMutableArray *mResult = [NSMutableArray arrayWithCapacity:count]; | |
| 975 | + | |
| 976 | + int i; | |
| 977 | + for (i = 0; i < count; i++) | |
| 978 | + { | |
| 979 | + xmlNodePtr node = xpathObj->nodesetval->nodeTab[i]; | |
| 980 | + | |
| 981 | + [mResult addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node freeOnDealloc:NO]]; | |
| 982 | + } | |
| 983 | + | |
| 984 | + result = mResult; | |
| 985 | + } | |
| 986 | + } | |
| 987 | + | |
| 988 | + if(xpathObj) xmlXPathFreeObject(xpathObj); | |
| 989 | + if(xpathCtx) xmlXPathFreeContext(xpathCtx); | |
| 990 | + | |
| 991 | + if (isTempDoc) | |
| 992 | + { | |
| 993 | + xmlUnlinkNode((xmlNodePtr)genericPtr); | |
| 994 | + xmlFreeDoc(doc); | |
| 995 | + | |
| 996 | + // xmlUnlinkNode doesn't remove the doc ptr | |
| 997 | + [[self class] recursiveStripDocPointersFromNode:(xmlNodePtr)genericPtr]; | |
| 998 | + } | |
| 999 | + | |
| 1000 | + return result; | |
| 1001 | +} | |
| 1002 | + | |
| 1003 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1004 | +#pragma mark Private API | |
| 1005 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1006 | + | |
| 1007 | +// ---------- MEMORY MANAGEMENT ARCHITECTURE ---------- | |
| 1008 | +// | |
| 1009 | +// KissXML is designed to be read-access thread-safe. | |
| 1010 | +// It is not write-access thread-safe as this would require significant overhead. | |
| 1011 | +// | |
| 1012 | +// What exactly does read-access thread-safe mean? | |
| 1013 | +// It means that multiple threads can safely read from the same xml structure, | |
| 1014 | +// so long as none of them attempt to alter the xml structure (add/remove nodes, change attributes, etc). | |
| 1015 | +// | |
| 1016 | +// This read-access thread-safety includes parsed xml structures as well as xml structures created by you. | |
| 1017 | +// Let's walk through a few examples to get a deeper understanding. | |
| 1018 | +// | |
| 1019 | +// | |
| 1020 | +// | |
| 1021 | +// Example #1 - Parallel processing of children | |
| 1022 | +// | |
| 1023 | +// DDXMLElement *root = [[DDXMLElement alloc] initWithXMLString:str error:nil]; | |
| 1024 | +// NSArray *children = [root children]; | |
| 1025 | +// | |
| 1026 | +// dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); | |
| 1027 | +// dispatch_apply([children count], q, ^(size_t i) { | |
| 1028 | +// DDXMLElement *child = [children objectAtIndex:i]; | |
| 1029 | +// <process child> | |
| 1030 | +// }); | |
| 1031 | +// | |
| 1032 | +// | |
| 1033 | +// | |
| 1034 | +// Example #2 - Safe vs Unsafe sub-element processing | |
| 1035 | +// | |
| 1036 | +// DDXMLElement *root = [[DDXMLElement alloc] initWithXMLString:str error:nil]; | |
| 1037 | +// DDXMLElement *child = [root elementForName:@"starbucks"]; | |
| 1038 | +// | |
| 1039 | +// dispatch_async(queue, ^{ | |
| 1040 | +// <process child> | |
| 1041 | +// }); | |
| 1042 | +// | |
| 1043 | +// [root release]; <-------------- NOT safe! | |
| 1044 | +// | |
| 1045 | +// But why is it not safe? | |
| 1046 | +// Does it have something to do with the child? | |
| 1047 | +// Do I need to retain the child? | |
| 1048 | +// Doesn't the child get retained automatically by dispatch_async? | |
| 1049 | +// | |
| 1050 | +// Yes, the child does get retainied automatically by dispatch_async, but that's not the problem. | |
| 1051 | +// XML represents a heirarchy of nodes. For example: | |
| 1052 | +// | |
| 1053 | +// <root> | |
| 1054 | +// <starbucks> | |
| 1055 | +// <coffee/> | |
| 1056 | +// </starbucks> | |
| 1057 | +// </root> | |
| 1058 | +// | |
| 1059 | +// Each element within the heirarchy has references/pointers to its parent, children, siblings, etc. | |
| 1060 | +// This is necessary to support the traversal strategies one requires to work with XML. | |
| 1061 | +// This also means its not thread-safe to deallocate the root node of an element if | |
| 1062 | +// you are still using/accessing a child node. | |
| 1063 | +// So let's rewrite example 2 in a thread-safe manner this time. | |
| 1064 | +// | |
| 1065 | +// DDXMLElement *root = [[DDXMLElement alloc] initWithXMLString:str error:nil]; | |
| 1066 | +// DDXMLElement *child = [root elementForName:@"starbucks"]; | |
| 1067 | +// | |
| 1068 | +// [child detach]; <-------------- Detached from root, and can safely be used even if we now dealloc root. | |
| 1069 | +// | |
| 1070 | +// dispatch_async(queue, ^{ | |
| 1071 | +// <process child> | |
| 1072 | +// }); | |
| 1073 | +// | |
| 1074 | +// [root release]; <-------------- Thread-safe thanks to the detach above. | |
| 1075 | +// | |
| 1076 | +// | |
| 1077 | +// | |
| 1078 | +// Example #3 - Building up an element | |
| 1079 | +// | |
| 1080 | +// DDXMLElement *coffee = [[DDXMLElement alloc] initWithName:@"coffee"]; | |
| 1081 | +// DDXMLElement *starbucks = [[DDXMLElement alloc] initWithName:@"starbucks"]; | |
| 1082 | +// DDXMLElement *root = [[DDXMLElement alloc] initWithName:@"root"]; | |
| 1083 | +// | |
| 1084 | +// At this point we have 3 root nodes (root, starbucks, coffee) | |
| 1085 | +// | |
| 1086 | +// [starbucks addChild:coffee]; | |
| 1087 | +// | |
| 1088 | +// At this point we have 2 root nodes (root, starbucks). | |
| 1089 | +// The coffee node is now a child of starbucks, so it is no-longer a "root" node since | |
| 1090 | +// it has a parent within the xml tree heirarchy. | |
| 1091 | +// | |
| 1092 | +// [coffee addChild:starbucks]; | |
| 1093 | +// | |
| 1094 | +// At this point we have only 1 root node (root). | |
| 1095 | +// Again, the others are no-longer "root" nodes since they have a parent within the xml tree heirarchy. | |
| 1096 | +// | |
| 1097 | +// [coffee release]; coffee = nil; | |
| 1098 | +// | |
| 1099 | +// If you have a reference to a child node, you can safely release that reference. | |
| 1100 | +// Since coffee is embedded in the tree heirarchy, the coffee node doesn't disappear. | |
| 1101 | +// | |
| 1102 | +// DDXMLElement *coffee2 = [starbucks elementForName:@"coffee"]; | |
| 1103 | +// | |
| 1104 | +// So the above will return a new reference to the coffee node. | |
| 1105 | +// | |
| 1106 | +// [root release]; root = nil; | |
| 1107 | +// | |
| 1108 | +// Now, we have just released the root node. | |
| 1109 | +// This means that it is no longer safe to use starbucks or coffee2. | |
| 1110 | +// | |
| 1111 | +// [starbucks release]; starbucks = nil; | |
| 1112 | +// | |
| 1113 | +// Yes, this is safe. Just don't do anything else with starbucks besides release it. | |
| 1114 | + | |
| 1115 | +/** | |
| 1116 | + * Returns whether or not the node has a parent. | |
| 1117 | + * Use this method instead of parent when you only need to ensure parent is nil. | |
| 1118 | + * This prevents the unnecessary creation of a parent node wrapper. | |
| 1119 | +**/ | |
| 1120 | +- (BOOL)hasParent | |
| 1121 | +{ | |
| 1122 | + // Note: DDXMLNamespaceNode overrides this method | |
| 1123 | + | |
| 1124 | + xmlStdPtr node = (xmlStdPtr)genericPtr; | |
| 1125 | + | |
| 1126 | + return (node->parent != NULL); | |
| 1127 | +} | |
| 1128 | + | |
| 1129 | ++ (void)stripDocPointersFromAttr:(xmlAttrPtr)attr | |
| 1130 | +{ | |
| 1131 | + xmlNodePtr child = attr->children; | |
| 1132 | + while (child != NULL) | |
| 1133 | + { | |
| 1134 | + child->doc = NULL; | |
| 1135 | + child = child->next; | |
| 1136 | + } | |
| 1137 | + | |
| 1138 | + attr->doc = NULL; | |
| 1139 | +} | |
| 1140 | + | |
| 1141 | ++ (void)recursiveStripDocPointersFromNode:(xmlNodePtr)node | |
| 1142 | +{ | |
| 1143 | + xmlAttrPtr attr = node->properties; | |
| 1144 | + while (attr != NULL) | |
| 1145 | + { | |
| 1146 | + [self stripDocPointersFromAttr:attr]; | |
| 1147 | + attr = attr->next; | |
| 1148 | + } | |
| 1149 | + | |
| 1150 | + xmlNodePtr child = node->children; | |
| 1151 | + while (child != NULL) | |
| 1152 | + { | |
| 1153 | + [self recursiveStripDocPointersFromNode:child]; | |
| 1154 | + child = child->next; | |
| 1155 | + } | |
| 1156 | + | |
| 1157 | + node->doc = NULL; | |
| 1158 | +} | |
| 1159 | + | |
| 1160 | +/** | |
| 1161 | + * Detaches the given attribute from the given node. | |
| 1162 | + * The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list. | |
| 1163 | + * Then, if flag is YES, the attribute's parent, prev, next and doc pointers are destroyed. | |
| 1164 | +**/ | |
| 1165 | ++ (void)detachAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node andNullifyPointers:(BOOL)flag | |
| 1166 | +{ | |
| 1167 | + // Update the surrounding prev/next pointers | |
| 1168 | + if (attr->prev == NULL) | |
| 1169 | + { | |
| 1170 | + if (attr->next == NULL) | |
| 1171 | + { | |
| 1172 | + node->properties = NULL; | |
| 1173 | + } | |
| 1174 | + else | |
| 1175 | + { | |
| 1176 | + node->properties = attr->next; | |
| 1177 | + attr->next->prev = NULL; | |
| 1178 | + } | |
| 1179 | + } | |
| 1180 | + else | |
| 1181 | + { | |
| 1182 | + if (attr->next == NULL) | |
| 1183 | + { | |
| 1184 | + attr->prev->next = NULL; | |
| 1185 | + } | |
| 1186 | + else | |
| 1187 | + { | |
| 1188 | + attr->prev->next = attr->next; | |
| 1189 | + attr->next->prev = attr->prev; | |
| 1190 | + } | |
| 1191 | + } | |
| 1192 | + | |
| 1193 | + if (flag) | |
| 1194 | + { | |
| 1195 | + // Nullify pointers | |
| 1196 | + attr->parent = NULL; | |
| 1197 | + attr->prev = NULL; | |
| 1198 | + attr->next = NULL; | |
| 1199 | + if(attr->doc != NULL) [self stripDocPointersFromAttr:attr]; | |
| 1200 | + } | |
| 1201 | +} | |
| 1202 | + | |
| 1203 | +/** | |
| 1204 | + * Detaches the given attribute from the given node. | |
| 1205 | + * The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list. | |
| 1206 | + * Then the attribute's parent, prev, next and doc pointers are destroyed. | |
| 1207 | +**/ | |
| 1208 | ++ (void)detachAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node | |
| 1209 | +{ | |
| 1210 | + [self detachAttribute:attr fromNode:node andNullifyPointers:YES]; | |
| 1211 | +} | |
| 1212 | + | |
| 1213 | +/** | |
| 1214 | + * Removes and free's the given attribute from the given node. | |
| 1215 | + * The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list. | |
| 1216 | +**/ | |
| 1217 | ++ (void)removeAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node | |
| 1218 | +{ | |
| 1219 | + // We perform a bit of optimization here. | |
| 1220 | + // No need to bother nullifying pointers since we're about to free the node anyway. | |
| 1221 | + [self detachAttribute:attr fromNode:node andNullifyPointers:NO]; | |
| 1222 | + | |
| 1223 | + xmlFreeProp(attr); | |
| 1224 | +} | |
| 1225 | + | |
| 1226 | +/** | |
| 1227 | + * Removes and frees all attributes from the given node. | |
| 1228 | + * Upon return, the given node's properties pointer is NULL. | |
| 1229 | +**/ | |
| 1230 | ++ (void)removeAllAttributesFromNode:(xmlNodePtr)node | |
| 1231 | +{ | |
| 1232 | + xmlAttrPtr attr = node->properties; | |
| 1233 | + | |
| 1234 | + while (attr != NULL) | |
| 1235 | + { | |
| 1236 | + xmlAttrPtr nextAttr = attr->next; | |
| 1237 | + | |
| 1238 | + xmlFreeProp(attr); | |
| 1239 | + | |
| 1240 | + attr = nextAttr; | |
| 1241 | + } | |
| 1242 | + | |
| 1243 | + node->properties = NULL; | |
| 1244 | +} | |
| 1245 | + | |
| 1246 | +/** | |
| 1247 | + * Detaches the given namespace from the given node. | |
| 1248 | + * The namespace's surrounding next pointers are properly updated to remove the namespace from the node's nsDef list. | |
| 1249 | + * Then the namespace's parent and next pointers are destroyed. | |
| 1250 | +**/ | |
| 1251 | ++ (void)detachNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node | |
| 1252 | +{ | |
| 1253 | + // Namespace nodes have no previous pointer, so we have to search for the node | |
| 1254 | + | |
| 1255 | + xmlNsPtr previousNs = NULL; | |
| 1256 | + xmlNsPtr currentNs = node->nsDef; | |
| 1257 | + | |
| 1258 | + while (currentNs != NULL) | |
| 1259 | + { | |
| 1260 | + if (currentNs == ns) | |
| 1261 | + { | |
| 1262 | + if (previousNs == NULL) | |
| 1263 | + node->nsDef = currentNs->next; | |
| 1264 | + else | |
| 1265 | + previousNs->next = currentNs->next; | |
| 1266 | + | |
| 1267 | + break; | |
| 1268 | + } | |
| 1269 | + | |
| 1270 | + previousNs = currentNs; | |
| 1271 | + currentNs = currentNs->next; | |
| 1272 | + } | |
| 1273 | + | |
| 1274 | + if (node->ns == ns) | |
| 1275 | + { | |
| 1276 | + node->ns = NULL; | |
| 1277 | + } | |
| 1278 | + | |
| 1279 | + // Nullify pointers | |
| 1280 | + //ns->_private = NULL; Todo | |
| 1281 | + ns->next = NULL; | |
| 1282 | +} | |
| 1283 | + | |
| 1284 | +/** | |
| 1285 | + * Removes the given namespace from the given node. | |
| 1286 | + * The namespace's surrounding next pointers are properly updated to remove the namespace from the nsDef list. | |
| 1287 | + * Then the namespace is freed if it's no longer being referenced. | |
| 1288 | + * Otherwise, it's nsParent and next pointers are destroyed. | |
| 1289 | +**/ | |
| 1290 | ++ (void)removeNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node | |
| 1291 | +{ | |
| 1292 | + [self detachNamespace:ns fromNode:node]; | |
| 1293 | + | |
| 1294 | + xmlFreeNs(ns); | |
| 1295 | +} | |
| 1296 | + | |
| 1297 | +/** | |
| 1298 | + * Removes all namespaces from the given node. | |
| 1299 | + * All namespaces are either freed, or their nsParent and next pointers are properly destroyed. | |
| 1300 | + * Upon return, the given node's nsDef pointer is NULL. | |
| 1301 | +**/ | |
| 1302 | ++ (void)removeAllNamespacesFromNode:(xmlNodePtr)node | |
| 1303 | +{ | |
| 1304 | + xmlNsPtr ns = node->nsDef; | |
| 1305 | + | |
| 1306 | + while (ns != NULL) | |
| 1307 | + { | |
| 1308 | + xmlNsPtr nextNs = ns->next; | |
| 1309 | + | |
| 1310 | + xmlFreeNs(ns); | |
| 1311 | + | |
| 1312 | + ns = nextNs; | |
| 1313 | + } | |
| 1314 | + | |
| 1315 | + node->nsDef = NULL; | |
| 1316 | + node->ns = NULL; | |
| 1317 | +} | |
| 1318 | + | |
| 1319 | +/** | |
| 1320 | + * Detaches the given child from the given node. | |
| 1321 | + * The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list. | |
| 1322 | + * Then, if flag is YES, the child's parent, prev, next and doc pointers are destroyed. | |
| 1323 | +**/ | |
| 1324 | ++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node andNullifyPointers:(BOOL)flag | |
| 1325 | +{ | |
| 1326 | + // Update the surrounding prev/next pointers | |
| 1327 | + if (child->prev == NULL) | |
| 1328 | + { | |
| 1329 | + if (child->next == NULL) | |
| 1330 | + { | |
| 1331 | + node->children = NULL; | |
| 1332 | + node->last = NULL; | |
| 1333 | + } | |
| 1334 | + else | |
| 1335 | + { | |
| 1336 | + node->children = child->next; | |
| 1337 | + child->next->prev = NULL; | |
| 1338 | + } | |
| 1339 | + } | |
| 1340 | + else | |
| 1341 | + { | |
| 1342 | + if (child->next == NULL) | |
| 1343 | + { | |
| 1344 | + node->last = child->prev; | |
| 1345 | + child->prev->next = NULL; | |
| 1346 | + } | |
| 1347 | + else | |
| 1348 | + { | |
| 1349 | + child->prev->next = child->next; | |
| 1350 | + child->next->prev = child->prev; | |
| 1351 | + } | |
| 1352 | + } | |
| 1353 | + | |
| 1354 | + if (flag) | |
| 1355 | + { | |
| 1356 | + // Nullify pointers | |
| 1357 | + child->parent = NULL; | |
| 1358 | + child->prev = NULL; | |
| 1359 | + child->next = NULL; | |
| 1360 | + if(child->doc != NULL) [self recursiveStripDocPointersFromNode:child]; | |
| 1361 | + } | |
| 1362 | +} | |
| 1363 | + | |
| 1364 | +/** | |
| 1365 | + * Detaches the given child from the given node. | |
| 1366 | + * The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list. | |
| 1367 | + * Then the child's parent, prev, next and doc pointers are destroyed. | |
| 1368 | +**/ | |
| 1369 | ++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node | |
| 1370 | +{ | |
| 1371 | + [self detachChild:child fromNode:node andNullifyPointers:YES]; | |
| 1372 | +} | |
| 1373 | + | |
| 1374 | +/** | |
| 1375 | + * Removes the given child from the given node. | |
| 1376 | + * The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list. | |
| 1377 | + * Then the child is recursively freed if it's no longer being referenced. | |
| 1378 | + * Otherwise, it's parent, prev, next and doc pointers are destroyed. | |
| 1379 | + * | |
| 1380 | + * During the recursive free, subnodes still being referenced are properly handled. | |
| 1381 | +**/ | |
| 1382 | ++ (void)removeChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node | |
| 1383 | +{ | |
| 1384 | + // We perform a bit of optimization here. | |
| 1385 | + // No need to bother nullifying pointers since we're about to free the node anyway. | |
| 1386 | + [self detachChild:child fromNode:node andNullifyPointers:NO]; | |
| 1387 | + | |
| 1388 | + xmlFreeNode(child); | |
| 1389 | +} | |
| 1390 | + | |
| 1391 | +/** | |
| 1392 | + * Removes all children from the given node. | |
| 1393 | + * All children are either recursively freed, or their parent, prev, next and doc pointers are properly destroyed. | |
| 1394 | + * Upon return, the given node's children pointer is NULL. | |
| 1395 | + * | |
| 1396 | + * During the recursive free, subnodes still being referenced are properly handled. | |
| 1397 | +**/ | |
| 1398 | ++ (void)removeAllChildrenFromNode:(xmlNodePtr)node | |
| 1399 | +{ | |
| 1400 | + xmlNodePtr child = node->children; | |
| 1401 | + | |
| 1402 | + while (child != NULL) | |
| 1403 | + { | |
| 1404 | + xmlNodePtr nextChild = child->next; | |
| 1405 | + | |
| 1406 | + xmlFreeNode(child); | |
| 1407 | + | |
| 1408 | + child = nextChild; | |
| 1409 | + } | |
| 1410 | + | |
| 1411 | + node->children = NULL; | |
| 1412 | + node->last = NULL; | |
| 1413 | +} | |
| 1414 | + | |
| 1415 | +/** | |
| 1416 | + * Returns the last error encountered by libxml. | |
| 1417 | + * Errors are caught in the MyErrorHandler method within DDXMLDocument. | |
| 1418 | +**/ | |
| 1419 | ++ (NSError *)lastError | |
| 1420 | +{ | |
| 1421 | + NSValue *lastErrorValue = [[[NSThread currentThread] threadDictionary] objectForKey:DDLastErrorKey]; | |
| 1422 | + if(lastErrorValue) | |
| 1423 | + { | |
| 1424 | + xmlError lastError; | |
| 1425 | + [lastErrorValue getValue:&lastError]; | |
| 1426 | + | |
| 1427 | + int errCode = lastError.code; | |
| 1428 | + NSString *errMsg = [[NSString stringWithFormat:@"%s", lastError.message] stringByTrimming]; | |
| 1429 | + | |
| 1430 | + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; | |
| 1431 | + | |
| 1432 | + return [NSError errorWithDomain:@"DDXMLErrorDomain" code:errCode userInfo:info]; | |
| 1433 | + } | |
| 1434 | + else | |
| 1435 | + { | |
| 1436 | + return nil; | |
| 1437 | + } | |
| 1438 | +} | |
| 1439 | + | |
| 1440 | +static void MyErrorHandler(void * userData, xmlErrorPtr error) | |
| 1441 | +{ | |
| 1442 | + // This method is called by libxml when an error occurs. | |
| 1443 | + // We register for this error in the initialize method below. | |
| 1444 | + | |
| 1445 | + // Extract error message and store in the current thread's dictionary. | |
| 1446 | + // This ensure's thread safey, and easy access for all other DDXML classes. | |
| 1447 | + | |
| 1448 | + if (error == NULL) | |
| 1449 | + { | |
| 1450 | + [[[NSThread currentThread] threadDictionary] removeObjectForKey:DDLastErrorKey]; | |
| 1451 | + } | |
| 1452 | + else | |
| 1453 | + { | |
| 1454 | + NSValue *errorValue = [NSValue valueWithBytes:error objCType:@encode(xmlError)]; | |
| 1455 | + | |
| 1456 | + [[[NSThread currentThread] threadDictionary] setObject:errorValue forKey:DDLastErrorKey]; | |
| 1457 | + } | |
| 1458 | +} | |
| 1459 | + | |
| 1460 | +@end | |
| 1461 | + | |
| 1462 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1463 | +#pragma mark - | |
| 1464 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1465 | + | |
| 1466 | +@implementation DDXMLNamespaceNode | |
| 1467 | + | |
| 1468 | +/** | |
| 1469 | + * Returns a DDXML wrapper object for the given primitive node. | |
| 1470 | + * The given node MUST be non-NULL and of the proper type. | |
| 1471 | +**/ | |
| 1472 | ++ (id)nodeWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent freeOnDealloc:(BOOL)flag | |
| 1473 | +{ | |
| 1474 | + return [[[DDXMLNamespaceNode alloc] initWithNsPrimitive:ns nsParent:parent freeOnDealloc:flag] autorelease]; | |
| 1475 | +} | |
| 1476 | + | |
| 1477 | +/** | |
| 1478 | + * Returns a DDXML wrapper object for the given primitive node. | |
| 1479 | + * The given node MUST be non-NULL and of the proper type. | |
| 1480 | +**/ | |
| 1481 | +- (id)initWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent freeOnDealloc:(BOOL)flag | |
| 1482 | +{ | |
| 1483 | + if ((self = [super init])) | |
| 1484 | + { | |
| 1485 | + genericPtr = (xmlKindPtr)ns; | |
| 1486 | + nsParentPtr = parent; | |
| 1487 | + freeOnDealloc = flag; | |
| 1488 | + } | |
| 1489 | + return self; | |
| 1490 | +} | |
| 1491 | + | |
| 1492 | ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 1493 | +{ | |
| 1494 | + // Promote initializers which use proper parameter types to enable compiler to catch more mistakes. | |
| 1495 | + NSAssert(NO, @"Use nodeWithNsPrimitive:nsParent:freeOnDealloc:"); | |
| 1496 | + | |
| 1497 | + return nil; | |
| 1498 | +} | |
| 1499 | + | |
| 1500 | +- (id)initWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 1501 | +{ | |
| 1502 | + // Promote initializers which use proper parameter types to enable compiler to catch more mistakes. | |
| 1503 | + NSAssert(NO, @"Use initWithNsPrimitive:nsParent:freeOnDealloc:"); | |
| 1504 | + | |
| 1505 | + [self release]; | |
| 1506 | + return nil; | |
| 1507 | +} | |
| 1508 | + | |
| 1509 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1510 | +#pragma mark Properties | |
| 1511 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1512 | + | |
| 1513 | +- (void)setName:(NSString *)name | |
| 1514 | +{ | |
| 1515 | + xmlNsPtr ns = (xmlNsPtr)genericPtr; | |
| 1516 | + | |
| 1517 | + xmlFree((xmlChar *)ns->prefix); | |
| 1518 | + ns->prefix = xmlStrdup([name xmlChar]); | |
| 1519 | +} | |
| 1520 | + | |
| 1521 | +- (NSString *)name | |
| 1522 | +{ | |
| 1523 | + xmlNsPtr ns = (xmlNsPtr)genericPtr; | |
| 1524 | + if (ns->prefix != NULL) | |
| 1525 | + return [NSString stringWithUTF8String:((const char*)ns->prefix)]; | |
| 1526 | + else | |
| 1527 | + return @""; | |
| 1528 | +} | |
| 1529 | + | |
| 1530 | +- (void)setStringValue:(NSString *)string | |
| 1531 | +{ | |
| 1532 | + xmlNsPtr ns = (xmlNsPtr)genericPtr; | |
| 1533 | + | |
| 1534 | + xmlFree((xmlChar *)ns->href); | |
| 1535 | + ns->href = xmlEncodeSpecialChars(NULL, [string xmlChar]); | |
| 1536 | +} | |
| 1537 | + | |
| 1538 | +- (NSString *)stringValue | |
| 1539 | +{ | |
| 1540 | + return [NSString stringWithUTF8String:((const char *)((xmlNsPtr)genericPtr)->href)]; | |
| 1541 | +} | |
| 1542 | + | |
| 1543 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1544 | +#pragma mark Tree Navigation | |
| 1545 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1546 | + | |
| 1547 | +- (NSUInteger)index | |
| 1548 | +{ | |
| 1549 | + xmlNsPtr ns = (xmlNsPtr)genericPtr; | |
| 1550 | + | |
| 1551 | + // The xmlNsPtr has no prev pointer, so we have to search from the parent | |
| 1552 | + | |
| 1553 | + if (nsParentPtr == NULL) | |
| 1554 | + { | |
| 1555 | + return 0; | |
| 1556 | + } | |
| 1557 | + | |
| 1558 | + NSUInteger result = 0; | |
| 1559 | + | |
| 1560 | + xmlNsPtr currentNs = nsParentPtr->nsDef; | |
| 1561 | + while (currentNs != NULL) | |
| 1562 | + { | |
| 1563 | + if (currentNs == ns) | |
| 1564 | + { | |
| 1565 | + return result; | |
| 1566 | + } | |
| 1567 | + result++; | |
| 1568 | + currentNs = currentNs->next; | |
| 1569 | + } | |
| 1570 | + | |
| 1571 | + return 0; // Yes 0, not result, because ns wasn't found in list | |
| 1572 | +} | |
| 1573 | + | |
| 1574 | +- (NSUInteger)level | |
| 1575 | +{ | |
| 1576 | + NSUInteger result = 0; | |
| 1577 | + | |
| 1578 | + xmlNodePtr currentNode = nsParentPtr; | |
| 1579 | + while (currentNode != NULL) | |
| 1580 | + { | |
| 1581 | + result++; | |
| 1582 | + currentNode = currentNode->parent; | |
| 1583 | + } | |
| 1584 | + | |
| 1585 | + return result; | |
| 1586 | +} | |
| 1587 | + | |
| 1588 | +- (DDXMLDocument *)rootDocument | |
| 1589 | +{ | |
| 1590 | + xmlStdPtr node = (xmlStdPtr)nsParentPtr; | |
| 1591 | + | |
| 1592 | + if (node == NULL || node->doc == NULL) | |
| 1593 | + return nil; | |
| 1594 | + else | |
| 1595 | + return [DDXMLDocument nodeWithDocPrimitive:node->doc freeOnDealloc:NO]; | |
| 1596 | +} | |
| 1597 | + | |
| 1598 | +- (DDXMLNode *)parent | |
| 1599 | +{ | |
| 1600 | + if (nsParentPtr == NULL) | |
| 1601 | + return nil; | |
| 1602 | + else | |
| 1603 | + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)nsParentPtr freeOnDealloc:NO]; | |
| 1604 | +} | |
| 1605 | + | |
| 1606 | +- (NSUInteger)childCount | |
| 1607 | +{ | |
| 1608 | + return 0; | |
| 1609 | +} | |
| 1610 | + | |
| 1611 | +- (NSArray *)children | |
| 1612 | +{ | |
| 1613 | + return nil; | |
| 1614 | +} | |
| 1615 | + | |
| 1616 | +- (DDXMLNode *)childAtIndex:(NSUInteger)index | |
| 1617 | +{ | |
| 1618 | + return nil; | |
| 1619 | +} | |
| 1620 | + | |
| 1621 | +- (DDXMLNode *)previousSibling | |
| 1622 | +{ | |
| 1623 | + return nil; | |
| 1624 | +} | |
| 1625 | + | |
| 1626 | +- (DDXMLNode *)nextSibling | |
| 1627 | +{ | |
| 1628 | + return nil; | |
| 1629 | +} | |
| 1630 | + | |
| 1631 | +- (DDXMLNode *)previousNode | |
| 1632 | +{ | |
| 1633 | + return nil; | |
| 1634 | +} | |
| 1635 | + | |
| 1636 | +- (DDXMLNode *)nextNode | |
| 1637 | +{ | |
| 1638 | + return nil; | |
| 1639 | +} | |
| 1640 | + | |
| 1641 | +- (void)detach | |
| 1642 | +{ | |
| 1643 | + if (nsParentPtr != NULL) | |
| 1644 | + { | |
| 1645 | + [DDXMLNode detachNamespace:(xmlNsPtr)genericPtr fromNode:nsParentPtr]; | |
| 1646 | + | |
| 1647 | + freeOnDealloc = YES; | |
| 1648 | + nsParentPtr = NULL; | |
| 1649 | + } | |
| 1650 | +} | |
| 1651 | + | |
| 1652 | +- (xmlStdPtr)XPathPreProcess:(NSMutableString *)result | |
| 1653 | +{ | |
| 1654 | + xmlStdPtr parent = (xmlStdPtr)nsParentPtr; | |
| 1655 | + | |
| 1656 | + if (parent == NULL) | |
| 1657 | + [result appendFormat:@"namespace::%@", [self name]]; | |
| 1658 | + else | |
| 1659 | + [result appendFormat:@"/namespace::%@", [self name]]; | |
| 1660 | + | |
| 1661 | + return parent; | |
| 1662 | +} | |
| 1663 | + | |
| 1664 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1665 | +#pragma mark QNames | |
| 1666 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1667 | + | |
| 1668 | +- (NSString *)localName | |
| 1669 | +{ | |
| 1670 | + // Strangely enough, the localName of a namespace is the prefix, and the prefix is an empty string | |
| 1671 | + xmlNsPtr ns = (xmlNsPtr)genericPtr; | |
| 1672 | + if (ns->prefix != NULL) | |
| 1673 | + return [NSString stringWithUTF8String:((const char *)ns->prefix)]; | |
| 1674 | + else | |
| 1675 | + return @""; | |
| 1676 | +} | |
| 1677 | + | |
| 1678 | +- (NSString *)prefix | |
| 1679 | +{ | |
| 1680 | + // Strangely enough, the localName of a namespace is the prefix, and the prefix is an empty string | |
| 1681 | + return @""; | |
| 1682 | +} | |
| 1683 | + | |
| 1684 | +- (void)setURI:(NSString *)URI | |
| 1685 | +{ | |
| 1686 | + // Do nothing | |
| 1687 | +} | |
| 1688 | + | |
| 1689 | +- (NSString *)URI | |
| 1690 | +{ | |
| 1691 | + return nil; | |
| 1692 | +} | |
| 1693 | + | |
| 1694 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1695 | +#pragma mark Private API | |
| 1696 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1697 | + | |
| 1698 | +- (BOOL)hasParent | |
| 1699 | +{ | |
| 1700 | + return (nsParentPtr != NULL); | |
| 1701 | +} | |
| 1702 | + | |
| 1703 | +- (xmlNodePtr)nsParentPtr | |
| 1704 | +{ | |
| 1705 | + return nsParentPtr; | |
| 1706 | +} | |
| 1707 | + | |
| 1708 | +- (void)setNsParentPtr:(xmlNodePtr)parentPtr | |
| 1709 | +{ | |
| 1710 | + nsParentPtr = parentPtr; | |
| 1711 | +} | |
| 1712 | + | |
| 1713 | +@end | |
| 1714 | + | |
| 1715 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1716 | +#pragma mark - | |
| 1717 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1718 | + | |
| 1719 | +@implementation DDXMLAttributeNode | |
| 1720 | + | |
| 1721 | ++ (id)nodeWithAttrPrimitive:(xmlAttrPtr)attr freeOnDealloc:(BOOL)flag | |
| 1722 | +{ | |
| 1723 | + return [[[DDXMLAttributeNode alloc] initWithAttrPrimitive:attr freeOnDealloc:flag] autorelease]; | |
| 1724 | +} | |
| 1725 | + | |
| 1726 | +- (id)initWithAttrPrimitive:(xmlAttrPtr)attr freeOnDealloc:(BOOL)flag | |
| 1727 | +{ | |
| 1728 | + self = [super initWithPrimitive:(xmlKindPtr)attr freeOnDealloc:flag]; | |
| 1729 | + return self; | |
| 1730 | +} | |
| 1731 | + | |
| 1732 | ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 1733 | +{ | |
| 1734 | + // Promote initializers which use proper parameter types to enable compiler to catch more mistakes. | |
| 1735 | + NSAssert(NO, @"Use nodeWithAttrPrimitive:nsParent:freeOnDealloc:"); | |
| 1736 | + | |
| 1737 | + return nil; | |
| 1738 | +} | |
| 1739 | + | |
| 1740 | +- (id)initWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 1741 | +{ | |
| 1742 | + // Promote initializers which use proper parameter types to enable compiler to catch more mistakes. | |
| 1743 | + NSAssert(NO, @"Use initWithAttrPrimitive:nsParent:freeOnDealloc:"); | |
| 1744 | + | |
| 1745 | + [self release]; | |
| 1746 | + return nil; | |
| 1747 | +} | |
| 1748 | + | |
| 1749 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1750 | +#pragma mark Properties | |
| 1751 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1752 | + | |
| 1753 | +- (void)setStringValue:(NSString *)string | |
| 1754 | +{ | |
| 1755 | + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; | |
| 1756 | + | |
| 1757 | + if (attr->children != NULL) | |
| 1758 | + { | |
| 1759 | + xmlChar *escapedString = xmlEncodeSpecialChars(attr->doc, [string xmlChar]); | |
| 1760 | + xmlNodeSetContent((xmlNodePtr)attr, escapedString); | |
| 1761 | + xmlFree(escapedString); | |
| 1762 | + } | |
| 1763 | + else | |
| 1764 | + { | |
| 1765 | + xmlNodePtr text = xmlNewText([string xmlChar]); | |
| 1766 | + attr->children = text; | |
| 1767 | + } | |
| 1768 | +} | |
| 1769 | + | |
| 1770 | +- (NSString *)stringValue | |
| 1771 | +{ | |
| 1772 | + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; | |
| 1773 | + | |
| 1774 | + if (attr->children != NULL) | |
| 1775 | + { | |
| 1776 | + return [NSString stringWithUTF8String:(const char *)attr->children->content]; | |
| 1777 | + } | |
| 1778 | + | |
| 1779 | + return nil; | |
| 1780 | +} | |
| 1781 | + | |
| 1782 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1783 | +#pragma mark Tree Navigation | |
| 1784 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1785 | + | |
| 1786 | +- (DDXMLNode *)previousNode | |
| 1787 | +{ | |
| 1788 | + return nil; | |
| 1789 | +} | |
| 1790 | + | |
| 1791 | +- (DDXMLNode *)nextNode | |
| 1792 | +{ | |
| 1793 | + return nil; | |
| 1794 | +} | |
| 1795 | + | |
| 1796 | +- (void)detach | |
| 1797 | +{ | |
| 1798 | + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; | |
| 1799 | + | |
| 1800 | + if (attr->parent != NULL) | |
| 1801 | + { | |
| 1802 | + [[self class] detachAttribute:attr fromNode:attr->parent]; | |
| 1803 | + freeOnDealloc = YES; | |
| 1804 | + } | |
| 1805 | +} | |
| 1806 | + | |
| 1807 | +- (xmlStdPtr)XPathPreProcess:(NSMutableString *)result | |
| 1808 | +{ | |
| 1809 | + // Note: DDXMLNamespaceNode overrides this method | |
| 1810 | + // Note: DDXMLAttributeNode overrides this method | |
| 1811 | + | |
| 1812 | + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; | |
| 1813 | + xmlStdPtr parent = (xmlStdPtr)attr->parent; | |
| 1814 | + | |
| 1815 | + if (parent == NULL) | |
| 1816 | + [result appendFormat:@"@%@", [self name]]; | |
| 1817 | + else | |
| 1818 | + [result appendFormat:@"/@%@", [self name]]; | |
| 1819 | + | |
| 1820 | + return parent; | |
| 1821 | +} | |
| 1822 | + | |
| 1823 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1824 | +#pragma mark QNames | |
| 1825 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 1826 | + | |
| 1827 | +- (void)setURI:(NSString *)URI | |
| 1828 | +{ | |
| 1829 | + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; | |
| 1830 | + if (attr->ns != NULL) | |
| 1831 | + { | |
| 1832 | + // An attribute can only have a single namespace attached to it. | |
| 1833 | + // In addition, this namespace can only be accessed via the URI method. | |
| 1834 | + // There is no way, within the API, to get a DDXMLNode wrapper for the attribute's namespace. | |
| 1835 | + xmlFreeNs(attr->ns); | |
| 1836 | + attr->ns = NULL; | |
| 1837 | + } | |
| 1838 | + | |
| 1839 | + if (URI) | |
| 1840 | + { | |
| 1841 | + // Create a new xmlNsPtr, and make ns point to it | |
| 1842 | + xmlNsPtr ns = xmlNewNs(NULL, [URI xmlChar], NULL); | |
| 1843 | + attr->ns = ns; | |
| 1844 | + } | |
| 1845 | +} | |
| 1846 | + | |
| 1847 | +- (NSString *)URI | |
| 1848 | +{ | |
| 1849 | + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; | |
| 1850 | + if (attr->ns != NULL) | |
| 1851 | + { | |
| 1852 | + return [NSString stringWithUTF8String:((const char *)attr->ns->href)]; | |
| 1853 | + } | |
| 1854 | + | |
| 1855 | + return nil; | |
| 1856 | +} | |
| 1857 | + | |
| 1858 | +@end |
| @@ -0,0 +1,183 @@ | ||
| 1 | +#import "DDXML.h" | |
| 2 | + | |
| 3 | + | |
| 4 | +// We can't rely solely on NSAssert, because many developers disable them for release builds. | |
| 5 | +// Our API contract requires us to keep these assertions intact. | |
| 6 | +#define DDXMLAssert(condition, desc, ...) \ | |
| 7 | + do{ \ | |
| 8 | + if(!(condition)) \ | |
| 9 | + { \ | |
| 10 | + [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \ | |
| 11 | + object:self \ | |
| 12 | + file:[NSString stringWithUTF8String:__FILE__] \ | |
| 13 | + lineNumber:__LINE__ \ | |
| 14 | + description:(desc), ##__VA_ARGS__]; \ | |
| 15 | + } \ | |
| 16 | + }while(NO) | |
| 17 | + | |
| 18 | +#define DDLastErrorKey @"DDXML:LastError" | |
| 19 | + | |
| 20 | +/** | |
| 21 | + * DDXMLNode can represent several underlying types, such as xmlNodePtr, xmlDocPtr, xmlAttrPtr, xmlNsPtr, etc. | |
| 22 | + * All of these are pointers to structures, and all of those structures start with a pointer, and a type. | |
| 23 | + * The xmlKind struct is used as a generic structure, and a stepping stone. | |
| 24 | + * We use it to check the type of a structure, and then perform the appropriate cast. | |
| 25 | + * | |
| 26 | + * For example: | |
| 27 | + * if(genericPtr->type == XML_ATTRIBUTE_NODE) | |
| 28 | + * { | |
| 29 | + * xmlAttrPtr attr = (xmlAttrPtr)genericPtr; | |
| 30 | + * // Do something with attr | |
| 31 | + * } | |
| 32 | +**/ | |
| 33 | +struct _xmlKind { | |
| 34 | + void * ignore; | |
| 35 | + xmlElementType type; | |
| 36 | +}; | |
| 37 | +typedef struct _xmlKind *xmlKindPtr; | |
| 38 | + | |
| 39 | +/** | |
| 40 | + * Most xml types all start with this standard structure. In fact, all do except the xmlNsPtr. | |
| 41 | + * We will occasionally take advantage of this to simplify code when the code wouldn't vary from type to type. | |
| 42 | + * Obviously, you cannnot cast a xmlNsPtr to a xmlStdPtr. | |
| 43 | +**/ | |
| 44 | +struct _xmlStd { | |
| 45 | + void * _private; | |
| 46 | + xmlElementType type; | |
| 47 | + const xmlChar *name; | |
| 48 | + struct _xmlNode *children; | |
| 49 | + struct _xmlNode *last; | |
| 50 | + struct _xmlNode *parent; | |
| 51 | + struct _xmlStd *next; | |
| 52 | + struct _xmlStd *prev; | |
| 53 | + struct _xmlDoc *doc; | |
| 54 | +}; | |
| 55 | +typedef struct _xmlStd *xmlStdPtr; | |
| 56 | + | |
| 57 | + | |
| 58 | +NS_INLINE BOOL IsXmlAttrPtr(void *kindPtr) | |
| 59 | +{ | |
| 60 | + return ((xmlKindPtr)kindPtr)->type == XML_ATTRIBUTE_NODE; | |
| 61 | +} | |
| 62 | + | |
| 63 | +NS_INLINE BOOL IsXmlNodePtr(void *kindPtr) | |
| 64 | +{ | |
| 65 | + switch (((xmlKindPtr)kindPtr)->type) | |
| 66 | + { | |
| 67 | + case XML_ELEMENT_NODE : | |
| 68 | + case XML_PI_NODE : | |
| 69 | + case XML_COMMENT_NODE : | |
| 70 | + case XML_TEXT_NODE : | |
| 71 | + case XML_CDATA_SECTION_NODE : return YES; | |
| 72 | + default : return NO; | |
| 73 | + } | |
| 74 | +} | |
| 75 | + | |
| 76 | +NS_INLINE BOOL IsXmlDocPtr(void *kindPtr) | |
| 77 | +{ | |
| 78 | + return ((xmlKindPtr)kindPtr)->type == XML_DOCUMENT_NODE; | |
| 79 | +} | |
| 80 | + | |
| 81 | +NS_INLINE BOOL IsXmlDtdPtr(void *kindPtr) | |
| 82 | +{ | |
| 83 | + return ((xmlKindPtr)kindPtr)->type == XML_DTD_NODE; | |
| 84 | +} | |
| 85 | + | |
| 86 | +NS_INLINE BOOL IsXmlNsPtr(void *kindPtr) | |
| 87 | +{ | |
| 88 | + return ((xmlKindPtr)kindPtr)->type == XML_NAMESPACE_DECL; | |
| 89 | +} | |
| 90 | + | |
| 91 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 92 | +#pragma mark - | |
| 93 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 94 | + | |
| 95 | +@interface DDXMLNamespaceNode : DDXMLNode | |
| 96 | +{ | |
| 97 | + // The xmlNsPtr type doesn't store a reference to it's parent. | |
| 98 | + // This is here to fix the problem, and make this class more compatible with the NSXML classes. | |
| 99 | + xmlNodePtr nsParentPtr; | |
| 100 | +} | |
| 101 | + | |
| 102 | ++ (id)nodeWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent freeOnDealloc:(BOOL)flag; | |
| 103 | +- (id)initWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent freeOnDealloc:(BOOL)flag; | |
| 104 | + | |
| 105 | +- (xmlNodePtr)nsParentPtr; | |
| 106 | +- (void)setNsParentPtr:(xmlNodePtr)parentPtr; | |
| 107 | + | |
| 108 | +// Overrides several methods in DDXMLNode | |
| 109 | + | |
| 110 | +@end | |
| 111 | + | |
| 112 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 113 | +#pragma mark - | |
| 114 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 115 | + | |
| 116 | +@interface DDXMLAttributeNode : DDXMLNode | |
| 117 | + | |
| 118 | ++ (id)nodeWithAttrPrimitive:(xmlAttrPtr)attr freeOnDealloc:(BOOL)flag; | |
| 119 | +- (id)initWithAttrPrimitive:(xmlAttrPtr)attr freeOnDealloc:(BOOL)flag; | |
| 120 | + | |
| 121 | +// Overrides several methods in DDXMLNode | |
| 122 | + | |
| 123 | +@end | |
| 124 | + | |
| 125 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 126 | +#pragma mark - | |
| 127 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 128 | + | |
| 129 | +@interface DDXMLNode (PrivateAPI) | |
| 130 | + | |
| 131 | ++ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag; | |
| 132 | + | |
| 133 | ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag; | |
| 134 | +- (id)initWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag; | |
| 135 | + | |
| 136 | +- (BOOL)hasParent; | |
| 137 | + | |
| 138 | ++ (void)recursiveStripDocPointersFromNode:(xmlNodePtr)node; | |
| 139 | + | |
| 140 | ++ (void)detachAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node; | |
| 141 | ++ (void)removeAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node; | |
| 142 | ++ (void)removeAllAttributesFromNode:(xmlNodePtr)node; | |
| 143 | + | |
| 144 | ++ (void)detachNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node; | |
| 145 | ++ (void)removeNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node; | |
| 146 | ++ (void)removeAllNamespacesFromNode:(xmlNodePtr)node; | |
| 147 | + | |
| 148 | ++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node; | |
| 149 | ++ (void)removeChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node; | |
| 150 | ++ (void)removeAllChildrenFromNode:(xmlNodePtr)node; | |
| 151 | + | |
| 152 | +- (void)nodeFree; | |
| 153 | + | |
| 154 | ++ (NSError *)lastError; | |
| 155 | + | |
| 156 | +@end | |
| 157 | + | |
| 158 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 159 | +#pragma mark - | |
| 160 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 161 | + | |
| 162 | +@interface DDXMLElement (PrivateAPI) | |
| 163 | + | |
| 164 | ++ (id)nodeWithElementPrimitive:(xmlNodePtr)node freeOnDealloc:(BOOL)flag; | |
| 165 | +- (id)initWithElementPrimitive:(xmlNodePtr)node freeOnDealloc:(BOOL)flag; | |
| 166 | + | |
| 167 | +- (NSArray *)elementsForName:(NSString *)name uri:(NSString *)URI; | |
| 168 | + | |
| 169 | ++ (DDXMLNode *)resolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr; | |
| 170 | ++ (NSString *)resolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr; | |
| 171 | + | |
| 172 | +@end | |
| 173 | + | |
| 174 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 175 | +#pragma mark - | |
| 176 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 177 | + | |
| 178 | +@interface DDXMLDocument (PrivateAPI) | |
| 179 | + | |
| 180 | ++ (id)nodeWithDocPrimitive:(xmlDocPtr)doc freeOnDealloc:(BOOL)flag; | |
| 181 | +- (id)initWithDocPrimitive:(xmlDocPtr)doc freeOnDealloc:(BOOL)flag; | |
| 182 | + | |
| 183 | +@end |
| @@ -0,0 +1,67 @@ | ||
| 1 | +#import <Foundation/Foundation.h> | |
| 2 | +#import "DDXMLElement.h" | |
| 3 | +#import "DDXMLNode.h" | |
| 4 | + | |
| 5 | + | |
| 6 | +enum { | |
| 7 | + DDXMLDocumentXMLKind = 0, | |
| 8 | + DDXMLDocumentXHTMLKind, | |
| 9 | + DDXMLDocumentHTMLKind, | |
| 10 | + DDXMLDocumentTextKind | |
| 11 | +}; | |
| 12 | +typedef NSUInteger DDXMLDocumentContentKind; | |
| 13 | + | |
| 14 | +@interface DDXMLDocument : DDXMLNode | |
| 15 | +{ | |
| 16 | +} | |
| 17 | + | |
| 18 | +- (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error; | |
| 19 | +//- (id)initWithContentsOfURL:(NSURL *)url options:(NSUInteger)mask error:(NSError **)error; | |
| 20 | +- (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error; | |
| 21 | +//- (id)initWithRootElement:(DDXMLElement *)element; | |
| 22 | + | |
| 23 | +//+ (Class)replacementClassForClass:(Class)cls; | |
| 24 | + | |
| 25 | +//- (void)setCharacterEncoding:(NSString *)encoding; //primitive | |
| 26 | +//- (NSString *)characterEncoding; //primitive | |
| 27 | + | |
| 28 | +//- (void)setVersion:(NSString *)version; | |
| 29 | +//- (NSString *)version; | |
| 30 | + | |
| 31 | +//- (void)setStandalone:(BOOL)standalone; | |
| 32 | +//- (BOOL)isStandalone; | |
| 33 | + | |
| 34 | +//- (void)setDocumentContentKind:(DDXMLDocumentContentKind)kind; | |
| 35 | +//- (DDXMLDocumentContentKind)documentContentKind; | |
| 36 | + | |
| 37 | +//- (void)setMIMEType:(NSString *)MIMEType; | |
| 38 | +//- (NSString *)MIMEType; | |
| 39 | + | |
| 40 | +//- (void)setDTD:(DDXMLDTD *)documentTypeDeclaration; | |
| 41 | +//- (DDXMLDTD *)DTD; | |
| 42 | + | |
| 43 | +//- (void)setRootElement:(DDXMLNode *)root; | |
| 44 | +- (DDXMLElement *)rootElement; | |
| 45 | + | |
| 46 | +//- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index; | |
| 47 | + | |
| 48 | +//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index; | |
| 49 | + | |
| 50 | +//- (void)removeChildAtIndex:(NSUInteger)index; | |
| 51 | + | |
| 52 | +//- (void)setChildren:(NSArray *)children; | |
| 53 | + | |
| 54 | +//- (void)addChild:(DDXMLNode *)child; | |
| 55 | + | |
| 56 | +//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node; | |
| 57 | + | |
| 58 | +- (NSData *)XMLData; | |
| 59 | +- (NSData *)XMLDataWithOptions:(NSUInteger)options; | |
| 60 | + | |
| 61 | +//- (id)objectByApplyingXSLT:(NSData *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error; | |
| 62 | +//- (id)objectByApplyingXSLTString:(NSString *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error; | |
| 63 | +//- (id)objectByApplyingXSLTAtURL:(NSURL *)xsltURL arguments:(NSDictionary *)argument error:(NSError **)error; | |
| 64 | + | |
| 65 | +//- (BOOL)validateAndReturnError:(NSError **)error; | |
| 66 | + | |
| 67 | +@end |
| @@ -0,0 +1,671 @@ | ||
| 1 | +#import "DDXMLPrivate.h" | |
| 2 | +#import "NSString+DDXML.h" | |
| 3 | + | |
| 4 | + | |
| 5 | +@implementation DDXMLElement | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Returns a DDXML wrapper object for the given primitive node. | |
| 9 | + * The given node MUST be non-NULL and of the proper type. | |
| 10 | +**/ | |
| 11 | ++ (id)nodeWithElementPrimitive:(xmlNodePtr)node freeOnDealloc:(BOOL)flag | |
| 12 | +{ | |
| 13 | + return [[[DDXMLElement alloc] initWithElementPrimitive:node freeOnDealloc:flag] autorelease]; | |
| 14 | +} | |
| 15 | + | |
| 16 | +- (id)initWithElementPrimitive:(xmlNodePtr)node freeOnDealloc:(BOOL)flag | |
| 17 | +{ | |
| 18 | + self = [super initWithPrimitive:(xmlKindPtr)node freeOnDealloc:flag]; | |
| 19 | + return self; | |
| 20 | +} | |
| 21 | + | |
| 22 | ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 23 | +{ | |
| 24 | + // Promote initializers which use proper parameter types to enable compiler to catch more mistakes | |
| 25 | + NSAssert(NO, @"Use nodeWithElementPrimitive:freeOnDealloc:"); | |
| 26 | + | |
| 27 | + return nil; | |
| 28 | +} | |
| 29 | + | |
| 30 | +- (id)initWithPrimitive:(xmlKindPtr)kindPtr freeOnDealloc:(BOOL)flag | |
| 31 | +{ | |
| 32 | + // Promote initializers which use proper parameter types to enable compiler to catch more mistakes. | |
| 33 | + NSAssert(NO, @"Use initWithElementPrimitive:freeOnDealloc:"); | |
| 34 | + | |
| 35 | + [self release]; | |
| 36 | + return nil; | |
| 37 | +} | |
| 38 | + | |
| 39 | +- (id)initWithName:(NSString *)name | |
| 40 | +{ | |
| 41 | + // Note: Make every guarantee that genericPtr is not null | |
| 42 | + | |
| 43 | + xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]); | |
| 44 | + if (node == NULL) | |
| 45 | + { | |
| 46 | + [self release]; | |
| 47 | + return nil; | |
| 48 | + } | |
| 49 | + | |
| 50 | + return [self initWithElementPrimitive:node freeOnDealloc:YES]; | |
| 51 | +} | |
| 52 | + | |
| 53 | +- (id)initWithName:(NSString *)name URI:(NSString *)URI | |
| 54 | +{ | |
| 55 | + // Note: Make every guarantee that genericPtr is not null | |
| 56 | + | |
| 57 | + xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]); | |
| 58 | + if (node == NULL) | |
| 59 | + { | |
| 60 | + [self release]; | |
| 61 | + return nil; | |
| 62 | + } | |
| 63 | + | |
| 64 | + DDXMLElement *result = [self initWithElementPrimitive:node freeOnDealloc:YES]; | |
| 65 | + [result setURI:URI]; | |
| 66 | + | |
| 67 | + return result; | |
| 68 | +} | |
| 69 | + | |
| 70 | +- (id)initWithName:(NSString *)name stringValue:(NSString *)string | |
| 71 | +{ | |
| 72 | + // Note: Make every guarantee that genericPtr is not null | |
| 73 | + | |
| 74 | + xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]); | |
| 75 | + if (node == NULL) | |
| 76 | + { | |
| 77 | + [self release]; | |
| 78 | + return nil; | |
| 79 | + } | |
| 80 | + | |
| 81 | + DDXMLElement *result = [self initWithElementPrimitive:node freeOnDealloc:YES]; | |
| 82 | + [result setStringValue:string]; | |
| 83 | + | |
| 84 | + return result; | |
| 85 | +} | |
| 86 | + | |
| 87 | +- (id)initWithXMLString:(NSString *)string error:(NSError **)error | |
| 88 | +{ | |
| 89 | + DDXMLDocument *doc = [[DDXMLDocument alloc] initWithXMLString:string options:0 error:error]; | |
| 90 | + if (doc == nil) | |
| 91 | + { | |
| 92 | + [self release]; | |
| 93 | + return nil; | |
| 94 | + } | |
| 95 | + | |
| 96 | + DDXMLElement *result = [doc rootElement]; | |
| 97 | + [result detach]; | |
| 98 | + [doc release]; | |
| 99 | + | |
| 100 | + [self release]; | |
| 101 | + return [result retain]; | |
| 102 | +} | |
| 103 | + | |
| 104 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 105 | +#pragma mark Elements by name | |
| 106 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 107 | + | |
| 108 | +/** | |
| 109 | + * Returns the child element nodes (as DDXMLElement objects) of the receiver that have a specified name. | |
| 110 | + * | |
| 111 | + * If name is a qualified name, then this method invokes elementsForLocalName:URI: with the URI parameter set to | |
| 112 | + * the URI associated with the prefix. Otherwise comparison is based on string equality of the qualified or | |
| 113 | + * non-qualified name. | |
| 114 | +**/ | |
| 115 | +- (NSArray *)elementsForName:(NSString *)name | |
| 116 | +{ | |
| 117 | + if (name == nil) return [NSArray array]; | |
| 118 | + | |
| 119 | + // We need to check to see if name has a prefix. | |
| 120 | + // If it does have a prefix, we need to figure out what the corresponding URI is for that prefix, | |
| 121 | + // and then search for any elements that have the same name (including prefix) OR have the same URI. | |
| 122 | + // Otherwise we loop through the children as usual and do a string compare on the name | |
| 123 | + | |
| 124 | + NSString *prefix = [[self class] prefixForName:name]; | |
| 125 | + if ([prefix length] > 0) | |
| 126 | + { | |
| 127 | + xmlNodePtr node = (xmlNodePtr)genericPtr; | |
| 128 | + xmlNsPtr ns = xmlSearchNs(node->doc, node, [prefix xmlChar]); | |
| 129 | + if (ns != NULL) | |
| 130 | + { | |
| 131 | + NSString *uri = [NSString stringWithUTF8String:((const char *)ns->href)]; | |
| 132 | + return [self elementsForName:name uri:uri]; | |
| 133 | + } | |
| 134 | + | |
| 135 | + // Note: We used xmlSearchNs instead of resolveNamespaceForName: because | |
| 136 | + // we want to avoid creating wrapper objects when possible. | |
| 137 | + } | |
| 138 | + | |
| 139 | + return [self elementsForName:name uri:nil]; | |
| 140 | +} | |
| 141 | + | |
| 142 | +- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI | |
| 143 | +{ | |
| 144 | + if (localName == nil) return [NSArray array]; | |
| 145 | + | |
| 146 | + // We need to figure out what the prefix is for this URI. | |
| 147 | + // Then we search for elements that are named prefix:localName OR (named localName AND have the given URI). | |
| 148 | + | |
| 149 | + NSString *prefix = [self resolvePrefixForNamespaceURI:URI]; | |
| 150 | + if (prefix != nil) | |
| 151 | + { | |
| 152 | + NSString *name = [NSString stringWithFormat:@"%@:%@", prefix, localName]; | |
| 153 | + | |
| 154 | + return [self elementsForName:name uri:URI]; | |
| 155 | + } | |
| 156 | + else | |
| 157 | + { | |
| 158 | + return [self elementsForName:localName uri:URI]; | |
| 159 | + } | |
| 160 | +} | |
| 161 | + | |
| 162 | +/** | |
| 163 | + * Helper method elementsForName and elementsForLocalName:URI: so work isn't duplicated. | |
| 164 | + * The name parameter is required, URI is optional. | |
| 165 | +**/ | |
| 166 | +- (NSArray *)elementsForName:(NSString *)name uri:(NSString *)uri | |
| 167 | +{ | |
| 168 | + // Supplied: name, !uri : match: name | |
| 169 | + // Supplied: p:name, uri : match: p:name || (name && uri) | |
| 170 | + // Supplied: name, uri : match: name && uri | |
| 171 | + | |
| 172 | + NSMutableArray *result = [NSMutableArray array]; | |
| 173 | + | |
| 174 | + xmlNodePtr node = (xmlNodePtr)genericPtr; | |
| 175 | + | |
| 176 | + BOOL hasPrefix = [[[self class] prefixForName:name] length] > 0; | |
| 177 | + NSString *localName = [[self class] localNameForName:name]; | |
| 178 | + | |
| 179 | + xmlNodePtr child = node->children; | |
| 180 | + while (child != NULL) | |
| 181 | + { | |
| 182 | + if (child->type == XML_ELEMENT_NODE) | |
| 183 | + { | |
| 184 | + BOOL match = NO; | |
| 185 | + if (uri == nil) | |
| 186 | + { | |
| 187 | + match = xmlStrEqual(child->name, [name xmlChar]); | |
| 188 | + } | |
| 189 | + else | |
| 190 | + { | |
| 191 | + BOOL nameMatch = xmlStrEqual(child->name, [name xmlChar]); | |
| 192 | + BOOL localNameMatch = xmlStrEqual(child->name, [localName xmlChar]); | |
| 193 | + | |
| 194 | + BOOL uriMatch = NO; | |
| 195 | + if (child->ns != NULL) | |
| 196 | + { | |
| 197 | + uriMatch = xmlStrEqual(child->ns->href, [uri xmlChar]); | |
| 198 | + } | |
| 199 | + | |
| 200 | + if (hasPrefix) | |
| 201 | + match = nameMatch || (localNameMatch && uriMatch); | |
| 202 | + else | |
| 203 | + match = nameMatch && uriMatch; | |
| 204 | + } | |
| 205 | + | |
| 206 | + if (match) | |
| 207 | + { | |
| 208 | + [result addObject:[DDXMLElement nodeWithElementPrimitive:child freeOnDealloc:NO]]; | |
| 209 | + } | |
| 210 | + } | |
| 211 | + | |
| 212 | + child = child->next; | |
| 213 | + } | |
| 214 | + | |
| 215 | + return result; | |
| 216 | +} | |
| 217 | + | |
| 218 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 219 | +#pragma mark Attributes | |
| 220 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 221 | + | |
| 222 | +- (BOOL)hasAttributeWithName:(NSString *)name | |
| 223 | +{ | |
| 224 | + xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties; | |
| 225 | + if (attr != NULL) | |
| 226 | + { | |
| 227 | + const xmlChar *xmlName = [name xmlChar]; | |
| 228 | + | |
| 229 | + do | |
| 230 | + { | |
| 231 | + if (xmlStrEqual(attr->name, xmlName)) | |
| 232 | + { | |
| 233 | + return YES; | |
| 234 | + } | |
| 235 | + attr = attr->next; | |
| 236 | + | |
| 237 | + } while (attr != NULL); | |
| 238 | + } | |
| 239 | + | |
| 240 | + return NO; | |
| 241 | +} | |
| 242 | + | |
| 243 | +- (void)addAttribute:(DDXMLNode *)attribute | |
| 244 | +{ | |
| 245 | + // NSXML version uses this same assertion | |
| 246 | + DDXMLAssert([attribute hasParent] == NO, @"Cannot add an attribute with a parent; detach or copy first"); | |
| 247 | + DDXMLAssert(IsXmlAttrPtr(attribute->genericPtr), @"Not an attribute"); | |
| 248 | + | |
| 249 | + [self removeAttributeForName:[attribute name]]; | |
| 250 | + | |
| 251 | + // xmlNodePtr xmlAddChild(xmlNodePtr parent, xmlNodePtr cur) | |
| 252 | + // Add a new node to @parent, at the end of the child (or property) list merging | |
| 253 | + // adjacent TEXT nodes (in which case @cur is freed). If the new node is ATTRIBUTE, it is added | |
| 254 | + // into properties instead of children. If there is an attribute with equal name, it is first destroyed. | |
| 255 | + | |
| 256 | + xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)attribute->genericPtr); | |
| 257 | + | |
| 258 | + // The attribute is now part of the xml tree heirarchy | |
| 259 | + attribute->freeOnDealloc = NO; | |
| 260 | +} | |
| 261 | + | |
| 262 | +- (void)removeAttribute:(xmlAttrPtr)attr | |
| 263 | +{ | |
| 264 | + [[self class] removeAttribute:attr fromNode:(xmlNodePtr)genericPtr]; | |
| 265 | +} | |
| 266 | + | |
| 267 | +- (void)removeAllAttributes | |
| 268 | +{ | |
| 269 | + [[self class] removeAllAttributesFromNode:(xmlNodePtr)genericPtr]; | |
| 270 | +} | |
| 271 | + | |
| 272 | +- (void)removeAttributeForName:(NSString *)name | |
| 273 | +{ | |
| 274 | + xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties; | |
| 275 | + if (attr != NULL) | |
| 276 | + { | |
| 277 | + const xmlChar *xmlName = [name xmlChar]; | |
| 278 | + | |
| 279 | + do | |
| 280 | + { | |
| 281 | + if (xmlStrEqual(attr->name, xmlName)) | |
| 282 | + { | |
| 283 | + [self removeAttribute:attr]; | |
| 284 | + return; | |
| 285 | + } | |
| 286 | + attr = attr->next; | |
| 287 | + | |
| 288 | + } while(attr != NULL); | |
| 289 | + } | |
| 290 | +} | |
| 291 | + | |
| 292 | +- (NSArray *)attributes | |
| 293 | +{ | |
| 294 | + NSMutableArray *result = [NSMutableArray array]; | |
| 295 | + | |
| 296 | + xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties; | |
| 297 | + while (attr != NULL) | |
| 298 | + { | |
| 299 | + [result addObject:[DDXMLAttributeNode nodeWithAttrPrimitive:attr freeOnDealloc:NO]]; | |
| 300 | + | |
| 301 | + attr = attr->next; | |
| 302 | + } | |
| 303 | + | |
| 304 | + return result; | |
| 305 | +} | |
| 306 | + | |
| 307 | +- (DDXMLNode *)attributeForName:(NSString *)name | |
| 308 | +{ | |
| 309 | + const xmlChar *attrName = [name xmlChar]; | |
| 310 | + | |
| 311 | + xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties; | |
| 312 | + while (attr != NULL) | |
| 313 | + { | |
| 314 | + if (attr->ns && attr->ns->prefix) | |
| 315 | + { | |
| 316 | + // If the attribute name was originally something like "xml:quack", | |
| 317 | + // then attr->name is "quack" and attr->ns->prefix is "xml". | |
| 318 | + // | |
| 319 | + // So if the user is searching for "xml:quack" we need to take the prefix into account. | |
| 320 | + // Note that "xml:quack" is what would be printed if we output the attribute. | |
| 321 | + | |
| 322 | + if (xmlStrQEqual(attr->ns->prefix, attr->name, attrName)) | |
| 323 | + { | |
| 324 | + return [DDXMLAttributeNode nodeWithAttrPrimitive:attr freeOnDealloc:NO]; | |
| 325 | + } | |
| 326 | + } | |
| 327 | + else | |
| 328 | + { | |
| 329 | + if (xmlStrEqual(attr->name, attrName)) | |
| 330 | + { | |
| 331 | + return [DDXMLAttributeNode nodeWithAttrPrimitive:attr freeOnDealloc:NO]; | |
| 332 | + } | |
| 333 | + } | |
| 334 | + | |
| 335 | + attr = attr->next; | |
| 336 | + } | |
| 337 | + return nil; | |
| 338 | +} | |
| 339 | + | |
| 340 | +/** | |
| 341 | + * Sets the list of attributes for the element. | |
| 342 | + * Any previously set attributes are removed. | |
| 343 | +**/ | |
| 344 | +- (void)setAttributes:(NSArray *)attributes | |
| 345 | +{ | |
| 346 | + [self removeAllAttributes]; | |
| 347 | + | |
| 348 | + NSUInteger i; | |
| 349 | + for (i = 0; i < [attributes count]; i++) | |
| 350 | + { | |
| 351 | + DDXMLNode *attribute = [attributes objectAtIndex:i]; | |
| 352 | + [self addAttribute:attribute]; | |
| 353 | + | |
| 354 | + // Note: The addAttributes method properly sets the freeOnDealloc ivar. | |
| 355 | + } | |
| 356 | +} | |
| 357 | + | |
| 358 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 359 | +#pragma mark Namespaces | |
| 360 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 361 | + | |
| 362 | +- (void)addNamespace:(DDXMLNode *)namespace | |
| 363 | +{ | |
| 364 | + // NSXML version uses this same assertion | |
| 365 | + DDXMLAssert([namespace hasParent] == NO, @"Cannot add a namespace with a parent; detach or copy first"); | |
| 366 | + DDXMLAssert(IsXmlNsPtr(namespace->genericPtr), @"Not a namespace"); | |
| 367 | + | |
| 368 | + xmlNodePtr node = (xmlNodePtr)genericPtr; | |
| 369 | + xmlNsPtr ns = (xmlNsPtr)namespace->genericPtr; | |
| 370 | + | |
| 371 | + // Beware: [namespace prefix] does NOT return what you might expect. Use [namespace name] instead. | |
| 372 | + | |
| 373 | + NSString *namespaceName = [namespace name]; | |
| 374 | + | |
| 375 | + [self removeNamespaceForPrefix:namespaceName]; | |
| 376 | + | |
| 377 | + xmlNsPtr currentNs = node->nsDef; | |
| 378 | + if (currentNs == NULL) | |
| 379 | + { | |
| 380 | + node->nsDef = ns; | |
| 381 | + } | |
| 382 | + else | |
| 383 | + { | |
| 384 | + while (currentNs->next != NULL) | |
| 385 | + { | |
| 386 | + currentNs = currentNs->next; | |
| 387 | + } | |
| 388 | + | |
| 389 | + currentNs->next = ns; | |
| 390 | + } | |
| 391 | + | |
| 392 | + // The namespace is now part of the xml tree heirarchy | |
| 393 | + namespace->freeOnDealloc = NO; | |
| 394 | + | |
| 395 | + if ([namespace isKindOfClass:[DDXMLNamespaceNode class]]) | |
| 396 | + { | |
| 397 | + DDXMLNamespaceNode *ddNamespace = (DDXMLNamespaceNode *)namespace; | |
| 398 | + | |
| 399 | + // The xmlNs structure doesn't contain a reference to the parent, so we manage our own reference | |
| 400 | + [ddNamespace setNsParentPtr:node]; | |
| 401 | + } | |
| 402 | + | |
| 403 | + // Did we just add a default namespace | |
| 404 | + if ([namespaceName isEqualToString:@""]) | |
| 405 | + { | |
| 406 | + node->ns = ns; | |
| 407 | + | |
| 408 | + // Note: The removeNamespaceForPrefix method above properly handled removing any previous default namespace | |
| 409 | + } | |
| 410 | +} | |
| 411 | + | |
| 412 | +- (void)removeNamespace:(xmlNsPtr)ns | |
| 413 | +{ | |
| 414 | + [[self class] removeNamespace:ns fromNode:(xmlNodePtr)genericPtr]; | |
| 415 | +} | |
| 416 | + | |
| 417 | +- (void)removeAllNamespaces | |
| 418 | +{ | |
| 419 | + [[self class] removeAllNamespacesFromNode:(xmlNodePtr)genericPtr]; | |
| 420 | +} | |
| 421 | + | |
| 422 | +- (void)removeNamespaceForPrefix:(NSString *)name | |
| 423 | +{ | |
| 424 | + // If name is nil or the empty string, the user is wishing to remove the default namespace | |
| 425 | + const xmlChar *xmlName = [name length] > 0 ? [name xmlChar] : NULL; | |
| 426 | + | |
| 427 | + xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef; | |
| 428 | + while (ns != NULL) | |
| 429 | + { | |
| 430 | + if (xmlStrEqual(ns->prefix, xmlName)) | |
| 431 | + { | |
| 432 | + [self removeNamespace:ns]; | |
| 433 | + break; | |
| 434 | + } | |
| 435 | + ns = ns->next; | |
| 436 | + } | |
| 437 | + | |
| 438 | + // Note: The removeNamespace method properly handles the situation where the namespace is the default namespace | |
| 439 | +} | |
| 440 | + | |
| 441 | +- (NSArray *)namespaces | |
| 442 | +{ | |
| 443 | + NSMutableArray *result = [NSMutableArray array]; | |
| 444 | + | |
| 445 | + xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef; | |
| 446 | + while (ns != NULL) | |
| 447 | + { | |
| 448 | + [result addObject:[DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr freeOnDealloc:NO]]; | |
| 449 | + | |
| 450 | + ns = ns->next; | |
| 451 | + } | |
| 452 | + | |
| 453 | + return result; | |
| 454 | +} | |
| 455 | + | |
| 456 | +- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix | |
| 457 | +{ | |
| 458 | + // If the prefix is nil or the empty string, the user is requesting the default namespace | |
| 459 | + | |
| 460 | + if ([prefix length] == 0) | |
| 461 | + { | |
| 462 | + // Requesting the default namespace | |
| 463 | + xmlNsPtr ns = ((xmlNodePtr)genericPtr)->ns; | |
| 464 | + if (ns != NULL) | |
| 465 | + { | |
| 466 | + return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr freeOnDealloc:NO]; | |
| 467 | + } | |
| 468 | + } | |
| 469 | + else | |
| 470 | + { | |
| 471 | + xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef; | |
| 472 | + while (ns != NULL) | |
| 473 | + { | |
| 474 | + if (xmlStrEqual(ns->prefix, [prefix xmlChar])) | |
| 475 | + { | |
| 476 | + return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr freeOnDealloc:NO]; | |
| 477 | + } | |
| 478 | + ns = ns->next; | |
| 479 | + } | |
| 480 | + } | |
| 481 | + | |
| 482 | + return nil; | |
| 483 | +} | |
| 484 | + | |
| 485 | +- (void)setNamespaces:(NSArray *)namespaces | |
| 486 | +{ | |
| 487 | + [self removeAllNamespaces]; | |
| 488 | + | |
| 489 | + NSUInteger i; | |
| 490 | + for (i = 0; i < [namespaces count]; i++) | |
| 491 | + { | |
| 492 | + DDXMLNode *namespace = [namespaces objectAtIndex:i]; | |
| 493 | + [self addNamespace:namespace]; | |
| 494 | + | |
| 495 | + // Note: The addNamespace method properly sets the freeOnDealloc ivar. | |
| 496 | + } | |
| 497 | +} | |
| 498 | + | |
| 499 | +/** | |
| 500 | + * Recursively searches the given node for the given namespace | |
| 501 | +**/ | |
| 502 | ++ (DDXMLNode *)resolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr | |
| 503 | +{ | |
| 504 | + if (nodePtr == NULL) return nil; | |
| 505 | + | |
| 506 | + xmlNsPtr ns = nodePtr->nsDef; | |
| 507 | + while (ns != NULL) | |
| 508 | + { | |
| 509 | + if (xmlStrEqual(ns->prefix, [prefix xmlChar])) | |
| 510 | + { | |
| 511 | + return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:nodePtr freeOnDealloc:NO]; | |
| 512 | + } | |
| 513 | + ns = ns->next; | |
| 514 | + } | |
| 515 | + | |
| 516 | + return [self resolveNamespaceForPrefix:prefix atNode:nodePtr->parent]; | |
| 517 | +} | |
| 518 | + | |
| 519 | +/** | |
| 520 | + * Returns the namespace node with the prefix matching the given qualified name. | |
| 521 | + * Eg: You pass it "a:dog", it returns the namespace (defined in this node or parent nodes) that has the "a" prefix. | |
| 522 | +**/ | |
| 523 | +- (DDXMLNode *)resolveNamespaceForName:(NSString *)name | |
| 524 | +{ | |
| 525 | + // If the user passes nil or an empty string for name, they're looking for the default namespace. | |
| 526 | + if ([name length] == 0) | |
| 527 | + { | |
| 528 | + return [[self class] resolveNamespaceForPrefix:nil atNode:(xmlNodePtr)genericPtr]; | |
| 529 | + } | |
| 530 | + | |
| 531 | + NSString *prefix = [[self class] prefixForName:name]; | |
| 532 | + | |
| 533 | + if ([prefix length] > 0) | |
| 534 | + { | |
| 535 | + // Unfortunately we can't use xmlSearchNs because it returns an xmlNsPtr. | |
| 536 | + // This gives us mostly what we want, except we also need to know the nsParent. | |
| 537 | + // So we do the recursive search ourselves. | |
| 538 | + | |
| 539 | + return [[self class] resolveNamespaceForPrefix:prefix atNode:(xmlNodePtr)genericPtr]; | |
| 540 | + } | |
| 541 | + | |
| 542 | + return nil; | |
| 543 | +} | |
| 544 | + | |
| 545 | +/** | |
| 546 | + * Recursively searches the given node for a namespace with the given URI, and a set prefix. | |
| 547 | +**/ | |
| 548 | ++ (NSString *)resolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr | |
| 549 | +{ | |
| 550 | + if (nodePtr == NULL) return nil; | |
| 551 | + | |
| 552 | + xmlNsPtr ns = nodePtr->nsDef; | |
| 553 | + while (ns != NULL) | |
| 554 | + { | |
| 555 | + if (xmlStrEqual(ns->href, [uri xmlChar])) | |
| 556 | + { | |
| 557 | + if (ns->prefix != NULL) | |
| 558 | + { | |
| 559 | + return [NSString stringWithUTF8String:((const char *)ns->prefix)]; | |
| 560 | + } | |
| 561 | + } | |
| 562 | + ns = ns->next; | |
| 563 | + } | |
| 564 | + | |
| 565 | + return [self resolvePrefixForURI:uri atNode:nodePtr->parent]; | |
| 566 | +} | |
| 567 | + | |
| 568 | +/** | |
| 569 | + * Returns the prefix associated with the specified URI. | |
| 570 | + * Returns a string that is the matching prefix or nil if it finds no matching prefix. | |
| 571 | +**/ | |
| 572 | +- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI | |
| 573 | +{ | |
| 574 | + // We can't use xmlSearchNsByHref because it will return xmlNsPtr's with NULL prefixes. | |
| 575 | + // We're looking for a definitive prefix for the given URI. | |
| 576 | + | |
| 577 | + return [[self class] resolvePrefixForURI:namespaceURI atNode:(xmlNodePtr)genericPtr]; | |
| 578 | +} | |
| 579 | + | |
| 580 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 581 | +#pragma mark Children | |
| 582 | +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 583 | + | |
| 584 | +- (void)addChild:(DDXMLNode *)child | |
| 585 | +{ | |
| 586 | + // NSXML version uses these same assertions | |
| 587 | + DDXMLAssert([child hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first"); | |
| 588 | + DDXMLAssert(IsXmlNodePtr(child->genericPtr), | |
| 589 | + @"Elements can only have text, elements, processing instructions, and comments as children"); | |
| 590 | + | |
| 591 | + xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr); | |
| 592 | + | |
| 593 | + // The node is now part of the xml tree heirarchy | |
| 594 | + child->freeOnDealloc = NO; | |
| 595 | +} | |
| 596 | + | |
| 597 | +- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index | |
| 598 | +{ | |
| 599 | + // NSXML version uses these same assertions | |
| 600 | + DDXMLAssert([child hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first"); | |
| 601 | + DDXMLAssert(IsXmlNodePtr(child->genericPtr), | |
| 602 | + @"Elements can only have text, elements, processing instructions, and comments as children"); | |
| 603 | + | |
| 604 | + NSUInteger i = 0; | |
| 605 | + | |
| 606 | + xmlNodePtr childNodePtr = ((xmlNodePtr)genericPtr)->children; | |
| 607 | + while (childNodePtr != NULL) | |
| 608 | + { | |
| 609 | + // Ignore all but element, comment, text, or processing instruction nodes | |
| 610 | + if (IsXmlNodePtr(childNodePtr)) | |
| 611 | + { | |
| 612 | + if (i == index) | |
| 613 | + { | |
| 614 | + xmlAddPrevSibling(childNodePtr, (xmlNodePtr)child->genericPtr); | |
| 615 | + child->freeOnDealloc = NO; | |
| 616 | + return; | |
| 617 | + } | |
| 618 | + | |
| 619 | + i++; | |
| 620 | + } | |
| 621 | + childNodePtr = childNodePtr->next; | |
| 622 | + } | |
| 623 | + | |
| 624 | + if (i == index) | |
| 625 | + { | |
| 626 | + xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr); | |
| 627 | + child->freeOnDealloc = NO; | |
| 628 | + return; | |
| 629 | + } | |
| 630 | + | |
| 631 | + // NSXML version uses this same assertion | |
| 632 | + DDXMLAssert(NO, @"index (%u) beyond bounds (%u)", (unsigned)index, (unsigned)++i); | |
| 633 | +} | |
| 634 | + | |
| 635 | +- (void)removeChildAtIndex:(NSUInteger)index | |
| 636 | +{ | |
| 637 | + NSUInteger i = 0; | |
| 638 | + | |
| 639 | + xmlNodePtr child = ((xmlNodePtr)genericPtr)->children; | |
| 640 | + while (child != NULL) | |
| 641 | + { | |
| 642 | + // Ignore all but element, comment, text, or processing instruction nodes | |
| 643 | + if (IsXmlNodePtr(child)) | |
| 644 | + { | |
| 645 | + if (i == index) | |
| 646 | + { | |
| 647 | + [DDXMLNode removeChild:child fromNode:(xmlNodePtr)genericPtr]; | |
| 648 | + return; | |
| 649 | + } | |
| 650 | + | |
| 651 | + i++; | |
| 652 | + } | |
| 653 | + child = child->next; | |
| 654 | + } | |
| 655 | +} | |
| 656 | + | |
| 657 | +- (void)setChildren:(NSArray *)children | |
| 658 | +{ | |
| 659 | + [DDXMLNode removeAllChildrenFromNode:(xmlNodePtr)genericPtr]; | |
| 660 | + | |
| 661 | + NSUInteger i; | |
| 662 | + for (i = 0; i < [children count]; i++) | |
| 663 | + { | |
| 664 | + DDXMLNode *child = [children objectAtIndex:i]; | |
| 665 | + [self addChild:child]; | |
| 666 | + | |
| 667 | + // Note: The addChild method properly sets the freeOnDealloc ivar. | |
| 668 | + } | |
| 669 | +} | |
| 670 | + | |
| 671 | +@end |
| @@ -0,0 +1,21 @@ | ||
| 1 | +#import <Foundation/Foundation.h> | |
| 2 | +#import "DDXML.h" | |
| 3 | + | |
| 4 | +// These methods are not part of the standard NSXML API. | |
| 5 | +// But any developer working extensively with XML will likely appreciate them. | |
| 6 | + | |
| 7 | +@interface DDXMLElement (DDAdditions) | |
| 8 | + | |
| 9 | ++ (DDXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns; | |
| 10 | + | |
| 11 | +- (DDXMLElement *)elementForName:(NSString *)name; | |
| 12 | +- (DDXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns; | |
| 13 | + | |
| 14 | +- (NSString *)xmlns; | |
| 15 | +- (void)setXmlns:(NSString *)ns; | |
| 16 | + | |
| 17 | +- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string; | |
| 18 | + | |
| 19 | +- (NSDictionary *)attributesAsDictionary; | |
| 20 | + | |
| 21 | +@end |
| @@ -0,0 +1,115 @@ | ||
| 1 | +#import "DDXMLElementAdditions.h" | |
| 2 | + | |
| 3 | +@implementation DDXMLElement (DDAdditions) | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * Quick method to create an element | |
| 7 | +**/ | |
| 8 | ++ (DDXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns | |
| 9 | +{ | |
| 10 | + DDXMLElement *element = [DDXMLElement elementWithName:name]; | |
| 11 | + [element setXmlns:ns]; | |
| 12 | + return element; | |
| 13 | +} | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * This method returns the first child element for the given name. | |
| 17 | + * If no child element exists for the given name, returns nil. | |
| 18 | +**/ | |
| 19 | +- (DDXMLElement *)elementForName:(NSString *)name | |
| 20 | +{ | |
| 21 | + NSArray *elements = [self elementsForName:name]; | |
| 22 | + if([elements count] > 0) | |
| 23 | + { | |
| 24 | + return [elements objectAtIndex:0]; | |
| 25 | + } | |
| 26 | + else | |
| 27 | + { | |
| 28 | + // Note: If you port this code to work with Apple's NSXML, beware of the following: | |
| 29 | + // | |
| 30 | + // There is a bug in the NSXMLElement elementsForName: method. | |
| 31 | + // Consider the following XML fragment: | |
| 32 | + // | |
| 33 | + // <query xmlns="jabber:iq:private"> | |
| 34 | + // <x xmlns="some:other:namespace"></x> | |
| 35 | + // </query> | |
| 36 | + // | |
| 37 | + // Calling [query elementsForName:@"x"] results in an empty array! | |
| 38 | + // | |
| 39 | + // However, it will work properly if you use the following: | |
| 40 | + // [query elementsForLocalName:@"x" URI:@"some:other:namespace"] | |
| 41 | + // | |
| 42 | + // The trouble with this is that we may not always know the xmlns in advance, | |
| 43 | + // so in this particular case there is no way to access the element without looping through the children. | |
| 44 | + // | |
| 45 | + // This bug was submitted to apple on June 1st, 2007 and was classified as "serious". | |
| 46 | + // | |
| 47 | + // --!!-- This bug does NOT exist in DDXML --!!-- | |
| 48 | + | |
| 49 | + return nil; | |
| 50 | + } | |
| 51 | +} | |
| 52 | + | |
| 53 | +/** | |
| 54 | + * This method returns the first child element for the given name and given xmlns. | |
| 55 | + * If no child elements exist for the given name and given xmlns, returns nil. | |
| 56 | +**/ | |
| 57 | +- (DDXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns | |
| 58 | +{ | |
| 59 | + NSArray *elements = [self elementsForLocalName:name URI:xmlns]; | |
| 60 | + if([elements count] > 0) | |
| 61 | + { | |
| 62 | + return [elements objectAtIndex:0]; | |
| 63 | + } | |
| 64 | + else | |
| 65 | + { | |
| 66 | + return nil; | |
| 67 | + } | |
| 68 | +} | |
| 69 | + | |
| 70 | +/** | |
| 71 | + * Returns the common xmlns "attribute", which is only accessible via the namespace methods. | |
| 72 | + * The xmlns value is often used in jabber elements. | |
| 73 | +**/ | |
| 74 | +- (NSString *)xmlns | |
| 75 | +{ | |
| 76 | + return [[self namespaceForPrefix:@""] stringValue]; | |
| 77 | +} | |
| 78 | + | |
| 79 | +- (void)setXmlns:(NSString *)ns | |
| 80 | +{ | |
| 81 | + // If you use setURI: then the xmlns won't be displayed in the XMLString. | |
| 82 | + // Adding the namespace this way works properly. | |
| 83 | + // | |
| 84 | + // This applies to both Apple's NSXML and DDXML. | |
| 85 | + | |
| 86 | + [self addNamespace:[DDXMLNode namespaceWithName:@"" stringValue:ns]]; | |
| 87 | +} | |
| 88 | + | |
| 89 | +/** | |
| 90 | + * Shortcut to avoid having to manually create a DDXMLNode everytime. | |
| 91 | +**/ | |
| 92 | +- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string | |
| 93 | +{ | |
| 94 | + [self addAttribute:[DDXMLNode attributeWithName:name stringValue:string]]; | |
| 95 | +} | |
| 96 | + | |
| 97 | +/** | |
| 98 | + * Returns all the attributes as a dictionary. | |
| 99 | +**/ | |
| 100 | +- (NSDictionary *)attributesAsDictionary | |
| 101 | +{ | |
| 102 | + NSArray *attributes = [self attributes]; | |
| 103 | + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[attributes count]]; | |
| 104 | + | |
| 105 | + uint i; | |
| 106 | + for(i = 0; i < [attributes count]; i++) | |
| 107 | + { | |
| 108 | + DDXMLNode *node = [attributes objectAtIndex:i]; | |
| 109 | + | |
| 110 | + [result setObject:[node stringValue] forKey:[node name]]; | |
| 111 | + } | |
| 112 | + return result; | |
| 113 | +} | |
| 114 | + | |
| 115 | +@end |
| @@ -0,0 +1,3 @@ | ||
| 1 | +#import "DDXMLNode.h" | |
| 2 | +#import "DDXMLElement.h" | |
| 3 | +#import "DDXMLDocument.h" |