2002-12-24 Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
[mono.git] / mcs / class / System.XML / System.Xml / XmlNode.cs
1 //
2 // System.Xml.XmlNode
3 //
4 // Author:
5 //   Kral Ferch <kral_ferch@hotmail.com>
6 //   Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
7 //
8 // (C) 2002 Kral Ferch
9 // (C) 2002 Atsushi Enomoto
10 //
11
12 using System;
13 using System.Collections;
14 using System.IO;
15 using System.Text;
16 using System.Xml.XPath;
17
18 namespace System.Xml
19 {
20         public abstract class XmlNode : ICloneable, IEnumerable, IXPathNavigable
21         {
22                 #region Fields
23
24                 XmlDocument ownerDocument;
25                 XmlNode parentNode;
26
27                 #endregion
28
29                 #region Constructors
30
31                 internal XmlNode (XmlDocument ownerDocument)
32                 {
33                         this.ownerDocument = ownerDocument;
34                 }
35
36                 #endregion
37
38                 #region Properties
39
40                 public virtual XmlAttributeCollection Attributes {
41                         get { return null; }
42                 }
43
44                 public virtual string BaseURI {
45                         get {
46                                 // Isn't it conformant to W3C XML Base Recommendation?
47                                 // As far as I tested, there are not...
48                                 return (ParentNode != null) ? ParentNode.BaseURI : OwnerDocument.BaseURI;
49                         }
50                 }
51
52                 public virtual XmlNodeList ChildNodes {
53                         get {
54                                 return new XmlNodeListChildren (this);
55                         }
56                 }
57
58                 public virtual XmlNode FirstChild {
59                         get {
60                                 if (LastChild != null) {
61                                         return LastLinkedChild.NextLinkedSibling;
62                                 }
63                                 else {
64                                         return null;
65                                 }
66                         }
67                 }
68
69                 public virtual bool HasChildNodes {
70                         get { return LastChild != null; }
71                 }
72
73                 [MonoTODO("confirm whether this way is right for each not-overriden types.")]
74                 public virtual string InnerText {
75                         get {
76                                 StringBuilder builder = new StringBuilder ();
77                                 AppendChildValues (this, builder);
78                                 return builder.ToString ();
79                         }
80
81                         set { throw new NotImplementedException (); }
82                 }
83
84                 private void AppendChildValues (XmlNode parent, StringBuilder builder)
85                 {
86                         XmlNode node = parent.FirstChild;
87
88                         while (node != null) {
89                                 if (node.NodeType == XmlNodeType.Text)
90                                         builder.Append (node.Value);
91                                 AppendChildValues (node, builder);
92                                 node = node.NextSibling;
93                         }
94                 }
95
96                 [MonoTODO("Setter.")]
97                 public virtual string InnerXml {
98                         get {
99                                 StringWriter sw = new StringWriter ();
100                                 XmlTextWriter xtw = new XmlTextWriter (sw);
101
102                                 WriteContentTo (xtw);
103
104                                 return sw.GetStringBuilder ().ToString ();
105                         }
106
107                         set { throw new NotImplementedException (); }
108                 }
109
110                 public virtual bool IsReadOnly {
111                         get { return false; }
112                 }
113
114                 [System.Runtime.CompilerServices.IndexerName("Item")]
115                 public virtual XmlElement this [string name] {
116                         get { 
117                                 foreach (XmlNode node in ChildNodes) {
118                                         if ((node.NodeType == XmlNodeType.Element) &&
119                                             (node.Name == name)) {
120                                                 return (XmlElement) node;
121                                         }
122                                 }
123
124                                 return null;
125                         }
126                 }
127
128                 [System.Runtime.CompilerServices.IndexerName("Item")]
129                 public virtual XmlElement this [string localname, string ns] {
130                         get { 
131                                 foreach (XmlNode node in ChildNodes) {
132                                         if ((node.NodeType == XmlNodeType.Element) &&
133                                             (node.LocalName == localname) && 
134                                             (node.NamespaceURI == ns)) {
135                                                 return (XmlElement) node;
136                                         }
137                                 }
138
139                                 return null;
140                         }
141                 }
142
143                 public virtual XmlNode LastChild {
144                         get { return LastLinkedChild; }
145                 }
146
147                 internal virtual XmlLinkedNode LastLinkedChild {
148                         get { return null; }
149                         set { }
150                 }
151
152                 public abstract string LocalName { get; }
153
154                 public abstract string Name     { get; }
155
156                 public virtual string NamespaceURI {
157                         get { return String.Empty; }
158                 }
159
160                 public virtual XmlNode NextSibling {
161                         get { return null; }
162                 }
163
164                 public abstract XmlNodeType NodeType { get;     }
165
166                 internal virtual XPathNodeType XPathNodeType {
167                         get {
168                                 return (XPathNodeType) (-1);
169                         }
170                 }
171
172                 public virtual string OuterXml {
173                         get {
174                                 StringWriter sw = new StringWriter ();
175                                 XmlTextWriter xtw = new XmlTextWriter (sw);
176
177                                 WriteTo (xtw);
178
179                                 return sw.GetStringBuilder ().ToString ();
180                         }
181                 }
182
183                 public virtual XmlDocument OwnerDocument {
184                         get { return ownerDocument; }
185                 }
186
187                 public virtual XmlNode ParentNode {
188                         get { return parentNode; }
189                 }
190
191                 public virtual string Prefix {
192                         get { return String.Empty; }
193                         set {}
194                 }
195
196                 public virtual XmlNode PreviousSibling {
197                         get { return null; }
198                 }
199
200                 public virtual string Value {
201                         get { return null; }
202                         set { throw new InvalidOperationException ("This node does not have a value"); }
203                 }
204
205                 internal virtual string XmlLang {
206                         get {
207                                 if(Attributes != null)
208                                         foreach(XmlAttribute attr in Attributes)
209                                                 if(attr.Name == "xml:lang")
210                                                         return attr.Value;
211                                 return (ParentNode != null) ? ParentNode.XmlLang : OwnerDocument.XmlLang;
212                         }
213                 }
214
215                 internal virtual XmlSpace XmlSpace {
216                         get {
217                                 if(Attributes != null) {
218                                         foreach(XmlAttribute attr in Attributes) {
219                                                 if(attr.Name == "xml:space") {
220                                                         switch(attr.Value) {
221                                                         case "preserve": return XmlSpace.Preserve;
222                                                         case "default": return XmlSpace.Default;
223                                                         }
224                                                         break;
225                                                 }
226                                         }
227                                 }
228                                 return (ParentNode != null) ? ParentNode.XmlSpace : OwnerDocument.XmlSpace;
229                         }
230                 }
231
232                 #endregion
233
234                 #region Methods
235
236                 public virtual XmlNode AppendChild (XmlNode newChild)
237                 {
238                         // I assume that AppendChild(n) equals to InsertAfter(n, this.LastChild) or InsertBefore(n, null)
239                         return InsertBefore (newChild, null);
240
241                         // Below are formerly used logic.
242 /*                      XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
243
244                         if (NodeType == XmlNodeType.Document || NodeType == XmlNodeType.Element || NodeType == XmlNodeType.Attribute || NodeType == XmlNodeType.DocumentFragment) {
245                                 
246                                 if (IsReadOnly)
247                                         throw new ArgumentException ("The specified node is readonly.");
248
249                                 if (newChild.OwnerDocument != ownerDoc)
250                                         throw new ArgumentException ("Can't append a node created by another document.");
251
252                                 // checking validity finished. then appending...
253
254                                 ownerDoc.onNodeInserting (newChild, this);
255
256                                 if(newChild.ParentNode != null)
257                                         newChild.ParentNode.RemoveChild(newChild);
258
259                                 if(newChild.NodeType == XmlNodeType.DocumentFragment)
260                                 {
261                                         int x = newChild.ChildNodes.Count;
262                                         for(int i=0; i<x; i++)
263                                         {
264                                                 // When this logic became to remove children in order, then index will have never to increments.
265                                                 XmlNode n = newChild.ChildNodes [0];
266                                                 this.AppendChild(n);    // recursively invokes events. (It is compatible with MS implementation.)
267                                         }
268                                 }
269                                 else
270                                 {
271                                         XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
272                                         XmlLinkedNode lastLinkedChild = LastLinkedChild;
273
274                                         newLinkedChild.parentNode = this;
275                                 
276                                         if (lastLinkedChild != null) 
277                                         {
278                                                 newLinkedChild.NextLinkedSibling = lastLinkedChild.NextLinkedSibling;
279                                                 lastLinkedChild.NextLinkedSibling = newLinkedChild;
280                                         } 
281                                         else
282                                                 newLinkedChild.NextLinkedSibling = newLinkedChild;
283                                 
284                                         LastLinkedChild = newLinkedChild;
285
286                                         ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
287
288                                 }
289                                 return newChild;
290                         } else
291                                 throw new InvalidOperationException();
292 */              }
293
294                 public virtual XmlNode Clone ()
295                 {
296                         // By MS document, it is equivalent to CloneNode(true).
297                         return this.CloneNode (true);
298                 }
299
300                 public abstract XmlNode CloneNode (bool deep);
301
302                 [MonoTODO]
303                 public XPathNavigator CreateNavigator ()
304                 {
305                         return new XmlDocumentNavigator (this);
306                 }
307
308                 public IEnumerator GetEnumerator ()
309                 {
310                         return new XmlNodeListChildren (this).GetEnumerator ();
311                 }
312
313                 [MonoTODO("performance problem.")]
314                 public virtual string GetNamespaceOfPrefix (string prefix)
315                 {
316                         XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
317                         return nsmgr.LookupNamespace (prefix);
318                 }
319
320                 [MonoTODO("performance problem.")]
321                 public virtual string GetPrefixOfNamespace (string namespaceURI)
322                 {
323                         XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
324                         return nsmgr.LookupPrefix (namespaceURI);
325                 }
326
327                 object ICloneable.Clone ()
328                 {
329                         return Clone ();
330                 }
331
332                 IEnumerator IEnumerable.GetEnumerator ()
333                 {
334                         return GetEnumerator ();
335                 }
336
337                 public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
338                 {
339                         // I assume that insertAfter(n1, n2) equals to InsertBefore(n1, n2.PreviousSibling).
340
341                         // I took this way because rather than calling InsertAfter() from InsertBefore()
342                         //   because current implementation of 'NextSibling' looks faster than 'PreviousSibling'.
343                         XmlNode argNode = null;
344                         if(refChild != null)
345                                 argNode = refChild.NextSibling;
346                         else if(ChildNodes.Count > 0)
347                                 argNode = FirstChild;
348                         return InsertBefore (newChild, argNode);
349                 }
350
351                 [MonoTODO("If inserted node is entity reference, then check conforming entity. Wait for DTD implementation.")]
352                 public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
353                 {
354                         XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
355
356                         if (NodeType == XmlNodeType.Document ||
357                             NodeType == XmlNodeType.Element ||
358                             NodeType == XmlNodeType.Attribute ||
359                             NodeType == XmlNodeType.DocumentFragment) {
360                                 if (IsReadOnly)
361                                         throw new ArgumentException ("The specified node is readonly.");
362
363                                 if (newChild.OwnerDocument != ownerDoc)
364                                         throw new ArgumentException ("Can't append a node created by another document.");
365
366                                 if (refChild != null && newChild.OwnerDocument != refChild.OwnerDocument)
367                                                 throw new ArgumentException ("argument nodes are on the different documents.");
368
369                                 if (refChild != null && this == ownerDoc &&
370                                     ownerDoc.DocumentElement != null &&
371                                     (newChild is XmlElement ||
372                                      newChild is XmlCharacterData ||
373                                      newChild is XmlEntityReference))
374                                         throw new XmlException ("cannot insert this node to this position.");
375
376                                 // checking validity finished. then appending...
377
378                                 ownerDoc.onNodeInserting (newChild, this);
379
380                                 if(newChild.ParentNode != null)
381                                         newChild.ParentNode.RemoveChild (newChild);
382
383                                 if(newChild.NodeType == XmlNodeType.DocumentFragment) {
384                                         int x = newChild.ChildNodes.Count;
385                                         for(int i=0; i<x; i++) {
386                                                 XmlNode n = newChild.ChildNodes [0];
387                                                 this.InsertBefore (n, refChild);        // recursively invokes events. (It is compatible with MS implementation.)
388                                         }
389                                 }
390                                 else {
391                                         XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
392                                         XmlLinkedNode lastLinkedChild = LastLinkedChild;
393
394                                         newLinkedChild.parentNode = this;
395
396                                         if(refChild == null) {
397                                                 // append last, so:
398                                                 // * set nextSibling of previous lastchild to newChild
399                                                 // * set lastchild = newChild
400                                                 // * set next of newChild to firstChild
401                                                 if(LastLinkedChild != null) {
402                                                         XmlLinkedNode formerFirst = FirstChild as XmlLinkedNode;
403                                                         LastLinkedChild.NextLinkedSibling = newLinkedChild;
404                                                         LastLinkedChild = newLinkedChild;
405                                                         newLinkedChild.NextLinkedSibling = formerFirst;
406                                                 }
407                                                 else {
408                                                         LastLinkedChild = newLinkedChild;
409                                                         LastLinkedChild.NextLinkedSibling = newLinkedChild;     // FirstChild
410                                                 }
411                                         }
412                                         else {
413                                                 // append not last, so:
414                                                 // * if newchild is first, then set next of lastchild is newChild.
415                                                 //   otherwise, set next of previous sibling to newChild
416                                                 // * set next of newChild to refChild
417                                                 XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
418                                                 if(prev == null)
419                                                         LastLinkedChild.NextLinkedSibling = newLinkedChild;
420                                                 else
421                                                         prev.NextLinkedSibling = newLinkedChild;
422                                                 newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
423                                         }
424                                         ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
425                                 }
426                                 return newChild;
427                         } 
428                         else
429                                 throw new InvalidOperationException ();
430                 }
431
432                 [MonoTODO]
433                 public virtual void Normalize ()
434                 {
435                         throw new NotImplementedException ();
436                 }
437
438                 public virtual XmlNode PrependChild (XmlNode newChild)
439                 {
440                         return InsertAfter (newChild, null);
441                 }
442
443                 public virtual void RemoveAll ()
444                 {
445                         XmlNode next = null;
446                         for (XmlNode node = FirstChild; node != null; node = next) {
447                                 next = node.NextSibling;
448                                 RemoveChild (node);
449                         }
450                 }
451
452                 public virtual XmlNode RemoveChild (XmlNode oldChild)
453                 {
454                         XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
455                         if(oldChild.ParentNode != this)
456                                 throw new XmlException ("specified child is not child of this node.");
457
458                         ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
459
460                         if (NodeType == XmlNodeType.Document || NodeType == XmlNodeType.Element || NodeType == XmlNodeType.Attribute || NodeType == XmlNodeType.DocumentFragment) {
461                                 if (IsReadOnly)
462                                         throw new ArgumentException ();
463
464                                 if (Object.ReferenceEquals (LastLinkedChild, LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (LastLinkedChild, oldChild))
465                                         LastLinkedChild = null;
466                                 else {
467                                         XmlLinkedNode oldLinkedChild = (XmlLinkedNode)oldChild;
468                                         XmlLinkedNode beforeLinkedChild = LastLinkedChild;
469                                         
470                                         while (!Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, LastLinkedChild) && !Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild))
471                                                 beforeLinkedChild = beforeLinkedChild.NextLinkedSibling;
472
473                                         if (!Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild))
474                                                 throw new ArgumentException ();
475
476                                         beforeLinkedChild.NextLinkedSibling = oldLinkedChild.NextLinkedSibling;
477                                         oldLinkedChild.NextLinkedSibling = null;
478                                  }
479
480                                 ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
481                                 oldChild.parentNode = null;     // clear parent 'after' above logic.
482
483                                 return oldChild;
484                         } 
485                         else
486                                 throw new ArgumentException ();
487                 }
488
489                 [MonoTODO]
490                 public virtual XmlNode ReplaceChild (XmlNode newChild, XmlNode oldChild)
491                 {
492                         throw new NotImplementedException ();
493                 }
494
495                 public XmlNodeList SelectNodes (string xpath)
496                 {
497                         return SelectNodes (xpath, null);
498                 }
499
500                 [MonoTODO]
501                 public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
502                 {
503                         XPathNavigator nav = CreateNavigator ();
504                         XPathExpression expr = nav.Compile (xpath);
505                         if (nsmgr != null)
506                                 expr.SetContext (nsmgr);
507                         XPathNodeIterator iter = nav.Select (expr);
508                         ArrayList rgNodes = new ArrayList ();
509                         while (iter.MoveNext ())
510                         {
511                                 rgNodes.Add (((XmlDocumentNavigator) iter.Current).Node);
512                         }
513                         return new XmlNodeArrayList (rgNodes);
514                 }
515
516                 public XmlNode SelectSingleNode (string xpath)
517                 {
518                         return SelectSingleNode (xpath, null);
519                 }
520
521                 [MonoTODO]
522                 public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
523                 {
524                         XPathNavigator nav = CreateNavigator ();
525                         XPathExpression expr = nav.Compile (xpath);
526                         if (nsmgr != null)
527                                 expr.SetContext (nsmgr);
528                         XPathNodeIterator iter = nav.Select (expr);
529                         if (!iter.MoveNext ())
530                                 return null;
531                         return ((XmlDocumentNavigator) iter.Current).Node;
532                 }
533
534                 internal void SetParentNode (XmlNode parent)
535                 {
536                         parentNode = parent;
537                 }
538
539                 [MonoTODO]
540                 public virtual bool Supports (string feature, string version)
541                 {
542                         throw new NotImplementedException ();
543                 }
544
545                 public abstract void WriteContentTo (XmlWriter w);
546
547                 public abstract void WriteTo (XmlWriter w);
548
549                 // It parses this and all the ancestor elements,
550                 // find 'xmlns' declarations, stores and then return them.
551                 // TODO: tests
552                 internal XmlNamespaceManager ConstructNamespaceManager ()
553                 {
554                         XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;
555                         XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable);
556                         XmlElement el = null;
557                         switch(this.NodeType) {
558                         case XmlNodeType.Attribute:
559                                 el = ((XmlAttribute)this).OwnerElement;
560                                 break;
561                         case XmlNodeType.Element:
562                                 el = this as XmlElement;
563                                 break;
564                         default:
565                                 el = this.ParentNode as XmlElement;
566                                 break;
567                         }
568
569                         while(el != null) {
570                                 foreach(XmlAttribute attr in el.Attributes) {
571                                         if(attr.Prefix == "xmlns" || (attr.Name == "xmlns" && attr.Prefix == String.Empty)) {
572                                                 if(nsmgr.LookupNamespace (attr.LocalName) == null )
573                                                         nsmgr.AddNamespace (attr.LocalName, attr.Value);
574                                         }
575                                 }
576                                 // When reached to document, then it will set null value :)
577                                 el = el.ParentNode as XmlElement;
578                         }
579                         return nsmgr;
580                 }
581                 #endregion
582         }
583 }