2002-10-18 Duncan Mak <duncan@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml / XmlDocument.cs
1 //
2 // System.Xml.XmlDocument
3 //
4 // Authors:
5 //   Daniel Weber (daniel-weber@austin.rr.com)
6 //   Kral Ferch <kral_ferch@hotmail.com>
7 //   Jason Diamond <jason@injektilo.org>
8 //   Miguel de Icaza (miguel@ximian.com)
9 //   Duncan Mak (duncan@ximian.com)
10 //
11 // (C) 2001 Daniel Weber
12 // (C) 2002 Kral Ferch, Jason Diamond, Miguel de Icaza, Duncan Mak
13 //
14
15 using System;
16 using System.IO;
17 using System.Text;
18 using System.Xml.XPath;
19 using System.Diagnostics;
20 using System.Collections;
21
22 namespace System.Xml
23 {
24         public class XmlDocument : XmlNode
25         {
26                 #region Fields
27
28                 XmlLinkedNode lastLinkedChild;
29                 XmlNameTable nameTable;
30                 string baseURI = String.Empty;
31
32                 #endregion
33
34                 #region Constructors
35
36                 public XmlDocument () : base (null) { }
37
38                 [MonoTODO]
39                 protected internal XmlDocument (XmlImplementation imp) : base (null)
40                 {
41                         throw new NotImplementedException ();
42                 }
43
44                 public XmlDocument (XmlNameTable nt) : base (null)
45                 {
46                         nameTable = nt;
47                 }
48
49                 #endregion
50
51                 #region Events
52
53                 public event XmlNodeChangedEventHandler NodeChanged;
54
55                 public event XmlNodeChangedEventHandler NodeChanging;
56
57                 public event XmlNodeChangedEventHandler NodeInserted;
58
59                 public event XmlNodeChangedEventHandler NodeInserting;
60
61                 public event XmlNodeChangedEventHandler NodeRemoved;
62
63                 public event XmlNodeChangedEventHandler NodeRemoving;
64
65                 #endregion
66
67                 #region Properties
68
69                 public override string BaseURI {
70                         get {
71                                 return baseURI;
72                         }
73                 }
74
75                 public XmlElement DocumentElement {
76                         get {
77                                 XmlNode node = FirstChild;
78
79                                 while (node != null) {
80                                         if (node is XmlElement)
81                                                 break;
82                                         node = node.NextSibling;
83                                 }
84
85                                 return node != null ? node as XmlElement : null;
86                         }
87                 }
88
89                 [MonoTODO]
90                 public virtual XmlDocumentType DocumentType {
91                         get { throw new NotImplementedException(); }
92                 }
93
94                 [MonoTODO]
95                 public XmlImplementation Implementation {
96                         get { throw new NotImplementedException(); }
97                 }
98
99                 [MonoTODO ("Setter.")]
100                 public override string InnerXml {
101                         get {
102                                 // Not sure why this is an override.  Passing through for now.
103                                 return base.InnerXml;
104                         }
105                         set { throw new NotImplementedException(); }
106                 }
107
108                 public override bool IsReadOnly {
109                         get { return false; }
110                 }
111
112                 internal override XmlLinkedNode LastLinkedChild {
113                         get     {
114                                 return lastLinkedChild;
115                         }
116
117                         set {
118                                 lastLinkedChild = value;
119                         }
120                 }
121                 
122                 public override string LocalName {
123                         get { return "#document"; }
124                 }
125
126                 public override string Name {
127                         get { return "#document"; }
128                 }
129
130                 public XmlNameTable NameTable {
131                         get { return nameTable; }
132                 }
133
134                 public override XmlNodeType NodeType {
135                         get { return XmlNodeType.Document; }
136                 }
137
138                 public override XmlDocument OwnerDocument {
139                         get { return null; }
140                 }
141
142                 [MonoTODO]
143                 public bool PreserveWhitespace {
144                         get { throw new NotImplementedException(); }
145                         set { throw new NotImplementedException(); }
146                 }
147
148                 [MonoTODO]
149                 public virtual XmlResolver XmlResolver {
150                         set { throw new NotImplementedException(); }
151                 }
152
153                 #endregion
154
155                 #region Methods
156
157                 [MonoTODO]
158                 public override XmlNode CloneNode (bool deep)
159                 {
160                         throw new NotImplementedException ();
161                 }
162
163                 public XmlAttribute CreateAttribute (string name)
164                 {
165                         return CreateAttribute (name, String.Empty);
166                 }
167
168                 public XmlAttribute CreateAttribute (string qualifiedName, string namespaceURI)
169                 {
170                         string prefix;
171                         string localName;
172
173                         ParseName (qualifiedName, out prefix, out localName);
174
175                         return CreateAttribute (prefix, localName, namespaceURI);
176                 }
177
178                 public virtual XmlAttribute CreateAttribute (string prefix, string localName, string namespaceURI)
179                 {
180                         if ((localName == null) || (localName == String.Empty))
181                                 throw new ArgumentException ("The attribute local name cannot be empty.");
182
183                         return new XmlAttribute (prefix, localName, namespaceURI, this);
184                 }
185
186                 public virtual XmlCDataSection CreateCDataSection (string data)
187                 {
188                         return new XmlCDataSection (data, this);
189                 }
190
191                 public virtual XmlComment CreateComment (string data)
192                 {
193                         return new XmlComment(data, this);
194                 }
195
196                 [MonoTODO]
197                 protected internal virtual XmlAttribute CreateDefaultAttribute (string prefix, string localName, string namespaceURI)
198                 {
199                         throw new NotImplementedException ();
200                 }
201
202                 [MonoTODO]
203                 public virtual XmlDocumentFragment CreateDocumentFragment ()
204                 {
205                         throw new NotImplementedException ();
206                 }
207
208                 public virtual XmlDocumentType CreateDocumentType (string name, string publicId,
209                                                                    string systemId, string internalSubset)
210                 {
211                         return new XmlDocumentType (name, publicId, systemId, internalSubset, this);
212                 }
213
214                 public XmlElement CreateElement (string name)
215                 {
216                         return CreateElement (name, String.Empty);
217                 }
218
219                 public XmlElement CreateElement (
220                         string qualifiedName, 
221                         string namespaceURI)
222                 {
223                         string prefix;
224                         string localName;
225
226                         ParseName (qualifiedName, out prefix, out localName);
227
228                         return CreateElement (prefix, localName, namespaceURI);
229                 }
230
231                 public virtual XmlElement CreateElement (
232                         string prefix,
233                         string localName,
234                         string namespaceURI)
235                 {
236                         if ((localName == null) || (localName == String.Empty))
237                                 throw new ArgumentException ("The local name for elements or attributes cannot be null or an empty string.");
238
239                         return new XmlElement (prefix != null ? prefix : String.Empty, localName, namespaceURI != null ? namespaceURI : String.Empty, this);
240                 }
241
242                 [MonoTODO]
243                 public virtual XmlEntityReference CreateEntityReference (string name)
244                 {
245                         throw new NotImplementedException ();
246                 }
247
248                 [MonoTODO]
249                 protected internal virtual XPathNavigator CreateNavigator (XmlNode node)
250                 {
251                         throw new NotImplementedException ();
252                 }
253
254                 public virtual XmlNode CreateNode (
255                         string nodeTypeString,
256                         string name,
257                         string namespaceURI)
258                 {
259                         return CreateNode (GetNodeTypeFromString (nodeTypeString), name, namespaceURI);
260                 }
261
262                 public virtual XmlNode CreateNode (
263                         XmlNodeType type,
264                         string name,
265                         string namespaceURI)
266                 {
267                         string prefix = null;
268                         string localName = name;
269
270                         if ((type == XmlNodeType.Attribute) || (type == XmlNodeType.Element) || (type == XmlNodeType.EntityReference))
271                                 ParseName (name, out prefix, out localName);
272                         
273                         return CreateNode (type, prefix, localName, namespaceURI);
274                 }
275
276                 public virtual XmlNode CreateNode (
277                         XmlNodeType type,
278                         string prefix,
279                         string name,
280                         string namespaceURI)
281                 {
282                         switch (type) {
283                                 case XmlNodeType.Attribute: return CreateAttribute (prefix, name, namespaceURI);
284                                 case XmlNodeType.CDATA: return CreateCDataSection (null);
285                                 case XmlNodeType.Comment: return CreateComment (null);
286                                 case XmlNodeType.Document: return new XmlDocument (); // TODO - test to see which constructor to use, i.e. use existing NameTable or not.
287                                 case XmlNodeType.DocumentFragment: return CreateDocumentFragment ();
288                                 case XmlNodeType.DocumentType: return CreateDocumentType (null, null, null, null);
289                                 case XmlNodeType.Element: return CreateElement (prefix, name, namespaceURI);
290                                 case XmlNodeType.EntityReference: return CreateEntityReference (null);
291                                 case XmlNodeType.ProcessingInstruction: return CreateProcessingInstruction (null, null);
292                                 case XmlNodeType.SignificantWhitespace: return CreateSignificantWhitespace (String.Empty);
293                                 case XmlNodeType.Text: return CreateTextNode (null);
294                                 case XmlNodeType.Whitespace: return CreateWhitespace (String.Empty);
295                                 case XmlNodeType.XmlDeclaration: return CreateXmlDeclaration ("1.0", null, null);
296                                 default: throw new ArgumentOutOfRangeException(String.Format("{0}\nParameter name: {1}",
297                                                          "Specified argument was out of the range of valid values", type.ToString ()));
298                         }
299                 }
300
301                 public virtual XmlProcessingInstruction CreateProcessingInstruction (
302                         string target,
303                         string data)
304                 {
305                         return new XmlProcessingInstruction (target, data, this);
306                 }
307
308                 public virtual XmlSignificantWhitespace CreateSignificantWhitespace (string text)
309                 {
310                         foreach (char c in text)
311                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
312                                     throw new ArgumentException ("Invalid whitespace characters.");
313                          
314                         return new XmlSignificantWhitespace (text, this);
315                 }
316
317                 public virtual XmlText CreateTextNode (string text)
318                 {
319                         return new XmlText (text, this);
320                 }
321
322                 public virtual XmlWhitespace CreateWhitespace (string text)
323                 {
324                         foreach (char c in text)
325                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
326                                     throw new ArgumentException ("Invalid whitespace characters.");
327                          
328                         return new XmlWhitespace (text, this);
329                 }
330
331                 public virtual XmlDeclaration CreateXmlDeclaration (string version, string encoding,
332                                                                     string standalone)
333                 {
334                         if (version != "1.0")
335                                 throw new ArgumentException ("version string is not correct.");
336
337                         if  ((standalone != null) && !((standalone == "yes") || (standalone == "no")))
338                                 throw new ArgumentException ("standalone string is not correct.");
339                         
340                         return new XmlDeclaration (version, encoding, standalone, this);
341                 }
342
343                 [MonoTODO]
344                 public virtual XmlElement GetElementById (string elementId)
345                 {
346                         throw new NotImplementedException ();
347                 }
348
349                 public virtual XmlNodeList GetElementsByTagName (string name)
350                 {
351                         ArrayList nodeArrayList = new ArrayList ();
352                         this.searchNodesRecursively (this, name, String.Empty, nodeArrayList);
353                         return new XmlNodeArrayList (nodeArrayList);
354                 }
355
356                 private void searchNodesRecursively (XmlNode argNode, string argName, string argNamespaceURI, 
357                         ArrayList argArrayList)
358                 {
359                         XmlNodeList xmlNodeList = argNode.ChildNodes;
360                         foreach (XmlNode node in xmlNodeList){
361                                 if (node.LocalName.Equals (argName) && node.NamespaceURI.Equals (argNamespaceURI))
362                                         argArrayList.Add (node);
363                                 else    
364                                         this.searchNodesRecursively (node, argName, argNamespaceURI, argArrayList);
365                         }
366                 }
367
368                 public virtual XmlNodeList GetElementsByTagName (string localName, string namespaceURI)
369                 {
370                         ArrayList nodeArrayList = new ArrayList ();
371                         this.searchNodesRecursively (this, localName, namespaceURI, nodeArrayList);
372                         return new XmlNodeArrayList (nodeArrayList);
373                 }
374
375                 private XmlNodeType GetNodeTypeFromString (string nodeTypeString)
376                 {
377                         switch (nodeTypeString) {
378                                 case "attribute": return XmlNodeType.Attribute;
379                                 case "cdatasection": return XmlNodeType.CDATA;
380                                 case "comment": return XmlNodeType.Comment;
381                                 case "document": return XmlNodeType.Document;
382                                 case "documentfragment": return XmlNodeType.DocumentFragment;
383                                 case "documenttype": return XmlNodeType.DocumentType;
384                                 case "element": return XmlNodeType.Element;
385                                 case "entityreference": return XmlNodeType.EntityReference;
386                                 case "processinginstruction": return XmlNodeType.ProcessingInstruction;
387                                 case "significantwhitespace": return XmlNodeType.SignificantWhitespace;
388                                 case "text": return XmlNodeType.Text;
389                                 case "whitespace": return XmlNodeType.Whitespace;
390                                 default:
391                                         throw new ArgumentException(String.Format("The string doesn't represent any node type : {0}.", nodeTypeString));
392                         }
393                 }
394
395                 [MonoTODO]
396                 public virtual XmlNode ImportNode (XmlNode node, bool deep)
397                 {
398                         // How to resolve default attribute values?
399                         switch(node.NodeType)
400                         {
401                                 case XmlNodeType.Attribute:
402                                         {
403                                                 XmlAttribute src_att = node as XmlAttribute;
404                                                 XmlAttribute dst_att = this.CreateAttribute(src_att.Prefix, src_att.LocalName, src_att.NamespaceURI);
405                                                 // TODO: resolve default attribute values
406                                                 dst_att.Value = src_att.Value;
407                                                 return dst_att;
408                                         }
409
410                                 case XmlNodeType.CDATA:
411                                         return this.CreateCDataSection(node.Value);
412
413                                 case XmlNodeType.Comment:
414                                         return this.CreateComment(node.Value);
415
416                                 case XmlNodeType.Document:
417                                         throw new XmlException("Document cannot be imported.");
418
419                                 case XmlNodeType.DocumentFragment:
420                                         {
421                                                 XmlDocumentFragment df = this.CreateDocumentFragment();
422                                                 if(deep)
423                                                 {
424                                                         foreach(XmlNode n in node.ChildNodes)
425                                                         {
426                                                                 df.AppendChild(this.ImportNode(n, deep));
427                                                         }
428                                                 }
429                                                 return df;
430                                         }
431
432                                 case XmlNodeType.DocumentType:
433                                         throw new XmlException("DocumentType cannot be imported.");
434
435                                 case XmlNodeType.Element:
436                                         {
437                                                 XmlElement src = (XmlElement)node;
438                                                 XmlElement dst = this.CreateElement(src.Prefix, src.LocalName, src.NamespaceURI);
439                                                 foreach(XmlAttribute attr in src.Attributes)
440                                                 {
441                                                         // TODO: create default attribute values
442                                                         dst.SetAttributeNode((XmlAttribute)this.ImportNode(attr, deep));
443                                                 }
444                                                 if(deep)
445                                                 {
446                                                         foreach(XmlNode n in src.ChildNodes)
447                                                                 dst.AppendChild(this.ImportNode(n, deep));
448                                                 }
449                                                 return dst;
450                                         }
451
452                                 case XmlNodeType.EndElement:
453                                         throw new XmlException ("Illegal ImportNode call for NodeType.EndElement");
454                                 case XmlNodeType.EndEntity:
455                                         throw new XmlException ("Illegal ImportNode call for NodeType.EndEntity");
456                                 case XmlNodeType.Entity:
457                                         throw new NotImplementedException ();
458
459                                 // [2002.10.14] CreateEntityReference not implemented.
460                                 case XmlNodeType.EntityReference:
461                                         throw new NotImplementedException("ImportNode of EntityReference not implemented mainly because CreateEntityReference was implemented in the meantime.");
462 //                                      return this.CreateEntityReference(node.Name);
463
464                                 case XmlNodeType.None:
465                                         throw new XmlException ("Illegal ImportNode call for NodeType.None");
466                                 case XmlNodeType.Notation:
467                                         throw new NotImplementedException ();
468
469                                 case XmlNodeType.ProcessingInstruction:
470                                         XmlProcessingInstruction pi = node as XmlProcessingInstruction;
471                                         return this.CreateProcessingInstruction(pi.Target, pi.Data);
472
473                                 case XmlNodeType.SignificantWhitespace:
474                                         return this.CreateSignificantWhitespace(node.Value);
475
476                                 case XmlNodeType.Text:
477                                         return this.CreateTextNode(node.Value);
478
479                                 case XmlNodeType.Whitespace:
480                                         return this.CreateWhitespace(node.Value);
481
482                                 // I don't know how to test it...
483                                 case XmlNodeType.XmlDeclaration:
484                                 //      return this.CreateNode(XmlNodeType.XmlDeclaration, String.Empty, node.Value);
485                                         throw new NotImplementedException ();
486
487                                 default:
488                                         throw new NotImplementedException ();
489                         }
490                 }
491
492                 public virtual void Load (Stream inStream)
493                 {
494                         XmlReader xmlReader = new XmlTextReader (inStream);
495                         Load (xmlReader);
496                 }
497
498                 public virtual void Load (string filename)
499                 {
500                         baseURI = filename;
501                         XmlReader xmlReader = new XmlTextReader (new StreamReader (filename));
502                         Load (xmlReader);
503                 }
504
505                 public virtual void Load (TextReader txtReader)
506                 {
507                         Load (new XmlTextReader (txtReader));
508                 }
509
510                 public virtual void Load (XmlReader xmlReader)
511                 {
512                         // Reset our document
513                         // For now this just means removing all our children but later this
514                         // may turn out o need to call a private method that resets other things
515                         // like properties we have, etc.
516                         RemoveAll ();
517
518                         XmlNode currentNode = this;
519                         XmlNode newNode;
520
521                         while (xmlReader.Read ()) 
522                         {
523                                 switch (xmlReader.NodeType) {
524
525                                 case XmlNodeType.CDATA:
526                                         newNode = CreateCDataSection(xmlReader.Value);
527                                         currentNode.AppendChild (newNode);
528                                         break;
529
530                                 case XmlNodeType.Comment:
531                                         newNode = CreateComment (xmlReader.Value);
532                                         currentNode.AppendChild (newNode);
533                                         break;
534
535                                 case XmlNodeType.Element:
536                                         XmlElement element = CreateElement (xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
537                                         currentNode.AppendChild (element);
538
539                                         // set the element's attributes.
540                                         while (xmlReader.MoveToNextAttribute ()) {
541                                                 XmlAttribute attribute = CreateAttribute (xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
542                                                 attribute.Value = xmlReader.Value;
543                                                 element.SetAttributeNode (attribute);
544                                         }
545
546                                         xmlReader.MoveToElement ();
547
548                                         // if this element isn't empty, push it onto our "stack".
549                                         if (!xmlReader.IsEmptyElement)
550                                                 currentNode = element;
551
552                                         break;
553
554                                 case XmlNodeType.EndElement:
555                                         currentNode = currentNode.ParentNode;
556                                         break;
557
558                                 case XmlNodeType.ProcessingInstruction:
559                                         newNode = CreateProcessingInstruction (xmlReader.Name, xmlReader.Value);
560                                         currentNode.AppendChild (newNode);
561                                         break;
562
563                                 case XmlNodeType.Text:
564                                         newNode = CreateTextNode (xmlReader.Value);
565                                         currentNode.AppendChild (newNode);
566                                         break;
567                                 }
568                         }
569                 }
570
571                 public virtual void LoadXml (string xml)
572                 {
573                         XmlReader xmlReader = new XmlTextReader (new StringReader (xml));
574                         Load (xmlReader);
575                 }
576
577                 internal void onNodeChanged (XmlNode node, XmlNode Parent)
578                 {
579                         if (NodeChanged != null)
580                                 NodeChanged (node, new XmlNodeChangedEventArgs
581                                         (XmlNodeChangedAction.Change,
582                                         node, Parent, Parent));
583                 }
584
585                 internal void onNodeChanging(XmlNode node, XmlNode Parent)
586                 {
587                         if (NodeChanging != null)
588                                 NodeChanging (node, new XmlNodeChangedEventArgs
589                                         (XmlNodeChangedAction.Change,
590                                         node, Parent, Parent));
591                 }
592
593                 internal void onNodeInserted (XmlNode node, XmlNode newParent)
594                 {
595                         if (NodeInserted != null)
596                                 NodeInserted (node, new XmlNodeChangedEventArgs
597                                         (XmlNodeChangedAction.Insert,
598                                         node, null, newParent));
599                 }
600
601                 internal void onNodeInserting (XmlNode node, XmlNode newParent)
602                 {
603                         if (NodeInserting != null)
604                                 NodeInserting (node, new XmlNodeChangedEventArgs
605                                         (XmlNodeChangedAction.Insert,
606                                         node, null, newParent));
607                 }
608
609                 internal void onNodeRemoved (XmlNode node, XmlNode oldParent)
610                 {
611                         if (NodeRemoved != null)
612                                 NodeRemoved (node, new XmlNodeChangedEventArgs
613                                         (XmlNodeChangedAction.Remove,
614                                         node, oldParent, null));
615                 }
616
617                 internal void onNodeRemoving (XmlNode node, XmlNode oldParent)
618                 {
619                         if (NodeRemoving != null)
620                                 NodeRemoving (node, new XmlNodeChangedEventArgs
621                                         (XmlNodeChangedAction.Remove,
622                                         node, oldParent, null));
623                 }
624
625                 private void ParseName (string name, out string prefix, out string localName)
626                 {
627                         int indexOfColon = name.IndexOf (':');
628                         
629                         if (indexOfColon != -1) {
630                                 prefix = name.Substring (0, indexOfColon);
631                                 localName = name.Substring (indexOfColon + 1);
632                         } else {
633                                 prefix = "";
634                                 localName = name;
635                         }
636                 }
637
638                 [MonoTODO]
639                 public virtual XmlNode ReadNode(XmlReader reader)
640                 {
641                         throw new NotImplementedException ();
642                 }
643
644                 [MonoTODO ("Verify what encoding is used by default;  Should use PreserveWhiteSpace")]
645                 public virtual void Save(Stream outStream)
646                 {
647                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, Encoding.UTF8);
648                         WriteContentTo (xmlWriter);
649                         xmlWriter.Close ();
650                 }
651
652                 [MonoTODO ("Verify what encoding is used by default; Should use PreseveWhiteSpace")]
653                 public virtual void Save (string filename)
654                 {
655                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, Encoding.UTF8);
656                         WriteContentTo (xmlWriter);
657                         xmlWriter.Close ();
658                 }
659
660                 [MonoTODO]
661                 public virtual void Save (TextWriter writer)
662                 {
663                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
664                         WriteContentTo (xmlWriter);
665                         xmlWriter.Flush ();
666                 }
667
668                 [MonoTODO ("Should preserve white space if PreserveWhisspace is set")]
669                 public virtual void Save (XmlWriter xmlWriter)
670                 {
671                         //
672                         // This should preserve white space if PreserveWhiteSpace is true
673                         //
674                         WriteContentTo (xmlWriter);
675                         xmlWriter.Flush ();
676                 }
677
678                 public override void WriteContentTo (XmlWriter w)
679                 {
680                         foreach(XmlNode childNode in ChildNodes)
681                                 childNode.WriteTo(w);
682                 }
683
684                 public override void WriteTo (XmlWriter w)
685                 {
686                         WriteContentTo(w);
687                 }
688
689                 #endregion
690         }
691 }