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