1/* Implementation for NSXMLElement for GNUStep 2 Copyright (C) 2008 Free Software Foundation, Inc. 3 4 Written by: Richard Frith-Macdonald <rfm@gnu.org> 5 Created: September 2008 6 7 This file is part of the GNUstep Base Library. 8 9 This library is free software; you can redistribute it and/or 10 modify it under the terms of the GNU Lesser General Public 11 License as published by the Free Software Foundation; either 12 version 3 of the License, or (at your option) any later version. 13 14 This library is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 Lesser General Public License for more details. 18 19 You should have received a copy of the GNU Lesser General Public 20 License along with this library; if not, write to the Free 21 Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 Boston, MA 02110 USA. 23*/ 24 25#import "common.h" 26 27#if defined(HAVE_LIBXML) 28 29#define GSInternal NSXMLElementInternal 30 31#import "NSXMLPrivate.h" 32#import "GSInternal.h" 33GS_PRIVATE_INTERNAL(NSXMLElement) 34 35extern void cleanup_namespaces(xmlNodePtr node, xmlNsPtr ns); 36extern void ensure_oldNs(xmlNodePtr node); 37 38@implementation NSXMLElement 39 40- (void) dealloc 41{ 42 if (GS_EXISTS_INTERNAL && internal != nil) 43 { 44 /* 45 NSArray *subNodes = [internal->subNodes copy]; 46 NSEnumerator *enumerator = [subNodes objectEnumerator]; 47 NSXMLNode *subNode; 48 49 while ((subNode = [enumerator nextObject]) != nil) 50 { 51 if ([subNode kind] == NSXMLNamespaceKind) 52 { 53 [self removeNamespaceForPrefix: [subNode name]]; 54 } 55 } 56 */ 57 } 58 59 [super dealloc]; 60} 61 62- (void) _createInternal 63{ 64 GS_CREATE_INTERNAL(NSXMLElement); 65} 66 67- (id) init 68{ 69 return [self initWithKind: NSXMLElementKind options: 0]; 70} 71 72- (id) initWithKind: (NSXMLNodeKind)theKind options: (NSUInteger)theOptions 73{ 74 if (NSXMLElementKind == theKind) 75 { 76 return [super initWithKind: theKind options: theOptions]; 77 } 78 else 79 { 80 [self release]; 81 // This cast is here to keep clang quite that expects an init* method to 82 // return an object of the same class, which is not true here. 83 return (NSXMLElement*)[[NSXMLNode alloc] initWithKind: theKind 84 options: theOptions]; 85 } 86} 87 88- (id) initWithName: (NSString*)name 89{ 90 return [self initWithName: name URI: nil]; 91} 92 93- (id) initWithName: (NSString*)name URI: (NSString*)URI 94{ 95 if ((self = [self initWithKind: NSXMLElementKind]) != nil) 96 { 97 [self setName: name]; 98 // Without this check this could unset a namespace set via the name 99 if (URI != nil) 100 { 101 [self setURI: URI]; 102 } 103 } 104 return self; 105} 106 107- (id) initWithName: (NSString*)name stringValue: (NSString*)string 108{ 109 if ((self = [self initWithName: name URI: nil]) != nil) 110 { 111 NSXMLNode *t; 112 113 t = [[NSXMLNode alloc] initWithKind: NSXMLTextKind]; 114 [t setStringValue: string]; 115 [self addChild: t]; 116 [t release]; 117 } 118 return self; 119} 120 121- (id) initWithXMLString: (NSString*)string 122 error: (NSError**)error 123{ 124 NSXMLElement *result = nil; 125 NSXMLDocument *tempDoc = 126 [[NSXMLDocument alloc] initWithXMLString: string 127 options: 0 128 error: error]; 129 if (tempDoc != nil) 130 { 131 result = RETAIN([tempDoc rootElement]); 132 [result detach]; // detach from document. 133 } 134 [tempDoc release]; 135 [self release]; 136 137 return result; 138} 139 140- (id) objectValue 141{ 142 if (internal->objectValue == nil) 143 { 144 return @""; 145 } 146 return internal->objectValue; 147} 148 149- (NSArray*) elementsForName: (NSString*)name 150{ 151 NSString *prefix = [[self class] prefixForName: name]; 152 153 if ((nil != prefix) && [prefix length] > 0) 154 { 155 NSXMLNode *ns = [self namespaceForPrefix: prefix]; 156 157 if (nil != ns) 158 { 159 NSString *localName = [[self class] localNameForName: name]; 160 161 // Namespace nodes have the URI as their stringValue 162 return [self elementsForLocalName: localName URI: [ns stringValue]]; 163 } 164 } 165 166 { 167 NSMutableArray *results = [NSMutableArray arrayWithCapacity: 10]; 168 xmlNodePtr cur = NULL; 169 const xmlChar *xmlName = XMLSTRING(name); 170 171 for (cur = internal->node.node->children; cur != NULL; cur = cur->next) 172 { 173 if (cur->type == XML_ELEMENT_NODE) 174 { 175 // no namespace or default namespace 176 if ((xmlStrcmp(xmlName, cur->name) == 0) && 177 ((cur->ns == NULL) || (cur->ns->prefix == NULL) || 178 (xmlStrcmp(cur->ns->prefix, (const xmlChar*)"") == 0))) 179 { 180 NSXMLNode *theNode = [NSXMLNode _objectForNode: cur]; 181 [results addObject: theNode]; 182 } 183 } 184 } 185 186 return results; 187 } 188} 189 190- (NSArray*) elementsForLocalName: (NSString*)localName URI: (NSString*)URI 191{ 192 NSMutableArray *results = [NSMutableArray arrayWithCapacity: 10]; 193 xmlNodePtr cur = NULL; 194 const xmlChar *href = XMLSTRING(URI); 195 const xmlChar *xmlName = XMLSTRING(localName); 196 xmlNsPtr parentNS = xmlSearchNsByHref(internal->node.node->doc, internal->node.node, href); 197 198 for (cur = internal->node.node->children; cur != NULL; cur = cur->next) 199 { 200 if (cur->type == XML_ELEMENT_NODE) 201 { 202 if (xmlStrcmp(xmlName, cur->name) == 0) 203 { 204 xmlNsPtr childNS = parentNS; 205 206 if (cur->nsDef != NULL) 207 { 208 childNS = xmlSearchNsByHref(internal->node.node->doc, cur, href); 209 } 210 211 212 if (((childNS != NULL) && 213 ((cur->ns == childNS) || 214 ((cur->ns == NULL) && 215 (xmlStrcmp(childNS->prefix, (const xmlChar*)"") == 0)))) || 216 ((cur->ns != NULL) && (xmlStrcmp(cur->ns->href, href) == 0))) 217 { 218 NSXMLNode *theNode = [NSXMLNode _objectForNode: cur]; 219 [results addObject: theNode]; 220 } 221 } 222 } 223 } 224 225 return results; 226} 227 228- (void) addAttribute: (NSXMLNode*)attribute 229{ 230 xmlNodePtr theNode = internal->node.node; 231 xmlAttrPtr attr = (xmlAttrPtr)[attribute _node]; 232 xmlAttrPtr oldAttr; 233 234 if (nil != [attribute parent]) 235 { 236 [NSException raise: NSInternalInconsistencyException 237 format: @"Tried to add attribute to multiple parents."]; 238 } 239 240 if (attr->ns != NULL) 241 { 242 xmlNsPtr ns = attr->ns; 243 xmlDocPtr tmp = attr->doc; 244 BOOL resolved = NO; 245 246 if (ns->href == NULL) 247 { 248 xmlNsPtr newNs = xmlSearchNs(theNode->doc, theNode, ns->prefix); 249 250 if (newNs != NULL) 251 { 252 ns = newNs; 253 attr->ns = ns; 254 resolved = YES; 255 } 256 } 257 else //if (ns->prefix == NULL) 258 { 259 xmlNsPtr newNs = xmlSearchNsByHref(theNode->doc, theNode, ns->href); 260 261 if (newNs != NULL) 262 { 263 ns = newNs; 264 attr->ns = ns; 265 resolved = YES; 266 } 267 } 268 269 if (!resolved && (tmp != NULL)) 270 { 271 xmlNsPtr cur = tmp->oldNs; 272 xmlNsPtr last = NULL; 273 xmlNsPtr oldNs1; 274 275 // Need to transfer the namespace to the new tree 276 // Unlink in old 277 while (cur) 278 { 279 if (cur == ns) 280 { 281 if (last == NULL) 282 { 283 tmp->oldNs = NULL; 284 } 285 else 286 { 287 last->next = cur->next; 288 } 289 cur->next = NULL; 290 break; 291 } 292 last = cur; 293 cur = cur->next; 294 } 295 296 // Insert in new 297 ensure_oldNs(theNode); 298 oldNs1 = theNode->doc->oldNs; 299 while (oldNs1) 300 { 301 if (oldNs1->next == NULL) 302 { 303 oldNs1->next = cur; 304 break; 305 } 306 oldNs1 = oldNs1->next; 307 } 308 } 309 310#if LIBXML_VERSION >= 20620 311 xmlDOMWrapAdoptNode(NULL, attr->doc, (xmlNodePtr)attr, 312 theNode->doc, theNode, 0); 313#else 314 xmlSetTreeDoc((xmlNodePtr)attr, theNode->doc); 315#endif 316 xmlFreeDoc(tmp); 317 318 oldAttr = xmlHasNsProp(theNode, attr->name, ns->href); 319 } 320 else 321 { 322 oldAttr = xmlHasProp(theNode, attr->name); 323 } 324 325 if (NULL != oldAttr) 326 { 327 /* 328 * As per Cocoa documentation, we only add the attribute if it's not 329 * already set. xmlHasProp() also looks at the DTD for default attributes 330 * and we need to make sure that we only bail out here on #FIXED 331 * attributes. 332 */ 333 334 // Do not replace plain attributes. 335 if (XML_ATTRIBUTE_NODE == oldAttr->type) 336 { 337 return; 338 } 339 else if (XML_ATTRIBUTE_DECL == oldAttr->type) 340 { 341 // If the attribute is from a DTD, do not replace it if it's #FIXED 342 xmlAttributePtr attrDecl = (xmlAttributePtr)oldAttr; 343 if (XML_ATTRIBUTE_FIXED == attrDecl->def) 344 { 345 return; 346 } 347 } 348 } 349 xmlAddChild(theNode, (xmlNodePtr)attr); 350 [self _addSubNode: attribute]; 351} 352 353- (void) removeAttributeForName: (NSString*)name 354{ 355 NSXMLNode *attrNode = [self attributeForName: name]; 356 357 [attrNode detach]; 358} 359 360- (void) setAttributes: (NSArray*)attributes 361{ 362 NSEnumerator *enumerator = [attributes objectEnumerator]; 363 NSXMLNode *attribute; 364 365 // FIXME: Remove all previous attributes 366 while ((attribute = [enumerator nextObject]) != nil) 367 { 368 [self addAttribute: attribute]; 369 } 370} 371 372- (void) setAttributesAsDictionary: (NSDictionary*)attributes 373{ 374 [self setAttributesWithDictionary: attributes]; 375} 376 377- (void) setAttributesWithDictionary: (NSDictionary*)attributes 378{ 379 NSEnumerator *en = [attributes keyEnumerator]; 380 NSString *key; 381 382 // FIXME: Remove all previous attributes 383 while ((key = [en nextObject]) != nil) 384 { 385 NSString *val = [[attributes objectForKey: key] stringValue]; 386 NSXMLNode *attribute = [NSXMLNode attributeWithName: key 387 stringValue: val]; 388 [self addAttribute: attribute]; 389 } 390} 391 392- (NSArray*) attributes 393{ 394 NSMutableArray *attributes = [NSMutableArray array]; 395 xmlNodePtr theNode = internal->node.node; 396 xmlAttrPtr attributeNode = theNode->properties; 397 398 while (attributeNode) 399 { 400 NSXMLNode *attribute; 401 402 attribute = [NSXMLNode _objectForNode: (xmlNodePtr)attributeNode]; 403 [attributes addObject: attribute]; 404 attributeNode = attributeNode->next; 405 } 406 return attributes; 407} 408 409- (NSXMLNode*) attributeForName: (NSString*)name 410{ 411 NSString *prefix = [[self class] prefixForName: name]; 412 413 if ((nil != prefix) && [prefix length] > 0) 414 { 415 NSXMLNode *ns = [self namespaceForPrefix: prefix]; 416 417 if (nil != ns) 418 { 419 NSString *localName = [[self class] localNameForName: name]; 420 421 // Namespace nodes have the URI as their stringValue 422 return [self attributeForLocalName: localName URI: [ns stringValue]]; 423 } 424 } 425 426 { 427 NSXMLNode *result = nil; 428 xmlNodePtr theNode = internal->node.node; 429 xmlAttrPtr attributeNode = xmlHasProp(theNode, XMLSTRING(name)); 430 431 if (NULL != attributeNode) 432 { 433 result = [NSXMLNode _objectForNode: (xmlNodePtr)attributeNode]; 434 } 435 436 return result; 437 } 438} 439 440- (NSXMLNode*) attributeForLocalName: (NSString*)localName 441 URI: (NSString*)URI 442{ 443 NSXMLNode *result = nil; 444 xmlNodePtr theNode = internal->node.node; 445 xmlAttrPtr attributeNode = xmlHasNsProp(theNode, XMLSTRING(localName), 446 XMLSTRING(URI)); 447 448 if (NULL != attributeNode) 449 { 450 result = [NSXMLNode _objectForNode: (xmlNodePtr)attributeNode]; 451 } 452 453 return result; 454} 455 456- (void) addNamespace: (NSXMLNode*)aNamespace 457{ 458 xmlNsPtr ns = xmlCopyNamespace((xmlNsPtr)[aNamespace _node]); 459 xmlNodePtr theNode = internal->node.node; 460 const xmlChar *prefix = ns->prefix; 461 462 if (theNode->nsDef == NULL) 463 { 464 theNode->nsDef = ns; 465 } 466 else 467 { 468 xmlNsPtr cur = theNode->nsDef; 469 xmlNsPtr last = NULL; 470 471 while (cur != NULL) 472 { 473 if ((prefix != NULL) && 474 (cur->prefix != NULL) && 475 (xmlStrcmp(prefix, cur->prefix) == 0)) 476 { 477 break; 478 } 479 if (cur->next == NULL) 480 { 481 cur->next = ns; 482 return; 483 } 484 last = cur; 485 cur = cur->next; 486 } 487 488 // Found the same prefix 489 if (cur->href == NULL) 490 { 491 // This was a fake namespace we added 492 if (theNode->ns == cur) 493 { 494 theNode->ns = ns; 495 } 496 if (last == NULL) 497 { 498 theNode->nsDef = ns; 499 } 500 else 501 { 502 last->next = ns; 503 } 504 ns->next = cur->next; 505 cur->next = NULL; 506 } 507 } 508 509 // Are we setting a default namespace? 510 if ((theNode->ns == NULL) && (xmlStrcmp(prefix, (const xmlChar*)"") == 0)) 511 { 512 theNode->ns = ns; 513 } 514 515 // Need to replace fake namespaces in subnodes 516 cleanup_namespaces(theNode, ns); 517} 518 519- (void) removeNamespaceForPrefix: (NSString*)name 520{ 521 xmlNodePtr theNode = internal->node.node; 522 523 if (theNode->nsDef != NULL) 524 { 525 xmlNsPtr cur = theNode->nsDef; 526 xmlNsPtr last = NULL; 527 const xmlChar *prefix = XMLSTRING(name); 528 529 while (cur != NULL) 530 { 531 if ((cur->prefix != NULL) && 532 (xmlStrcmp(prefix, cur->prefix) == 0)) 533 { 534 if (last == NULL) 535 { 536 internal->node.node->nsDef = cur->next; 537 } 538 else 539 { 540 last->next = cur->next; 541 } 542 cur->next = NULL; 543 if (theNode->ns == cur) 544 { 545 theNode->ns = NULL; 546 } 547 xmlFreeNs(cur); 548 return; 549 } 550 last = cur; 551 cur = cur->next; 552 } 553 } 554} 555 556- (void) setNamespaces: (NSArray*)namespaces 557{ 558 NSEnumerator *en = [namespaces objectEnumerator]; 559 NSXMLNode *namespace = nil; 560 561 // Remove old namespaces 562 xmlFreeNsList(internal->node.node->nsDef); 563 internal->node.node->nsDef = NULL; 564 565 // Add new ones 566 while ((namespace = (NSXMLNode *)[en nextObject]) != nil) 567 { 568 [self addNamespace: namespace]; 569 } 570} 571 572- (NSArray*) namespaces 573{ 574 // FIXME: Should we use xmlGetNsList()? 575 NSMutableArray *result = nil; 576 xmlNsPtr ns = internal->node.node->nsDef; 577 578 if (ns) 579 { 580 xmlNsPtr cur = NULL; 581 582 result = [NSMutableArray array]; 583 for (cur = ns; cur != NULL; cur = cur->next) 584 { 585 [result addObject: [NSXMLNode _objectForNode: 586 (xmlNodePtr)xmlCopyNamespace(cur)]]; 587 } 588 } 589 590 return result; 591} 592 593- (NSXMLNode*) namespaceForPrefix: (NSString*)name 594{ 595 if (name != nil) 596 { 597 const xmlChar *prefix = XMLSTRING(name); 598 xmlNodePtr theNode = internal->node.node; 599 xmlNsPtr ns; 600 601 ns = xmlSearchNs(theNode->doc, theNode, prefix); 602 if ((ns == NULL) && ([name length] == 0)) 603 { 604 prefix = NULL; 605 ns = xmlSearchNs(theNode->doc, theNode, prefix); 606 } 607 608 if (ns != NULL) 609 { 610 return [NSXMLNode _objectForNode: (xmlNodePtr)xmlCopyNamespace(ns)]; 611 } 612 } 613 614 return nil; 615} 616 617- (NSXMLNode*) resolveNamespaceForName: (NSString*)name 618{ 619 NSString *prefix = [[self class] prefixForName: name]; 620 621 // Return the default namespace for an empty prefix 622 if (nil != prefix) 623 { 624 return [self namespaceForPrefix: prefix]; 625 } 626 627 return nil; 628} 629 630- (NSString*) resolvePrefixForNamespaceURI: (NSString*)namespaceURI 631{ 632 const xmlChar *uri = XMLSTRING(namespaceURI); 633 xmlNsPtr ns = xmlSearchNsByHref(internal->node.node->doc, internal->node.node, uri); 634 635 if (ns) 636 { 637 return StringFromXMLStringPtr(ns->prefix); 638 } 639 640 return nil; 641} 642 643- (void) insertChild: (NSXMLNode*)child atIndex: (NSUInteger)index 644{ 645 NSXMLNodeKind theKind = [child kind]; 646 NSUInteger childCount = [self childCount]; 647 648 // Check to make sure this is a valid addition... 649 NSAssert(nil != child, NSInvalidArgumentException); 650 NSAssert(index <= childCount, NSInvalidArgumentException); 651 NSAssert(NSXMLAttributeKind != theKind, NSInvalidArgumentException); 652 NSAssert(NSXMLDTDKind != theKind, NSInvalidArgumentException); 653 NSAssert(NSXMLDocumentKind != theKind, NSInvalidArgumentException); 654 NSAssert(NSXMLElementDeclarationKind != theKind, NSInvalidArgumentException); 655 NSAssert(NSXMLEntityDeclarationKind != theKind, NSInvalidArgumentException); 656 NSAssert(NSXMLInvalidKind != theKind, NSInvalidArgumentException); 657 NSAssert(NSXMLNamespaceKind != theKind, NSInvalidArgumentException); 658 NSAssert(NSXMLNotationDeclarationKind != theKind, NSInvalidArgumentException); 659 660/* On OSX we get NSInternalInconsistencyException if we try to add an element 661 * which is already a child of some other parent. So presumably we shouldn't 662 * be auto-removing... 663 * 664 * if (nil != [child parent]) 665 * { 666 * [child detach]; 667 * } 668 */ 669 NSAssert(nil == [child parent], NSInternalInconsistencyException); 670 671 [self _insertChild: child atIndex: index]; 672} 673 674- (void) insertChildren: (NSArray*)children atIndex: (NSUInteger)index 675{ 676 NSEnumerator *enumerator = [children objectEnumerator]; 677 NSXMLNode *child; 678 679 while ((child = [enumerator nextObject]) != nil) 680 { 681 [self insertChild: child atIndex: index++]; 682 } 683} 684 685- (void) removeChildAtIndex: (NSUInteger)index 686{ 687 NSXMLNode *child; 688 689 if (index >= [self childCount]) 690 { 691 [NSException raise: NSRangeException 692 format: @"index too large"]; 693 } 694 695 child = [self childAtIndex: index]; 696 [child detach]; 697} 698 699- (void) setChildren: (NSArray*)children 700{ 701 NSUInteger count = [self childCount]; 702 703 while (count-- > 0) 704 { 705 [self removeChildAtIndex: count]; 706 } 707 708 [self insertChildren: children atIndex: 0]; 709} 710 711- (void) addChild: (NSXMLNode*)child 712{ 713 [self insertChild: child atIndex: [self childCount]]; 714} 715 716- (void) replaceChildAtIndex: (NSUInteger)index withNode: (NSXMLNode*)theNode 717{ 718 [self insertChild: theNode atIndex: index]; 719 [self removeChildAtIndex: index + 1]; 720} 721 722static void 723joinTextNodes(xmlNodePtr nodeA, xmlNodePtr nodeB, NSMutableArray *nodesToDelete) 724{ 725 NSXMLNode *objA = (nodeA->_private); 726 NSXMLNode *objB = (nodeB->_private); 727 728 xmlTextMerge(nodeA, nodeB); // merge nodeB into nodeA 729 730 if (objA != nil) // objA gets the merged node 731 { 732 if (objB != nil) // objB is now invalid 733 { 734 /* set it to be invalid and make sure it's not 735 * pointing to a freed node 736 */ 737 [objB _invalidate]; 738 [nodesToDelete addObject: objB]; 739 } 740 } 741 else if (objB != nil) // there is no objA -- objB gets the merged node 742 { 743 [objB _setNode: nodeA]; // nodeA is the remaining (merged) node 744 } 745} 746 747- (void) normalizeAdjacentTextNodesPreservingCDATA: (BOOL)preserve 748{ 749 NSEnumerator *subEnum = [internal->subNodes objectEnumerator]; 750 NSXMLNode *subNode = nil; 751 NSMutableArray *nodesToDelete = [NSMutableArray array]; 752 753 while ((subNode = [subEnum nextObject])) 754 { 755 xmlNodePtr theNode = [subNode _node]; 756 xmlNodePtr prev = theNode->prev; 757 xmlNodePtr next = theNode->next; 758 759 if (theNode->type == XML_ELEMENT_NODE) 760 { 761 [(NSXMLElement *)subNode 762 normalizeAdjacentTextNodesPreservingCDATA:preserve]; 763 } 764 else if (theNode->type == XML_TEXT_NODE 765 || (theNode->type == XML_CDATA_SECTION_NODE && !preserve)) 766 { 767 if (next && (next->type == XML_TEXT_NODE 768 || (next->type == XML_CDATA_SECTION_NODE && !preserve))) 769 { 770 //combine node & node->next 771 joinTextNodes(theNode, theNode->next, nodesToDelete); 772 } 773 if (prev && (prev->type == XML_TEXT_NODE 774 || (prev->type == XML_CDATA_SECTION_NODE && !preserve))) 775 { 776 /* combine node->prev & node 777 * join the text of both nodes 778 * assign the joined text to the earlier of the two 779 * nodes that has an ObjC object 780 * unlink the other node 781 * delete the other node's object (maybe add it to a 782 * list of nodes to delete when we're done? -- 783 * or just set its node to null, and then remove it 784 * from our subNodes when we're done iterating it) 785 * (or maybe we need to turn it into an NSInvalidNode too??) 786 */ 787 joinTextNodes(theNode->prev, theNode, nodesToDelete); 788 } 789 790 } 791 } 792 if ([nodesToDelete count] > 0) 793 { 794 subEnum = [nodesToDelete objectEnumerator]; 795 while ((subNode = [subEnum nextObject])) 796 { 797 [self _removeSubNode: subNode]; 798 } 799 } 800} 801 802@end 803 804#endif /* HAVE_LIBXML */ 805