2003-01-26 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
242                 public virtual XmlNode Clone ()
243                 {
244                         // By MS document, it is equivalent to CloneNode(true).
245                         return this.CloneNode (true);
246                 }
247
248                 public abstract XmlNode CloneNode (bool deep);
249
250                 [MonoTODO]
251                 public XPathNavigator CreateNavigator ()
252                 {
253                         return new XmlDocumentNavigator (this);
254                 }
255
256                 public IEnumerator GetEnumerator ()
257                 {
258                         return new XmlNodeListChildren (this).GetEnumerator ();
259                 }
260
261                 [MonoTODO("performance problem.")]
262                 public virtual string GetNamespaceOfPrefix (string prefix)
263                 {
264                         XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
265                         return nsmgr.LookupNamespace (prefix);
266                 }
267
268                 [MonoTODO("performance problem.")]
269                 public virtual string GetPrefixOfNamespace (string namespaceURI)
270                 {
271                         XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
272                         string ns = nsmgr.LookupPrefix (namespaceURI);
273                         return (ns != null) ? ns : String.Empty;
274                 }
275
276                 object ICloneable.Clone ()
277                 {
278                         return Clone ();
279                 }
280
281                 IEnumerator IEnumerable.GetEnumerator ()
282                 {
283                         return GetEnumerator ();
284                 }
285
286                 public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
287                 {
288                         // I assume that insertAfter(n1, n2) equals to InsertBefore(n1, n2.PreviousSibling).
289
290                         // I took this way because rather than calling InsertAfter() from InsertBefore()
291                         //   because current implementation of 'NextSibling' looks faster than 'PreviousSibling'.
292                         XmlNode argNode = null;
293                         if(refChild != null)
294                                 argNode = refChild.NextSibling;
295                         else if(ChildNodes.Count > 0)
296                                 argNode = FirstChild;
297                         return InsertBefore (newChild, argNode);
298                 }
299
300                 [MonoTODO("If inserted node is entity reference, then check conforming entity. Wait for DTD implementation.")]
301                 public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
302                 {
303                         XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
304
305                         if (NodeType == XmlNodeType.Document ||
306                             NodeType == XmlNodeType.Element ||
307                             NodeType == XmlNodeType.Attribute ||
308                             NodeType == XmlNodeType.DocumentFragment) {
309                                 if (IsReadOnly)
310                                         throw new ArgumentException ("The specified node is readonly.");
311
312                                 if (newChild.OwnerDocument != ownerDoc)
313                                         throw new ArgumentException ("Can't append a node created by another document.");
314
315                                 if (refChild != null && newChild.OwnerDocument != refChild.OwnerDocument)
316                                                 throw new ArgumentException ("argument nodes are on the different documents.");
317
318                                 // This check is done by MS.NET 1.0, but isn't done for MS.NET 1.1. 
319                                 // Skip this check in the meantime...
320 //                              if(this == ownerDoc && ownerDoc.DocumentElement != null && (newChild is XmlElement))
321 //                                      throw new XmlException ("multiple document element not allowed.");
322
323                                 // checking validity finished. then appending...
324
325                                 ownerDoc.onNodeInserting (newChild, this);
326
327                                 if(newChild.ParentNode != null)
328                                         newChild.ParentNode.RemoveChild (newChild);
329
330                                 if(newChild.NodeType == XmlNodeType.DocumentFragment) {
331                                         int x = newChild.ChildNodes.Count;
332                                         for(int i=0; i<x; i++) {
333                                                 XmlNode n = newChild.ChildNodes [0];
334                                                 this.InsertBefore (n, refChild);        // recursively invokes events. (It is compatible with MS implementation.)
335                                         }
336                                 }
337                                 else {
338                                         XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
339                                         XmlLinkedNode lastLinkedChild = LastLinkedChild;
340
341                                         newLinkedChild.parentNode = this;
342
343                                         if(refChild == null) {
344                                                 // append last, so:
345                                                 // * set nextSibling of previous lastchild to newChild
346                                                 // * set lastchild = newChild
347                                                 // * set next of newChild to firstChild
348                                                 if(LastLinkedChild != null) {
349                                                         XmlLinkedNode formerFirst = FirstChild as XmlLinkedNode;
350                                                         LastLinkedChild.NextLinkedSibling = newLinkedChild;
351                                                         LastLinkedChild = newLinkedChild;
352                                                         newLinkedChild.NextLinkedSibling = formerFirst;
353                                                 }
354                                                 else {
355                                                         LastLinkedChild = newLinkedChild;
356                                                         LastLinkedChild.NextLinkedSibling = newLinkedChild;     // FirstChild
357                                                 }
358                                         }
359                                         else {
360                                                 // append not last, so:
361                                                 // * if newchild is first, then set next of lastchild is newChild.
362                                                 //   otherwise, set next of previous sibling to newChild
363                                                 // * set next of newChild to refChild
364                                                 XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
365                                                 if(prev == null)
366                                                         LastLinkedChild.NextLinkedSibling = newLinkedChild;
367                                                 else
368                                                         prev.NextLinkedSibling = newLinkedChild;
369                                                 newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
370                                         }
371                                         ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
372                                 }
373                                 return newChild;
374                         } 
375                         else
376                                 throw new InvalidOperationException ();
377                 }
378
379                 [MonoTODO]
380                 public virtual void Normalize ()
381                 {
382                         throw new NotImplementedException ();
383                 }
384
385                 public virtual XmlNode PrependChild (XmlNode newChild)
386                 {
387                         return InsertAfter (newChild, null);
388                 }
389
390                 public virtual void RemoveAll ()
391                 {
392                         XmlNode next = null;
393                         for (XmlNode node = FirstChild; node != null; node = next) {
394                                 next = node.NextSibling;
395                                 RemoveChild (node);
396                         }
397                 }
398
399                 public virtual XmlNode RemoveChild (XmlNode oldChild)
400                 {
401                         XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
402                         if(oldChild.ParentNode != this)
403                                 throw new XmlException ("specified child is not child of this node.");
404
405                         ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
406
407                         if (NodeType == XmlNodeType.Document || NodeType == XmlNodeType.Element || NodeType == XmlNodeType.Attribute || NodeType == XmlNodeType.DocumentFragment) {
408                                 if (IsReadOnly)
409                                         throw new ArgumentException ();
410
411                                 if (Object.ReferenceEquals (LastLinkedChild, LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (LastLinkedChild, oldChild))
412                                         LastLinkedChild = null;
413                                 else {
414                                         XmlLinkedNode oldLinkedChild = (XmlLinkedNode)oldChild;
415                                         XmlLinkedNode beforeLinkedChild = LastLinkedChild;
416                                         
417                                         while (!Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, LastLinkedChild) && !Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild))
418                                                 beforeLinkedChild = beforeLinkedChild.NextLinkedSibling;
419
420                                         if (!Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild))
421                                                 throw new ArgumentException ();
422
423                                         beforeLinkedChild.NextLinkedSibling = oldLinkedChild.NextLinkedSibling;
424                                         oldLinkedChild.NextLinkedSibling = null;
425                                  }
426
427                                 ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
428                                 oldChild.parentNode = null;     // clear parent 'after' above logic.
429
430                                 return oldChild;
431                         } 
432                         else
433                                 throw new ArgumentException ();
434                 }
435
436                 public virtual XmlNode ReplaceChild (XmlNode newChild, XmlNode oldChild)
437                 {
438                         if(oldChild.ParentNode != this)
439                                 throw new InvalidOperationException ("oldChild is not a child of this node.");
440                         XmlNode parent = this.ParentNode;
441                         while(parent != null) {
442                                 if(newChild == parent)
443                                         throw new InvalidOperationException ("newChild is ancestor of this node.");
444                                 parent = parent.ParentNode;
445                         }
446                         foreach(XmlNode n in ChildNodes) {
447                                 if(n == oldChild) {
448                                         XmlNode prev = oldChild.PreviousSibling;
449                                         RemoveChild (oldChild);
450                                         InsertAfter (newChild, prev);
451                                         break;
452                                 }
453                         }
454                         return oldChild;
455                 }
456
457                 public XmlNodeList SelectNodes (string xpath)
458                 {
459                         return SelectNodes (xpath, null);
460                 }
461
462                 [MonoTODO]
463                 public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
464                 {
465                         XPathNavigator nav = CreateNavigator ();
466                         XPathExpression expr = nav.Compile (xpath);
467                         if (nsmgr != null)
468                                 expr.SetContext (nsmgr);
469                         XPathNodeIterator iter = nav.Select (expr);
470                         ArrayList rgNodes = new ArrayList ();
471                         while (iter.MoveNext ())
472                         {
473                                 rgNodes.Add (((XmlDocumentNavigator) iter.Current).Node);
474                         }
475                         return new XmlNodeArrayList (rgNodes);
476                 }
477
478                 public XmlNode SelectSingleNode (string xpath)
479                 {
480                         return SelectSingleNode (xpath, null);
481                 }
482
483                 [MonoTODO]
484                 public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
485                 {
486                         XPathNavigator nav = CreateNavigator ();
487                         XPathExpression expr = nav.Compile (xpath);
488                         if (nsmgr != null)
489                                 expr.SetContext (nsmgr);
490                         XPathNodeIterator iter = nav.Select (expr);
491                         if (!iter.MoveNext ())
492                                 return null;
493                         return ((XmlDocumentNavigator) iter.Current).Node;
494                 }
495
496                 internal void SetParentNode (XmlNode parent)
497                 {
498                         parentNode = parent;
499                 }
500
501                 [MonoTODO]
502                 public virtual bool Supports (string feature, string version)
503                 {
504                         throw new NotImplementedException ();
505                 }
506
507                 public abstract void WriteContentTo (XmlWriter w);
508
509                 public abstract void WriteTo (XmlWriter w);
510
511                 // It parses this and all the ancestor elements,
512                 // find 'xmlns' declarations, stores and then return them.
513                 // TODO: tests
514                 internal XmlNamespaceManager ConstructNamespaceManager ()
515                 {
516                         XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;
517                         XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable);
518                         XmlElement el = null;
519                         switch(this.NodeType) {
520                         case XmlNodeType.Attribute:
521                                 el = ((XmlAttribute)this).OwnerElement;
522                                 break;
523                         case XmlNodeType.Element:
524                                 el = this as XmlElement;
525                                 break;
526                         default:
527                                 el = this.ParentNode as XmlElement;
528                                 break;
529                         }
530
531                         while(el != null) {
532                                 foreach(XmlAttribute attr in el.Attributes) {
533                                         if(attr.Prefix == "xmlns" || (attr.Name == "xmlns" && attr.Prefix == String.Empty)) {
534                                                 if(nsmgr.LookupNamespace (attr.LocalName) == null )
535                                                         nsmgr.AddNamespace (attr.LocalName, attr.Value);
536                                         }
537                                 }
538                                 // When reached to document, then it will set null value :)
539                                 el = el.ParentNode as XmlElement;
540                         }
541                         return nsmgr;
542                 }
543                 #endregion
544         }
545 }