2003-01-13 Ville Palo <vi64pa@koti.soon.fi>
[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 (inStream);
556                         Load (xmlReader);
557                 }
558
559                 public virtual void Load (string filename)
560                 {
561                         baseURI = filename;
562                         XmlReader xmlReader = new XmlTextReader (new StreamReader (filename));
563                         Load (xmlReader);
564                 }
565
566                 public virtual void Load (TextReader txtReader)
567                 {
568                         Load (new XmlTextReader (txtReader));
569                 }
570
571                 public virtual void Load (XmlReader xmlReader)
572                 {
573                         // Reset our document
574                         // For now this just means removing all our children but later this
575                         // may turn out o need to call a private method that resets other things
576                         // like properties we have, etc.
577                         RemoveAll ();
578
579                         // create all contents with use of ReadNode()
580                         do {
581                                 XmlNode n = ReadNode (xmlReader);
582                                 if(n == null) break;
583                                 AppendChild (n);
584                         } while (true);
585                 }
586
587                 public virtual void LoadXml (string xml)
588                 {
589                         XmlReader xmlReader = new XmlTextReader (new StringReader (xml));
590                         Load (xmlReader);
591                 }
592
593                 internal void onNodeChanged (XmlNode node, XmlNode Parent)
594                 {
595                         if (NodeChanged != null)
596                                 NodeChanged (node, new XmlNodeChangedEventArgs
597                                         (XmlNodeChangedAction.Change,
598                                         node, Parent, Parent));
599                 }
600
601                 internal void onNodeChanging(XmlNode node, XmlNode Parent)
602                 {
603                         if (NodeChanging != null)
604                                 NodeChanging (node, new XmlNodeChangedEventArgs
605                                         (XmlNodeChangedAction.Change,
606                                         node, Parent, Parent));
607                 }
608
609                 internal void onNodeInserted (XmlNode node, XmlNode newParent)
610                 {
611                         if (NodeInserted != null)
612                                 NodeInserted (node, new XmlNodeChangedEventArgs
613                                         (XmlNodeChangedAction.Insert,
614                                         node, null, newParent));
615                 }
616
617                 internal void onNodeInserting (XmlNode node, XmlNode newParent)
618                 {
619                         if (NodeInserting != null)
620                                 NodeInserting (node, new XmlNodeChangedEventArgs
621                                         (XmlNodeChangedAction.Insert,
622                                         node, null, newParent));
623                 }
624
625                 internal void onNodeRemoved (XmlNode node, XmlNode oldParent)
626                 {
627                         if (NodeRemoved != null)
628                                 NodeRemoved (node, new XmlNodeChangedEventArgs
629                                         (XmlNodeChangedAction.Remove,
630                                         node, oldParent, null));
631                 }
632
633                 internal void onNodeRemoving (XmlNode node, XmlNode oldParent)
634                 {
635                         if (NodeRemoving != null)
636                                 NodeRemoving (node, new XmlNodeChangedEventArgs
637                                         (XmlNodeChangedAction.Remove,
638                                         node, oldParent, null));
639                 }
640
641                 private void ParseName (string name, out string prefix, out string localName)
642                 {
643                         int indexOfColon = name.IndexOf (':');
644                         
645                         if (indexOfColon != -1) {
646                                 prefix = name.Substring (0, indexOfColon);
647                                 localName = name.Substring (indexOfColon + 1);
648                         } else {
649                                 prefix = "";
650                                 localName = name;
651                         }
652                 }
653
654                 // Checks that Element's name is valid
655                 private void CheckName (String name)
656                 {
657                         // TODO: others validations?
658                         if (name.IndexOf (" ") >= 0)
659                                 throw new XmlException ("The ' ' characted cannot be included in a name");
660                 }
661
662                 // Reads XmlReader and creates Attribute Node.
663                 private XmlAttribute ReadAttributeNode(XmlReader reader)
664                 {
665                         if(reader.NodeType == XmlNodeType.Element)
666                                 reader.MoveToFirstAttribute ();
667                         else if(reader.NodeType != XmlNodeType.Attribute)
668                                 throw new InvalidOperationException ("bad position to read attribute.");
669                         XmlAttribute attribute = CreateAttribute (reader.Prefix, reader.LocalName, reader.NamespaceURI);
670                         ReadAttributeNodeValue (reader, attribute);
671                         return attribute;
672                 }
673
674                 // Reads attribute from XmlReader and then creates attribute value children. XmlAttribute also uses this.
675                 internal void ReadAttributeNodeValue(XmlReader reader, XmlAttribute attribute)
676                 {
677                         while(reader.ReadAttributeValue ()) {
678                                 if(reader.NodeType == XmlNodeType.EntityReference)
679                                         // FIXME: if DocumentType is available, then try to resolve it.
680                                         attribute.AppendChild (CreateEntityReference (reader.Name));
681                                 // FIXME: else if(NodeType == EndEntity) -- reset BaseURI and so on -- ;
682                                 else
683                                         // (IMHO) Children of Attribute is likely restricted to Text and EntityReference.
684                                         attribute.AppendChild (CreateTextNode (reader.Value));
685                         }
686                 }
687
688                 [MonoTODO("DTD parser is not completed.")]
689                 public virtual XmlNode ReadNode(XmlReader reader)
690                 {
691                         // This logic was formerly defined in 'XmlNode.ConstructDOM()'
692
693                         XmlNode resultNode = null;
694                         XmlNode newNode = null;
695                         XmlNode currentNode = null;
696                         // It was originally XmlDocument.Load(reader reader) when mcs was v0.16.
697                         int startDepth = reader.Depth;
698                         bool atStart = true;
699                         bool ignoredWhitespace;
700
701                         do {
702                                 ignoredWhitespace = false;
703                                 reader.Read ();
704                                 // This complicated check is because we shouldn't make
705                                 // improper additional XmlReader.Read() by this method itself.
706                                 if(atStart && (reader.NodeType == XmlNodeType.EndElement || 
707                                         reader.NodeType == XmlNodeType.EndEntity))
708                                         throw new InvalidOperationException ("the XmlReader now holds invalid position.");
709                                 atStart = false;
710                                 switch (reader.NodeType) {
711
712                                 case XmlNodeType.Attribute:
713                                         newNode = ReadAttributeNode (reader);
714                                         break;
715
716                                 case XmlNodeType.CDATA:
717                                         newNode = CreateCDataSection (reader.Value);
718                                         if(currentNode != null)
719                                                 currentNode.AppendChild (newNode);
720                                         break;
721
722                                 case XmlNodeType.Comment:
723                                         newNode = CreateComment (reader.Value);
724                                         if(currentNode != null)
725                                                 currentNode.AppendChild (newNode);
726                                         break;
727
728                                 case XmlNodeType.Element:
729                                         XmlElement element = CreateElement (reader.Prefix, reader.LocalName, reader.NamespaceURI);
730                                         element.IsEmpty = reader.IsEmptyElement;
731                                         if(currentNode != null)
732                                                 currentNode.AppendChild (element);
733                                         else
734                                                 resultNode = element;
735
736                                         // set the element's attributes.
737                                         while (reader.MoveToNextAttribute ()) {
738                                                 element.SetAttributeNode (ReadAttributeNode (reader));
739                                         }
740
741                                         reader.MoveToElement ();
742
743                                         if (!reader.IsEmptyElement)
744                                                 currentNode = element;
745
746                                         break;
747
748                                 case XmlNodeType.EndElement:
749                                         if(currentNode.Name != reader.Name)
750                                                 throw new XmlException ("mismatch end tag.");
751                                         currentNode = currentNode.ParentNode;
752                                         break;
753
754                                 case XmlNodeType.EndEntity:
755                                         break;  // no operation
756
757                                 case XmlNodeType.ProcessingInstruction:
758                                         newNode = CreateProcessingInstruction (reader.Name, reader.Value);
759                                         if(currentNode != null)
760                                                 currentNode.AppendChild (newNode);
761                                         break;
762
763                                 case XmlNodeType.Text:
764                                         newNode = CreateTextNode (reader.Value);
765                                         if(currentNode != null)
766                                                 currentNode.AppendChild (newNode);
767                                         break;
768
769                                 case XmlNodeType.XmlDeclaration:
770                                         // empty strings are dummy, then gives over setting value contents to setter.
771                                         newNode = CreateXmlDeclaration ("1.0" , String.Empty, String.Empty);
772                                         ((XmlDeclaration)newNode).Value = reader.Value;
773                                         if(currentNode != null)
774                                                 throw new XmlException ("XmlDeclaration at invalid position.");
775                                         break;
776
777                                 case XmlNodeType.DocumentType:
778                                         // This logic is kinda hack;-)
779                                         XmlTextReader xtReader = reader as XmlTextReader;
780                                         if(xtReader == null) {
781                                                 xtReader = new XmlTextReader (reader.ReadOuterXml (),
782                                                         XmlNodeType.DocumentType,
783                                                         new XmlParserContext (NameTable, ConstructNamespaceManager(), XmlLang, XmlSpace));
784                                                 xtReader.Read ();
785                                         }
786                                         newNode = CreateDocumentType (xtReader.Name,
787                                                 xtReader.GetAttribute ("PUBLIC"),
788                                                 xtReader.GetAttribute ("SYSTEM"),
789                                                 xtReader.Value);
790                                         if(currentNode != null)
791                                                 throw new XmlException ("XmlDocumentType at invalid position.");
792                                         break;
793
794                                 case XmlNodeType.EntityReference:
795                                         newNode = CreateEntityReference (reader.Name);
796                                         if(currentNode != null)
797                                                 currentNode.AppendChild (newNode);
798                                         break;
799
800                                 case XmlNodeType.SignificantWhitespace:
801                                         newNode = CreateSignificantWhitespace (reader.Value);
802                                         if(currentNode != null)
803                                                 currentNode.AppendChild (newNode);
804                                         break;
805
806                                 case XmlNodeType.Whitespace:
807                                         if(PreserveWhitespace) {
808                                                 newNode = CreateWhitespace (reader.Value);
809                                                 if(currentNode != null)
810                                                         currentNode.AppendChild (newNode);
811                                         }
812                                         else
813                                                 ignoredWhitespace = true;
814                                         break;
815                                 }
816                         } while(ignoredWhitespace ||
817                                 reader.Depth > startDepth || 
818                                 // This complicated condition is because reader.Depth was set
819                                 // before XmlTextReader.depth increments ;-)
820                                 (reader.Depth == startDepth && reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement == false)
821                                 );
822                         return resultNode != null ? resultNode : newNode;
823                 }
824
825                 public virtual void Save(Stream outStream)
826                 {
827                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, Encoding.UTF8);
828                         xmlWriter.Formatting = Formatting.Indented;
829                         WriteContentTo (xmlWriter);
830                         xmlWriter.Close ();
831                 }
832
833                 public virtual void Save (string filename)
834                 {
835                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, Encoding.UTF8);
836                         xmlWriter.Formatting = Formatting.Indented;
837                         WriteContentTo (xmlWriter);
838                         xmlWriter.Close ();
839                 }
840
841                 [MonoTODO]
842                 public virtual void Save (TextWriter writer)
843                 {
844                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
845                         xmlWriter.Formatting = Formatting.Indented;
846                         WriteContentTo (xmlWriter);
847                         xmlWriter.Flush ();
848                 }
849
850                 public virtual void Save (XmlWriter xmlWriter)
851                 {
852                         //
853                         // This should preserve white space if PreserveWhiteSpace is true
854                         //
855                         WriteContentTo (xmlWriter);
856                         xmlWriter.Flush ();
857                 }
858
859                 public override void WriteContentTo (XmlWriter w)
860                 {
861                         foreach(XmlNode childNode in ChildNodes) {
862                                 childNode.WriteTo (w);
863                         }
864                 }
865
866                 public override void WriteTo (XmlWriter w)
867                 {
868                         WriteContentTo (w);
869                 }
870
871                 private void AddDefaultNameTableKeys ()
872                 {
873                         // The following keys are default of MS .NET Framework
874                         nameTable.Add ("#text");
875                         nameTable.Add ("xml");
876                         nameTable.Add ("xmlns");
877                         nameTable.Add ("#entity");
878                         nameTable.Add ("#document-fragment");
879                         nameTable.Add ("#comment");
880                         nameTable.Add ("space");
881                         nameTable.Add ("id");
882                         nameTable.Add ("#whitespace");
883                         nameTable.Add ("http://www.w3.org/2000/xmlns/");
884                         nameTable.Add ("#cdata-section");
885                         nameTable.Add ("lang");
886                         nameTable.Add ("#document");
887                         nameTable.Add ("#significant-whitespace");
888                 }
889                 #endregion
890         }
891 }