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