2004-03-04 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                 XmlNameTable nameTable;
33                 string baseURI = String.Empty;
34                 XmlImplementation implementation;
35                 bool preserveWhitespace = false;
36                 XmlResolver resolver;
37                 Hashtable idTable = new Hashtable ();
38
39                 // MS.NET rejects undeclared entities _only_ during Load(),
40                 // while ReadNode() never rejects such node. So it signs
41                 // whether we are on Load() or not (MS.NET uses Loader class,
42                 // but we don't have to implement Load() as such)
43                 bool loadMode;
44
45                 #endregion
46
47                 #region Constructors
48
49                 public XmlDocument () : this (null, null)
50                 {
51                 }
52
53                 protected internal XmlDocument (XmlImplementation imp) : this (imp, null)
54                 {
55                 }
56
57                 public XmlDocument (XmlNameTable nt) : this (null, nt)
58                 {
59                 }
60
61                 XmlDocument (XmlImplementation impl, XmlNameTable nt) : base (null)
62                 {
63                         if (impl == null)
64                                 implementation = new XmlImplementation ();
65                         else
66                                 implementation = impl;
67
68                         nameTable = (nt != null) ? nt : implementation.InternalNameTable;
69                         AddDefaultNameTableKeys ();
70                         resolver = new XmlUrlResolver ();
71                 }
72                 #endregion
73
74                 #region Events
75
76                 public event XmlNodeChangedEventHandler NodeChanged;
77
78                 public event XmlNodeChangedEventHandler NodeChanging;
79
80                 public event XmlNodeChangedEventHandler NodeInserted;
81
82                 public event XmlNodeChangedEventHandler NodeInserting;
83
84                 public event XmlNodeChangedEventHandler NodeRemoved;
85
86                 public event XmlNodeChangedEventHandler NodeRemoving;
87
88                 #endregion
89
90                 #region Properties
91
92                 public override string BaseURI {
93                         get {
94                                 return baseURI;
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                 public virtual XmlDocumentType DocumentType {
113                         get {
114                                 for (int i = 0; i < ChildNodes.Count; i++) {
115                                         XmlNode n = ChildNodes [i];
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 bool IsStandalone {
141                         get {
142                                 return FirstChild != null &&
143                                         FirstChild.NodeType == XmlNodeType.XmlDeclaration &&
144                                         ((XmlDeclaration) this.FirstChild).Standalone == "yes";
145                         }
146                 }
147
148                 public override string LocalName {
149                         get { return "#document"; }
150                 }
151
152                 public override string Name {
153                         get { return "#document"; }
154                 }
155
156                 public XmlNameTable NameTable {
157                         get { return nameTable; }
158                 }
159
160                 public override XmlNodeType NodeType {
161                         get { return XmlNodeType.Document; }
162                 }
163
164                 internal override XPathNodeType XPathNodeType {
165                         get {
166                                 return XPathNodeType.Root;
167                         }
168                 }
169
170                 public override XmlDocument OwnerDocument {
171                         get { return null; }
172                 }
173
174                 public bool PreserveWhitespace {
175                         get { return preserveWhitespace; }
176                         set { preserveWhitespace = value; }
177                 }
178
179                 internal XmlResolver Resolver {
180                         get { return resolver; }
181                 }
182
183                 internal override string XmlLang {
184                         get { return String.Empty; }
185                 }
186
187                 public virtual XmlResolver XmlResolver {
188                         set { resolver = value; }
189                 }
190
191                 internal override XmlSpace XmlSpace {
192                         get {
193                                 return XmlSpace.None;
194                         }
195                 }
196                 
197                 internal Encoding TextEncoding {
198                         get {
199                                 XmlDeclaration dec = FirstChild as XmlDeclaration;
200                         
201                                 if (dec == null || dec.Encoding == "")
202                                         return null;
203                                 
204                                 return Encoding.GetEncoding (dec.Encoding);
205                         }
206                 }
207
208                 #endregion
209
210                 #region Methods
211                 internal void AddIdenticalAttribute (XmlAttribute attr)
212                 {
213                         idTable [attr.Value] = attr;
214                 }
215
216                 public override XmlNode CloneNode (bool deep)
217                 {
218                         XmlDocument doc = implementation != null ? implementation.CreateDocument () : new XmlDocument ();
219                         doc.baseURI = baseURI;
220
221                         if(deep)
222                         {
223                                 for (int i = 0; i < ChildNodes.Count; i++)
224                                         doc.AppendChild (doc.ImportNode (ChildNodes [i], deep));
225                         }
226                         return doc;
227                 }
228
229                 public XmlAttribute CreateAttribute (string name)
230                 {
231                         string prefix;
232                         string localName;
233                         string namespaceURI = String.Empty;
234
235                         ParseName (name, out prefix, out localName);
236
237                         if (prefix == "xmlns" || (prefix == "" && localName == "xmlns"))
238                                 namespaceURI = XmlNamespaceManager.XmlnsXmlns;
239                         else if (prefix == "xml")
240                                 namespaceURI = XmlNamespaceManager.XmlnsXml;
241
242                         return CreateAttribute (prefix, localName, namespaceURI );
243                 }
244
245                 public XmlAttribute CreateAttribute (string qualifiedName, string namespaceURI)
246                 {
247                         string prefix;
248                         string localName;
249
250                         ParseName (qualifiedName, out prefix, out localName);
251
252                         return CreateAttribute (prefix, localName, namespaceURI);
253                 }
254
255                 public virtual XmlAttribute CreateAttribute (string prefix, string localName, string namespaceURI)
256                 {
257                         return CreateAttribute (prefix, localName, namespaceURI, false, true);
258                 }
259
260                 internal XmlAttribute CreateAttribute (string prefix, string localName, string namespaceURI, bool atomizedNames, bool checkNamespace)
261                 {
262                         if ((localName == null) || (localName == String.Empty))
263                                 throw new ArgumentException ("The attribute local name cannot be empty.");
264
265                         return new XmlAttribute (prefix, localName, namespaceURI, this, atomizedNames, checkNamespace);
266                 }
267
268                 public virtual XmlCDataSection CreateCDataSection (string data)
269                 {
270                         return new XmlCDataSection (data, this);
271                 }
272
273                 public virtual XmlComment CreateComment (string data)
274                 {
275                         return new XmlComment (data, this);
276                 }
277
278                 protected internal virtual XmlAttribute CreateDefaultAttribute (string prefix, string localName, string namespaceURI)
279                 {
280                         XmlAttribute attr = CreateAttribute (prefix, localName, namespaceURI);
281                         attr.isDefault = true;
282                         return attr;
283                 }
284
285                 public virtual XmlDocumentFragment CreateDocumentFragment ()
286                 {
287                         return new XmlDocumentFragment (this);
288                 }
289
290                 public virtual XmlDocumentType CreateDocumentType (string name, string publicId,
291                                                                    string systemId, string internalSubset)
292                 {
293                         return new XmlDocumentType (name, publicId, systemId, internalSubset, this);
294                 }
295
296                 private XmlDocumentType CreateDocumentType (DTDObjectModel dtd)
297                 {
298                         return new XmlDocumentType (dtd, this);
299                 }
300
301                 public XmlElement CreateElement (string name)
302                 {
303                         return CreateElement (name, String.Empty);
304                 }
305
306                 public XmlElement CreateElement (
307                         string qualifiedName, 
308                         string namespaceURI)
309                 {
310                         string prefix;
311                         string localName;
312
313                         ParseName (qualifiedName, out prefix, out localName);
314                         
315                         return CreateElement (prefix, localName, namespaceURI);
316                 }
317
318                 public virtual XmlElement CreateElement (
319                         string prefix,
320                         string localName,
321                         string namespaceURI)
322                 {
323                         if ((localName == null) || (localName == String.Empty))
324                                 throw new ArgumentException ("The local name for elements or attributes cannot be null or an empty string.");
325                         // FIXME: MS.NET has a weird behavior that they can Load() from XmlTextReader 
326                         // whose Namespaces = false, but their CreateElement() never allows qualified name.
327                         // I leave it as it is.
328                         if (!XmlChar.IsName (localName))
329                                 throw new ArgumentException ("Invalid name.", "localName");
330                         return new XmlElement (prefix != null ? prefix : String.Empty, localName, namespaceURI != null ? namespaceURI : String.Empty, this, false);
331                 }
332
333                 public virtual XmlEntityReference CreateEntityReference (string name)
334                 {
335                         return new XmlEntityReference (name, this);
336                 }
337
338                 protected internal virtual XPathNavigator CreateNavigator (XmlNode node)
339                 {
340                         return new XmlDocumentNavigator (node);
341                 }
342
343                 public virtual XmlNode CreateNode (
344                         string nodeTypeString,
345                         string name,
346                         string namespaceURI)
347                 {
348                         return CreateNode (GetNodeTypeFromString (nodeTypeString), name, namespaceURI);
349                 }
350
351                 public virtual XmlNode CreateNode (
352                         XmlNodeType type,
353                         string name,
354                         string namespaceURI)
355                 {
356                         string prefix = null;
357                         string localName = name;
358
359                         if ((type == XmlNodeType.Attribute) || (type == XmlNodeType.Element) || (type == XmlNodeType.EntityReference))
360                                 ParseName (name, out prefix, out localName);
361                         
362                         return CreateNode (type, prefix, localName, namespaceURI);
363                 }
364
365                 public virtual XmlNode CreateNode (
366                         XmlNodeType type,
367                         string prefix,
368                         string name,
369                         string namespaceURI)
370                 {
371                         switch (type) {
372                                 case XmlNodeType.Attribute: return CreateAttribute (prefix, name, namespaceURI);
373                                 case XmlNodeType.CDATA: return CreateCDataSection (null);
374                                 case XmlNodeType.Comment: return CreateComment (null);
375                                 case XmlNodeType.Document: return new XmlDocument ();
376                                 case XmlNodeType.DocumentFragment: return CreateDocumentFragment ();
377                                 case XmlNodeType.DocumentType: return CreateDocumentType (null, null, null, null);
378                                 case XmlNodeType.Element: return CreateElement (prefix, name, namespaceURI);
379                                 case XmlNodeType.EntityReference: return CreateEntityReference (null);
380                                 case XmlNodeType.ProcessingInstruction: return CreateProcessingInstruction (null, null);
381                                 case XmlNodeType.SignificantWhitespace: return CreateSignificantWhitespace (String.Empty);
382                                 case XmlNodeType.Text: return CreateTextNode (null);
383                                 case XmlNodeType.Whitespace: return CreateWhitespace (String.Empty);
384                                 case XmlNodeType.XmlDeclaration: return CreateXmlDeclaration ("1.0", null, null);
385                                 default: throw new ArgumentOutOfRangeException(String.Format("{0}\nParameter name: {1}",
386                                                          "Specified argument was out of the range of valid values", type.ToString ()));
387                         }
388                 }
389
390                 public virtual XmlProcessingInstruction CreateProcessingInstruction (
391                         string target,
392                         string data)
393                 {
394                         return new XmlProcessingInstruction (target, data, this);
395                 }
396
397                 public virtual XmlSignificantWhitespace CreateSignificantWhitespace (string text)
398                 {
399                         if (!XmlChar.IsWhitespace (text))
400                                     throw new ArgumentException ("Invalid whitespace characters.");
401                          
402                         return new XmlSignificantWhitespace (text, this);
403                 }
404
405                 public virtual XmlText CreateTextNode (string text)
406                 {
407                         return new XmlText (text, this);
408                 }
409
410                 public virtual XmlWhitespace CreateWhitespace (string text)
411                 {
412                         if (!XmlChar.IsWhitespace (text))
413                             throw new ArgumentException ("Invalid whitespace characters.");
414                          
415                         return new XmlWhitespace (text, this);
416                 }
417
418                 public virtual XmlDeclaration CreateXmlDeclaration (string version, string encoding,
419                                                                     string standalone)
420                 {
421                         if (version != "1.0")
422                                 throw new ArgumentException ("version string is not correct.");
423
424                         if  ((standalone != null && standalone != String.Empty) && !((standalone == "yes") || (standalone == "no")))
425                                 throw new ArgumentException ("standalone string is not correct.");
426
427                         return new XmlDeclaration (version, encoding, standalone, this);
428                 }
429
430                 // FIXME: Currently XmlAttributeCollection.SetNamedItem() does
431                 // add to the identity table, but in fact I delayed identity
432                 // check on GetIdenticalAttribute. To make such way complete,
433                 // we have to use MultiMap, not Hashtable.
434                 //
435                 // Well, MS.NET is also fragile around ID.
436                 public virtual XmlElement GetElementById (string elementId)
437                 {
438                         XmlAttribute attr = GetIdenticalAttribute (elementId);
439                         return attr != null ? attr.OwnerElement : null;
440                 }
441
442                 public virtual XmlNodeList GetElementsByTagName (string name)
443                 {
444                         ArrayList nodeArrayList = new ArrayList ();
445                         this.SearchDescendantElements (name, name == "*", nodeArrayList);
446                         return new XmlNodeArrayList (nodeArrayList);
447                 }
448
449                 public virtual XmlNodeList GetElementsByTagName (string localName, string namespaceURI)
450                 {
451                         ArrayList nodeArrayList = new ArrayList ();
452                         this.SearchDescendantElements (localName, localName == "*", namespaceURI, namespaceURI == "*", nodeArrayList);
453                         return new XmlNodeArrayList (nodeArrayList);
454                 }
455
456                 private XmlNodeType GetNodeTypeFromString (string nodeTypeString)
457                 {
458                         switch (nodeTypeString) {
459                                 case "attribute": return XmlNodeType.Attribute;
460                                 case "cdatasection": return XmlNodeType.CDATA;
461                                 case "comment": return XmlNodeType.Comment;
462                                 case "document": return XmlNodeType.Document;
463                                 case "documentfragment": return XmlNodeType.DocumentFragment;
464                                 case "documenttype": return XmlNodeType.DocumentType;
465                                 case "element": return XmlNodeType.Element;
466                                 case "entityreference": return XmlNodeType.EntityReference;
467                                 case "processinginstruction": return XmlNodeType.ProcessingInstruction;
468                                 case "significantwhitespace": return XmlNodeType.SignificantWhitespace;
469                                 case "text": return XmlNodeType.Text;
470                                 case "whitespace": return XmlNodeType.Whitespace;
471                                 default:
472                                         throw new ArgumentException(String.Format("The string doesn't represent any node type : {0}.", nodeTypeString));
473                         }
474                 }
475
476                 internal XmlAttribute GetIdenticalAttribute (string id)
477                 {
478                         XmlAttribute attr = this.idTable [id] as XmlAttribute;
479                         if (attr == null)
480                                 return null;
481                         if (attr.OwnerElement == null || !attr.OwnerElement.IsRooted) {
482 //                              idTable.Remove (id);
483                                 return null;
484                         }
485                         return attr;
486                 }
487
488                 public virtual XmlNode ImportNode (XmlNode node, bool deep)
489                 {
490                         switch (node.NodeType) {
491                         case XmlNodeType.Attribute:
492                                 XmlAttribute srcAtt = node as XmlAttribute;
493                                 XmlAttribute dstAtt = this.CreateAttribute (srcAtt.Prefix, srcAtt.LocalName, srcAtt.NamespaceURI);
494                                 for (int i = 0; i < srcAtt.ChildNodes.Count; i++)
495                                         dstAtt.AppendChild (this.ImportNode (srcAtt.ChildNodes [i], deep));
496                                 return dstAtt;
497
498                         case XmlNodeType.CDATA:
499                                 return this.CreateCDataSection (node.Value);
500
501                         case XmlNodeType.Comment:
502                                 return this.CreateComment (node.Value);
503
504                         case XmlNodeType.Document:
505                                 throw new XmlException ("Document cannot be imported.");
506
507                         case XmlNodeType.DocumentFragment:
508                                 XmlDocumentFragment df = this.CreateDocumentFragment ();
509                                 if(deep)
510                                         for (int i = 0; i < node.ChildNodes.Count; i++)
511                                                 df.AppendChild (this.ImportNode (node.ChildNodes [i], deep));
512                                 return df;
513
514                         case XmlNodeType.DocumentType:
515                                 throw new XmlException ("DocumentType cannot be imported.");
516
517                         case XmlNodeType.Element:
518                                 XmlElement src = (XmlElement)node;
519                                 XmlElement dst = this.CreateElement (src.Prefix, src.LocalName, src.NamespaceURI);
520                                 for (int i = 0; i < src.Attributes.Count; i++) {
521                                         XmlAttribute attr = src.Attributes [i];
522                                         if(attr.Specified)      // copies only specified attributes
523                                                 dst.SetAttributeNode ((XmlAttribute) this.ImportNode (attr, deep));
524                                 }
525                                 if(deep)
526                                         for (int i = 0; i < src.ChildNodes.Count; i++)
527                                                 dst.AppendChild (this.ImportNode (src.ChildNodes [i], 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)
603                                                 break;
604                                         if (preserveWhitespace || n.NodeType != XmlNodeType.Whitespace)
605                                                 AppendChild (n);
606                                 } while (true);
607                         } finally {
608                                 loadMode = false;
609                         }
610                 }
611
612                 public virtual void LoadXml (string xml)
613                 {
614                         XmlTextReader xmlReader = new XmlTextReader (
615                                 xml, XmlNodeType.Document, null);
616                         try {
617                                 xmlReader.XmlResolver = resolver;
618                                 Load (xmlReader);
619                         } finally {
620                                 xmlReader.Close ();
621                         }
622                 }
623
624                 internal void onNodeChanged (XmlNode node, XmlNode Parent)
625                 {
626                         if (NodeChanged != null)
627                                 NodeChanged (node, new XmlNodeChangedEventArgs
628                                         (XmlNodeChangedAction.Change,
629                                         node, Parent, Parent));
630                 }
631
632                 internal void onNodeChanging(XmlNode node, XmlNode Parent)
633                 {
634                         if (NodeChanging != null)
635                                 NodeChanging (node, new XmlNodeChangedEventArgs
636                                         (XmlNodeChangedAction.Change,
637                                         node, Parent, Parent));
638                 }
639
640                 internal void onNodeInserted (XmlNode node, XmlNode newParent)
641                 {
642                         if (NodeInserted != null)
643                                 NodeInserted (node, new XmlNodeChangedEventArgs
644                                         (XmlNodeChangedAction.Insert,
645                                         node, null, newParent));
646                 }
647
648                 internal void onNodeInserting (XmlNode node, XmlNode newParent)
649                 {
650                         if (NodeInserting != null)
651                                 NodeInserting (node, new XmlNodeChangedEventArgs
652                                         (XmlNodeChangedAction.Insert,
653                                         node, null, newParent));
654                 }
655
656                 internal void onNodeRemoved (XmlNode node, XmlNode oldParent)
657                 {
658                         if (NodeRemoved != null)
659                                 NodeRemoved (node, new XmlNodeChangedEventArgs
660                                         (XmlNodeChangedAction.Remove,
661                                         node, oldParent, null));
662                 }
663
664                 internal void onNodeRemoving (XmlNode node, XmlNode oldParent)
665                 {
666                         if (NodeRemoving != null)
667                                 NodeRemoving (node, new XmlNodeChangedEventArgs
668                                         (XmlNodeChangedAction.Remove,
669                                         node, oldParent, null));
670                 }
671
672                 private void ParseName (string name, out string prefix, out string localName)
673                 {
674                         int indexOfColon = name.IndexOf (':');
675                         
676                         if (indexOfColon != -1) {
677                                 prefix = name.Substring (0, indexOfColon);
678                                 localName = name.Substring (indexOfColon + 1);
679                         } else {
680                                 prefix = "";
681                                 localName = name;
682                         }
683                 }
684
685                 // Reads XmlReader and creates Attribute Node.
686                 private XmlAttribute ReadAttributeNode(XmlReader reader)
687                 {
688                         if(reader.NodeType == XmlNodeType.Element)
689                                 reader.MoveToFirstAttribute ();
690                         else if(reader.NodeType != XmlNodeType.Attribute)
691                                 throw new InvalidOperationException (MakeReaderErrorMessage ("bad position to read attribute.", reader));
692                         XmlAttribute attribute = CreateAttribute (reader.Prefix, reader.LocalName, reader.NamespaceURI, false, false); // different NameTable
693                         ReadAttributeNodeValue (reader, attribute);
694
695                         // Keep the current reader position
696                         bool res;
697                         if (attribute.NamespaceURI == string.Empty || attribute.NamespaceURI == null)
698                                 res = reader.MoveToAttribute (attribute.Name);
699                         else 
700                                 res = reader.MoveToAttribute (attribute.LocalName, attribute.NamespaceURI);
701                         if (reader.IsDefault)
702                                 attribute.SetDefault ();
703                         return attribute;
704                 }
705
706                 // Reads attribute from XmlReader and then creates attribute value children. XmlAttribute also uses this.
707                 internal void ReadAttributeNodeValue (XmlReader reader, XmlAttribute attribute)
708                 {
709                         while (reader.ReadAttributeValue ()) {
710                                 if (reader.NodeType == XmlNodeType.EntityReference)
711                                         attribute.AppendChild (CreateEntityReference (reader.Name));
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                         switch (reader.ReadState) {
721                         case ReadState.Interactive:
722                                 break;
723                         case ReadState.Initial:
724                                 reader.Read ();
725                                 break;
726                         default:
727                                 return null;
728                         }
729
730                         XmlNode n;
731                         switch (reader.NodeType) {
732
733                         case XmlNodeType.Attribute:
734                                 return ReadAttributeNode (reader);
735
736                         case XmlNodeType.CDATA:
737                                 n = CreateCDataSection (reader.Value);
738                                 break;
739
740                         case XmlNodeType.Comment:
741                                 n = CreateComment (reader.Value);
742                                 break;
743
744                         case XmlNodeType.Element:
745                                 XmlElement element = CreateElement (reader.Prefix, reader.LocalName, reader.NamespaceURI);
746                                 element.IsEmpty = reader.IsEmptyElement;
747
748                                 // set the element's attributes.
749                                 if (reader.MoveToFirstAttribute ()) {
750                                         do {
751                                                 element.SetAttributeNode (ReadAttributeNode (reader));
752                                         } while (reader.MoveToNextAttribute ());
753                                         reader.MoveToElement ();
754                                 }
755
756                                 int depth = reader.Depth;
757
758                                 if (element.IsEmpty) {
759                                         n = element;
760                                         break;
761                                 }
762
763                                 reader.Read ();
764                                 while (reader.Depth > depth) {
765                                         n = ReadNode (reader);
766                                         if (preserveWhitespace || n.NodeType != XmlNodeType.Whitespace)
767                                                 element.AppendChild (n);
768                                 }
769                                 n = element;
770                                 break;
771
772                         case XmlNodeType.ProcessingInstruction:
773                                 n = CreateProcessingInstruction (reader.Name, reader.Value);
774                                 break;
775
776                         case XmlNodeType.Text:
777                                 n = CreateTextNode (reader.Value);
778                                 break;
779
780                         case XmlNodeType.XmlDeclaration:
781                                 n = CreateXmlDeclaration ("1.0" , String.Empty, String.Empty);
782                                 n.Value = reader.Value;
783                                 break;
784
785                         case XmlNodeType.DocumentType:
786                                 DTDObjectModel dtd = null;
787                                 XmlTextReader xtReader = reader as XmlTextReader;
788                                 if (xtReader != null)
789                                         dtd = xtReader.DTD;
790                                 XmlNodeReader xnReader = reader as XmlNodeReader;
791                                 if (xnReader != null)
792                                         dtd = xnReader.GetInternalParserContext ().Dtd;
793                                 XmlValidatingReader xvReader = reader as XmlValidatingReader;
794                                 if (xvReader != null)
795                                         dtd = xvReader.GetInternalParserContext ().Dtd;
796                                 IHasXmlParserContext ctxReader = reader as IHasXmlParserContext;
797                                 if (ctxReader != null)
798                                         dtd = ctxReader.ParserContext.Dtd;
799
800                                 if (dtd != null)
801                                         n = CreateDocumentType (dtd);
802                                 else
803                                         n = CreateDocumentType (reader.Name, reader ["PUBLIC"], reader ["SYSTEM"], reader.Value);
804                                 break;
805
806                         case XmlNodeType.EntityReference:
807                                 if (this.loadMode && this.DocumentType != null &&
808                                         DocumentType.Entities.GetNamedItem (reader.Name) == null)
809                                         throw new XmlException ("Reference to undeclared entity was found.");
810
811                                 n = CreateEntityReference (reader.Name);
812                                 break;
813
814                         case XmlNodeType.SignificantWhitespace:
815                                 n = CreateSignificantWhitespace (reader.Value);
816                                 break;
817
818                         case XmlNodeType.Whitespace:
819                                 n = CreateWhitespace (reader.Value);
820                                 break;
821
822                         case XmlNodeType.None:
823                                 return null;
824
825                         default:
826                                 // No idea why MS does throw NullReferenceException ;-P
827                                 throw new NullReferenceException ("Unexpected node type " + reader.NodeType + ".");
828                         }
829
830                         reader.Read ();
831                         return n;
832                 }
833
834                 private string MakeReaderErrorMessage (string message, XmlReader reader)
835                 {
836                         IXmlLineInfo li = reader as IXmlLineInfo;
837                         if (li != null)
838                                 return String.Format ("{0} Line number = {1}, Inline position = {2}.", message, li.LineNumber, li.LinePosition);
839                         else
840                                 return message;
841                 }
842
843                 internal void RemoveIdenticalAttribute (string id)
844                 {
845                         idTable.Remove (id);
846                 }
847
848                 public virtual void Save(Stream outStream)
849                 {
850                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, TextEncoding);
851                         xmlWriter.Formatting = Formatting.Indented;
852                         WriteContentTo (xmlWriter);
853                         xmlWriter.Flush ();
854                 }
855
856                 public virtual void Save (string filename)
857                 {
858                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, TextEncoding);
859                         try {
860                                 xmlWriter.Formatting = Formatting.Indented;
861                                 WriteContentTo (xmlWriter);
862                         } finally {
863                                 xmlWriter.Close ();
864                         }
865                 }
866
867                 public virtual void Save (TextWriter writer)
868                 {
869                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
870                         xmlWriter.Formatting = Formatting.Indented;
871                         WriteContentTo (xmlWriter);
872                         xmlWriter.Flush ();
873                 }
874
875                 public virtual void Save (XmlWriter xmlWriter)
876                 {
877                         //
878                         // This should preserve white space if PreserveWhiteSpace is true
879                         //
880                         bool autoXmlDecl = FirstChild != null && FirstChild.NodeType != XmlNodeType.XmlDeclaration;
881                         if (autoXmlDecl)
882                                 xmlWriter.WriteStartDocument ();
883                         WriteContentTo (xmlWriter);
884                         if (autoXmlDecl)
885                                 xmlWriter.WriteEndDocument ();
886                         xmlWriter.Flush ();
887                 }
888
889                 public override void WriteContentTo (XmlWriter w)
890                 {
891                         for (int i = 0; i < ChildNodes.Count; i++)
892                                 ChildNodes [i].WriteTo (w);
893                 }
894
895                 public override void WriteTo (XmlWriter w)
896                 {
897                         WriteContentTo (w);
898                 }
899
900                 private void AddDefaultNameTableKeys ()
901                 {
902                         // The following keys are default of MS .NET Framework
903                         nameTable.Add ("#text");
904                         nameTable.Add ("xml");
905                         nameTable.Add ("xmlns");
906                         nameTable.Add ("#entity");
907                         nameTable.Add ("#document-fragment");
908                         nameTable.Add ("#comment");
909                         nameTable.Add ("space");
910                         nameTable.Add ("id");
911                         nameTable.Add ("#whitespace");
912                         nameTable.Add ("http://www.w3.org/2000/xmlns/");
913                         nameTable.Add ("#cdata-section");
914                         nameTable.Add ("lang");
915                         nameTable.Add ("#document");
916                         nameTable.Add ("#significant-whitespace");
917                 }
918                 #endregion
919         }
920 }