Initial implementation of XmlDocument.LoadXml courtesy of Kral Ferch <kral.ferch...
[mono.git] / mcs / class / System.XML / System.Xml / XmlNode.cs
1 // -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 //
3 // System.Xml.XmlNode
4 //
5 // Author:
6 //   Daniel Weber (daniel-weber@austin.rr.com)
7 //
8 // (C) 2001 Daniel Weber
9
10 using System;
11 using System.Collections;
12 using System.Xml.XPath;
13
14 namespace System.Xml 
15 {
16         public abstract class XmlNode : ICloneable, IEnumerable, IXPathNavigable 
17         {
18                 //======= Private data members ==============================================
19                 private XmlNodeListAsArrayList _childNodes;
20                 protected XmlDocument FOwnerDocument;
21                 protected XmlNode _parent;
22
23                 // Names of node
24                 // for <foo:bar xmlns:foo="http://www.foobar.com/schema/foobar">... </foo:bar>
25                 //      qualified name: foo:bar
26                 //      namespaceURI = "http://www.foobar.com/schema/foobar"
27                 //  localName = bar
28                 //      prefix = foo
29                 // Note that namespaces can be nested (child namespace != parent namespace)
30                 // namespaces are optional
31                 protected string Fname;
32                 protected string FnamespaceURI;
33                 protected string Fprefix;
34                 protected string FlocalName;
35
36                 // baseURI holds the location from which the document was loaded
37                 // If the node was created from a document at c:\tmp.xml, then that's what will be here
38                 protected string FbaseURI;
39
40                 // value of the node (overriden in classes that do something different)
41                 //      default behavior is just to store it
42                 protected string Fvalue;
43                 
44                 //=====================================================================
45                 // ============ Properties ============================================
46                 //=====================================================================
47                 /// <summary>
48                 /// Get the XmlAttributeCollection representing the attributes
49                 ///   on the node type.  Returns null if the node type is not XmlElement.
50                 /// </summary>
51                 public virtual XmlAttributeCollection Attributes 
52                 {
53                         get 
54                         {
55                                 return null;
56                         }
57                 }
58                 /// <summary>
59                 ///  Return the base Uniform Resource Indicator (URI) used to resolve
60                 ///  this node, or String.Empty.
61                 /// </summary>
62                 public virtual string BaseURI 
63                 {
64                         get 
65                         {
66                                 return FbaseURI;
67                         }
68
69                 }
70                 /// <summary>
71                 /// Return all child nodes of this node.  If there are no children,
72                 ///  return an empty XmlNodeList;
73                 /// </summary>
74                 public virtual XmlNodeList ChildNodes 
75                 {
76                         get 
77                         {
78                                 if (_childNodes == null)
79                                         _childNodes = new XmlNodeListAsArrayList();
80
81                                 return _childNodes as XmlNodeList;
82                         }
83                 }
84                 
85                 /// <summary>
86                 /// Return first child node as XmlNode or null
87                 /// if the node has no children
88                 /// </summary>
89                 public virtual XmlNode FirstChild 
90                 {
91                         get
92                         {
93                                 if (ChildNodes.Count == 0)
94                                         return null;
95                                 else
96                                         return ChildNodes[0];
97                         }
98                 }
99
100                 /// <summary>
101                 ///             Return true if the node has children
102                 /// </summary>
103                 public virtual bool HasChildNodes 
104                 {
105                         get 
106                         {
107                                 if (ChildNodes.Count == 0)
108                                         return true;
109                                 else
110                                         return false;
111                         }
112                 }
113
114                 /// <summary>
115                 /// Get or Set the concatenated values of node and children
116                 /// </summary>
117                 public virtual string InnerText
118                 {
119                         get
120                         {
121                                 // TODO - implement set InnerText()
122                                 throw new NotImplementedException();
123                         }
124
125                         set 
126                         {
127                                 // TODO - implement set InnerText()
128                                 throw new NotImplementedException();
129                         }
130                 }
131
132                 /// <summary>
133                 /// Get/Set the XML representing just the child nodes of this node
134                 /// </summary>
135                 public virtual string InnerXml
136                 {
137                         get
138                         {
139                                 // TODO - implement set InnerXml()
140                                 throw new NotImplementedException();
141                         }
142
143                         set 
144                         {
145                                 // TODO - implement set InnerXml()
146                                 throw new NotImplementedException();
147                         }
148                 }
149
150                 /// <summary>
151                 /// Property Get - true if node is read-only
152                 /// </summary>
153                 public virtual bool IsReadOnly
154                 {
155                         get
156                         {
157                                 return OwnerDocument.IsReadOnly;
158                         }
159                 }
160
161                 /// <summary>
162                 /// Return the child element named [string].  Returns XmlElement
163                 /// Indexer for XmlNode class.
164                 /// </summary>
165                 [System.Runtime.CompilerServices.IndexerNameAttribute("Item")]
166                 public virtual XmlElement this [String index]
167                 {
168                         get 
169                         {
170                                 // TODO - implement XmlNode.Item(string)
171                                 throw new NotImplementedException();
172                         }
173                 }
174
175                 /// <summary>
176                 /// Get the last child node, or null if there are no nodes
177                 /// </summary>
178                 public virtual XmlNode LastChild
179                 {
180                         get
181                         {
182                 if (_childNodes.Count == 0)
183                                  return null;
184                                 else
185                                  return _childNodes.Item(_childNodes.Count - 1);
186                         }
187
188                 }
189
190                 /// <summary>
191                 /// Returns the local name of the node with qualifiers removed
192                 /// LocalName of ns:elementName = "elementName"
193                 /// </summary>
194                 public abstract string LocalName {get;}
195
196                 /// <summary>
197                 /// Get the qualified node name
198                 /// derived classes must implement as behavior varies
199                 /// by tag type.
200                 /// </summary>
201                 public abstract string Name { get; }
202
203                 /// <summary>
204                 /// Get the namespace URI or String.Empty if none
205                 /// </summary>
206                 public virtual string NamespaceURI
207                 {
208                         get
209                         {
210                                 // TODO - implement Namespace URI, or determine abstractness
211                                 throw new NotImplementedException("XmlNode.NamespaceURI not implemented");
212                         }
213                 }
214
215                 /// <summary>
216                 /// Get the node immediatelly following this node, or null
217                 /// </summary>
218                 public virtual XmlNode NextSibling 
219                 {
220                         get
221                         {
222                                 
223                                 if (_parent != null)
224                                 {
225                                         XmlNodeListAsArrayList children = _parent.ChildNodes as XmlNodeListAsArrayList;
226                                         int ourIndex = children.data.IndexOf(this);
227                                         return children[ourIndex + 1];
228                                 }
229                                 else
230                                         return null;
231                         }
232                 }
233
234                 public virtual XmlNodeType NodeType 
235                 {
236                         get
237                         {
238                                 return XmlNodeType.None;
239                         }
240                 }
241
242                 /// <summary>
243                 /// Return the string representing this node and all it's children
244                 /// </summary>
245                 public virtual string OuterXml
246                 {
247                         get
248                         {
249                                 // TODO - implement OuterXml {get;}
250                                 throw new NotImplementedException();
251                         }
252                 }
253
254                 /// <summary>
255                 /// Return owning document.
256                 /// If this nodeType is a document, return null
257                 /// </summary>
258                 public virtual XmlDocument OwnerDocument
259                 {
260                         get
261                         {
262                                 return FOwnerDocument;
263                         }
264                 }
265
266                 /// <summary>
267                 /// Returns the parent node, or null
268                 /// Return value depends on superclass node type
269                 /// </summary>
270                 public virtual XmlNode ParentNode
271                 {
272                         get
273                         {
274                                 return _parent;
275                         }
276                 }
277                 
278                 /// <summary>
279                 /// set/get the namespace prefix for this node, or 
280                 /// string.empty if it does not exist
281                 /// </summary>
282                 public virtual string Prefix 
283                 {
284                         get
285                         {
286                                 return Fprefix;
287                         }
288                         
289                         set
290                         {
291                                 // TODO - validation on XmlNode.Prefix {set;}? (no)
292                                 Fprefix = value;
293                         }
294                 }
295
296                 /// <summary>
297                 /// The preceding XmlNode or null
298                 /// </summary>
299                 public virtual XmlNode PreviousSibling {
300                         get
301                         {
302                                 if (_parent != null)
303                                 {
304                                         XmlNodeListAsArrayList children = _parent.ChildNodes as XmlNodeListAsArrayList;
305                                         int ourIndex = children.data.IndexOf(this);
306                                         return children[ourIndex - 1];
307                                 }
308                                 else
309                                         return null;
310                         }
311                 }
312
313                 /// <summary>
314                 /// Get/Set the value for this node
315                 /// </summary>
316                 public virtual string Value 
317                 {
318                         get
319                         {
320                                 return Fvalue;
321                         }
322                         
323                         set
324                         {
325                                 Fvalue = value;
326                         }
327                 }
328
329                 //=====================================================================
330                 //======= Methods =====================================================
331                 //=====================================================================
332                 /// <summary>
333                 /// Appends the specified node to the end of the child node list
334                 /// </summary>
335                 /// <param name="newChild"></param>
336                 /// <returns></returns>
337                 public virtual XmlNode AppendChild (XmlNode newChild)
338                 {
339                         ((XmlNodeListAsArrayList)ChildNodes).Add(newChild);
340                         return newChild;
341                 }
342
343                 /// <summary>
344                 /// Return a clone of this node
345                 /// </summary>
346                 /// <returns></returns>
347                 public virtual object Clone()
348                 {
349                         // TODO - implement XmlNode.Clone() as object
350                         throw new NotImplementedException("object XmlNode.Clone() not implmented");
351                 }
352
353                 /// <summary>
354                 /// Return a clone of the node
355                 /// </summary>
356                 /// <param name="deep">Make copy of all children</param>
357                 /// <returns>Cloned node</returns>
358                 public abstract XmlNode CloneNode( bool deep);
359
360                 /// <summary>
361                 /// Return an XPathNavigator for navigating this node
362                 /// </summary>
363                 /// <returns></returns>
364                 public System.Xml.XPath.XPathNavigator CreateNavigator()
365                 {
366                         // TODO - implement CreateNavigator()
367                         throw new NotImplementedException();
368                 }
369
370                 /// <summary>
371                 /// Provide support for "for each" 
372                 /// </summary>
373                 /// <returns></returns>
374                 public IEnumerator GetEnumerator()
375                 {
376                         return _childNodes.data.GetEnumerator();
377                 }
378
379                 /// <summary>
380                 /// Look up the closest namespace for this node that is in scope for the given prefix
381                 /// </summary>
382                 /// <param name="prefix"></param>
383                 /// <returns>Namespace URI</returns>
384                 public virtual string GetNamespaceOfPrefix(string prefix)
385                 {
386                         // TODO - implement GetNamespaceOfPrefix()
387                         throw new NotImplementedException();
388                 }
389
390                 /// <summary>
391                 /// Get the closest xmlns declaration for the given namespace URI that is in scope.
392                 /// Returns the prefix defined in that declaration.
393                 /// </summary>
394                 /// <param name="namespaceURI"></param>
395                 /// <returns></returns>
396                 public virtual string GetPrefixOfNamespace(string namespaceURI)
397                 {
398                         // TODO - implement GetPrefixOfNamespace
399                         throw new NotImplementedException();
400                 }
401                 
402                 /// <summary>
403                 /// Insert newChild directly after the reference node.
404                 /// If refChild is null, newChild is inserted at the beginning of childnodes.
405                 /// If newChild is a document fragment, all nodes are inserted after refChild.
406                 /// If newChild is already in the tree, it is first removed.
407                 /// </summary>
408                 /// <exception cref="ArgumentException">NewChild was created from differant document.
409                 /// RefChild not a child of this node or null.
410                 /// Node is read-only</exception>
411                 /// <exception cref="InvalidOperationException">Node is of type that does not have children
412                 /// Node to insert is an ancestor of this node.</exception>
413                 /// <param name="newChild">Child node to insert.</param>
414                 /// <param name="refChild">Reference node to insert after</param>
415                 /// <returns>Removed node, or null if no node removed.</returns>
416                 public virtual XmlNode InsertAfter(XmlNode newChild, XmlNode refChild)
417                 {
418                         // Checks parent not ancestor, arguments valid, etc.  Throws exception on error
419                         InsertionCheck(newChild, refChild);
420
421                         // Scan the node list, looking for refChild and seeing if newChild is in the list
422                         // Note that if refNode is null (prepend), we don't want to do the .Equals(null)
423                         XmlNode retval = null;
424                         int refNodeIndex = -1;
425                         
426                         for (int i = 0; i < _childNodes.Count; i++)
427                         {
428                                 XmlNode e = _childNodes.data[i] as XmlNode;
429                                 if (e.Equals(newChild))
430                                 {
431                                         retval = e;
432                                         FOwnerDocument.onNodeRemoving(newChild, newChild.ParentNode);
433                                         _childNodes.data.RemoveAt(i);
434                                         newChild.setParent(null);
435                                         FOwnerDocument.onNodeRemoved(newChild, null);
436                                         break;
437                 
438                                 }
439
440                                 if ( (refChild != null ) & ( e.Equals(refChild) ) )
441                                 {
442                                         refNodeIndex = i;
443
444                                         if (retval != null)
445                                                 break;
446                                 }
447                         }
448
449                         if ( ( refNodeIndex == -1 ) & (refChild != null) )
450                                 throw new ArgumentException("Reference node not found (and not null) in call to XmlNode.InsertAfter()");
451
452                         FOwnerDocument.onNodeInserting(newChild, this);
453
454                         if (refChild == null)
455                                 refNodeIndex = 0;
456                         else
457                                 refNodeIndex++;                 // insert after reference...
458
459                         if (newChild.NodeType == XmlNodeType.DocumentFragment)
460                         {
461                                 // Insert all children, starting from refNodeIndex (0,1,2...n)
462                                 for (int i = 0; i < newChild.ChildNodes.Count; i++)
463                                 {
464                                         XmlNode e = newChild.ChildNodes[i] as XmlNode;
465                                         FOwnerDocument.onNodeInserting(e, this);
466                                         _childNodes.data.Insert(refNodeIndex, newChild.ChildNodes[i]);
467                                         e.setParent(this);
468                                         FOwnerDocument.onNodeInserted(newChild, this);
469                                         refNodeIndex ++;
470                                 }
471                         }
472                         else
473                         {
474                                 FOwnerDocument.onNodeInserting(newChild, this);
475                                 _childNodes.data.Insert(refNodeIndex, newChild);
476                                 newChild.setParent(this);
477                                 FOwnerDocument.onNodeInserted(newChild, this);
478                         }
479
480                         return retval;
481                 }
482                 
483                 /// <summary>
484                 /// Insert newChild directly before the reference node.
485                 /// If refChild is null, newChild is inserted at the end of childnodes.
486                 /// If newChild is a document fragment, all nodes are inserted before refChild.
487                 /// If newChild is already in the tree, it is first removed.
488                 /// </summary>
489                 /// <exception cref="ArgumentException">NewChild was created from different document.
490                 /// RefChild not a child of this node, or is null.
491                 /// Node is read-only</exception>
492                 /// <exception cref="InvalidOperationException">Node is of type that does not have children.
493                 /// Node to insert is an ancestor of this node.</exception>
494                 /// <param name="newChild">Child node to insert.</param>
495                 /// <param name="refChild">Reference node to insert after</param>
496                 /// <returns>Removed node, or null if no node removed.</returns>
497                 public virtual XmlNode InsertBefore(XmlNode newChild, XmlNode refChild)
498                 {
499                         // Checks parent not ancestor, arguments valid, etc.  Throws exception on error
500                         InsertionCheck(newChild, refChild);
501
502                         // Scan the node list, looking for refChild and seeing if newChild is in the list
503                         XmlNode retval = null;
504                         int refNodeIndex = -1;
505                         
506                         for (int i = 0; i < _childNodes.Count; i++)
507                         {
508                                 XmlNode e = _childNodes.data[i] as XmlNode;
509                                 if (e.Equals(newChild))
510                                 {
511                                         retval = e;
512                                         FOwnerDocument.onNodeRemoving(newChild, newChild.ParentNode);
513                                         _childNodes.data.RemoveAt(i);
514                                         newChild.setParent(null);
515                                         FOwnerDocument.onNodeRemoved(newChild, null);
516                                         break;
517                                 }
518
519                                 if ( (refChild != null ) & ( e.Equals(refChild) ) )
520                                 {
521                                         refNodeIndex = i;
522
523                                         if (retval != null)
524                                                 break;
525                                 }
526                         }
527
528                         if ( ( refNodeIndex == -1 ) & (refChild != null) )
529                                 throw new ArgumentException("Reference node not found (and not null) in call to XmlNode.InsertAfter()");
530
531                         if (refChild == null)
532                                 refNodeIndex = _childNodes.Count;
533
534                         if (newChild.NodeType == XmlNodeType.DocumentFragment)
535                         {
536                                 // Insert all children, starting from refNodeIndex (0,1,2...n)
537                                 for (int i = 0; i < newChild.ChildNodes.Count; i++)
538                                 {
539                                         XmlNode e = newChild.ChildNodes[i] as XmlNode;
540                                         FOwnerDocument.onNodeInserting(e, this);
541                                         _childNodes.data.Insert(refNodeIndex, newChild.ChildNodes[i]);
542                                         e.setParent(this);
543                                         FOwnerDocument.onNodeInserted(newChild, this);
544                                         refNodeIndex ++;
545                                 }
546                         }
547                         else
548                         {
549                                 newChild.OwnerDocument.onNodeInserting(newChild, this);
550                                 _childNodes.data.Insert(refNodeIndex, newChild);
551                                 newChild.setParent(this);
552                                 newChild.OwnerDocument.onNodeInserted(newChild, this);
553                         }
554
555                         return retval;
556                 }
557
558                 /// <summary>
559                 /// Put all nodes under this node in "normal" form
560                 /// Whatever that means...
561                 /// </summary>
562                 public virtual void Normalize()
563                 {
564                         // TODO - Implement Normalize()
565                         throw new NotImplementedException();
566                 }
567
568                 /// <summary>
569                 /// Add the specified child to the beginning of the child node list
570                 /// </summary>
571                 /// <param name="newChild">Node to add</param>
572                 /// <returns>The node added</returns>
573                 public virtual XmlNode PrependChild(XmlNode newChild)
574                 {
575                         return InsertAfter(newChild, null);
576                 }
577
578                 /// <summary>
579                 /// Remove all children and attributes
580                 /// </summary>
581                 public virtual void RemoveAll()
582                 {
583                         if (_childNodes == null)
584                                 return;
585                         else
586                         {
587                                 // Remove in order, 0..n
588                                 while (_childNodes.Count > 0)
589                                 {
590                                         XmlNode e = _childNodes[0];
591                                         FOwnerDocument.onNodeRemoving(e, this);
592                                         e.setParent(null);
593                                         _childNodes.data.RemoveAt(0);
594                                         FOwnerDocument.onNodeRemoved(e, null);
595                                 }
596                         }
597                 }
598
599                 /// <summary>
600                 /// Remove specified child node
601                 /// </summary>
602                 /// <param name="oldChild"></param>
603                 /// <returns>Removed node</returns>
604                 public virtual XmlNode RemoveChild(XmlNode oldChild)
605                 {
606                         // TODO - implement RemoveChild(oldChild)
607                         throw new NotImplementedException();
608                 }
609
610                 /// <summary>
611                 /// Select a list of nodes matching the xpath
612                 /// </summary>
613                 /// <param name="xpath"></param>
614                 /// <returns>matching nodes</returns>
615                 public XmlNodeList SelectNodes( string xpath)
616                 {
617                         // TODO - imlement SelectNodes(xpath)
618                         throw new NotImplementedException();
619                 }
620
621                 /// <summary>
622                 /// Select a list of nodes matching the xpath.  Any prefixes are resolved
623                 /// using the passed namespace manager
624                 /// </summary>
625                 /// <param name="xpath"></param>
626                 /// <param name="nsmgr"></param>
627                 /// <returns></returns>
628                 public XmlNodeList SelectNodes(string xpath, XmlNamespaceManager nsmgr)
629                 {
630                         // TODO - implement SelectNodes(xpath, nsmgr)
631                         throw new NotImplementedException();
632                 }
633
634                 /// <summary>
635                 /// Selects the first node that matches xpath
636                 /// </summary>
637                 /// <param name="?"></param>
638                 /// <returns></returns>
639                 public XmlNode SelectSingleNode(string xpatch)
640                 {
641                         // TODO - implement SelectSingeNode(xpath)
642                         throw new NotImplementedException();
643                 }
644
645                 /// <summary>
646                 /// Returns the first node that matches xpath
647                 /// Uses the passed namespace manager to resolve namespace URI's
648                 /// </summary>
649                 /// <param name="xpath"></param>
650                 /// <param name="nsmgr"></param>
651                 /// <returns></returns>
652                 public XmlNode SelectSingleNode(string xpath, XmlNamespaceManager nsmgr)
653                 {
654                         // Implement SelectSingleNode(xpath, nsmgr)
655                         throw new NotImplementedException();
656                 }
657
658                 /// <summary>
659                 /// Tests if the DOM implementation supports the passed feature
660                 /// </summary>
661                 /// <param name="feature"></param>
662                 /// <param name="version"></param>
663                 /// <returns></returns>
664                 public virtual bool Supports(string feature, string version)
665                 {
666                         //TODO - implement Supports(feature, version)
667                         throw new NotImplementedException();
668                 }
669
670                 /// <summary>
671                 /// Returns a string representation of the current node and it's children
672                 /// </summary>
673                 /// <returns></returns>
674                 public override string ToString()
675                 {
676                         // TODO - implement ToString()
677                         throw new NotImplementedException();
678                 }
679
680                 /// <summary>
681                 /// Saves all children of the current node to the passed writer
682                 /// </summary>
683                 /// <param name="w"></param>
684                 public abstract void WriteContentTo(XmlWriter w);
685                 
686                 /// <summary>
687                 /// Saves the current node to writer w
688                 /// </summary>
689                 /// <param name="w"></param>
690                 public abstract void WriteTo(XmlWriter w);
691
692                 //======= Internal methods    ===============================================
693                 /// <summary>
694                 /// accessor {set;} for parentNode only visible internally.
695                 /// </summary>
696                 /// <param name="newParent">new parent node.</param>
697                 internal void setParent( XmlNode newParent)
698                 {
699                         _parent = newParent;
700                 }
701                 
702                 //======= Protected methods    ==============================================
703
704                 //======= Private Methods ===================================================
705                 /// <summary>
706                 /// Helper function to perform checks required before insrting a node.
707                 /// Throws applicable exceptions on error.
708                 /// </summary>
709                 /// <param name="newChild"></param>
710                 /// <param name="refChild"></param>
711                 private void InsertionCheck( XmlNode newChild, XmlNode refChild)
712                 {
713                         if (newChild == null)
714                                 throw new ArgumentNullException("Null newNode passed to InsertAfter()");
715
716                         if (newChild.Equals(this))
717                                 throw new ArgumentException("Cannot insert node onto itself");
718
719                         if (NodeType != XmlNodeType.Document && !FOwnerDocument.Equals( newChild.OwnerDocument) )
720                                 throw new ArgumentException("Reference node has different owner document than this node");
721                 
722                         XmlDocument ownerDocument = newChild.OwnerDocument;
723
724                         if (ownerDocument.IsReadOnly )
725                                 throw new ArgumentException("Operation not supported - tree is read-only");
726
727                         //Check that insert node is not in our path to the root
728                         XmlNode curParent = _parent;
729                         while ( (curParent != null) & (! ownerDocument.Equals(curParent) ))
730                         {
731                                 if (curParent.Equals(newChild) )
732                                         throw new ArgumentException("Cannot insert ancestor a node");
733                                 curParent = curParent.ParentNode;
734                         }
735                 }
736
737                 // Constructors
738                 //===========================================================================
739                 //When we're first created, we won't know parent, etc.
740                 internal XmlNode( XmlDocument aOwnerDoc )
741                 {
742                         // Don't create childnodes object, since not all derived classes have children
743                         FOwnerDocument = aOwnerDoc;
744                 }
745
746         }       // XmlNode
747 }       // using namespace System.Xml
748
749