2003-12-11 Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
[mono.git] / mcs / class / System.XML / System.Xml / XmlDocument.cs
1 //
2 // System.Xml.XmlDocument
3 //
4 // Authors:
5 //   Daniel Weber (daniel-weber@austin.rr.com)
6 //   Kral Ferch <kral_ferch@hotmail.com>
7 //   Jason Diamond <jason@injektilo.org>
8 //   Miguel de Icaza (miguel@ximian.com)
9 //   Duncan Mak (duncan@ximian.com)
10 //   Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
11 //
12 // (C) 2001 Daniel Weber
13 // (C) 2002 Kral Ferch, Jason Diamond, Miguel de Icaza, Duncan Mak,
14 //   Atsushi Enomoto
15 //
16
17 using System;
18 using System.IO;
19 using System.Text;
20 using System.Xml.XPath;
21 using System.Diagnostics;
22 using System.Collections;
23 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                         if(deep)
222                         {
223                                 foreach(XmlNode n in ChildNodes)
224                                         doc.AppendChild (doc.ImportNode (n, 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                         if ((localName == null) || (localName == String.Empty))
258                                 throw new ArgumentException ("The attribute local name cannot be empty.");
259
260                         return new XmlAttribute (prefix, localName, namespaceURI, this);
261                 }
262
263                 public virtual XmlCDataSection CreateCDataSection (string data)
264                 {
265                         return new XmlCDataSection (data, this);
266                 }
267
268                 public virtual XmlComment CreateComment (string data)
269                 {
270                         return new XmlComment (data, this);
271                 }
272
273                 protected internal virtual XmlAttribute CreateDefaultAttribute (string prefix, string localName, string namespaceURI)
274                 {
275                         XmlAttribute attr = CreateAttribute (prefix, localName, namespaceURI);
276                         attr.isDefault = true;
277                         return attr;
278                 }
279
280                 public virtual XmlDocumentFragment CreateDocumentFragment ()
281                 {
282                         return new XmlDocumentFragment (this);
283                 }
284
285                 public virtual XmlDocumentType CreateDocumentType (string name, string publicId,
286                                                                    string systemId, string internalSubset)
287                 {
288                         return new XmlDocumentType (name, publicId, systemId, internalSubset, this);
289                 }
290
291                 private XmlDocumentType CreateDocumentType (DTDObjectModel dtd)
292                 {
293                         return new XmlDocumentType (dtd, this);
294                 }
295
296                 public XmlElement CreateElement (string name)
297                 {
298                         return CreateElement (name, String.Empty);
299                 }
300
301                 public XmlElement CreateElement (
302                         string qualifiedName, 
303                         string namespaceURI)
304                 {
305                         string prefix;
306                         string localName;
307
308                         ParseName (qualifiedName, out prefix, out localName);
309                         
310                         return CreateElement (prefix, localName, namespaceURI);
311                 }
312
313                 public virtual XmlElement CreateElement (
314                         string prefix,
315                         string localName,
316                         string namespaceURI)
317                 {
318                         if ((localName == null) || (localName == String.Empty))
319                                 throw new ArgumentException ("The local name for elements or attributes cannot be null or an empty string.");
320                         // FIXME: MS.NET has a weird behavior that they can Load() from XmlTextReader 
321                         // whose Namespaces = false, but their CreateElement() never allows qualified name.
322                         // I leave it as it is.
323                         if (!XmlChar.IsName (localName))
324                                 throw new ArgumentException ("Invalid name.", "localName");
325                         return new XmlElement (prefix != null ? prefix : String.Empty, localName, namespaceURI != null ? namespaceURI : String.Empty, this);
326                 }
327
328                 public virtual XmlEntityReference CreateEntityReference (string name)
329                 {
330                         return new XmlEntityReference (name, this);
331                 }
332
333                 protected internal virtual XPathNavigator CreateNavigator (XmlNode node)
334                 {
335                         return new XmlDocumentNavigator (node);
336                 }
337
338                 public virtual XmlNode CreateNode (
339                         string nodeTypeString,
340                         string name,
341                         string namespaceURI)
342                 {
343                         return CreateNode (GetNodeTypeFromString (nodeTypeString), name, namespaceURI);
344                 }
345
346                 public virtual XmlNode CreateNode (
347                         XmlNodeType type,
348                         string name,
349                         string namespaceURI)
350                 {
351                         string prefix = null;
352                         string localName = name;
353
354                         if ((type == XmlNodeType.Attribute) || (type == XmlNodeType.Element) || (type == XmlNodeType.EntityReference))
355                                 ParseName (name, out prefix, out localName);
356                         
357                         return CreateNode (type, prefix, localName, namespaceURI);
358                 }
359
360                 public virtual XmlNode CreateNode (
361                         XmlNodeType type,
362                         string prefix,
363                         string name,
364                         string namespaceURI)
365                 {
366                         switch (type) {
367                                 case XmlNodeType.Attribute: return CreateAttribute (prefix, name, namespaceURI);
368                                 case XmlNodeType.CDATA: return CreateCDataSection (null);
369                                 case XmlNodeType.Comment: return CreateComment (null);
370                                 case XmlNodeType.Document: return new XmlDocument ();
371                                 case XmlNodeType.DocumentFragment: return CreateDocumentFragment ();
372                                 case XmlNodeType.DocumentType: return CreateDocumentType (null, null, null, null);
373                                 case XmlNodeType.Element: return CreateElement (prefix, name, namespaceURI);
374                                 case XmlNodeType.EntityReference: return CreateEntityReference (null);
375                                 case XmlNodeType.ProcessingInstruction: return CreateProcessingInstruction (null, null);
376                                 case XmlNodeType.SignificantWhitespace: return CreateSignificantWhitespace (String.Empty);
377                                 case XmlNodeType.Text: return CreateTextNode (null);
378                                 case XmlNodeType.Whitespace: return CreateWhitespace (String.Empty);
379                                 case XmlNodeType.XmlDeclaration: return CreateXmlDeclaration ("1.0", null, null);
380                                 default: throw new ArgumentOutOfRangeException(String.Format("{0}\nParameter name: {1}",
381                                                          "Specified argument was out of the range of valid values", type.ToString ()));
382                         }
383                 }
384
385                 public virtual XmlProcessingInstruction CreateProcessingInstruction (
386                         string target,
387                         string data)
388                 {
389                         return new XmlProcessingInstruction (target, data, this);
390                 }
391
392                 public virtual XmlSignificantWhitespace CreateSignificantWhitespace (string text)
393                 {
394                         foreach (char c in text)
395                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
396                                     throw new ArgumentException ("Invalid whitespace characters.");
397                          
398                         return new XmlSignificantWhitespace (text, this);
399                 }
400
401                 public virtual XmlText CreateTextNode (string text)
402                 {
403                         return new XmlText (text, this);
404                 }
405
406                 public virtual XmlWhitespace CreateWhitespace (string text)
407                 {
408                         foreach (char c in text)
409                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
410                                     throw new ArgumentException ("Invalid whitespace characters.");
411                          
412                         return new XmlWhitespace (text, this);
413                 }
414
415                 public virtual XmlDeclaration CreateXmlDeclaration (string version, string encoding,
416                                                                     string standalone)
417                 {
418                         if (version != "1.0")
419                                 throw new ArgumentException ("version string is not correct.");
420
421                         if  ((standalone != null && standalone != String.Empty) && !((standalone == "yes") || (standalone == "no")))
422                                 throw new ArgumentException ("standalone string is not correct.");
423
424                         return new XmlDeclaration (version, encoding, standalone, this);
425                 }
426
427                 [MonoTODO]
428                 // FIXME: Currently XmlAttributeCollection.SetNamedItem() does
429                 // add to the identity table, but in fact I delayed identity
430                 // check on GetIdenticalAttribute. To make such way complete,
431                 // we have to use MultiMap, not Hashtable.
432                 public virtual XmlElement GetElementById (string elementId)
433                 {
434                         XmlAttribute attr = GetIdenticalAttribute (elementId);
435                         return attr != null ? attr.OwnerElement : null;
436                 }
437
438                 public virtual XmlNodeList GetElementsByTagName (string name)
439                 {
440                         ArrayList nodeArrayList = new ArrayList ();
441                         this.searchNodesRecursively (this, name, nodeArrayList);
442                         return new XmlNodeArrayList (nodeArrayList);
443                 }
444
445                 private void searchNodesRecursively (XmlNode argNode, string argName, 
446                         ArrayList argArrayList)
447                 {
448                         XmlNodeList xmlNodeList = argNode.ChildNodes;
449                         foreach (XmlNode node in xmlNodeList){
450                                 if (node.Name.Equals (argName))
451                                         argArrayList.Add (node);
452                                 else    
453                                         this.searchNodesRecursively (node, argName, argArrayList);
454                         }
455                 }
456
457                 private void searchNodesRecursively (XmlNode argNode, string argName, string argNamespaceURI, 
458                         ArrayList argArrayList)
459                 {
460                         XmlNodeList xmlNodeList = argNode.ChildNodes;
461                         foreach (XmlNode node in xmlNodeList){
462                                 if (node.LocalName.Equals (argName) && node.NamespaceURI.Equals (argNamespaceURI))
463                                         argArrayList.Add (node);
464                                 else    
465                                         this.searchNodesRecursively (node, argName, argNamespaceURI, argArrayList);
466                         }
467                 }
468
469                 public virtual XmlNodeList GetElementsByTagName (string localName, string namespaceURI)
470                 {
471                         ArrayList nodeArrayList = new ArrayList ();
472                         this.searchNodesRecursively (this, localName, namespaceURI, nodeArrayList);
473                         return new XmlNodeArrayList (nodeArrayList);
474                 }
475
476                 private XmlNodeType GetNodeTypeFromString (string nodeTypeString)
477                 {
478                         switch (nodeTypeString) {
479                                 case "attribute": return XmlNodeType.Attribute;
480                                 case "cdatasection": return XmlNodeType.CDATA;
481                                 case "comment": return XmlNodeType.Comment;
482                                 case "document": return XmlNodeType.Document;
483                                 case "documentfragment": return XmlNodeType.DocumentFragment;
484                                 case "documenttype": return XmlNodeType.DocumentType;
485                                 case "element": return XmlNodeType.Element;
486                                 case "entityreference": return XmlNodeType.EntityReference;
487                                 case "processinginstruction": return XmlNodeType.ProcessingInstruction;
488                                 case "significantwhitespace": return XmlNodeType.SignificantWhitespace;
489                                 case "text": return XmlNodeType.Text;
490                                 case "whitespace": return XmlNodeType.Whitespace;
491                                 default:
492                                         throw new ArgumentException(String.Format("The string doesn't represent any node type : {0}.", nodeTypeString));
493                         }
494                 }
495
496                 internal XmlAttribute GetIdenticalAttribute (string id)
497                 {
498                         XmlAttribute attr = this.idTable [id] as XmlAttribute;
499                         if (attr == null)
500                                 return null;
501                         if (attr.OwnerElement == null || !attr.OwnerElement.IsRooted) {
502 //                              idTable.Remove (id);
503                                 return null;
504                         }
505                         return attr;
506                 }
507
508                 public virtual XmlNode ImportNode (XmlNode node, bool deep)
509                 {
510                         switch (node.NodeType) {
511                         case XmlNodeType.Attribute:
512                                 XmlAttribute srcAtt = node as XmlAttribute;
513                                 XmlAttribute dstAtt = this.CreateAttribute (srcAtt.Prefix, srcAtt.LocalName, srcAtt.NamespaceURI);
514                                 foreach (XmlNode child in srcAtt.ChildNodes)
515                                         dstAtt.AppendChild (this.ImportNode (child, deep));
516                                 return dstAtt;
517
518                         case XmlNodeType.CDATA:
519                                 return this.CreateCDataSection (node.Value);
520
521                         case XmlNodeType.Comment:
522                                 return this.CreateComment (node.Value);
523
524                         case XmlNodeType.Document:
525                                 throw new XmlException ("Document cannot be imported.");
526
527                         case XmlNodeType.DocumentFragment:
528                                 XmlDocumentFragment df = this.CreateDocumentFragment ();
529                                 if(deep)
530                                         foreach(XmlNode n in node.ChildNodes)
531                                                 df.AppendChild (this.ImportNode (n, deep));
532                                 return df;
533
534                         case XmlNodeType.DocumentType:
535                                 throw new XmlException ("DocumentType cannot be imported.");
536
537                         case XmlNodeType.Element:
538                                 XmlElement src = (XmlElement)node;
539                                 XmlElement dst = this.CreateElement (src.Prefix, src.LocalName, src.NamespaceURI);
540                                 foreach(XmlAttribute attr in src.Attributes)
541                                         if(attr.Specified)      // copies only specified attributes
542                                                 dst.SetAttributeNode ((XmlAttribute)this.ImportNode (attr, deep));
543                                 if(deep)
544                                         foreach(XmlNode n in src.ChildNodes)
545                                                 dst.AppendChild (this.ImportNode (n, deep));
546                                 return dst;
547
548                         case XmlNodeType.EndElement:
549                                 throw new XmlException ("Illegal ImportNode call for NodeType.EndElement");
550                         case XmlNodeType.EndEntity:
551                                 throw new XmlException ("Illegal ImportNode call for NodeType.EndEntity");
552
553                         case XmlNodeType.EntityReference:
554                                 return this.CreateEntityReference (node.Name);
555
556                         case XmlNodeType.None:
557                                 throw new XmlException ("Illegal ImportNode call for NodeType.None");
558
559                         case XmlNodeType.ProcessingInstruction:
560                                 XmlProcessingInstruction pi = node as XmlProcessingInstruction;
561                                 return this.CreateProcessingInstruction (pi.Target, pi.Data);
562
563                         case XmlNodeType.SignificantWhitespace:
564                                 return this.CreateSignificantWhitespace (node.Value);
565
566                         case XmlNodeType.Text:
567                                 return this.CreateTextNode (node.Value);
568
569                         case XmlNodeType.Whitespace:
570                                 return this.CreateWhitespace (node.Value);
571
572                         case XmlNodeType.XmlDeclaration:
573                                 XmlDeclaration srcDecl = node as XmlDeclaration;
574                                 return this.CreateXmlDeclaration (srcDecl.Version, srcDecl.Encoding, srcDecl.Standalone);
575
576                         default:
577                                 throw new InvalidOperationException ("Cannot import specified node type: " + node.NodeType);
578                         }
579                 }
580
581                 public virtual void Load (Stream inStream)
582                 {
583                         Load (new XmlTextReader (inStream));
584                 }
585
586                 public virtual void Load (string filename)
587                 {
588                         XmlTextReader xr = new XmlTextReader (filename);
589                         xr.XmlResolver = resolver;
590                         Load (xr);
591                         xr.Close ();
592                 }
593
594                 public virtual void Load (TextReader txtReader)
595                 {
596                         XmlTextReader xr = new XmlTextReader (txtReader);
597                         xr.XmlResolver = resolver;
598                         Load (xr);
599                 }
600
601                 public virtual void Load (XmlReader xmlReader)
602                 {
603                         // Reset our document
604                         // For now this just means removing all our children but later this
605                         // may turn out o need to call a private method that resets other things
606                         // like properties we have, etc.
607                         RemoveAll ();
608
609                         this.baseURI = xmlReader.BaseURI;
610                         // create all contents with use of ReadNode()
611                         do {
612                                 XmlNode n = ReadNode (xmlReader);
613                                 if(n == null) break;
614                                 AppendChild (n);
615                         } while (true);
616                 }
617
618                 public virtual void LoadXml (string xml)
619                 {
620                         XmlTextReader xmlReader = new XmlTextReader (
621                                 xml, XmlNodeType.Document, null);
622                         xmlReader.XmlResolver = resolver;
623                         Load (xmlReader);
624                 }
625
626                 internal void onNodeChanged (XmlNode node, XmlNode Parent)
627                 {
628                         if (NodeChanged != null)
629                                 NodeChanged (node, new XmlNodeChangedEventArgs
630                                         (XmlNodeChangedAction.Change,
631                                         node, Parent, Parent));
632                 }
633
634                 internal void onNodeChanging(XmlNode node, XmlNode Parent)
635                 {
636                         if (NodeChanging != null)
637                                 NodeChanging (node, new XmlNodeChangedEventArgs
638                                         (XmlNodeChangedAction.Change,
639                                         node, Parent, Parent));
640                 }
641
642                 internal void onNodeInserted (XmlNode node, XmlNode newParent)
643                 {
644                         if (NodeInserted != null)
645                                 NodeInserted (node, new XmlNodeChangedEventArgs
646                                         (XmlNodeChangedAction.Insert,
647                                         node, null, newParent));
648                 }
649
650                 internal void onNodeInserting (XmlNode node, XmlNode newParent)
651                 {
652                         if (NodeInserting != null)
653                                 NodeInserting (node, new XmlNodeChangedEventArgs
654                                         (XmlNodeChangedAction.Insert,
655                                         node, null, newParent));
656                 }
657
658                 internal void onNodeRemoved (XmlNode node, XmlNode oldParent)
659                 {
660                         if (NodeRemoved != null)
661                                 NodeRemoved (node, new XmlNodeChangedEventArgs
662                                         (XmlNodeChangedAction.Remove,
663                                         node, oldParent, null));
664                 }
665
666                 internal void onNodeRemoving (XmlNode node, XmlNode oldParent)
667                 {
668                         if (NodeRemoving != null)
669                                 NodeRemoving (node, new XmlNodeChangedEventArgs
670                                         (XmlNodeChangedAction.Remove,
671                                         node, oldParent, null));
672                 }
673
674                 private void ParseName (string name, out string prefix, out string localName)
675                 {
676                         int indexOfColon = name.IndexOf (':');
677                         
678                         if (indexOfColon != -1) {
679                                 prefix = name.Substring (0, indexOfColon);
680                                 localName = name.Substring (indexOfColon + 1);
681                         } else {
682                                 prefix = "";
683                                 localName = name;
684                         }
685                 }
686
687                 // Reads XmlReader and creates Attribute Node.
688                 private XmlAttribute ReadAttributeNode(XmlReader reader)
689                 {
690                         if(reader.NodeType == XmlNodeType.Element)
691                                 reader.MoveToFirstAttribute ();
692                         else if(reader.NodeType != XmlNodeType.Attribute)
693                                 throw new InvalidOperationException (MakeReaderErrorMessage ("bad position to read attribute.", reader));
694                         XmlAttribute attribute = CreateAttribute (reader.Prefix, reader.LocalName, reader.NamespaceURI);
695                         ReadAttributeNodeValue (reader, attribute);
696
697                         // Keep the current reader position
698                         bool res;
699                         if (attribute.NamespaceURI == string.Empty || attribute.NamespaceURI == null)
700                                 res = reader.MoveToAttribute (attribute.Name);
701                         else 
702                                 res = reader.MoveToAttribute (attribute.LocalName, attribute.NamespaceURI);
703                         if (reader.IsDefault)
704                                 attribute.SetDefault ();
705                         return attribute;
706                 }
707
708                 // Reads attribute from XmlReader and then creates attribute value children. XmlAttribute also uses this.
709                 internal void ReadAttributeNodeValue(XmlReader reader, XmlAttribute attribute)
710                 {
711                         while(reader.ReadAttributeValue ()) {
712                                 if(reader.NodeType == XmlNodeType.EntityReference)
713                                         // FIXME: if DocumentType is available, then try to resolve it.
714                                         attribute.AppendChild (CreateEntityReference (reader.Name));
715                                 // FIXME: else if(NodeType == EndEntity) -- reset BaseURI and so on -- ;
716                                 else
717                                         // Children of Attribute is restricted to CharacterData and EntityReference (Comment is not allowed).
718                                         attribute.AppendChild (CreateTextNode (reader.Value));
719                         }
720                 }
721
722                 public virtual XmlNode ReadNode (XmlReader reader)
723                 {
724                         XmlNode resultNode = null;
725                         XmlNode newNode = null;
726                         XmlNode currentNode = null;
727
728                         switch (reader.ReadState) {
729                         case ReadState.Interactive:
730                                 break;
731                         case ReadState.Initial:
732                                 reader.Read ();
733                                 break;
734                         default:
735                                 return null;
736                         }
737
738                         int startDepth = reader.Depth;
739                         bool ignoredWhitespace;
740                         bool reachedEOF = false;
741
742                         do {
743                                 ignoredWhitespace = false;
744                                 if (reader.ReadState != ReadState.Interactive)
745                                         if (reachedEOF)
746                                                 throw new Exception ("XML Reader reached to end while reading node.");
747                                         else
748                                                 reachedEOF = true;
749                                 switch (reader.NodeType) {
750
751                                 case XmlNodeType.Attribute:
752                                         newNode = ReadAttributeNode (reader);
753                                         break;
754
755                                 case XmlNodeType.CDATA:
756                                         newNode = CreateCDataSection (reader.Value);
757                                         if(currentNode != null)
758                                                 currentNode.AppendChild (newNode);
759                                         break;
760
761                                 case XmlNodeType.Comment:
762                                         newNode = CreateComment (reader.Value);
763                                         if(currentNode != null)
764                                                 currentNode.AppendChild (newNode);
765                                         break;
766
767                                 case XmlNodeType.Element:
768                                         XmlElement element = CreateElement (reader.Prefix, reader.LocalName, reader.NamespaceURI);
769                                         element.IsEmpty = reader.IsEmptyElement;
770
771                                         // set the element's attributes.
772                                         while (reader.MoveToNextAttribute ()) {
773                                                 element.SetAttributeNode (ReadAttributeNode (reader));
774                                         }
775
776                                         reader.MoveToElement ();
777
778                                         // MS.NET adds element to document after its attributes are filled.
779                                         if(currentNode != null)
780                                                 currentNode.AppendChild (element);
781                                         else
782                                                 resultNode = element;
783
784                                         if (!reader.IsEmptyElement)
785                                                 currentNode = element;
786
787                                         break;
788
789                                 case XmlNodeType.EndElement:
790                                         if (currentNode == null)
791                                                 throw new XmlException ("Unexpected end element.");
792                                         else if (currentNode.Name != reader.Name)
793                                                 throw new XmlException (reader as IXmlLineInfo, String.Format ("mismatch end tag. Expected {0} but found {1}", currentNode.Name, reader.Name));
794                                         currentNode = currentNode.ParentNode;
795                                         break;
796
797                                 case XmlNodeType.EndEntity:
798                                         break;  // no operation
799
800                                 case XmlNodeType.ProcessingInstruction:
801                                         newNode = CreateProcessingInstruction (reader.Name, reader.Value);
802                                         if(currentNode != null)
803                                                 currentNode.AppendChild (newNode);
804                                         break;
805
806                                 case XmlNodeType.Text:
807                                         newNode = CreateTextNode (reader.Value);
808                                         if(currentNode != null)
809                                                 currentNode.AppendChild (newNode);
810                                         break;
811
812                                 case XmlNodeType.XmlDeclaration:
813                                         // empty strings are dummy, then gives over setting value contents to setter.
814                                         newNode = CreateXmlDeclaration ("1.0" , String.Empty, String.Empty);
815                                         ((XmlDeclaration)newNode).Value = reader.Value;
816                                         if(currentNode != null)
817                                                 throw new XmlException (reader as IXmlLineInfo, "XmlDeclaration at invalid position.");
818                                         break;
819
820                                 case XmlNodeType.DocumentType:
821                                         if(currentNode != null)
822                                                 throw new XmlException (reader as IXmlLineInfo, "XmlDocumentType at invalid position.");
823
824                                         DTDObjectModel dtd = null;
825                                         XmlTextReader xtReader = reader as XmlTextReader;
826                                         if (xtReader != null)
827                                                 dtd = xtReader.DTD;
828                                         XmlNodeReader xnReader = reader as XmlNodeReader;
829                                         if (xnReader != null)
830                                                 dtd = xnReader.GetInternalParserContext ().Dtd;
831                                         XmlValidatingReader xvReader = reader as XmlValidatingReader;
832                                         if (xvReader != null)
833                                                 dtd = xvReader.GetInternalParserContext ().Dtd;
834                                         IHasXmlParserContext ctxReader = reader as IHasXmlParserContext;
835                                         if (ctxReader != null)
836                                                 dtd = ctxReader.ParserContext.Dtd;
837
838                                         if (dtd != null)
839                                                 newNode = CreateDocumentType (dtd);
840                                         else
841                                                 newNode = CreateDocumentType (reader.Name, reader ["PUBLIC"], reader ["SYSTEM"], reader.Value);
842
843                                         break;
844
845                                 case XmlNodeType.EntityReference:
846                                         newNode = CreateEntityReference (reader.Name);
847                                         if(currentNode != null)
848                                                 currentNode.AppendChild (newNode);
849                                         break;
850
851                                 case XmlNodeType.SignificantWhitespace:
852                                         newNode = CreateSignificantWhitespace (reader.Value);
853                                         if(currentNode != null)
854                                                 currentNode.AppendChild (newNode);
855                                         break;
856
857                                 case XmlNodeType.Whitespace:
858                                         if(PreserveWhitespace) {
859                                                 newNode = CreateWhitespace (reader.Value);
860                                                 if(currentNode != null)
861                                                         currentNode.AppendChild (newNode);
862                                         }
863                                         else
864                                                 ignoredWhitespace = true;
865                                         break;
866                                 }
867                                 // Read next, except for reading attribute node.
868                                 if (!(newNode is XmlAttribute) && !reader.Read ())
869                                         break;
870                         } while (ignoredWhitespace || reader.Depth > startDepth ||
871                                 (reader.Depth == startDepth && reader.NodeType == XmlNodeType.EndElement));
872                         if (startDepth < reader.Depth && reader.EOF)
873                                 throw new XmlException ("Unexpected end of xml reader.");
874                         return resultNode != null ? resultNode : newNode;
875                 }
876
877                 private string MakeReaderErrorMessage (string message, XmlReader reader)
878                 {
879                         IXmlLineInfo li = reader as IXmlLineInfo;
880                         if (li != null)
881                                 return String.Format ("{0} Line number = {1}, Inline position = {2}.", message, li.LineNumber, li.LinePosition);
882                         else
883                                 return message;
884                 }
885
886                 internal void RemoveIdenticalAttribute (string id)
887                 {
888                         idTable.Remove (id);
889                 }
890
891                 public virtual void Save(Stream outStream)
892                 {
893                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, TextEncoding);
894                         xmlWriter.Formatting = Formatting.Indented;
895                         WriteContentTo (xmlWriter);
896                         xmlWriter.Flush ();
897                 }
898
899                 public virtual void Save (string filename)
900                 {
901                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, TextEncoding);
902                         xmlWriter.Formatting = Formatting.Indented;
903                         WriteContentTo (xmlWriter);
904                         xmlWriter.Close ();
905                 }
906
907                 public virtual void Save (TextWriter writer)
908                 {
909                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
910                         xmlWriter.Formatting = Formatting.Indented;
911                         WriteContentTo (xmlWriter);
912                         xmlWriter.Flush ();
913                 }
914
915                 public virtual void Save (XmlWriter xmlWriter)
916                 {
917                         //
918                         // This should preserve white space if PreserveWhiteSpace is true
919                         //
920                         bool autoXmlDecl = FirstChild != null && FirstChild.NodeType != XmlNodeType.XmlDeclaration;
921                         if (autoXmlDecl)
922                                 xmlWriter.WriteStartDocument ();
923                         WriteContentTo (xmlWriter);
924                         if (autoXmlDecl)
925                                 xmlWriter.WriteEndDocument ();
926                         xmlWriter.Flush ();
927                 }
928
929                 public override void WriteContentTo (XmlWriter w)
930                 {
931                         foreach(XmlNode childNode in ChildNodes) {
932                                 childNode.WriteTo (w);
933                         }
934                 }
935
936                 public override void WriteTo (XmlWriter w)
937                 {
938                         WriteContentTo (w);
939                 }
940
941                 private void AddDefaultNameTableKeys ()
942                 {
943                         // The following keys are default of MS .NET Framework
944                         nameTable.Add ("#text");
945                         nameTable.Add ("xml");
946                         nameTable.Add ("xmlns");
947                         nameTable.Add ("#entity");
948                         nameTable.Add ("#document-fragment");
949                         nameTable.Add ("#comment");
950                         nameTable.Add ("space");
951                         nameTable.Add ("id");
952                         nameTable.Add ("#whitespace");
953                         nameTable.Add ("http://www.w3.org/2000/xmlns/");
954                         nameTable.Add ("#cdata-section");
955                         nameTable.Add ("lang");
956                         nameTable.Add ("#document");
957                         nameTable.Add ("#significant-whitespace");
958                 }
959                 #endregion
960         }
961 }