Added implementation of namepsace qualified GetElementsByTagName courtesy of
[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
32                 #endregion
33
34                 #region Constructors
35
36                 public XmlDocument () : base (null) { }
37
38                 [MonoTODO]
39                 protected internal XmlDocument (XmlImplementation imp) : base (null)
40                 {
41                         throw new NotImplementedException ();
42                 }
43
44                 public XmlDocument (XmlNameTable nt) : base (null)
45                 {
46                         nameTable = nt;
47                 }
48
49                 #endregion
50
51                 #region Events
52
53                 public event XmlNodeChangedEventHandler NodeChanged;
54
55                 public event XmlNodeChangedEventHandler NodeChanging;
56
57                 public event XmlNodeChangedEventHandler NodeInserted;
58
59                 public event XmlNodeChangedEventHandler NodeInserting;
60
61                 public event XmlNodeChangedEventHandler NodeRemoved;
62
63                 public event XmlNodeChangedEventHandler NodeRemoving;
64
65                 #endregion
66
67                 #region Properties
68
69                 public override string BaseURI {
70                         get {
71                                 return baseURI;
72                         }
73                 }
74
75                 public XmlElement DocumentElement {
76                         get {
77                                 XmlNode node = FirstChild;
78
79                                 while (node != null) {
80                                         if (node is XmlElement)
81                                                 break;
82                                         node = node.NextSibling;
83                                 }
84
85                                 return node != null ? node as XmlElement : null;
86                         }
87                 }
88
89                 [MonoTODO]
90                 public virtual XmlDocumentType DocumentType {
91                         get { throw new NotImplementedException(); }
92                 }
93
94                 [MonoTODO]
95                 public XmlImplementation Implementation {
96                         get { throw new NotImplementedException(); }
97                 }
98
99                 [MonoTODO ("Setter.")]
100                 public override string InnerXml {
101                         get {
102                                 // Not sure why this is an override.  Passing through for now.
103                                 return base.InnerXml;
104                         }
105                         set { throw new NotImplementedException(); }
106                 }
107
108                 public override bool IsReadOnly {
109                         get { return false; }
110                 }
111
112                 internal override XmlLinkedNode LastLinkedChild {
113                         get     {
114                                 return lastLinkedChild;
115                         }
116
117                         set {
118                                 lastLinkedChild = value;
119                         }
120                 }
121                 
122                 public override string LocalName {
123                         get { return "#document"; }
124                 }
125
126                 public override string Name {
127                         get { return "#document"; }
128                 }
129
130                 public XmlNameTable NameTable {
131                         get { return nameTable; }
132                 }
133
134                 public override XmlNodeType NodeType {
135                         get { return XmlNodeType.Document; }
136                 }
137
138                 public override XmlDocument OwnerDocument {
139                         get { return null; }
140                 }
141
142                 [MonoTODO]
143                 public bool PreserveWhitespace {
144                         get { throw new NotImplementedException(); }
145                         set { throw new NotImplementedException(); }
146                 }
147
148                 [MonoTODO]
149                 public virtual XmlResolver XmlResolver {
150                         set { throw new NotImplementedException(); }
151                 }
152
153                 #endregion
154
155                 #region Methods
156
157                 [MonoTODO]
158                 public override XmlNode CloneNode (bool deep)
159                 {
160                         throw new NotImplementedException ();
161                 }
162
163                 public XmlAttribute CreateAttribute (string name)
164                 {
165                         return CreateAttribute (name, String.Empty);
166                 }
167
168                 public XmlAttribute CreateAttribute (string qualifiedName, string namespaceURI)
169                 {
170                         string prefix;
171                         string localName;
172
173                         ParseName (qualifiedName, out prefix, out localName);
174
175                         return CreateAttribute (prefix, localName, namespaceURI);
176                 }
177
178                 public virtual XmlAttribute CreateAttribute (string prefix, string localName, string namespaceURI)
179                 {
180                         if ((localName == null) || (localName == String.Empty))
181                                 throw new ArgumentException ("The attribute local name cannot be empty.");
182
183                         return new XmlAttribute (prefix, localName, namespaceURI, this);
184                 }
185
186                 public virtual XmlCDataSection CreateCDataSection (string data)
187                 {
188                         return new XmlCDataSection (data, this);
189                 }
190
191                 public virtual XmlComment CreateComment (string data)
192                 {
193                         return new XmlComment(data, this);
194                 }
195
196                 [MonoTODO]
197                 protected internal virtual XmlAttribute CreateDefaultAttribute (string prefix, string localName, string namespaceURI)
198                 {
199                         throw new NotImplementedException ();
200                 }
201
202                 [MonoTODO]
203                 public virtual XmlDocumentFragment CreateDocumentFragment ()
204                 {
205                         throw new NotImplementedException ();
206                 }
207
208                 public virtual XmlDocumentType CreateDocumentType (string name, string publicId,
209                                                                    string systemId, string internalSubset)
210                 {
211                         return new XmlDocumentType (name, publicId, systemId, internalSubset, this);
212                 }
213
214                 public XmlElement CreateElement (string name)
215                 {
216                         return CreateElement (name, String.Empty);
217                 }
218
219                 public XmlElement CreateElement (
220                         string qualifiedName, 
221                         string namespaceURI)
222                 {
223                         string prefix;
224                         string localName;
225
226                         ParseName (qualifiedName, out prefix, out localName);
227
228                         return CreateElement (prefix, localName, namespaceURI);
229                 }
230
231                 public virtual XmlElement CreateElement (
232                         string prefix,
233                         string localName,
234                         string namespaceURI)
235                 {
236                         if ((localName == null) || (localName == String.Empty))
237                                 throw new ArgumentException ("The local name for elements or attributes cannot be null or an empty string.");
238
239                         return new XmlElement (prefix != null ? prefix : String.Empty, localName, namespaceURI != null ? namespaceURI : String.Empty, this);
240                 }
241
242                 [MonoTODO]
243                 public virtual XmlEntityReference CreateEntityReference (string name)
244                 {
245                         throw new NotImplementedException ();
246                 }
247
248                 public virtual XmlNode CreateNode (
249                         string nodeTypeString,
250                         string name,
251                         string namespaceURI)
252                 {
253                         return CreateNode (GetNodeTypeFromString (nodeTypeString), name, namespaceURI);
254                 }
255
256                 public virtual XmlNode CreateNode (
257                         XmlNodeType type,
258                         string name,
259                         string namespaceURI)
260                 {
261                         string prefix = null;
262                         string localName = name;
263
264                         if ((type == XmlNodeType.Attribute) || (type == XmlNodeType.Element) || (type == XmlNodeType.EntityReference))
265                                 ParseName (name, out prefix, out localName);
266                         
267                         return CreateNode (type, prefix, localName, namespaceURI);
268                 }
269
270                 public virtual XmlNode CreateNode (
271                         XmlNodeType type,
272                         string prefix,
273                         string name,
274                         string namespaceURI)
275                 {
276                         switch (type) {
277                                 case XmlNodeType.Attribute: return CreateAttribute (prefix, name, namespaceURI);
278                                 case XmlNodeType.CDATA: return CreateCDataSection (null);
279                                 case XmlNodeType.Comment: return CreateComment (null);
280                                 case XmlNodeType.Document: return new XmlDocument (); // TODO - test to see which constructor to use, i.e. use existing NameTable or not.
281                                 case XmlNodeType.DocumentFragment: return CreateDocumentFragment ();
282                                 case XmlNodeType.DocumentType: return CreateDocumentType (null, null, null, null);
283                                 case XmlNodeType.Element: return CreateElement (prefix, name, namespaceURI);
284                                 case XmlNodeType.EntityReference: return CreateEntityReference (null);
285                                 case XmlNodeType.ProcessingInstruction: return CreateProcessingInstruction (null, null);
286                                 case XmlNodeType.SignificantWhitespace: return CreateSignificantWhitespace (String.Empty);
287                                 case XmlNodeType.Text: return CreateTextNode (null);
288                                 case XmlNodeType.Whitespace: return CreateWhitespace (String.Empty);
289                                 case XmlNodeType.XmlDeclaration: return CreateXmlDeclaration ("1.0", null, null);
290                                 default: throw new ArgumentOutOfRangeException(String.Format("{0}\nParameter name: {1}",
291                                                          "Specified argument was out of the range of valid values", type.ToString ()));
292                         }
293                 }
294
295                 public virtual XmlProcessingInstruction CreateProcessingInstruction (
296                         string target,
297                         string data)
298                 {
299                         return new XmlProcessingInstruction (target, data, this);
300                 }
301
302                 public virtual XmlSignificantWhitespace CreateSignificantWhitespace (string text)
303                 {
304                         foreach (char c in text)
305                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
306                                     throw new ArgumentException ("Invalid whitespace characters.");
307                          
308                         return new XmlSignificantWhitespace (text, this);
309                 }
310
311                 public virtual XmlText CreateTextNode (string text)
312                 {
313                         return new XmlText (text, this);
314                 }
315
316                 public virtual XmlWhitespace CreateWhitespace (string text)
317                 {
318                         foreach (char c in text)
319                                 if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t'))
320                                     throw new ArgumentException ("Invalid whitespace characters.");
321                          
322                         return new XmlWhitespace (text, this);
323                 }
324
325                 public virtual XmlDeclaration CreateXmlDeclaration (string version, string encoding,
326                                                                     string standalone)
327                 {
328                         if (version != "1.0")
329                                 throw new ArgumentException ("version string is not correct.");
330
331                         if  ((standalone != null) && !((standalone == "yes") || (standalone == "no")))
332                                 throw new ArgumentException ("standalone string is not correct.");
333                         
334                         return new XmlDeclaration (version, encoding, standalone, this);
335                 }
336
337                 [MonoTODO]
338                 public virtual XmlElement GetElementById (string elementId)
339                 {
340                         throw new NotImplementedException ();
341                 }
342
343                 public virtual XmlNodeList GetElementsByTagName (string name)
344                 {
345                         ArrayList nodeArrayList = new ArrayList ();
346                         this.searchNodesRecursively (this, name, String.Empty, nodeArrayList);
347                         return new XmlNodeArrayList (nodeArrayList);
348                 }
349
350                 private void searchNodesRecursively (XmlNode argNode, string argName, string argNamespaceURI, 
351                         ArrayList argArrayList)
352                 {
353                         XmlNodeList xmlNodeList = argNode.ChildNodes;
354                         foreach (XmlNode node in xmlNodeList){
355                                 if (node.LocalName.Equals (argName) && node.NamespaceURI.Equals (argNamespaceURI))
356                                         argArrayList.Add (node);
357                                 else    
358                                         this.searchNodesRecursively (node, argName, argNamespaceURI, argArrayList);
359                         }
360                 }
361
362                 public virtual XmlNodeList GetElementsByTagName (string localName, string namespaceURI)
363                 {
364                         ArrayList nodeArrayList = new ArrayList ();
365                         this.searchNodesRecursively (this, localName, namespaceURI, nodeArrayList);
366                         return new XmlNodeArrayList (nodeArrayList);
367                 }
368
369                 private XmlNodeType GetNodeTypeFromString (string nodeTypeString)
370                 {
371                         switch (nodeTypeString) {
372                                 case "attribute": return XmlNodeType.Attribute;
373                                 case "cdatasection": return XmlNodeType.CDATA;
374                                 case "comment": return XmlNodeType.Comment;
375                                 case "document": return XmlNodeType.Document;
376                                 case "documentfragment": return XmlNodeType.DocumentFragment;
377                                 case "documenttype": return XmlNodeType.DocumentType;
378                                 case "element": return XmlNodeType.Element;
379                                 case "entityreference": return XmlNodeType.EntityReference;
380                                 case "processinginstruction": return XmlNodeType.ProcessingInstruction;
381                                 case "significantwhitespace": return XmlNodeType.SignificantWhitespace;
382                                 case "text": return XmlNodeType.Text;
383                                 case "whitespace": return XmlNodeType.Whitespace;
384                                 default:
385                                         throw new ArgumentException(String.Format("The string doesn't represent any node type : {0}.", nodeTypeString));
386                         }
387                 }
388
389                 [MonoTODO]
390                 public virtual XmlNode ImportNode (XmlNode node, bool deep)
391                 {
392                         throw new NotImplementedException ();
393                 }
394
395                 public virtual void Load (Stream inStream)
396                 {
397                         XmlReader xmlReader = new XmlTextReader (inStream);
398                         Load (xmlReader);
399                 }
400
401                 public virtual void Load (string filename)
402                 {
403                         baseURI = filename;
404                         XmlReader xmlReader = new XmlTextReader (new StreamReader (filename));
405                         Load (xmlReader);
406                 }
407
408                 [MonoTODO]
409                 public virtual void Load (TextReader txtReader)
410                 {
411                         throw new NotImplementedException ();
412                 }
413
414                 public virtual void Load (XmlReader xmlReader)
415                 {
416                         // Reset our document
417                         // For now this just means removing all our children but later this
418                         // may turn out o need to call a private method that resets other things
419                         // like properties we have, etc.
420                         RemoveAll ();
421
422                         XmlNode currentNode = this;
423                         XmlNode newNode;
424
425                         while (xmlReader.Read ()) 
426                         {
427                                 switch (xmlReader.NodeType) {
428
429                                 case XmlNodeType.CDATA:
430                                         newNode = CreateCDataSection(xmlReader.Value);
431                                         currentNode.AppendChild (newNode);
432                                         break;
433
434                                 case XmlNodeType.Comment:
435                                         newNode = CreateComment (xmlReader.Value);
436                                         currentNode.AppendChild (newNode);
437                                         break;
438
439                                 case XmlNodeType.Element:
440                                         XmlElement element = CreateElement (xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
441                                         currentNode.AppendChild (element);
442
443                                         // set the element's attributes.
444                                         while (xmlReader.MoveToNextAttribute ()) {
445                                                 XmlAttribute attribute = CreateAttribute (xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
446                                                 attribute.Value = xmlReader.Value;
447                                                 element.SetAttributeNode (attribute);
448                                         }
449
450                                         xmlReader.MoveToElement ();
451
452                                         // if this element isn't empty, push it onto our "stack".
453                                         if (!xmlReader.IsEmptyElement)
454                                                 currentNode = element;
455
456                                         break;
457
458                                 case XmlNodeType.EndElement:
459                                         currentNode = currentNode.ParentNode;
460                                         break;
461
462                                 case XmlNodeType.ProcessingInstruction:
463                                         newNode = CreateProcessingInstruction (xmlReader.Name, xmlReader.Value);
464                                         currentNode.AppendChild (newNode);
465                                         break;
466
467                                 case XmlNodeType.Text:
468                                         newNode = CreateTextNode (xmlReader.Value);
469                                         currentNode.AppendChild (newNode);
470                                         break;
471                                 }
472                         }
473                 }
474
475                 public virtual void LoadXml (string xml)
476                 {
477                         XmlReader xmlReader = new XmlTextReader (new StringReader (xml));
478                         Load (xmlReader);
479                 }
480
481                 internal void onNodeChanged (XmlNode node, XmlNode Parent)
482                 {
483                         if (NodeChanged != null)
484                                 NodeChanged (node, new XmlNodeChangedEventArgs
485                                         (XmlNodeChangedAction.Change,
486                                         node, Parent, Parent));
487                 }
488
489                 internal void onNodeChanging(XmlNode node, XmlNode Parent)
490                 {
491                         if (NodeChanging != null)
492                                 NodeChanging (node, new XmlNodeChangedEventArgs
493                                         (XmlNodeChangedAction.Change,
494                                         node, Parent, Parent));
495                 }
496
497                 internal void onNodeInserted (XmlNode node, XmlNode newParent)
498                 {
499                         if (NodeInserted != null)
500                                 NodeInserted (node, new XmlNodeChangedEventArgs
501                                         (XmlNodeChangedAction.Insert,
502                                         node, null, newParent));
503                 }
504
505                 internal void onNodeInserting (XmlNode node, XmlNode newParent)
506                 {
507                         if (NodeInserting != null)
508                                 NodeInserting (node, new XmlNodeChangedEventArgs
509                                         (XmlNodeChangedAction.Insert,
510                                         node, null, newParent));
511                 }
512
513                 internal void onNodeRemoved (XmlNode node, XmlNode oldParent)
514                 {
515                         if (NodeRemoved != null)
516                                 NodeRemoved (node, new XmlNodeChangedEventArgs
517                                         (XmlNodeChangedAction.Remove,
518                                         node, oldParent, null));
519                 }
520
521                 internal void onNodeRemoving (XmlNode node, XmlNode oldParent)
522                 {
523                         if (NodeRemoving != null)
524                                 NodeRemoving (node, new XmlNodeChangedEventArgs
525                                         (XmlNodeChangedAction.Remove,
526                                         node, oldParent, null));
527                 }
528
529                 private void ParseName (string name, out string prefix, out string localName)
530                 {
531                         int indexOfColon = name.IndexOf (':');
532                         
533                         if (indexOfColon != -1) {
534                                 prefix = name.Substring (0, indexOfColon);
535                                 localName = name.Substring (indexOfColon + 1);
536                         } else {
537                                 prefix = "";
538                                 localName = name;
539                         }
540                 }
541
542                 [MonoTODO]
543                 public virtual XmlNode ReadNode(XmlReader reader)
544                 {
545                         throw new NotImplementedException ();
546                 }
547
548                 [MonoTODO ("Verify what encoding is used by default;  Should use PreserveWhiteSpace")]
549                 public virtual void Save(Stream outStream)
550                 {
551                         XmlTextWriter xmlWriter = new XmlTextWriter (outStream, Encoding.UTF8);
552                         WriteContentTo (xmlWriter);
553                         xmlWriter.Close ();
554                 }
555
556                 [MonoTODO ("Verify what encoding is used by default; Should use PreseveWhiteSpace")]
557                 public virtual void Save (string filename)
558                 {
559                         XmlTextWriter xmlWriter = new XmlTextWriter (filename, Encoding.UTF8);
560                         WriteContentTo (xmlWriter);
561                         xmlWriter.Close ();
562                 }
563
564                 [MonoTODO]
565                 public virtual void Save (TextWriter writer)
566                 {
567                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);
568                         WriteContentTo (xmlWriter);
569                         xmlWriter.Flush ();
570                 }
571
572                 [MonoTODO ("Should preserve white space if PreserveWhisspace is set")]
573                 public virtual void Save (XmlWriter xmlWriter)
574                 {
575                         //
576                         // This should preserve white space if PreserveWhiteSpace is true
577                         //
578                         WriteContentTo (xmlWriter);
579                         xmlWriter.Flush ();
580                 }
581
582                 public override void WriteContentTo (XmlWriter w)
583                 {
584                         foreach(XmlNode childNode in ChildNodes)
585                                 childNode.WriteTo(w);
586                 }
587
588                 public override void WriteTo (XmlWriter w)
589                 {
590                         WriteContentTo(w);
591                 }
592
593                 #endregion
594         }
595 }