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