* XmlAttribute.cs : add internal 'IsDefault' property
[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 //
11 // (C) 2001 Daniel Weber
12 // (C) 2002 Kral Ferch, Jason Diamond, Miguel de Icaza, Duncan Mak
13 //
14
15 using System;
16 using System.IO;
17 using System.Text;
18 using System.Xml.XPath;
19 using System.Diagnostics;
20 using System.Collections;
21
22 namespace System.Xml
23 {
24         public class XmlDocument : XmlNode
25         {
26                 #region Fields
27
28                 XmlLinkedNode lastLinkedChild;
29                 XmlNameTable nameTable;
30                 string baseURI = String.Empty;
31                 XmlImplementation implementation;
32
33                 #endregion
34
35                 #region Constructors
36
37                 public XmlDocument () : base (null)
38                 {
39                         implementation = new XmlImplementation();
40                         nameTable = implementation.internalNameTable;
41                         AddDefaultNameTableKeys();
42                 }
43
44                 protected internal XmlDocument (XmlImplementation imp) : base (null)
45                 {
46                         implementation = imp;
47                         nameTable = imp.internalNameTable;
48                         AddDefaultNameTableKeys();
49                 }
50
51                 public XmlDocument (XmlNameTable nt) : base (null)
52                 {
53                         implementation = new XmlImplementation();
54                         implementation.internalNameTable = nt;
55                         nameTable = nt;
56                         AddDefaultNameTableKeys();
57                 }
58
59                 #endregion
60
61                 #region Events
62
63                 public event XmlNodeChangedEventHandler NodeChanged;
64
65                 public event XmlNodeChangedEventHandler NodeChanging;
66
67                 public event XmlNodeChangedEventHandler NodeInserted;
68
69                 public event XmlNodeChangedEventHandler NodeInserting;
70
71                 public event XmlNodeChangedEventHandler NodeRemoved;
72
73                 public event XmlNodeChangedEventHandler NodeRemoving;
74
75                 #endregion
76
77                 #region Properties
78
79                 public override string BaseURI {
80                         get {
81                                 return baseURI;
82                         }
83                 }
84
85                 public XmlElement DocumentElement {
86                         get {
87                                 XmlNode node = FirstChild;
88
89                                 while (node != null) {
90                                         if (node is XmlElement)
91                                                 break;
92                                         node = node.NextSibling;
93                                 }
94
95                                 return node != null ? node as XmlElement : null;
96                         }
97                 }
98
99                 [MonoTODO("It doesn't have internal subset object model.")]
100                 public virtual XmlDocumentType DocumentType {
101                         get {
102                                 foreach(XmlNode n in this.ChildNodes) {
103                                         if(n.NodeType == XmlNodeType.DocumentType)
104                                                 return (XmlDocumentType)n;
105                                 }
106                                 return null;
107                         }
108                 }
109
110                 public XmlImplementation Implementation {
111                         get { return implementation; }
112                 }
113
114                 [MonoTODO ("Setter.")]
115                 public override string InnerXml {
116                         get {
117                                 // Not sure why this is an override.  Passing through for now.
118                                 // ... Maybe its setter may be an override :-) [ginga]
119                                 return base.InnerXml;
120                         }
121                         set { throw new NotImplementedException(); }
122                 }
123
124                 public override bool IsReadOnly {
125                         get { return false; }
126                 }
127
128                 internal override XmlLinkedNode LastLinkedChild {
129                         get     {
130                                 return lastLinkedChild;
131                         }
132
133                         set {
134                                 lastLinkedChild = value;
135                         }
136                 }
137                 
138                 public override string LocalName {
139                         get { return "#document"; }
140                 }
141
142                 public override string Name {
143                         get { return "#document"; }
144                 }
145
146                 public XmlNameTable NameTable {
147                         get { return nameTable; }
148                 }
149
150                 public override XmlNodeType NodeType {
151                         get { return XmlNodeType.Document; }
152                 }
153
154                 internal override XPathNodeType XPathNodeType {
155                         get {
156                                 return XPathNodeType.Root;
157                         }
158                 }
159
160                 public override XmlDocument OwnerDocument {
161                         get { return null; }
162                 }
163
164                 [MonoTODO]
165                 public bool PreserveWhitespace {
166                         get { throw new NotImplementedException(); }
167                         set { throw new NotImplementedException(); }
168                 }
169
170                 [MonoTODO]
171                 public virtual XmlResolver XmlResolver {
172                         set { throw new NotImplementedException(); }
173                 }
174
175                 #endregion
176
177                 #region Methods
178
179                 [MonoTODO]
180                 public override XmlNode CloneNode (bool deep)
181                 {
182                         throw new NotImplementedException ();
183                 }
184
185                 public XmlAttribute CreateAttribute (string name)
186                 {
187                         return CreateAttribute (name, String.Empty);
188                 }
189
190                 public XmlAttribute CreateAttribute (string qualifiedName, string namespaceURI)
191                 {
192                         string prefix;
193                         string localName;
194
195                         ParseName (qualifiedName, out prefix, out localName);
196
197                         return CreateAttribute (prefix, localName, namespaceURI);
198                 }
199
200                 public virtual XmlAttribute CreateAttribute (string prefix, string localName, string namespaceURI)
201                 {
202                         if ((localName == null) || (localName == String.Empty))
203                                 throw new ArgumentException ("The attribute local name cannot be empty.");
204
205                         return new XmlAttribute (prefix, localName, namespaceURI, this);
206                 }
207
208                 public virtual XmlCDataSection CreateCDataSection (string data)
209                 {
210                         return new XmlCDataSection (data, this);
211                 }
212
213                 public virtual XmlComment CreateComment (string data)
214                 {
215                         return new XmlComment(data, this);
216                 }
217
218                 [MonoTODO]
219                 protected internal virtual XmlAttribute CreateDefaultAttribute (string prefix, string localName, string namespaceURI)
220                 {
221                         throw new NotImplementedException ();
222                 }
223
224 //              [MonoTODO]
225                 public virtual XmlDocumentFragment CreateDocumentFragment ()
226                 {
227                         return new XmlDocumentFragment(this);
228 //                      throw new NotImplementedException ();
229                 }
230
231                 public virtual XmlDocumentType CreateDocumentType (string name, string publicId,
232                                                                    string systemId, string internalSubset)
233                 {
234                         return new XmlDocumentType (name, publicId, systemId, internalSubset, this);
235                 }
236
237                 public XmlElement CreateElement (string name)
238                 {
239                         return CreateElement (name, String.Empty);
240                 }
241
242                 public XmlElement CreateElement (
243                         string qualifiedName, 
244                         string namespaceURI)
245                 {
246                         string prefix;
247                         string localName;
248
249                         ParseName (qualifiedName, out prefix, out localName);
250
251                         return CreateElement (prefix, localName, namespaceURI);
252                 }
253
254                 public virtual XmlElement CreateElement (
255                         string prefix,
256                         string localName,
257                         string namespaceURI)
258                 {
259                         if ((localName == null) || (localName == String.Empty))
260                                 throw new ArgumentException ("The local name for elements or attributes cannot be null or an empty string.");
261
262                         return new XmlElement (prefix != null ? prefix : String.Empty, localName, namespaceURI != null ? namespaceURI : String.Empty, this);
263                 }
264
265                 [MonoTODO]
266                 public virtual XmlEntityReference CreateEntityReference (string name)
267                 {
268                         throw new NotImplementedException ();
269                 }
270
271                 [MonoTODO]
272                 protected internal virtual XPathNavigator CreateNavigator (XmlNode node)
273                 {
274                         throw new NotImplementedException ();
275                 }
276
277                 public virtual XmlNode CreateNode (
278                         string nodeTypeString,
279                         string name,
280                         string namespaceURI)
281                 {
282                         return CreateNode (GetNodeTypeFromString (nodeTypeString), name, namespaceURI);
283                 }
284
285                 public virtual XmlNode CreateNode (
286                         XmlNodeType type,
287                         string name,
288                         string namespaceURI)
289                 {
290                         string prefix = null;
291                         string localName = name;
292
293                         if ((type == XmlNodeType.Attribute) || (type == XmlNodeType.Element) || (type == XmlNodeType.EntityReference))
294                                 ParseName (name, out prefix, out localName);
295                         
296                         return CreateNode (type, prefix, localName, namespaceURI);
297                 }
298
299                 public virtual XmlNode CreateNode (
300                         XmlNodeType type,
301                         string prefix,
302                         string name,
303                         string namespaceURI)
304                 {
305                         switch (type) {
306                                 case XmlNodeType.Attribute: return CreateAttribute (prefix, name, namespaceURI);
307                                 case XmlNodeType.CDATA: return CreateCDataSection (null);
308                                 case XmlNodeType.Comment: return CreateComment (null);
309                                 case XmlNodeType.Document: return new XmlDocument (); // TODO - test to see which constructor to use, i.e. use existing NameTable or not.
310                                 case XmlNodeType.DocumentFragment: return CreateDocumentFragment ();
311                                 case XmlNodeType.DocumentType: return CreateDocumentType (null, null, null, null);
312                                 case XmlNodeType.Element: return CreateElement (prefix, name, namespaceURI);
313                                 case XmlNodeType.EntityReference: return CreateEntityReference (null);
314                                 case XmlNodeType.ProcessingInstruction: return CreateProcessingInstruction (null, null);
315                                 case XmlNodeType.SignificantWhitespace: return CreateSignificantWhitespace (String.Empty);
316                                 case XmlNodeType.Text: return CreateTextNode (null);
317                                 case XmlNodeType.Whitespace: return CreateWhitespace (String.Empty);
318                                 case XmlNodeType.XmlDeclaration: return CreateXmlDeclaration ("1.0", null, null);
319                                 default: throw new ArgumentOutOfRangeException(String.Format("{0}\nParameter name: {1}",
320                                                          "Specified argument was out of the range of valid values", type.ToString ()));
321                         }
322                 }
323
324                 public virtual XmlProcessingInstruction CreateProcessingInstruction (
325                         string target,
326                         string data)
327                 {
328                         return new XmlProcessingInstruction (target, data, this);
329                 }
330
331                 public virtual XmlSignificantWhitespace CreateSignificantWhitespace (string text)
332                 {
333                         foreach (char c in text)
334                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
335                                     throw new ArgumentException ("Invalid whitespace characters.");
336                          
337                         return new XmlSignificantWhitespace (text, this);
338                 }
339
340                 public virtual XmlText CreateTextNode (string text)
341                 {
342                         return new XmlText (text, this);
343                 }
344
345                 public virtual XmlWhitespace CreateWhitespace (string text)
346                 {
347                         foreach (char c in text)
348                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
349                                     throw new ArgumentException ("Invalid whitespace characters.");
350                          
351                         return new XmlWhitespace (text, this);
352                 }
353
354                 public virtual XmlDeclaration CreateXmlDeclaration (string version, string encoding,
355                                                                     string standalone)
356                 {
357                         if (version != "1.0")
358                                 throw new ArgumentException ("version string is not correct.");
359
360                         if  ((standalone != null && standalone != String.Empty) && !((standalone == "yes") || (standalone == "no")))
361                                 throw new ArgumentException ("standalone string is not correct.");
362
363                         return new XmlDeclaration (version, encoding, standalone, this);
364                 }
365
366                 [MonoTODO]
367                 public virtual XmlElement GetElementById (string elementId)
368                 {
369                         throw new NotImplementedException ();
370                 }
371
372                 public virtual XmlNodeList GetElementsByTagName (string name)
373                 {
374                         ArrayList nodeArrayList = new ArrayList ();
375                         this.searchNodesRecursively (this, name, nodeArrayList);
376                         return new XmlNodeArrayList (nodeArrayList);
377                 }
378
379                 private void searchNodesRecursively (XmlNode argNode, string argName, 
380                         ArrayList argArrayList)
381                 {
382                         XmlNodeList xmlNodeList = argNode.ChildNodes;
383                         foreach (XmlNode node in xmlNodeList){
384                                 if (node.Name.Equals (argName))
385                                         argArrayList.Add (node);
386                                 else    
387                                         this.searchNodesRecursively (node, argName, argArrayList);
388                         }
389                 }
390
391                 private void searchNodesRecursively (XmlNode argNode, string argName, string argNamespaceURI, 
392                         ArrayList argArrayList)
393                 {
394                         XmlNodeList xmlNodeList = argNode.ChildNodes;
395                         foreach (XmlNode node in xmlNodeList){
396                                 if (node.LocalName.Equals (argName) && node.NamespaceURI.Equals (argNamespaceURI))
397                                         argArrayList.Add (node);
398                                 else    
399                                         this.searchNodesRecursively (node, argName, argNamespaceURI, argArrayList);
400                         }
401                 }
402
403                 public virtual XmlNodeList GetElementsByTagName (string localName, string namespaceURI)
404                 {
405                         ArrayList nodeArrayList = new ArrayList ();
406                         this.searchNodesRecursively (this, localName, namespaceURI, nodeArrayList);
407                         return new XmlNodeArrayList (nodeArrayList);
408                 }
409
410                 private XmlNodeType GetNodeTypeFromString (string nodeTypeString)
411                 {
412                         switch (nodeTypeString) {
413                                 case "attribute": return XmlNodeType.Attribute;
414                                 case "cdatasection": return XmlNodeType.CDATA;
415                                 case "comment": return XmlNodeType.Comment;
416                                 case "document": return XmlNodeType.Document;
417                                 case "documentfragment": return XmlNodeType.DocumentFragment;
418                                 case "documenttype": return XmlNodeType.DocumentType;
419                                 case "element": return XmlNodeType.Element;
420                                 case "entityreference": return XmlNodeType.EntityReference;
421                                 case "processinginstruction": return XmlNodeType.ProcessingInstruction;
422                                 case "significantwhitespace": return XmlNodeType.SignificantWhitespace;
423                                 case "text": return XmlNodeType.Text;
424                                 case "whitespace": return XmlNodeType.Whitespace;
425                                 default:
426                                         throw new ArgumentException(String.Format("The string doesn't represent any node type : {0}.", nodeTypeString));
427                         }
428                 }
429
430                 [MonoTODO("default attributes (of imported doc); EntityReferences; Entity; Notation")]
431                 public virtual XmlNode ImportNode (XmlNode node, bool deep)
432                 {
433                         switch(node.NodeType)
434                         {
435                                 case XmlNodeType.Attribute:
436                                         {
437                                                 XmlAttribute src_att = node as XmlAttribute;
438                                                 XmlAttribute dst_att = this.CreateAttribute(src_att.Prefix, src_att.LocalName, src_att.NamespaceURI);
439                                                 dst_att.Value = src_att.Value;  // always explicitly specified (whether source is specified or not)
440                                                 return dst_att;
441                                         }
442
443                                 case XmlNodeType.CDATA:
444                                         return this.CreateCDataSection(node.Value);
445
446                                 case XmlNodeType.Comment:
447                                         return this.CreateComment(node.Value);
448
449                                 case XmlNodeType.Document:
450                                         throw new XmlException("Document cannot be imported.");
451
452                                 case XmlNodeType.DocumentFragment:
453                                         {
454                                                 XmlDocumentFragment df = this.CreateDocumentFragment();
455                                                 if(deep)
456                                                 {
457                                                         foreach(XmlNode n in node.ChildNodes)
458                                                         {
459                                                                 df.AppendChild(this.ImportNode(n, deep));
460                                                         }
461                                                 }
462                                                 return df;
463                                         }
464
465                                 case XmlNodeType.DocumentType:
466                                         throw new XmlException("DocumentType cannot be imported.");
467
468                                 case XmlNodeType.Element:
469                                         {
470                                                 XmlElement src = (XmlElement)node;
471                                                 XmlElement dst = this.CreateElement(src.Prefix, src.LocalName, src.NamespaceURI);
472                                                 foreach(XmlAttribute attr in src.Attributes)
473                                                 {
474                                                         if(attr.Specified)      // copies only specified attributes
475                                                                 dst.SetAttributeNode((XmlAttribute)this.ImportNode(attr, deep));
476                                                         if(DocumentType != null)
477                                                         {
478                                                                 // TODO: create default attribute values
479                                                         }
480                                                 }
481                                                 if(deep)
482                                                 {
483                                                         foreach(XmlNode n in src.ChildNodes)
484                                                                 dst.AppendChild(this.ImportNode(n, deep));
485                                                 }
486                                                 return dst;
487                                         }
488
489                                 case XmlNodeType.EndElement:
490                                         throw new XmlException ("Illegal ImportNode call for NodeType.EndElement");
491                                 case XmlNodeType.EndEntity:
492                                         throw new XmlException ("Illegal ImportNode call for NodeType.EndEntity");
493
494                                 case XmlNodeType.Entity:
495                                         throw new NotImplementedException ();   // TODO
496
497                                 // [2002.10.14] CreateEntityReference not implemented.
498                                 case XmlNodeType.EntityReference:
499                                         throw new NotImplementedException("ImportNode of EntityReference not implemented mainly because CreateEntityReference was implemented in the meantime.");
500 //                                      return this.CreateEntityReference(node.Name);
501
502                                 case XmlNodeType.None:
503                                         throw new XmlException ("Illegal ImportNode call for NodeType.None");
504
505                                 case XmlNodeType.Notation:
506                                         throw new NotImplementedException ();   // TODO
507
508                                 case XmlNodeType.ProcessingInstruction:
509                                         XmlProcessingInstruction pi = node as XmlProcessingInstruction;
510                                         return this.CreateProcessingInstruction(pi.Target, pi.Data);
511
512                                 case XmlNodeType.SignificantWhitespace:
513                                         return this.CreateSignificantWhitespace(node.Value);
514
515                                 case XmlNodeType.Text:
516                                         return this.CreateTextNode(node.Value);
517
518                                 case XmlNodeType.Whitespace:
519                                         return this.CreateWhitespace(node.Value);
520
521                                 case XmlNodeType.XmlDeclaration:
522                                         XmlDeclaration srcDecl = node as XmlDeclaration;
523                                         return this.CreateXmlDeclaration(srcDecl.Version, srcDecl.Encoding, srcDecl.Standalone);
524
525                                 default:
526                                         throw new NotImplementedException ();
527                         }
528                 }
529
530                 public virtual void Load (Stream inStream)
531                 {
532                         XmlReader xmlReader = new XmlTextReader (inStream);
533                         Load (xmlReader);
534                 }
535
536                 public virtual void Load (string filename)
537                 {
538                         baseURI = filename;
539                         XmlReader xmlReader = new XmlTextReader (new StreamReader (filename));
540                         Load (xmlReader);
541                 }
542
543                 public virtual void Load (TextReader txtReader)
544                 {
545                         Load (new XmlTextReader (txtReader));
546                 }
547
548                 public virtual void Load (XmlReader xmlReader)
549                 {
550                         // Reset our document
551                         // For now this just means removing all our children but later this
552                         // may turn out o need to call a private method that resets other things
553                         // like properties we have, etc.
554                         RemoveAll ();
555
556                         XmlNode currentNode = this;
557
558                         // This method of XmlNode is previously written here.
559                         // Then I(ginga) moved them to use this logic with XmlElement.
560                         this.ConstructDOM(xmlReader, currentNode);
561                 }
562
563                 public virtual void LoadXml (string xml)
564                 {
565                         XmlReader xmlReader = new XmlTextReader (new StringReader (xml));
566                         Load (xmlReader);
567                 }
568
569                 internal void onNodeChanged (XmlNode node, XmlNode Parent)
570                 {
571                         if (NodeChanged != null)
572                                 NodeChanged (node, new XmlNodeChangedEventArgs
573                                         (XmlNodeChangedAction.Change,
574                                         node, Parent, Parent));
575                 }
576
577                 internal void onNodeChanging(XmlNode node, XmlNode Parent)
578                 {
579                         if (NodeChanging != null)
580                                 NodeChanging (node, new XmlNodeChangedEventArgs
581                                         (XmlNodeChangedAction.Change,
582                                         node, Parent, Parent));
583                 }
584
585                 internal void onNodeInserted (XmlNode node, XmlNode newParent)
586                 {
587                         if (NodeInserted != null)
588                                 NodeInserted (node, new XmlNodeChangedEventArgs
589                                         (XmlNodeChangedAction.Insert,
590                                         node, null, newParent));
591                 }
592
593                 internal void onNodeInserting (XmlNode node, XmlNode newParent)
594                 {
595                         if (NodeInserting != null)
596                                 NodeInserting (node, new XmlNodeChangedEventArgs
597                                         (XmlNodeChangedAction.Insert,
598                                         node, null, newParent));
599                 }
600
601                 internal void onNodeRemoved (XmlNode node, XmlNode oldParent)
602                 {
603                         if (NodeRemoved != null)
604                                 NodeRemoved (node, new XmlNodeChangedEventArgs
605                                         (XmlNodeChangedAction.Remove,
606                                         node, oldParent, null));
607                 }
608
609                 internal void onNodeRemoving (XmlNode node, XmlNode oldParent)
610                 {
611                         if (NodeRemoving != null)
612                                 NodeRemoving (node, new XmlNodeChangedEventArgs
613                                         (XmlNodeChangedAction.Remove,
614                                         node, oldParent, null));
615                 }
616
617                 private void ParseName (string name, out string prefix, out string localName)
618                 {
619                         int indexOfColon = name.IndexOf (':');
620                         
621                         if (indexOfColon != -1) {
622                                 prefix = name.Substring (0, indexOfColon);
623                                 localName = name.Substring (indexOfColon + 1);
624                         } else {
625                                 prefix = "";
626                                 localName = name;
627                         }
628                 }
629
630                 [MonoTODO]
631                 public virtual XmlNode ReadNode(XmlReader reader)
632                 {
633                         throw new NotImplementedException ();
634                 }
635
636                 [MonoTODO ("Verify what encoding is used by default;  Should use PreserveWhiteSpace")]
637                 public virtual void Save(Stream outStream)
638                 {
639                         // To implementor: utf-8 is OK, at least for (ginga's) Japanese environment.
640                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, Encoding.UTF8);
641                         WriteContentTo (xmlWriter);
642                         xmlWriter.Close ();
643                 }
644
645                 [MonoTODO ("Verify what encoding is used by default; Should use PreseveWhiteSpace")]
646                 public virtual void Save (string filename)
647                 {
648                         // To implementor: utf-8 is OK, at least for (ginga's) Japanese environment.
649                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, Encoding.UTF8);
650                         WriteContentTo (xmlWriter);
651                         xmlWriter.Close ();
652                 }
653
654                 [MonoTODO]
655                 public virtual void Save (TextWriter writer)
656                 {
657                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
658                         WriteContentTo (xmlWriter);
659                         xmlWriter.Flush ();
660                 }
661
662                 [MonoTODO ("Should preserve white space if PreserveWhisspace is set")]
663                 public virtual void Save (XmlWriter xmlWriter)
664                 {
665                         //
666                         // This should preserve white space if PreserveWhiteSpace is true
667                         //
668                         WriteContentTo (xmlWriter);
669                         xmlWriter.Flush ();
670                 }
671
672                 public override void WriteContentTo (XmlWriter w)
673                 {
674                         foreach(XmlNode childNode in ChildNodes)
675                                 childNode.WriteTo(w);
676                 }
677
678                 public override void WriteTo (XmlWriter w)
679                 {
680                         WriteContentTo(w);
681                 }
682
683                 private void AddDefaultNameTableKeys()
684                 {
685                         // The following keys are default of MS .NET Framework
686                         nameTable.Add("#text");
687                         nameTable.Add("xml");
688                         nameTable.Add("xmlns");
689                         nameTable.Add("#entity");
690                         nameTable.Add("#document-fragment");
691                         nameTable.Add("#comment");
692                         nameTable.Add("space");
693                         nameTable.Add("id");
694                         nameTable.Add("#whitespace");
695                         nameTable.Add("http://www.w3.org/2000/xmlns/");
696                         nameTable.Add("#cdata-section");
697                         nameTable.Add("lang");
698                         nameTable.Add("#document");
699                         nameTable.Add("#significant-whitespace");
700                 }
701                 #endregion
702         }
703 }