2003-02-27 Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
[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                         Load (new XmlTextReader (new XmlStreamReader (inStream)));
556                 }
557
558                 public virtual void Load (string filename)
559                 {
560                         Load (new XmlTextReader (new XmlStreamReader (filename)));
561                 }
562
563                 public virtual void Load (TextReader txtReader)
564                 {
565                         Load (new XmlTextReader (txtReader));
566                 }
567
568                 [MonoTODO("XmlValidatingReader should be used.")]
569                 public virtual void Load (XmlReader xmlReader)
570                 {
571                         // Reset our document
572                         // For now this just means removing all our children but later this
573                         // may turn out o need to call a private method that resets other things
574                         // like properties we have, etc.
575                         RemoveAll ();
576
577                         this.baseURI = xmlReader.BaseURI;
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                         bool reachedEOF = false;
701
702                         do {
703                                 ignoredWhitespace = false;
704                                 reader.Read ();
705                                 if (reader.NodeType == XmlNodeType.None)
706                                         if (reachedEOF)
707                                                 throw new Exception ("XML Reader reached to end while reading node.");
708                                         else
709                                                 reachedEOF = true;
710                                 // This complicated check is because we shouldn't make
711                                 // improper additional XmlReader.Read() by this method itself.
712                                 if(atStart && (reader.NodeType == XmlNodeType.EndElement || 
713                                         reader.NodeType == XmlNodeType.EndEntity))
714                                         throw new InvalidOperationException ("the XmlReader now holds invalid position.");
715                                 atStart = false;
716                                 switch (reader.NodeType) {
717
718                                 case XmlNodeType.Attribute:
719                                         newNode = ReadAttributeNode (reader);
720                                         break;
721
722                                 case XmlNodeType.CDATA:
723                                         newNode = CreateCDataSection (reader.Value);
724                                         if(currentNode != null)
725                                                 currentNode.AppendChild (newNode);
726                                         break;
727
728                                 case XmlNodeType.Comment:
729                                         newNode = CreateComment (reader.Value);
730                                         if(currentNode != null)
731                                                 currentNode.AppendChild (newNode);
732                                         break;
733
734                                 case XmlNodeType.Element:
735                                         XmlElement element = CreateElement (reader.Prefix, reader.LocalName, reader.NamespaceURI);
736                                         element.IsEmpty = reader.IsEmptyElement;
737                                         if(currentNode != null)
738                                                 currentNode.AppendChild (element);
739                                         else
740                                                 resultNode = element;
741
742                                         // set the element's attributes.
743                                         while (reader.MoveToNextAttribute ()) {
744                                                 element.SetAttributeNode (ReadAttributeNode (reader));
745                                         }
746
747                                         reader.MoveToElement ();
748
749                                         if (!reader.IsEmptyElement)
750                                                 currentNode = element;
751
752                                         break;
753
754                                 case XmlNodeType.EndElement:
755                                         if(currentNode.Name != reader.Name)
756                                                 throw new XmlException ("mismatch end tag.");
757                                         currentNode = currentNode.ParentNode;
758                                         break;
759
760                                 case XmlNodeType.EndEntity:
761                                         break;  // no operation
762
763                                 case XmlNodeType.ProcessingInstruction:
764                                         newNode = CreateProcessingInstruction (reader.Name, reader.Value);
765                                         if(currentNode != null)
766                                                 currentNode.AppendChild (newNode);
767                                         break;
768
769                                 case XmlNodeType.Text:
770                                         newNode = CreateTextNode (reader.Value);
771                                         if(currentNode != null)
772                                                 currentNode.AppendChild (newNode);
773                                         break;
774
775                                 case XmlNodeType.XmlDeclaration:
776                                         // empty strings are dummy, then gives over setting value contents to setter.
777                                         newNode = CreateXmlDeclaration ("1.0" , String.Empty, String.Empty);
778                                         ((XmlDeclaration)newNode).Value = reader.Value;
779                                         if(currentNode != null)
780                                                 throw new XmlException ("XmlDeclaration at invalid position.");
781                                         break;
782
783                                 case XmlNodeType.DocumentType:
784                                         // This logic is kinda hack;-)
785                                         XmlTextReader xtReader = reader as XmlTextReader;
786                                         if(xtReader == null) {
787                                                 xtReader = new XmlTextReader (reader.ReadOuterXml (),
788                                                         XmlNodeType.DocumentType,
789                                                         new XmlParserContext (NameTable, ConstructNamespaceManager(), XmlLang, XmlSpace));
790                                                 xtReader.Read ();
791                                         }
792                                         newNode = CreateDocumentType (xtReader.Name,
793                                                 xtReader.GetAttribute ("PUBLIC"),
794                                                 xtReader.GetAttribute ("SYSTEM"),
795                                                 xtReader.Value);
796                                         if(currentNode != null)
797                                                 throw new XmlException ("XmlDocumentType at invalid position.");
798                                         break;
799
800                                 case XmlNodeType.EntityReference:
801                                         newNode = CreateEntityReference (reader.Name);
802                                         if(currentNode != null)
803                                                 currentNode.AppendChild (newNode);
804                                         break;
805
806                                 case XmlNodeType.SignificantWhitespace:
807                                         newNode = CreateSignificantWhitespace (reader.Value);
808                                         if(currentNode != null)
809                                                 currentNode.AppendChild (newNode);
810                                         break;
811
812                                 case XmlNodeType.Whitespace:
813                                         if(PreserveWhitespace) {
814                                                 newNode = CreateWhitespace (reader.Value);
815                                                 if(currentNode != null)
816                                                         currentNode.AppendChild (newNode);
817                                         }
818                                         else
819                                                 ignoredWhitespace = true;
820                                         break;
821                                 }
822                         } while ((!reader.EOF && ignoredWhitespace) ||
823                                 reader.Depth > startDepth || 
824                                 // This complicated condition is because reader.Depth was set
825                                 // before XmlTextReader.depth increments ;-)
826                                 (reader.Depth == startDepth && reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement == false)
827                                 );
828                         return resultNode != null ? resultNode : newNode;
829                 }
830
831                 public virtual void Save(Stream outStream)
832                 {
833                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, Encoding.UTF8);
834                         xmlWriter.Formatting = Formatting.Indented;
835                         WriteContentTo (xmlWriter);
836                         xmlWriter.Close ();
837                 }
838
839                 public virtual void Save (string filename)
840                 {
841                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, Encoding.UTF8);
842                         xmlWriter.Formatting = Formatting.Indented;
843                         WriteContentTo (xmlWriter);
844                         xmlWriter.Close ();
845                 }
846
847                 [MonoTODO]
848                 public virtual void Save (TextWriter writer)
849                 {
850                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
851                         xmlWriter.Formatting = Formatting.Indented;
852                         WriteContentTo (xmlWriter);
853                         xmlWriter.Flush ();
854                 }
855
856                 public virtual void Save (XmlWriter xmlWriter)
857                 {
858                         //
859                         // This should preserve white space if PreserveWhiteSpace is true
860                         //
861                         WriteContentTo (xmlWriter);
862                         xmlWriter.Flush ();
863                 }
864
865                 public override void WriteContentTo (XmlWriter w)
866                 {
867                         foreach(XmlNode childNode in ChildNodes) {
868                                 childNode.WriteTo (w);
869                         }
870                 }
871
872                 public override void WriteTo (XmlWriter w)
873                 {
874                         WriteContentTo (w);
875                 }
876
877                 private void AddDefaultNameTableKeys ()
878                 {
879                         // The following keys are default of MS .NET Framework
880                         nameTable.Add ("#text");
881                         nameTable.Add ("xml");
882                         nameTable.Add ("xmlns");
883                         nameTable.Add ("#entity");
884                         nameTable.Add ("#document-fragment");
885                         nameTable.Add ("#comment");
886                         nameTable.Add ("space");
887                         nameTable.Add ("id");
888                         nameTable.Add ("#whitespace");
889                         nameTable.Add ("http://www.w3.org/2000/xmlns/");
890                         nameTable.Add ("#cdata-section");
891                         nameTable.Add ("lang");
892                         nameTable.Add ("#document");
893                         nameTable.Add ("#significant-whitespace");
894                 }
895                 #endregion
896         }
897 }