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