Reverted last patch. It breaks configuration reading
[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 //   Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
11 //
12 // (C) 2001 Daniel Weber
13 // (C) 2002 Kral Ferch, Jason Diamond, Miguel de Icaza, Duncan Mak,
14 //   Atsushi Enomoto
15 //
16
17 using System;
18 using System.IO;
19 using System.Text;
20 using System.Xml.XPath;
21 using System.Diagnostics;
22 using System.Collections;
23
24 namespace System.Xml
25 {
26         public class XmlDocument : XmlNode
27         {
28                 #region Fields
29
30                 XmlLinkedNode lastLinkedChild;
31                 XmlNameTable nameTable;
32                 string baseURI = String.Empty;
33                 XmlImplementation implementation;
34                 bool preserveWhitespace = false;
35                 WeakReference reusableXmlTextReader;
36
37                 #endregion
38
39                 #region Constructors
40
41                 public XmlDocument () : this (null, null)
42                 {
43                 }
44
45                 protected internal XmlDocument (XmlImplementation imp) : this (imp, null)
46                 {
47                 }
48
49                 public XmlDocument (XmlNameTable nt) : this (null, nt)
50                 {
51                 }
52
53                 XmlDocument (XmlImplementation impl, XmlNameTable nt) : base (null)
54                 {
55                         implementation = (impl != null) ? impl : new XmlImplementation ();
56                         nameTable = (nt != null) ? nt : implementation.internalNameTable;
57                         AddDefaultNameTableKeys ();
58                 }
59                 #endregion
60
61                 #region Events
62
63                 public event XmlNodeChangedEventHandler NodeChanged;
64
65                 public event XmlNodeChangedEventHandler NodeChanging;
66
67                 public event XmlNodeChangedEventHandler NodeInserted;
68
69                 public event XmlNodeChangedEventHandler NodeInserting;
70
71                 public event XmlNodeChangedEventHandler NodeRemoved;
72
73                 public event XmlNodeChangedEventHandler NodeRemoving;
74
75                 #endregion
76
77                 #region Properties
78
79                 public override string BaseURI {
80                         get {
81                                 return baseURI;
82                         }
83                 }
84
85                 // Used to read 'InnerXml's for its descendants at any place.
86                 internal XmlTextReader ReusableReader {
87                         get {
88                                 if(reusableXmlTextReader == null)
89                                         reusableXmlTextReader = new WeakReference (null);
90                                 if(!reusableXmlTextReader.IsAlive) {
91                                         XmlTextReader reader = new XmlTextReader ((TextReader)null);
92                                         reusableXmlTextReader.Target = reader;
93                                 }
94                                 return (XmlTextReader)reusableXmlTextReader.Target;
95                         }
96                 }
97
98                 public XmlElement DocumentElement {
99                         get {
100                                 XmlNode node = FirstChild;
101
102                                 while (node != null) {
103                                         if (node is XmlElement)
104                                                 break;
105                                         node = node.NextSibling;
106                                 }
107
108                                 return node != null ? node as XmlElement : null;
109                         }
110                 }
111
112                 [MonoTODO("It doesn't have internal subset object model.")]
113                 public virtual XmlDocumentType DocumentType {
114                         get {
115                                 foreach(XmlNode n in this.ChildNodes) {
116                                         if(n.NodeType == XmlNodeType.DocumentType)
117                                                 return (XmlDocumentType)n;
118                                 }
119                                 return null;
120                         }
121                 }
122
123                 public XmlImplementation Implementation {
124                         get { return implementation; }
125                 }
126
127                 public override string InnerXml {
128                         get {
129                                 return base.InnerXml;
130                         }
131                         set {   // reason for overriding
132                                 this.LoadXml (value);
133                         }
134                 }
135
136                 public override bool IsReadOnly {
137                         get { return false; }
138                 }
139
140                 internal override XmlLinkedNode LastLinkedChild {
141                         get     {
142                                 return lastLinkedChild;
143                         }
144
145                         set {
146                                 lastLinkedChild = value;
147                         }
148                 }
149                 
150                 public override string LocalName {
151                         get { return "#document"; }
152                 }
153
154                 public override string Name {
155                         get { return "#document"; }
156                 }
157
158                 public XmlNameTable NameTable {
159                         get { return nameTable; }
160                 }
161
162                 public override XmlNodeType NodeType {
163                         get { return XmlNodeType.Document; }
164                 }
165
166                 internal override XPathNodeType XPathNodeType {
167                         get {
168                                 return XPathNodeType.Root;
169                         }
170                 }
171
172                 public override XmlDocument OwnerDocument {
173                         get { return null; }
174                 }
175
176                 public bool PreserveWhitespace {
177                         get { return preserveWhitespace; }
178                         set { preserveWhitespace = value; }
179                 }
180
181                 internal override string XmlLang {
182                         get { return String.Empty; }
183                 }
184
185                 [MonoTODO]
186                 public virtual XmlResolver XmlResolver {
187                         set { throw new NotImplementedException (); }
188                 }
189
190                 internal override XmlSpace XmlSpace {
191                         get {
192                                 return XmlSpace.None;
193                         }
194                 }
195
196                 #endregion
197
198                 #region Methods
199
200                 [MonoTODO("Should BaseURI be cloned?")]
201                 public override XmlNode CloneNode (bool deep)
202                 {
203                         XmlDocument doc = implementation.CreateDocument ();
204                         doc.PreserveWhitespace = PreserveWhitespace;    // required?
205                         if(deep)
206                         {
207                                 foreach(XmlNode n in ChildNodes)
208                                         doc.AppendChild (doc.ImportNode (n, deep));
209                         }
210                         return doc;
211                 }
212
213                 public XmlAttribute CreateAttribute (string name)
214                 {
215                         return CreateAttribute (name, String.Empty);
216                 }
217
218                 public XmlAttribute CreateAttribute (string qualifiedName, string namespaceURI)
219                 {
220                         string prefix;
221                         string localName;
222
223                         ParseName (qualifiedName, out prefix, out localName);
224
225                         return CreateAttribute (prefix, localName, namespaceURI);
226                 }
227
228                 public virtual XmlAttribute CreateAttribute (string prefix, string localName, string namespaceURI)
229                 {
230                         if ((localName == null) || (localName == String.Empty))
231                                 throw new ArgumentException ("The attribute local name cannot be empty.");
232
233                         return new XmlAttribute (prefix, localName, namespaceURI, this);
234                 }
235
236                 public virtual XmlCDataSection CreateCDataSection (string data)
237                 {
238                         return new XmlCDataSection (data, this);
239                 }
240
241                 public virtual XmlComment CreateComment (string data)
242                 {
243                         return new XmlComment (data, this);
244                 }
245
246                 [MonoTODO]
247                 protected internal virtual XmlAttribute CreateDefaultAttribute (string prefix, string localName, string namespaceURI)
248                 {
249                         throw new NotImplementedException ();
250                 }
251
252                 public virtual XmlDocumentFragment CreateDocumentFragment ()
253                 {
254                         return new XmlDocumentFragment (this);
255                 }
256
257                 public virtual XmlDocumentType CreateDocumentType (string name, string publicId,
258                                                                    string systemId, string internalSubset)
259                 {
260                         return new XmlDocumentType (name, publicId, systemId, internalSubset, this);
261                 }
262
263                 public XmlElement CreateElement (string name)
264                 {
265                         return CreateElement (name, String.Empty);
266                 }
267
268                 public XmlElement CreateElement (
269                         string qualifiedName, 
270                         string namespaceURI)
271                 {
272                         string prefix;
273                         string localName;
274
275                         ParseName (qualifiedName, out prefix, out localName);
276                         
277                         return CreateElement (prefix, localName, namespaceURI);
278                 }
279
280                 public virtual XmlElement CreateElement (
281                         string prefix,
282                         string localName,
283                         string namespaceURI)
284                 {
285                         if ((localName == null) || (localName == String.Empty))
286                                 throw new ArgumentException ("The local name for elements or attributes cannot be null or an empty string.");
287                         CheckName (localName);
288                         return new XmlElement (prefix != null ? prefix : String.Empty, localName, namespaceURI != null ? namespaceURI : String.Empty, this);
289                 }
290
291                 public virtual XmlEntityReference CreateEntityReference (string name)
292                 {
293                         return new XmlEntityReference (name, this);
294                 }
295
296                 [MonoTODO]
297                 internal protected virtual XPathNavigator CreateNavigator (XmlNode node)
298                 {
299                         throw new NotImplementedException ();
300                 }
301
302                 public virtual XmlNode CreateNode (
303                         string nodeTypeString,
304                         string name,
305                         string namespaceURI)
306                 {
307                         return CreateNode (GetNodeTypeFromString (nodeTypeString), name, namespaceURI);
308                 }
309
310                 public virtual XmlNode CreateNode (
311                         XmlNodeType type,
312                         string name,
313                         string namespaceURI)
314                 {
315                         string prefix = null;
316                         string localName = name;
317
318                         if ((type == XmlNodeType.Attribute) || (type == XmlNodeType.Element) || (type == XmlNodeType.EntityReference))
319                                 ParseName (name, out prefix, out localName);
320                         
321                         return CreateNode (type, prefix, localName, namespaceURI);
322                 }
323
324                 public virtual XmlNode CreateNode (
325                         XmlNodeType type,
326                         string prefix,
327                         string name,
328                         string namespaceURI)
329                 {
330                         switch (type) {
331                                 case XmlNodeType.Attribute: return CreateAttribute (prefix, name, namespaceURI);
332                                 case XmlNodeType.CDATA: return CreateCDataSection (null);
333                                 case XmlNodeType.Comment: return CreateComment (null);
334                                 case XmlNodeType.Document: return new XmlDocument (); // TODO - test to see which constructor to use, i.e. use existing NameTable or not.
335                                 case XmlNodeType.DocumentFragment: return CreateDocumentFragment ();
336                                 case XmlNodeType.DocumentType: return CreateDocumentType (null, null, null, null);
337                                 case XmlNodeType.Element: return CreateElement (prefix, name, namespaceURI);
338                                 case XmlNodeType.EntityReference: return CreateEntityReference (null);
339                                 case XmlNodeType.ProcessingInstruction: return CreateProcessingInstruction (null, null);
340                                 case XmlNodeType.SignificantWhitespace: return CreateSignificantWhitespace (String.Empty);
341                                 case XmlNodeType.Text: return CreateTextNode (null);
342                                 case XmlNodeType.Whitespace: return CreateWhitespace (String.Empty);
343                                 case XmlNodeType.XmlDeclaration: return CreateXmlDeclaration ("1.0", null, null);
344                                 default: throw new ArgumentOutOfRangeException(String.Format("{0}\nParameter name: {1}",
345                                                          "Specified argument was out of the range of valid values", type.ToString ()));
346                         }
347                 }
348
349                 public virtual XmlProcessingInstruction CreateProcessingInstruction (
350                         string target,
351                         string data)
352                 {
353                         return new XmlProcessingInstruction (target, data, this);
354                 }
355
356                 public virtual XmlSignificantWhitespace CreateSignificantWhitespace (string text)
357                 {
358                         foreach (char c in text)
359                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
360                                     throw new ArgumentException ("Invalid whitespace characters.");
361                          
362                         return new XmlSignificantWhitespace (text, this);
363                 }
364
365                 public virtual XmlText CreateTextNode (string text)
366                 {
367                         return new XmlText (text, this);
368                 }
369
370                 public virtual XmlWhitespace CreateWhitespace (string text)
371                 {
372                         foreach (char c in text)
373                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
374                                     throw new ArgumentException ("Invalid whitespace characters.");
375                          
376                         return new XmlWhitespace (text, this);
377                 }
378
379                 public virtual XmlDeclaration CreateXmlDeclaration (string version, string encoding,
380                                                                     string standalone)
381                 {
382                         if (version != "1.0")
383                                 throw new ArgumentException ("version string is not correct.");
384
385                         if  ((standalone != null && standalone != String.Empty) && !((standalone == "yes") || (standalone == "no")))
386                                 throw new ArgumentException ("standalone string is not correct.");
387
388                         return new XmlDeclaration (version, encoding, standalone, this);
389                 }
390
391                 [MonoTODO]
392                 public virtual XmlElement GetElementById (string elementId)
393                 {
394                         throw new NotImplementedException ();
395                 }
396
397                 public virtual XmlNodeList GetElementsByTagName (string name)
398                 {
399                         ArrayList nodeArrayList = new ArrayList ();
400                         this.searchNodesRecursively (this, name, nodeArrayList);
401                         return new XmlNodeArrayList (nodeArrayList);
402                 }
403
404                 private void searchNodesRecursively (XmlNode argNode, string argName, 
405                         ArrayList argArrayList)
406                 {
407                         XmlNodeList xmlNodeList = argNode.ChildNodes;
408                         foreach (XmlNode node in xmlNodeList){
409                                 if (node.Name.Equals (argName))
410                                         argArrayList.Add (node);
411                                 else    
412                                         this.searchNodesRecursively (node, argName, argArrayList);
413                         }
414                 }
415
416                 private void searchNodesRecursively (XmlNode argNode, string argName, string argNamespaceURI, 
417                         ArrayList argArrayList)
418                 {
419                         XmlNodeList xmlNodeList = argNode.ChildNodes;
420                         foreach (XmlNode node in xmlNodeList){
421                                 if (node.LocalName.Equals (argName) && node.NamespaceURI.Equals (argNamespaceURI))
422                                         argArrayList.Add (node);
423                                 else    
424                                         this.searchNodesRecursively (node, argName, argNamespaceURI, argArrayList);
425                         }
426                 }
427
428                 public virtual XmlNodeList GetElementsByTagName (string localName, string namespaceURI)
429                 {
430                         ArrayList nodeArrayList = new ArrayList ();
431                         this.searchNodesRecursively (this, localName, namespaceURI, nodeArrayList);
432                         return new XmlNodeArrayList (nodeArrayList);
433                 }
434
435                 private XmlNodeType GetNodeTypeFromString (string nodeTypeString)
436                 {
437                         switch (nodeTypeString) {
438                                 case "attribute": return XmlNodeType.Attribute;
439                                 case "cdatasection": return XmlNodeType.CDATA;
440                                 case "comment": return XmlNodeType.Comment;
441                                 case "document": return XmlNodeType.Document;
442                                 case "documentfragment": return XmlNodeType.DocumentFragment;
443                                 case "documenttype": return XmlNodeType.DocumentType;
444                                 case "element": return XmlNodeType.Element;
445                                 case "entityreference": return XmlNodeType.EntityReference;
446                                 case "processinginstruction": return XmlNodeType.ProcessingInstruction;
447                                 case "significantwhitespace": return XmlNodeType.SignificantWhitespace;
448                                 case "text": return XmlNodeType.Text;
449                                 case "whitespace": return XmlNodeType.Whitespace;
450                                 default:
451                                         throw new ArgumentException(String.Format("The string doesn't represent any node type : {0}.", nodeTypeString));
452                         }
453                 }
454
455                 [MonoTODO("default attributes (of imported doc); Entity; Notation")]
456                 public virtual XmlNode ImportNode (XmlNode node, bool deep)
457                 {
458                         switch(node.NodeType)
459                         {
460                                 case XmlNodeType.Attribute:
461                                         {
462                                                 XmlAttribute src_att = node as XmlAttribute;
463                                                 XmlAttribute dst_att = this.CreateAttribute (src_att.Prefix, src_att.LocalName, src_att.NamespaceURI);
464                                                 dst_att.Value = src_att.Value;  // always explicitly specified (whether source is specified or not)
465                                                 return dst_att;
466                                         }
467
468                                 case XmlNodeType.CDATA:
469                                         return this.CreateCDataSection (node.Value);
470
471                                 case XmlNodeType.Comment:
472                                         return this.CreateComment (node.Value);
473
474                                 case XmlNodeType.Document:
475                                         throw new XmlException ("Document cannot be imported.");
476
477                                 case XmlNodeType.DocumentFragment:
478                                         {
479                                                 XmlDocumentFragment df = this.CreateDocumentFragment ();
480                                                 if(deep)
481                                                 {
482                                                         foreach(XmlNode n in node.ChildNodes)
483                                                         {
484                                                                 df.AppendChild (this.ImportNode (n, deep));
485                                                         }
486                                                 }
487                                                 return df;
488                                         }
489
490                                 case XmlNodeType.DocumentType:
491                                         throw new XmlException ("DocumentType cannot be imported.");
492
493                                 case XmlNodeType.Element:
494                                         {
495                                                 XmlElement src = (XmlElement)node;
496                                                 XmlElement dst = this.CreateElement (src.Prefix, src.LocalName, src.NamespaceURI);
497                                                 foreach(XmlAttribute attr in src.Attributes)
498                                                 {
499                                                         if(attr.Specified)      // copies only specified attributes
500                                                                 dst.SetAttributeNode ((XmlAttribute)this.ImportNode (attr, deep));
501                                                         if(DocumentType != null)
502                                                         {
503                                                                 // TODO: create default attribute values
504                                                         }
505                                                 }
506                                                 if(deep)
507                                                 {
508                                                         foreach(XmlNode n in src.ChildNodes)
509                                                                 dst.AppendChild (this.ImportNode (n, deep));
510                                                 }
511                                                 return dst;
512                                         }
513
514                                 case XmlNodeType.EndElement:
515                                         throw new XmlException ("Illegal ImportNode call for NodeType.EndElement");
516                                 case XmlNodeType.EndEntity:
517                                         throw new XmlException ("Illegal ImportNode call for NodeType.EndEntity");
518
519                                 case XmlNodeType.Entity:
520                                         throw new NotImplementedException ();   // TODO
521
522                                 case XmlNodeType.EntityReference:
523                                         return this.CreateEntityReference (node.Name);
524
525                                 case XmlNodeType.None:
526                                         throw new XmlException ("Illegal ImportNode call for NodeType.None");
527
528                                 case XmlNodeType.Notation:
529                                         throw new NotImplementedException ();   // TODO
530
531                                 case XmlNodeType.ProcessingInstruction:
532                                         XmlProcessingInstruction pi = node as XmlProcessingInstruction;
533                                         return this.CreateProcessingInstruction (pi.Target, pi.Data);
534
535                                 case XmlNodeType.SignificantWhitespace:
536                                         return this.CreateSignificantWhitespace (node.Value);
537
538                                 case XmlNodeType.Text:
539                                         return this.CreateTextNode (node.Value);
540
541                                 case XmlNodeType.Whitespace:
542                                         return this.CreateWhitespace (node.Value);
543
544                                 case XmlNodeType.XmlDeclaration:
545                                         XmlDeclaration srcDecl = node as XmlDeclaration;
546                                         return this.CreateXmlDeclaration (srcDecl.Version, srcDecl.Encoding, srcDecl.Standalone);
547
548                                 default:
549                                         throw new NotImplementedException ();
550                         }
551                 }
552
553                 public virtual void Load (Stream inStream)
554                 {
555                         XmlReader xmlReader = new XmlTextReader (new XmlInputStream (inStream));
556                         Load (xmlReader);
557                 }
558
559                 public virtual void Load (string filename)
560                 {
561                         //HACK, HACK
562                         if (filename.IndexOf (':') != -1) {
563                                 // While we fix Uri the code that uses it is only triggered by a colon in the filename.
564                                 Uri uri = new Uri (filename);
565                                 baseURI = filename;     // FIXME: resolve base
566                                 Stream stream = new XmlUrlResolver ().GetEntity (uri, null, typeof(Stream)) as Stream;
567                                 XmlReader xmlReader = new XmlTextReader (new XmlStreamReader (new XmlInputStream (stream)));
568                                 Load (xmlReader);
569                         } else {
570                                 //Remove this once Uri.Parse is fixed.
571                                 Load (File.OpenRead (filename));
572                         }
573                 }
574
575                 public virtual void Load (TextReader txtReader)
576                 {
577                         Load (new XmlTextReader (txtReader));
578                 }
579
580                 public virtual void Load (XmlReader xmlReader)
581                 {
582                         // Reset our document
583                         // For now this just means removing all our children but later this
584                         // may turn out o need to call a private method that resets other things
585                         // like properties we have, etc.
586                         RemoveAll ();
587
588                         // create all contents with use of ReadNode()
589                         do {
590                                 XmlNode n = ReadNode (xmlReader);
591                                 if(n == null) break;
592                                 AppendChild (n);
593                         } while (true);
594                 }
595
596                 public virtual void LoadXml (string xml)
597                 {
598                         XmlReader xmlReader = new XmlTextReader (new StringReader (xml));
599                         Load (xmlReader);
600                 }
601
602                 internal void onNodeChanged (XmlNode node, XmlNode Parent)
603                 {
604                         if (NodeChanged != null)
605                                 NodeChanged (node, new XmlNodeChangedEventArgs
606                                         (XmlNodeChangedAction.Change,
607                                         node, Parent, Parent));
608                 }
609
610                 internal void onNodeChanging(XmlNode node, XmlNode Parent)
611                 {
612                         if (NodeChanging != null)
613                                 NodeChanging (node, new XmlNodeChangedEventArgs
614                                         (XmlNodeChangedAction.Change,
615                                         node, Parent, Parent));
616                 }
617
618                 internal void onNodeInserted (XmlNode node, XmlNode newParent)
619                 {
620                         if (NodeInserted != null)
621                                 NodeInserted (node, new XmlNodeChangedEventArgs
622                                         (XmlNodeChangedAction.Insert,
623                                         node, null, newParent));
624                 }
625
626                 internal void onNodeInserting (XmlNode node, XmlNode newParent)
627                 {
628                         if (NodeInserting != null)
629                                 NodeInserting (node, new XmlNodeChangedEventArgs
630                                         (XmlNodeChangedAction.Insert,
631                                         node, null, newParent));
632                 }
633
634                 internal void onNodeRemoved (XmlNode node, XmlNode oldParent)
635                 {
636                         if (NodeRemoved != null)
637                                 NodeRemoved (node, new XmlNodeChangedEventArgs
638                                         (XmlNodeChangedAction.Remove,
639                                         node, oldParent, null));
640                 }
641
642                 internal void onNodeRemoving (XmlNode node, XmlNode oldParent)
643                 {
644                         if (NodeRemoving != null)
645                                 NodeRemoving (node, new XmlNodeChangedEventArgs
646                                         (XmlNodeChangedAction.Remove,
647                                         node, oldParent, null));
648                 }
649
650                 private void ParseName (string name, out string prefix, out string localName)
651                 {
652                         int indexOfColon = name.IndexOf (':');
653                         
654                         if (indexOfColon != -1) {
655                                 prefix = name.Substring (0, indexOfColon);
656                                 localName = name.Substring (indexOfColon + 1);
657                         } else {
658                                 prefix = "";
659                                 localName = name;
660                         }
661                 }
662
663                 // Checks that Element's name is valid
664                 private void CheckName (String name)
665                 {
666                         // TODO: others validations?
667                         if (name.IndexOf (" ") >= 0)
668                                 throw new XmlException ("The ' ' characted cannot be included in a name");
669                 }
670
671                 // Reads XmlReader and creates Attribute Node.
672                 private XmlAttribute ReadAttributeNode(XmlReader reader)
673                 {
674                         if(reader.NodeType == XmlNodeType.Element)
675                                 reader.MoveToFirstAttribute ();
676                         else if(reader.NodeType != XmlNodeType.Attribute)
677                                 throw new InvalidOperationException ("bad position to read attribute.");
678                         XmlAttribute attribute = CreateAttribute (reader.Prefix, reader.LocalName, reader.NamespaceURI);
679                         ReadAttributeNodeValue (reader, attribute);
680                         return attribute;
681                 }
682
683                 // Reads attribute from XmlReader and then creates attribute value children. XmlAttribute also uses this.
684                 internal void ReadAttributeNodeValue(XmlReader reader, XmlAttribute attribute)
685                 {
686                         while(reader.ReadAttributeValue ()) {
687                                 if(reader.NodeType == XmlNodeType.EntityReference)
688                                         // FIXME: if DocumentType is available, then try to resolve it.
689                                         attribute.AppendChild (CreateEntityReference (reader.Name));
690                                 // FIXME: else if(NodeType == EndEntity) -- reset BaseURI and so on -- ;
691                                 else
692                                         // (IMHO) Children of Attribute is likely restricted to Text and EntityReference.
693                                         attribute.AppendChild (CreateTextNode (reader.Value));
694                         }
695                 }
696
697                 [MonoTODO("DTD parser is not completed.")]
698                 public virtual XmlNode ReadNode(XmlReader reader)
699                 {
700                         // This logic was formerly defined in 'XmlNode.ConstructDOM()'
701
702                         XmlNode resultNode = null;
703                         XmlNode newNode = null;
704                         XmlNode currentNode = null;
705                         // It was originally XmlDocument.Load(reader reader) when mcs was v0.16.
706                         int startDepth = reader.Depth;
707                         bool atStart = true;
708                         bool ignoredWhitespace;
709                         bool reachedEOF = false;
710
711                         do {
712                                 ignoredWhitespace = false;
713                                 reader.Read ();
714                                 if (reader.NodeType == XmlNodeType.None)
715                                         if (reachedEOF)
716                                                 throw new Exception ("XML Reader reached to end while reading node.");
717                                         else
718                                                 reachedEOF = true;
719                                 // This complicated check is because we shouldn't make
720                                 // improper additional XmlReader.Read() by this method itself.
721                                 if(atStart && (reader.NodeType == XmlNodeType.EndElement || 
722                                         reader.NodeType == XmlNodeType.EndEntity))
723                                         throw new InvalidOperationException ("the XmlReader now holds invalid position.");
724                                 atStart = false;
725                                 switch (reader.NodeType) {
726
727                                 case XmlNodeType.Attribute:
728                                         newNode = ReadAttributeNode (reader);
729                                         break;
730
731                                 case XmlNodeType.CDATA:
732                                         newNode = CreateCDataSection (reader.Value);
733                                         if(currentNode != null)
734                                                 currentNode.AppendChild (newNode);
735                                         break;
736
737                                 case XmlNodeType.Comment:
738                                         newNode = CreateComment (reader.Value);
739                                         if(currentNode != null)
740                                                 currentNode.AppendChild (newNode);
741                                         break;
742
743                                 case XmlNodeType.Element:
744                                         XmlElement element = CreateElement (reader.Prefix, reader.LocalName, reader.NamespaceURI);
745                                         element.IsEmpty = reader.IsEmptyElement;
746                                         if(currentNode != null)
747                                                 currentNode.AppendChild (element);
748                                         else
749                                                 resultNode = element;
750
751                                         // set the element's attributes.
752                                         while (reader.MoveToNextAttribute ()) {
753                                                 element.SetAttributeNode (ReadAttributeNode (reader));
754                                         }
755
756                                         reader.MoveToElement ();
757
758                                         if (!reader.IsEmptyElement)
759                                                 currentNode = element;
760
761                                         break;
762
763                                 case XmlNodeType.EndElement:
764                                         if(currentNode.Name != reader.Name)
765                                                 throw new XmlException ("mismatch end tag.");
766                                         currentNode = currentNode.ParentNode;
767                                         break;
768
769                                 case XmlNodeType.EndEntity:
770                                         break;  // no operation
771
772                                 case XmlNodeType.ProcessingInstruction:
773                                         newNode = CreateProcessingInstruction (reader.Name, reader.Value);
774                                         if(currentNode != null)
775                                                 currentNode.AppendChild (newNode);
776                                         break;
777
778                                 case XmlNodeType.Text:
779                                         newNode = CreateTextNode (reader.Value);
780                                         if(currentNode != null)
781                                                 currentNode.AppendChild (newNode);
782                                         break;
783
784                                 case XmlNodeType.XmlDeclaration:
785                                         // empty strings are dummy, then gives over setting value contents to setter.
786                                         newNode = CreateXmlDeclaration ("1.0" , String.Empty, String.Empty);
787                                         ((XmlDeclaration)newNode).Value = reader.Value;
788                                         if(currentNode != null)
789                                                 throw new XmlException ("XmlDeclaration at invalid position.");
790                                         break;
791
792                                 case XmlNodeType.DocumentType:
793                                         // This logic is kinda hack;-)
794                                         XmlTextReader xtReader = reader as XmlTextReader;
795                                         if(xtReader == null) {
796                                                 xtReader = new XmlTextReader (reader.ReadOuterXml (),
797                                                         XmlNodeType.DocumentType,
798                                                         new XmlParserContext (NameTable, ConstructNamespaceManager(), XmlLang, XmlSpace));
799                                                 xtReader.Read ();
800                                         }
801                                         newNode = CreateDocumentType (xtReader.Name,
802                                                 xtReader.GetAttribute ("PUBLIC"),
803                                                 xtReader.GetAttribute ("SYSTEM"),
804                                                 xtReader.Value);
805                                         if(currentNode != null)
806                                                 throw new XmlException ("XmlDocumentType at invalid position.");
807                                         break;
808
809                                 case XmlNodeType.EntityReference:
810                                         newNode = CreateEntityReference (reader.Name);
811                                         if(currentNode != null)
812                                                 currentNode.AppendChild (newNode);
813                                         break;
814
815                                 case XmlNodeType.SignificantWhitespace:
816                                         newNode = CreateSignificantWhitespace (reader.Value);
817                                         if(currentNode != null)
818                                                 currentNode.AppendChild (newNode);
819                                         break;
820
821                                 case XmlNodeType.Whitespace:
822                                         if(PreserveWhitespace) {
823                                                 newNode = CreateWhitespace (reader.Value);
824                                                 if(currentNode != null)
825                                                         currentNode.AppendChild (newNode);
826                                         }
827                                         else
828                                                 ignoredWhitespace = true;
829                                         break;
830                                 }
831                         } while ((!reader.EOF && ignoredWhitespace) ||
832                                 reader.Depth > startDepth || 
833                                 // This complicated condition is because reader.Depth was set
834                                 // before XmlTextReader.depth increments ;-)
835                                 (reader.Depth == startDepth && reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement == false)
836                                 );
837                         return resultNode != null ? resultNode : newNode;
838                 }
839
840                 public virtual void Save(Stream outStream)
841                 {
842                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, Encoding.UTF8);
843                         xmlWriter.Formatting = Formatting.Indented;
844                         WriteContentTo (xmlWriter);
845                         xmlWriter.Close ();
846                 }
847
848                 public virtual void Save (string filename)
849                 {
850                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, Encoding.UTF8);
851                         xmlWriter.Formatting = Formatting.Indented;
852                         WriteContentTo (xmlWriter);
853                         xmlWriter.Close ();
854                 }
855
856                 [MonoTODO]
857                 public virtual void Save (TextWriter writer)
858                 {
859                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
860                         xmlWriter.Formatting = Formatting.Indented;
861                         WriteContentTo (xmlWriter);
862                         xmlWriter.Flush ();
863                 }
864
865                 public virtual void Save (XmlWriter xmlWriter)
866                 {
867                         //
868                         // This should preserve white space if PreserveWhiteSpace is true
869                         //
870                         WriteContentTo (xmlWriter);
871                         xmlWriter.Flush ();
872                 }
873
874                 public override void WriteContentTo (XmlWriter w)
875                 {
876                         foreach(XmlNode childNode in ChildNodes) {
877                                 childNode.WriteTo (w);
878                         }
879                 }
880
881                 public override void WriteTo (XmlWriter w)
882                 {
883                         WriteContentTo (w);
884                 }
885
886                 private void AddDefaultNameTableKeys ()
887                 {
888                         // The following keys are default of MS .NET Framework
889                         nameTable.Add ("#text");
890                         nameTable.Add ("xml");
891                         nameTable.Add ("xmlns");
892                         nameTable.Add ("#entity");
893                         nameTable.Add ("#document-fragment");
894                         nameTable.Add ("#comment");
895                         nameTable.Add ("space");
896                         nameTable.Add ("id");
897                         nameTable.Add ("#whitespace");
898                         nameTable.Add ("http://www.w3.org/2000/xmlns/");
899                         nameTable.Add ("#cdata-section");
900                         nameTable.Add ("lang");
901                         nameTable.Add ("#document");
902                         nameTable.Add ("#significant-whitespace");
903                 }
904                 #endregion
905         }
906 }