2002-09-18 Gonzalo Paniagua Javier <gonzalo@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                         throw new NotImplementedException ();
399                 }
400
401                 public virtual void Load (Stream inStream)
402                 {
403                         XmlReader xmlReader = new XmlTextReader (inStream);
404                         Load (xmlReader);
405                 }
406
407                 public virtual void Load (string filename)
408                 {
409                         baseURI = filename;
410                         XmlReader xmlReader = new XmlTextReader (new StreamReader (filename));
411                         Load (xmlReader);
412                 }
413
414                 [MonoTODO]
415                 public virtual void Load (TextReader txtReader)
416                 {
417                         throw new NotImplementedException ();
418                 }
419
420                 public virtual void Load (XmlReader xmlReader)
421                 {
422                         // Reset our document
423                         // For now this just means removing all our children but later this
424                         // may turn out o need to call a private method that resets other things
425                         // like properties we have, etc.
426                         RemoveAll ();
427
428                         XmlNode currentNode = this;
429                         XmlNode newNode;
430
431                         while (xmlReader.Read ()) 
432                         {
433                                 switch (xmlReader.NodeType) {
434
435                                 case XmlNodeType.CDATA:
436                                         newNode = CreateCDataSection(xmlReader.Value);
437                                         currentNode.AppendChild (newNode);
438                                         break;
439
440                                 case XmlNodeType.Comment:
441                                         newNode = CreateComment (xmlReader.Value);
442                                         currentNode.AppendChild (newNode);
443                                         break;
444
445                                 case XmlNodeType.Element:
446                                         XmlElement element = CreateElement (xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
447                                         currentNode.AppendChild (element);
448
449                                         // set the element's attributes.
450                                         while (xmlReader.MoveToNextAttribute ()) {
451                                                 XmlAttribute attribute = CreateAttribute (xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
452                                                 attribute.Value = xmlReader.Value;
453                                                 element.SetAttributeNode (attribute);
454                                         }
455
456                                         xmlReader.MoveToElement ();
457
458                                         // if this element isn't empty, push it onto our "stack".
459                                         if (!xmlReader.IsEmptyElement)
460                                                 currentNode = element;
461
462                                         break;
463
464                                 case XmlNodeType.EndElement:
465                                         currentNode = currentNode.ParentNode;
466                                         break;
467
468                                 case XmlNodeType.ProcessingInstruction:
469                                         newNode = CreateProcessingInstruction (xmlReader.Name, xmlReader.Value);
470                                         currentNode.AppendChild (newNode);
471                                         break;
472
473                                 case XmlNodeType.Text:
474                                         newNode = CreateTextNode (xmlReader.Value);
475                                         currentNode.AppendChild (newNode);
476                                         break;
477                                 }
478                         }
479                 }
480
481                 public virtual void LoadXml (string xml)
482                 {
483                         XmlReader xmlReader = new XmlTextReader (new StringReader (xml));
484                         Load (xmlReader);
485                 }
486
487                 internal void onNodeChanged (XmlNode node, XmlNode Parent)
488                 {
489                         if (NodeChanged != null)
490                                 NodeChanged (node, new XmlNodeChangedEventArgs
491                                         (XmlNodeChangedAction.Change,
492                                         node, Parent, Parent));
493                 }
494
495                 internal void onNodeChanging(XmlNode node, XmlNode Parent)
496                 {
497                         if (NodeChanging != null)
498                                 NodeChanging (node, new XmlNodeChangedEventArgs
499                                         (XmlNodeChangedAction.Change,
500                                         node, Parent, Parent));
501                 }
502
503                 internal void onNodeInserted (XmlNode node, XmlNode newParent)
504                 {
505                         if (NodeInserted != null)
506                                 NodeInserted (node, new XmlNodeChangedEventArgs
507                                         (XmlNodeChangedAction.Insert,
508                                         node, null, newParent));
509                 }
510
511                 internal void onNodeInserting (XmlNode node, XmlNode newParent)
512                 {
513                         if (NodeInserting != null)
514                                 NodeInserting (node, new XmlNodeChangedEventArgs
515                                         (XmlNodeChangedAction.Insert,
516                                         node, null, newParent));
517                 }
518
519                 internal void onNodeRemoved (XmlNode node, XmlNode oldParent)
520                 {
521                         if (NodeRemoved != null)
522                                 NodeRemoved (node, new XmlNodeChangedEventArgs
523                                         (XmlNodeChangedAction.Remove,
524                                         node, oldParent, null));
525                 }
526
527                 internal void onNodeRemoving (XmlNode node, XmlNode oldParent)
528                 {
529                         if (NodeRemoving != null)
530                                 NodeRemoving (node, new XmlNodeChangedEventArgs
531                                         (XmlNodeChangedAction.Remove,
532                                         node, oldParent, null));
533                 }
534
535                 private void ParseName (string name, out string prefix, out string localName)
536                 {
537                         int indexOfColon = name.IndexOf (':');
538                         
539                         if (indexOfColon != -1) {
540                                 prefix = name.Substring (0, indexOfColon);
541                                 localName = name.Substring (indexOfColon + 1);
542                         } else {
543                                 prefix = "";
544                                 localName = name;
545                         }
546                 }
547
548                 [MonoTODO]
549                 public virtual XmlNode ReadNode(XmlReader reader)
550                 {
551                         throw new NotImplementedException ();
552                 }
553
554                 [MonoTODO ("Verify what encoding is used by default;  Should use PreserveWhiteSpace")]
555                 public virtual void Save(Stream outStream)
556                 {
557                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, Encoding.UTF8);
558                         WriteContentTo (xmlWriter);
559                         xmlWriter.Close ();
560                 }
561
562                 [MonoTODO ("Verify what encoding is used by default; Should use PreseveWhiteSpace")]
563                 public virtual void Save (string filename)
564                 {
565                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, Encoding.UTF8);
566                         WriteContentTo (xmlWriter);
567                         xmlWriter.Close ();
568                 }
569
570                 [MonoTODO]
571                 public virtual void Save (TextWriter writer)
572                 {
573                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
574                         WriteContentTo (xmlWriter);
575                         xmlWriter.Flush ();
576                 }
577
578                 [MonoTODO ("Should preserve white space if PreserveWhisspace is set")]
579                 public virtual void Save (XmlWriter xmlWriter)
580                 {
581                         //
582                         // This should preserve white space if PreserveWhiteSpace is true
583                         //
584                         WriteContentTo (xmlWriter);
585                         xmlWriter.Flush ();
586                 }
587
588                 public override void WriteContentTo (XmlWriter w)
589                 {
590                         foreach(XmlNode childNode in ChildNodes)
591                                 childNode.WriteTo(w);
592                 }
593
594                 public override void WriteTo (XmlWriter w)
595                 {
596                         WriteContentTo(w);
597                 }
598
599                 #endregion
600         }
601 }