// // MGMXMLNode.m // MGMXML // // Created by Mr. Gecko on 9/22/10. // Copyright (c) 2010 Mr. Gecko's Media (James Coleman). All rights reserved. http://mrgeckosmedia.com/ // #import "MGMXMLNode.h" #import "MGMXMLAddons.h" #import "MGMXMLDocument.h" #import "MGMXMLElement.h" #import "MGMXMLNodeOptions.h" NSString * const MGMXMLErrorDomain = @"MGMXMLErrorDomain"; NSString * const MGMXMLLastError = @"MGMXMLLastError"; static NSMutableDictionary *MGMXMLInfo = nil; static void MGMXMLErrorHandler(void *userData, xmlErrorPtr error) { NSAutoreleasePool *pool = [NSAutoreleasePool new];; if (error==NULL) { [MGMXMLInfo removeObjectForKey:MGMXMLLastError]; } else { [MGMXMLInfo setObject:[NSValue valueWithXMLError:error] forKey:MGMXMLLastError]; } [pool drain]; } @implementation MGMXMLNode + (void)initialize { if (MGMXMLInfo==nil) { MGMXMLInfo = [NSMutableDictionary new]; initGenericErrorDefaultFunc(NULL); xmlSetStructuredErrorFunc(NULL, MGMXMLErrorHandler); xmlKeepBlanksDefault(0); } } - (id)init { if (self = [super init]) { commonXML = NULL; namespaceXML = NULL; parentNode = NULL; type = MGMXMLInvalidKind; } return self; } + (id)nodeWithTypeXMLPtr:(xmlTypPtr)theXMLPtr { return [[[self alloc] initWithTypeXMLPtr:theXMLPtr] autorelease]; } - (id)initWithTypeXMLPtr:(xmlTypPtr)theXMLPtr { if (self = [self init]) { if (theXMLPtr->type==MGMXMLNamespaceKind) { xmlNsPtr xmlPtr = (xmlNsPtr)theXMLPtr; if (xmlPtr->_private!=NULL) { [self release]; self = nil; return [(MGMXMLNode *)xmlPtr->_private retain]; } } else { xmlComPtr comXML = (xmlComPtr)theXMLPtr; if (comXML->_private!=NULL) { [self release]; self = nil; return [(MGMXMLNode *)comXML->_private retain]; } } [self setTypeXMLPtr:theXMLPtr]; } return self; } - (void)clearParent { parentNode = NULL; } - (void)freeXML { if (namespaceXML!=NULL) { namespaceXML->_private = NULL; if (parentNode==NULL) xmlFreeNs(namespaceXML); namespaceXML = NULL; parentNode = NULL; } if (commonXML!=NULL) { commonXML->_private = NULL; if (commonXML->parent==NULL) { if (type==MGMXMLDocumentKind) { xmlNodePtr child = commonXML->children; while (child!=NULL) { xmlNodePtr nextChild = child->next; if (child->type == XML_ELEMENT_NODE) { if (child->prev!=NULL) child->prev->next = child->next; if (child->next!=NULL) child->next->prev = child->prev; if (commonXML->children==child) commonXML->children = child->next; if (commonXML->last==child) commonXML->last = child->prev; [[self class] freeNode:child]; } child = nextChild; } xmlFreeDoc(MGMXMLDocPtr); } else if (type==MGMXMLAttributeKind) { xmlFreeProp(MGMXMLAttrPtr); } else if (type==MGMXMLDTDKind) { xmlFreeDtd(MGMXMLDTDPtr); } else { [[self class] freeNode:MGMXMLNodePtr]; } } commonXML = NULL; } type = MGMXMLInvalidKind; } - (void)dealloc { [self freeXML]; [super dealloc]; } + (void)stripDocumentFromAttribute:(xmlAttrPtr)theAttribute { xmlNodePtr child = theAttribute->children; while (child!=NULL) { child->doc = NULL; child = child->next; } theAttribute->doc = NULL; } + (void)stripDocumentFromNode:(xmlNodePtr)theNode { xmlAttrPtr attribute = theNode->properties; while (attribute!=NULL) { [self stripDocumentFromAttribute:attribute]; attribute = attribute->next; } xmlNodePtr child = theNode->children; while (child!=NULL) { [self stripDocumentFromNode:child]; child = child->next; } theNode->doc = NULL; } + (void)removeAttributesFromNode:(xmlNodePtr)theNode { xmlAttrPtr attribute = theNode->properties; while (attribute!=NULL) { xmlAttrPtr nextAttribute = attribute->next; if (attribute->_private==NULL) { xmlFreeProp(attribute); } else { attribute->parent = NULL; attribute->prev = NULL; attribute->next = NULL; if (attribute->doc!=NULL) [self stripDocumentFromAttribute:attribute]; } attribute = nextAttribute; } theNode->properties = NULL; } + (void)removeNamespacesFromNode:(xmlNodePtr)theNode { xmlNsPtr namespace = theNode->nsDef; while (namespace!=NULL){ xmlNsPtr nextNamespace = namespace->next; if (namespace->_private!=NULL) { [(MGMXMLNode *)namespace->_private clearParent]; namespace->next = NULL; } else { xmlFreeNs(namespace); } namespace = nextNamespace; } theNode->nsDef = NULL; theNode->ns = NULL; } + (void)removeChildrenFromNode:(xmlNodePtr)theNode { xmlNodePtr child = theNode->children; while (child!=NULL) { xmlNodePtr nextChild = child->next; [self freeNode:child]; child = nextChild; } theNode->children = NULL; theNode->last = NULL; } + (void)freeNode:(xmlNodePtr)theNode { if (![[self class] isNode:theNode->type]) { NSLog(@"Cannot free node as it is the wrong type %d.", theNode->type); } else { if (theNode->_private==NULL) { [self removeAttributesFromNode:theNode]; [self removeNamespacesFromNode:theNode]; [self removeChildrenFromNode:theNode]; xmlFreeNode(theNode); } else { theNode->parent = NULL; theNode->prev = NULL; theNode->next = NULL; if (theNode->doc!=NULL) [self stripDocumentFromNode:theNode]; } } } - (void)setTypeXMLPtr:(xmlTypPtr)theXMLPtr { [self freeXML]; type = theXMLPtr->type; if (type==XML_HTML_DOCUMENT_NODE) type = MGMXMLDocumentKind; if (type==MGMXMLNamespaceKind) { namespaceXML = (xmlNsPtr)theXMLPtr; namespaceXML->_private = self; } else { commonXML = (xmlComPtr)theXMLPtr; commonXML->_private = self; if (type==MGMXMLDocumentKind && [self isMemberOfClass:[MGMXMLNode class]]) self->isa = [MGMXMLDocument class]; else if (type==MGMXMLElementKind && [self isMemberOfClass:[MGMXMLNode class]]) self->isa = [MGMXMLElement class]; } } + (BOOL)isNode:(MGMXMLNodeKind)theType { switch (theType) { case MGMXMLElementKind: case MGMXMLProcessingInstructionKind: case MGMXMLCommentKind: case MGMXMLTextKind: case XML_CDATA_SECTION_NODE: return YES; break; default: break; } return NO; } - (BOOL)isNode { return [[self class] isNode:type]; } + (NSError *)lastError { if ([MGMXMLInfo objectForKey:MGMXMLLastError]!=nil) { xmlErrorPtr lastError = [[MGMXMLInfo objectForKey:MGMXMLLastError] xmlErrorValue]; NSString *description = [[NSString stringWithUTF8String:lastError->message] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; return [NSError errorWithDomain:MGMXMLErrorDomain code:lastError->code userInfo:[NSDictionary dictionaryWithObject:description forKey:NSLocalizedDescriptionKey]]; } return nil; } - (NSError *)lastError { return [[self class] lastError]; } - (MGMXMLNodeKind)kind { return type; } - (xmlComPtr)commonXML { return commonXML; } - (xmlNsPtr)nameSpaceXML { return namespaceXML; } - (void)setName:(NSString *)name { if (type==MGMXMLNamespaceKind) { if (namespaceXML->prefix!=NULL) xmlFree((xmlChar *)namespaceXML->prefix); namespaceXML->prefix = xmlStrdup([name xmlString]); } else { xmlNodeSetName(MGMXMLNodePtr, [name xmlString]); } } - (NSString *)name { if (type==MGMXMLNamespaceKind) { if (namespaceXML->prefix!=NULL) return [NSString stringWithXMLString:namespaceXML->prefix]; } else { if (MGMXMLNodePtr->name!=NULL) return [NSString stringWithXMLString:(xmlChar *)MGMXMLNodePtr->name]; } return nil; } - (NSString *)stringValue { if (type==MGMXMLNamespaceKind) { return [NSString stringWithXMLString:namespaceXML->href]; } else if (type==MGMXMLAttributeKind) { if (MGMXMLAttrPtr->children!=NULL) return [NSString stringWithXMLString:MGMXMLAttrPtr->children->content]; } else if ([self isNode]) { xmlChar *contentString = xmlNodeGetContent(MGMXMLNodePtr); NSString *stringValue = [NSString stringWithXMLString:contentString]; xmlFree(contentString); return stringValue; } return nil; } - (MGMXMLDocument *)rootDocument { if (MGMXMLNodePtr->doc!=NULL) return [MGMXMLDocument nodeWithTypeXMLPtr:(xmlTypPtr)MGMXMLNodePtr->doc]; return nil; } - (MGMXMLNode *)parent { return [MGMXMLNode nodeWithTypeXMLPtr:(xmlTypPtr)MGMXMLNodePtr->parent]; } - (MGMXMLNode *)childAtIndex:(NSUInteger)index { if (type==MGMXMLNamespaceKind) return nil; NSUInteger i = 0; xmlNodePtr child = commonXML->children; while (child!=NULL) { if (i==index) return [MGMXMLNode nodeWithTypeXMLPtr:(xmlTypPtr)child]; i++; child = child->next; } return nil; } + (void)detatchAttribute:(xmlAttrPtr)theAttribute fromNode:(xmlNodePtr)theNode { if (theAttribute->prev==NULL && theAttribute->next==NULL) { theNode->properties = NULL; } else if (theAttribute->prev==NULL) { theNode->properties = theAttribute->next; theAttribute->next->prev = NULL; } else if (theAttribute->next==NULL) { theAttribute->prev->next = NULL; } else { theAttribute->prev->next = theAttribute->next; theAttribute->next->prev = theAttribute->prev; } theAttribute->parent = NULL; theAttribute->prev = NULL; theAttribute->next = NULL; if (theAttribute->doc!=NULL) [[self class] stripDocumentFromAttribute:theAttribute]; } - (void)detach { if (type==MGMXMLNamespaceKind) { if (parentNode!=NULL) { xmlNsPtr previousNamespace = NULL; xmlNsPtr currentNamespace = parentNode->nsDef; while (currentNamespace!=NULL) { if (currentNamespace==namespaceXML) { if (previousNamespace!=NULL) previousNamespace->next = currentNamespace->next; else parentNode->nsDef = currentNamespace->next; break; } previousNamespace = currentNamespace; currentNamespace = currentNamespace->next; } namespaceXML->next = NULL; if (parentNode->ns==namespaceXML) parentNode->ns = NULL; parentNode = NULL; } return; } if (commonXML->parent==NULL) return; if (type==MGMXMLAttributeKind) { [[self class] detatchAttribute:MGMXMLAttrPtr fromNode:MGMXMLAttrPtr->parent]; } else if ([self isNode]) { if (commonXML->prev==NULL && commonXML->next==NULL) { commonXML->parent->children = NULL; commonXML->parent->last = NULL; } else if (commonXML->prev==NULL) { commonXML->parent->children = commonXML->next; commonXML->next->prev = NULL; } else if (commonXML->next==NULL) { commonXML->parent->last = commonXML->prev; commonXML->prev->next = NULL; } else { commonXML->prev->next = commonXML->next; commonXML->next->prev = commonXML->prev; } commonXML->parent = NULL; commonXML->prev = NULL; commonXML->next = NULL; if (commonXML->doc!=NULL) [[self class] stripDocumentFromNode:MGMXMLNodePtr]; } } + (NSString *)localNameForName:(NSString *)name { if (name!=nil && [name length]>0) { NSRange range = [name rangeOfString:@":"]; if (range.location!=NSNotFound) return [name substringFromIndex:range.location+range.length]; else return name; } return nil; } + (NSString *)prefixForName:(NSString *)name { if (name!=nil && [name length]>0) { NSRange range = [name rangeOfString:@":"]; if (range.location!=NSNotFound) return [name substringToIndex:range.location]; } return nil; } - (NSString *)description { return [self XMLString]; } - (NSString *)XMLString { return [self XMLStringWithOptions:0]; } - (NSString *)XMLStringWithOptions:(NSUInteger)options { if(options & MGMXMLNodeCompactEmptyElement) xmlSaveNoEmptyTags = 0; else xmlSaveNoEmptyTags = 1; int format = 0; if (options & MGMXMLNodePrettyPrint) { format = 1; xmlIndentTreeOutput = 1; } xmlBufferPtr bufferPtr = xmlBufferCreate(); if (type==MGMXMLNamespaceKind) xmlNodeDump(bufferPtr, NULL, MGMXMLNodePtr, 0, format); else xmlNodeDump(bufferPtr, commonXML->doc, MGMXMLNodePtr, 0, format); NSString *result = [NSString stringWithXMLString:bufferPtr->content]; if (type!=MGMXMLTextKind) result = [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; xmlBufferFree(bufferPtr); return result; } - (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error { BOOL shouldRemoveDocument = NO; xmlDocPtr document; if ([self isNode]) { document = MGMXMLNodePtr->doc; if (document==NULL) { shouldRemoveDocument = YES; document = xmlNewDoc(NULL); xmlDocSetRootElement(document, MGMXMLNodePtr); } } else if (type==MGMXMLDocumentKind) { document = MGMXMLDocPtr; } else { return nil; } xmlXPathContextPtr xPathContext = xmlXPathNewContext(document); xPathContext->node = MGMXMLNodePtr; xmlNodePtr rootNode = document->children; if (rootNode!=NULL) { xmlNsPtr namespace = rootNode->nsDef; while (namespace!=NULL) { xmlXPathRegisterNs(xPathContext, namespace->prefix, namespace->href); namespace = namespace->next; } } xmlXPathObjectPtr xPathObject = xmlXPathEvalExpression([xpath xmlString], xPathContext); NSMutableArray *nodes = [NSMutableArray array]; if (xPathObject==NULL) { if (error!=nil) *error = [self lastError]; nodes = nil; } else { int count = xmlXPathNodeSetGetLength(xPathObject->nodesetval); if (count!=0) { for (int i=0; inodesetval->nodeTab[i]]]; } } if (xPathObject) xmlXPathFreeObject(xPathObject); if (xPathContext) xmlXPathFreeContext(xPathContext); if (shouldRemoveDocument) { xmlUnlinkNode(MGMXMLNodePtr); xmlFreeDoc(document); [[self class] stripDocumentFromNode:MGMXMLNodePtr]; } return nodes; } @end