/* Implementation for NSXMLNode for GNUStep Copyright (C) 2008 Free Software Foundation, Inc. Written by: Richard Frith-Macdonald Created: September 2008 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. */ #import "common.h" #define GSInternal NSXMLNodeInternal #import "NSXMLPrivate.h" #import "GSInternal.h" GS_PRIVATE_INTERNAL(NSXMLNode) #if defined(HAVE_LIBXML) static int countAttributes(xmlNodePtr node) { int count = 0; xmlAttrPtr attr = node->properties; while (attr) { count++; attr = attr->next; } return count; } static BOOL isEqualAttr(const xmlAttrPtr attrA, const xmlAttrPtr attrB) { xmlChar *contentA; xmlChar *contentB; const xmlChar *nameA; const xmlChar *nameB; /* what has to be the same for two attributes to be equal -- * just their values?? */ if (attrB == attrA) { return YES; } if (attrA == NULL || attrB == NULL) { return NO; } nameA = attrA->name; nameB = attrB->name; if (xmlStrcmp(nameA, nameB) == 0) { // get the content... contentA = xmlNodeGetContent((const xmlNodePtr)attrA); contentB = xmlNodeGetContent((const xmlNodePtr)attrB); if (xmlStrcmp(contentA, contentB) == 0) { xmlFree(contentA); xmlFree(contentB); return YES; } xmlFree(contentA); xmlFree(contentB); return NO; } return NO; } static xmlAttrPtr findAttrWithName(xmlNodePtr node, const xmlChar* targetName) { xmlAttrPtr attr = node->properties; // find an attr in node with the given name, and return it, else NULL while ((attr != NULL) && xmlStrcmp(attr->name, targetName) != 0) { attr = attr->next; } return attr; } static BOOL isEqualAttributes(xmlNodePtr nodeA, xmlNodePtr nodeB) { xmlAttrPtr attrA = NULL; if (countAttributes(nodeA) != countAttributes(nodeB)) return NO; attrA = nodeA->properties; while (attrA) { xmlAttrPtr attrB = findAttrWithName(nodeB, attrA->name); if (!isEqualAttr(attrA, attrB)) { return NO; } attrA = attrA->next; } return YES; } static BOOL isEqualNode(xmlNodePtr nodeA, xmlNodePtr nodeB) { if (nodeA == nodeB) return YES; if (nodeA->type != nodeB->type) return NO; if (xmlStrcmp(nodeA->name, nodeB->name) != 0) return NO; if (nodeA->type == XML_ELEMENT_NODE) { xmlChar *contentA = NULL; xmlChar *contentB = NULL; if (!isEqualAttributes(nodeA, nodeB)) { return NO; } // Get the value of any text node underneath the current element. contentA = xmlNodeGetContent((const xmlNodePtr)nodeA); contentB = xmlNodeGetContent((const xmlNodePtr)nodeB); if (xmlStrcmp(contentA, contentB) != 0) { xmlFree(contentA); xmlFree(contentB); return NO; } xmlFree(contentA); xmlFree(contentB); } // FIXME: Handle more node types return YES; } static BOOL isEqualTree(xmlNodePtr nodeA, xmlNodePtr nodeB) { xmlNodePtr childA; xmlNodePtr childB; if (nodeA == nodeB) { return YES; } if (nodeA == NULL || nodeB == NULL) { return NO; } if (!isEqualNode(nodeA, nodeB)) { return NO; } // Check children childA = nodeA->children; childB = nodeB->children; while (isEqualTree(childA, childB)) { if (childA == NULL) { return YES; } else { childA = childA->next; childB = childB->next; } } return NO; } @implementation NSXMLNode (Private) - (void *) _node { return MY_NODE; } - (void) _setNode: (void *)_anode { DESTROY(internal->subNodes); if (_anode) { ((xmlNodePtr)_anode)->_private = self; } internal->node = _anode; } + (NSXMLNode *) _objectForNode: (xmlNodePtr)node { NSXMLNode *result = nil; if (node) { result = node->_private; if (result == nil) { Class cls; NSXMLNodeKind kind; xmlElementType type = node->type; switch (type) { case XML_DOCUMENT_NODE: cls = [NSXMLDocument class]; kind = NSXMLDocumentKind; break; case XML_ELEMENT_NODE: cls = [NSXMLElement class]; kind = NSXMLElementKind; break; case XML_TEXT_NODE: cls = [NSXMLNode class]; kind = NSXMLTextKind; break; case XML_PI_NODE: cls = [NSXMLNode class]; kind = NSXMLProcessingInstructionKind; break; case XML_COMMENT_NODE: cls = [NSXMLNode class]; kind = NSXMLCommentKind; break; case XML_ATTRIBUTE_NODE: cls = [NSXMLNode class]; kind = NSXMLAttributeKind; break; default: NSLog(@"ERROR: _objectForNode: called with a node of type %d", type); return nil; break; } if ((node->doc != NULL) && ((xmlNodePtr)(node->doc) != node)) { NSXMLDocument *doc; doc = (NSXMLDocument*)[self _objectForNode: (xmlNodePtr)node->doc]; if (doc != nil) { cls = [[doc class] replacementClassForClass: cls]; } } result = [[cls alloc] _initWithNode: node kind: kind]; AUTORELEASE(result); if (node->parent) { NSXMLNode *parent = [self _objectForNode: node->parent]; [parent _addSubNode: result]; } } } return result; } - (void) _addSubNode: (NSXMLNode *)subNode { if (!internal->subNodes) internal->subNodes = [[NSMutableArray alloc] init]; if ([internal->subNodes indexOfObjectIdenticalTo: subNode] == NSNotFound) { [internal->subNodes addObject: subNode]; } } - (void) _removeSubNode: (NSXMLNode *)subNode { // retain temporarily so we can safely remove from our subNodes list first [subNode retain]; [internal->subNodes removeObjectIdenticalTo: subNode]; // release temporary hold. Apple seems to do an autorelease here. [subNode autorelease]; } - (void) _createInternal { GS_CREATE_INTERNAL(NSXMLNode); } - (id) _initWithNode: (xmlNodePtr)node kind: (NSXMLNodeKind)kind { if ((self = [super init])) { [self _createInternal]; [self _setNode: node]; internal->kind = kind; } return self; } - (xmlNodePtr) _childNodeAtIndex: (NSUInteger)index { NSUInteger count = 0; xmlNodePtr node = MY_NODE; xmlNodePtr children = node->children; if (!children) return NULL; // the Cocoa docs say it returns nil if there are no children while (children != NULL && count++ < index) { children = children->next; } if (count < index) [NSException raise: NSRangeException format: @"child index too large"]; return children; } - (void) _insertChild: (NSXMLNode*)child atIndex: (NSUInteger)index { /* this private method provides the common insertion * implementation used by NSXMLElement and NSXMLDocument */ // Get all of the nodes... xmlNodePtr parentNode = MY_NODE; // we are the parent xmlNodePtr childNode = (xmlNodePtr)[child _node]; xmlNodePtr curNode = [self _childNodeAtIndex: index]; BOOL mergeTextNodes = NO; // is there a defined option for this? if (mergeTextNodes || childNode->type == XML_ATTRIBUTE_NODE) { // this uses the built-in libxml functions which merge adjacent text nodes xmlNodePtr addedNode = NULL; if (curNode == NULL) { addedNode = xmlAddChild(parentNode, childNode); } else { addedNode = xmlAddPrevSibling(curNode, childNode); } if (addedNode != childNode) { [child _setNode: NULL]; child = [NSXMLNode _objectForNode: addedNode]; } } else { /* here we avoid merging adjacent text nodes by linking * the new node in "by hand" */ childNode->parent = parentNode; if (curNode) { // insert childNode before an existing node curNode xmlNodePtr prevNode = curNode->prev; curNode->prev = childNode; childNode->next = curNode; if (prevNode) { childNode->prev = prevNode; prevNode->next = childNode; } else { /* in this case, this is the new "first child", * so update our parent to point to it */ parentNode->children = childNode; } } else { // not inserting before an existing node... add as new "last child" xmlNodePtr formerLastChild = parentNode->last; if (formerLastChild) { formerLastChild->next = childNode; childNode->prev = formerLastChild; parentNode->last = childNode; } else { // no former children -- this is the first parentNode->children = childNode; parentNode->last = childNode; } } } [self _addSubNode: child]; } - (void) _invalidate { internal->kind = NSXMLInvalidKind; [self _setNode: NULL]; } @end static void clearPrivatePointers(xmlNodePtr aNode) { if (!aNode) return; aNode->_private = NULL; clearPrivatePointers(aNode->children); clearPrivatePointers(aNode->next); if (aNode->type == XML_ELEMENT_NODE) clearPrivatePointers((xmlNodePtr)(aNode->properties)); // FIXME: Handle more node types } static int register_namespaces(xmlXPathContextPtr xpathCtx, const xmlChar* nsList) { xmlChar* nsListDup; xmlChar* prefix; xmlChar* href; xmlChar* next; assert(xpathCtx); assert(nsList); nsListDup = xmlStrdup(nsList); if (nsListDup == NULL) { NSLog(@"Error: unable to strdup namespaces list"); return -1; } next = nsListDup; while (next != NULL) { /* skip spaces */ while ((*next) == ' ') next++; if ((*next) == '\0') break; /* find prefix */ prefix = next; next = (xmlChar*)xmlStrchr(next, '='); if (next == NULL) { NSLog(@"Error: invalid namespaces list format"); xmlFree(nsListDup); return -1; } *(next++) = '\0'; /* find href */ href = next; next = (xmlChar*)xmlStrchr(next, ' '); if (next != NULL) { *(next++) = '\0'; } /* do register namespace */ if (xmlXPathRegisterNs(xpathCtx, prefix, href) != 0) { NSLog(@"Error: unable to register NS with prefix=\"%s\"" @" and href=\"%s\"", prefix, href); xmlFree(nsListDup); return -1; } } xmlFree(nsListDup); return 0; } static NSArray * execute_xpath(NSXMLNode *xmlNode, NSString *xpath_exp, NSString *nmspaces) { xmlNodePtr node = [xmlNode _node]; xmlDocPtr doc = node->doc; NSMutableArray *result = [NSMutableArray arrayWithCapacity: 10]; xmlChar* xpathExpr = (xmlChar *)XMLSTRING(xpath_exp); xmlChar* nsList = (xmlChar *)XMLSTRING(nmspaces); xmlXPathContextPtr xpathCtx = NULL; xmlXPathObjectPtr xpathObj = NULL; xmlNodeSetPtr nodeset = NULL; xmlNodePtr cur = NULL; int i = 0; assert(xpathExpr); /* Create xpath evaluation context */ xpathCtx = xmlXPathNewContext(doc); if (!xpathCtx) { NSLog(@"Error: unable to create new XPath context."); return nil; } /* Register namespaces from list (if any) */ if ((nsList != NULL) && (register_namespaces(xpathCtx, nsList) < 0)) { NSLog(@"Error: failed to register namespaces list \"%s\"", nsList); xmlXPathFreeContext(xpathCtx); return nil; } if (![xpath_exp hasPrefix: @"/"]) { // provide a context for relative paths xpathCtx->node = node; } /* Evaluate xpath expression */ xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); if (xpathObj == NULL) { NSLog(@"Error: unable to evaluate xpath expression \"%s\"", xpathExpr); xmlXPathFreeContext(xpathCtx); return nil; } /* results */ nodeset = xpathObj->nodesetval; /* if (nodeset == NULL || nodeset->nodeNr == 0) { xpathObj = xmlXPathEval(xpathExpr, xpathCtx); if (xpathObj != NULL) nodeset = xpathObj->nodesetval; if (nodeset) NSLog(@"Succeeded in evaluating as a path, using xmlXPathEval"); } */ if (nodeset) { /* Collect results */ for (i = 0; i < nodeset->nodeNr; i++) { id obj = nil; cur = nodeset->nodeTab[i]; obj = [NSXMLNode _objectForNode: cur]; if (obj) { [result addObject: obj]; } } } /* Cleanup */ xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); return result; } @implementation NSXMLNode + (id) attributeWithName: (NSString*)name stringValue: (NSString*)stringValue { NSXMLNode *n; n = [[[self alloc] initWithKind: NSXMLAttributeKind] autorelease]; [n setStringValue: stringValue]; [n setName: name]; return n; } + (id) attributeWithName: (NSString*)name URI: (NSString*)URI stringValue: (NSString*)stringValue { NSXMLNode *n; n = [[[self alloc] initWithKind: NSXMLAttributeKind] autorelease]; [n setURI: URI]; [n setStringValue: stringValue]; [n setName: name]; return n; } + (id) commentWithStringValue: (NSString*)stringValue { NSXMLNode *n; n = [[[self alloc] initWithKind: NSXMLCommentKind] autorelease]; [n setStringValue: stringValue]; return n; } + (id) DTDNodeWithXMLString: (NSString*)string { NSXMLNode *n; n = [[[NSXMLDTDNode alloc] initWithXMLString: string] autorelease]; return n; } + (id) document { NSXMLNode *n; n = [[[NSXMLDocument alloc] initWithKind: NSXMLDocumentKind] autorelease]; return n; } + (id) documentWithRootElement: (NSXMLElement*)element { NSXMLDocument *d; d = [[[NSXMLDocument alloc] initWithRootElement: element] autorelease]; return d; } + (id) elementWithName: (NSString*)name { NSXMLNode *n; n = [[[NSXMLElement alloc] initWithName: name] autorelease]; return n; } + (id) elementWithName: (NSString*)name children: (NSArray*)children attributes: (NSArray*)attributes { NSXMLElement *e = [self elementWithName: name]; [e insertChildren: children atIndex: 0]; [e setAttributes: attributes]; return e; } + (id) elementWithName: (NSString*)name URI: (NSString*)URI { NSXMLNode *n; n = [[[NSXMLElement alloc] initWithName: name URI: URI] autorelease]; return n; } + (id) elementWithName: (NSString*)name stringValue: (NSString*)string { NSXMLElement *e; e = [[NSXMLElement alloc] initWithName: name stringValue: string]; return e; } + (NSString*) localNameForName: (NSString*)name { const xmlChar *xmlName = XMLSTRING(name); xmlChar *prefix = NULL; xmlChar *localName; if (NULL == xmlName) return nil; localName = xmlSplitQName2(xmlName, &prefix); return StringFromXMLStringPtr(localName); } + (id) namespaceWithName: (NSString*)name stringValue: (NSString*)stringValue { NSXMLNode *n; n = [[[self alloc] initWithKind: NSXMLNamespaceKind] autorelease]; [n setName: name]; [n setStringValue: stringValue]; return n; } + (NSXMLNode*) predefinedNamespaceForPrefix: (NSString*)name { // FIXME: We should cache these instances if ([name isEqualToString: @"xml"]) { return [self namespaceWithName: @"xml" stringValue: @"http: //www.w3.org/XML/1998/namespace"]; } if ([name isEqualToString: @"xs"]) { return [self namespaceWithName: @"xs" stringValue: @"http: //www.w3.org/2001/XMLSchema"]; } if ([name isEqualToString: @"xsi"]) { return [self namespaceWithName: @"xsi" stringValue: @"http: //www.w3.org/2001/XMLSchema-instance"]; } if ([name isEqualToString: @"fn"]) { return [self namespaceWithName: @"fn" stringValue: @"http: //www.w3.org/2003/11/xpath-functions"]; } if ([name isEqualToString: @"local"]) { return [self namespaceWithName: @"local" stringValue: @"http: //www.w3.org/2003/11/xpath-local-functions"]; } return nil; } + (NSString*) prefixForName: (NSString*)name { const xmlChar *xmlName = XMLSTRING(name); xmlChar *prefix = NULL; if (NULL == xmlName) return nil; xmlSplitQName2(xmlName, &prefix); if (NULL == prefix) { return @""; } else { return StringFromXMLStringPtr(prefix); } } + (id) processingInstructionWithName: (NSString*)name stringValue: (NSString*)stringValue { NSXMLNode *n; n = [[[self alloc] initWithKind: NSXMLProcessingInstructionKind] autorelease]; [n setStringValue: stringValue]; return n; } + (id) textWithStringValue: (NSString*)stringValue { NSXMLNode *n; n = [[[self alloc] initWithKind: NSXMLTextKind] autorelease]; [n setStringValue: stringValue]; return n; } - (NSString*) canonicalXMLStringPreservingComments: (BOOL)comments { return [self notImplemented: _cmd]; // FIXME ... generate from libxml } - (NSXMLNode*) childAtIndex: (NSUInteger)index { xmlNodePtr childNode = [self _childNodeAtIndex: index]; return [NSXMLNode _objectForNode: childNode]; } - (NSUInteger) childCount { NSUInteger count = 0; xmlNodePtr children = NULL; xmlNodePtr node = MY_NODE; if (!node) { return 0; } for (children = node->children; children; children = children->next) { count++; } return count; } - (NSArray*) children { NSMutableArray *childrenArray = nil; if (NSXMLInvalidKind == internal->kind) { return nil; } else { xmlNodePtr children = NULL; xmlNodePtr node = MY_NODE; if ((node == NULL) || (node->children == NULL)) { return nil; } childrenArray = [NSMutableArray array]; for (children = node->children; children; children = children->next) { NSXMLNode *n = [NSXMLNode _objectForNode: children]; [childrenArray addObject: n]; } } return childrenArray; } - (id) copyWithZone: (NSZone*)zone { NSXMLNode *c = [[self class] allocWithZone: zone]; xmlNodePtr newNode = xmlCopyNode([self _node], 2); // make a deep copy clearPrivatePointers(newNode); c = [c _initWithNode: newNode kind: internal->kind]; GSIVar(c, options) = internal->options; [c setObjectValue: internal->objectValue]; [c setURI: [self URI]]; // [c setName: [self name]]; // [c setStringValue: [self stringValue]]; return c; } - (NSString*) description { return [NSString stringWithFormat:@"<%@ %@ %d>%@\n", NSStringFromClass([self class]), [self name], [self kind], [self XMLString]]; } - (void) dealloc { if (GS_EXISTS_INTERNAL) { xmlNodePtr node = MY_NODE; NSArray *subNodes = [internal->subNodes copy]; NSEnumerator *enumerator = [subNodes objectEnumerator]; NSXMLNode *subNode; while ((subNode = [enumerator nextObject]) != nil) { [subNode detach]; } [subNodes release]; [internal->URI release]; [internal->objectValue release]; [internal->subNodes release]; if (node) node->_private = NULL; if (node && node->parent == NULL) { if (node->type == XML_DOCUMENT_NODE) xmlFreeDoc((xmlDocPtr)node); else xmlFreeNode(node); // the top level node frees the entire tree } GS_DESTROY_INTERNAL(NSXMLNode); } [super dealloc]; } - (void) detach { xmlNodePtr node = MY_NODE; if (node) { xmlNodePtr parentNode = node->parent; NSXMLNode *parent = (parentNode ? parentNode->_private : nil); // separate our node from its parent and siblings xmlUnlinkNode(node); if (parent) { [parent _removeSubNode: self]; } } } - (NSUInteger) hash { return [[self name] hash]; } - (NSUInteger) index { xmlNodePtr node = MY_NODE; int count = 0; while ((node = node->prev)) { count++; // count our earlier sibling nodes } return count; } - (id) init { return [self initWithKind: NSXMLInvalidKind]; } - (id) initWithKind: (NSXMLNodeKind) kind { return [self initWithKind: kind options: 0]; } - (id) initWithKind: (NSXMLNodeKind)kind options: (NSUInteger)theOptions { Class theSubclass = [NSXMLNode class]; void *node = NULL; /* * We find the correct subclass for specific node kinds: */ switch (kind) { case NSXMLDocumentKind: theSubclass = [NSXMLDocument class]; break; case NSXMLInvalidKind: theSubclass = [NSXMLNode class]; break; case NSXMLElementKind: theSubclass = [NSXMLElement class]; break; case NSXMLDTDKind: theSubclass = [NSXMLDTD class]; break; case NSXMLEntityDeclarationKind: case NSXMLElementDeclarationKind: case NSXMLNotationDeclarationKind: theSubclass = [NSXMLDTDNode class]; break; case NSXMLAttributeDeclarationKind: [self release]; return nil; break; case NSXMLProcessingInstructionKind: case NSXMLCommentKind: case NSXMLTextKind: case NSXMLNamespaceKind: case NSXMLAttributeKind: break; default: kind = NSXMLInvalidKind; theSubclass = [NSXMLNode class]; break; } /* * Check whether we are already initializing an instance of the given * subclass. If we are not, release ourselves and allocate a subclass * instance instead. */ if (NO == [self isKindOfClass: theSubclass]) { [self release]; return [[theSubclass alloc] initWithKind: kind options: theOptions]; } /* If we are initializing for the correct class, we can actually perform * initializations: */ switch (kind) { case NSXMLDocumentKind: node = xmlNewDoc((xmlChar *)"1.0"); break; case NSXMLInvalidKind: case NSXMLElementKind: node = xmlNewNode(NULL,(xmlChar *)""); break; case NSXMLDTDKind: node = xmlNewDtd(NULL, (xmlChar *)"", (xmlChar *)"",(xmlChar *)""); break; case NSXMLEntityDeclarationKind: case NSXMLElementDeclarationKind: case NSXMLNotationDeclarationKind: node = xmlNewNode(NULL, (xmlChar *)""); break; case NSXMLProcessingInstructionKind: node = xmlNewPI((xmlChar *)"", (xmlChar *)""); break; case NSXMLCommentKind: node = xmlNewComment((xmlChar *)""); break; case NSXMLTextKind: node = xmlNewText((xmlChar *)""); break; case NSXMLNamespaceKind: node = xmlNewNs(NULL,(xmlChar *)"",(xmlChar *)""); break; case NSXMLAttributeKind: node = xmlNewProp(NULL,(xmlChar *)"",(xmlChar *)""); break; default: break; } if (nil == (self = [self _initWithNode: node kind: kind])) { return nil; } internal->options = theOptions; return self; } - (BOOL) isEqual: (id)other { if ([self kind] != [other kind]) { return NO; } /* NSLog(@"self %@ other %@", self, other); NSLog(@"s sV '%@' oV '%@', other sV '%@' oV '%@'", [self stringValue], [self objectValue], [other stringValue], [other objectValue]); */ return isEqualTree(MY_NODE, (xmlNodePtr)[other _node]); } - (NSXMLNodeKind) kind { return internal->kind; } - (NSUInteger) level { NSUInteger level = 0; xmlNodePtr tmp; if (!MY_NODE) { return 0; } tmp = MY_NODE->parent; while (tmp != NULL) { level++; tmp = tmp->parent; } return level; } - (NSString*) localName { return [[self class] localNameForName: [self name]]; } - (NSString*) name { if (NSXMLInvalidKind == internal->kind) { return nil; } return StringFromXMLStringPtr(MY_NODE->name); } - (NSXMLNode*) _nodeFollowingInNaturalDirection: (BOOL)forward { NSXMLNode *ancestor = self; NSXMLNode *candidate = nil; NSXMLNodeKind kind; /* Node walking is a depth-first thingy. Hence, we consider children first: */ if (0 != [self childCount]) { NSUInteger theIndex = 0; if (NO == forward) { theIndex = [self childCount] - 1; } candidate = [[self children] objectAtIndex: theIndex]; } /* If there are no children, we move on to siblings: */ /* If there are no siblings left for the receiver, we recurse down to the root * of the tree until we find an ancestor with further siblings: */ while ((nil == candidate) && (nil != ancestor)) { if (forward) { candidate = [ancestor nextSibling]; } else { candidate = [ancestor previousSibling]; } ancestor = [ancestor parent]; } /* No children, no next siblings, no next siblings for any ancestor: We are * the last node */ if (nil == candidate) { return nil; } /* Sanity check: Namespace and attribute nodes are skipped: */ kind = [candidate kind]; if ((NSXMLAttributeKind == kind) || (NSXMLNamespaceKind == kind)) { return [candidate _nodeFollowingInNaturalDirection: forward]; } return candidate; } - (NSXMLNode*) nextNode { return [self _nodeFollowingInNaturalDirection: YES]; } - (NSXMLNode*) nextSibling { return [NSXMLNode _objectForNode: MY_NODE->next]; } - (id) objectValue { return internal->objectValue; } - (NSXMLNode*) parent { return [NSXMLNode _objectForNode: MY_NODE->parent]; } - (NSString*) prefix { return [[self class] prefixForName: [self name]]; } - (NSXMLNode*) previousNode { return [self _nodeFollowingInNaturalDirection: NO]; } - (NSXMLNode*) previousSibling { return [NSXMLNode _objectForNode: MY_NODE->prev]; } - (NSXMLDocument*) rootDocument { return (NSXMLDocument *)[NSXMLNode _objectForNode: (xmlNodePtr)(MY_NODE->doc)]; } - (NSString*) stringValue { xmlNodePtr node = MY_NODE; xmlChar *content = xmlNodeGetContent(node); NSString *result = nil; /* if (node->type == XML_ATTRIBUTE_NODE || node->type == XML_ELEMENT_NODE) { node = node->children; } */ if (NULL != content) { result = StringFromXMLStringPtr(content); xmlFree(content); } else { result = @""; } return result; } - (void) setObjectValue: (id)value { NSString *stringValue; // FIXME: Use correct formatter here stringValue = [value description]; [self setStringValue: stringValue]; ASSIGN(internal->objectValue, value); } - (void) setName: (NSString *)name { if (NSXMLInvalidKind == internal->kind) { return; } xmlNodeSetName(MY_NODE, XMLSTRING(name)); } - (void) setStringValue: (NSString*)string { [self setStringValue: string resolvingEntities: NO]; } - (void) setStringValue: (NSString*)string resolvingEntities: (BOOL)resolve { xmlNodePtr node = MY_NODE; if (resolve == NO) { xmlNodeSetContent(node, XMLSTRING(string)); } else { // need to actually resolve entities... // is this the right functionality?? xmlEncodeSpecialChars() xmlChar *newstr = xmlEncodeEntitiesReentrant(node->doc, XMLSTRING(string)); xmlNodeSetContent(node, newstr); xmlMemFree(newstr); } ASSIGN(internal->objectValue, string); } - (void) setURI: (NSString*)URI { if (NSXMLInvalidKind == internal->kind) { return; } ASSIGNCOPY(internal->URI, URI); //xmlNodeSetBase(MY_NODE, XMLSTRING(URI)); } - (NSString*) URI { if (NSXMLInvalidKind == internal->kind) { return nil; } return internal->URI; //return StringFromXMLStringPtr(xmlNodeGetBase(NULL, MY_NODE)); } - (NSString*) XMLString { return [self XMLStringWithOptions: NSXMLNodeOptionsNone]; } - (NSString*) XMLStringWithOptions: (NSUInteger)options { NSString *string = nil; xmlNodePtr node = (xmlNodePtr)[self _node]; xmlChar *buf = NULL; xmlDocPtr doc = node->doc; xmlBufferPtr buffer = xmlBufferCreate(); int error = 0; int len = 0; error = xmlNodeDump(buffer, doc, node, 1, 1); if (-1 == error) { xmlBufferFree(buffer); return nil; } buf = buffer->content; len = buffer->use; string = StringFromXMLString(buf,len); xmlBufferFree(buffer); return string; } - (NSString*) XPath { xmlNodePtr node = MY_NODE; return StringFromXMLStringPtr(xmlGetNodePath(node)); } - (NSArray*) nodesForXPath: (NSString*)anxpath error: (NSError**)error { if (error != NULL) { *error = NULL; } return execute_xpath(self, anxpath, NULL); } - (NSArray*) objectsForXQuery: (NSString*)xquery constants: (NSDictionary*)constants error: (NSError**)error { return [self notImplemented: _cmd]; } - (NSArray*) objectsForXQuery: (NSString*)xquery error: (NSError**)error { return [self notImplemented: _cmd]; } @end #endif