e2d979845c2374ba8b5376e9ff74c1ca3ed8b3d8
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Dom / XmlElement.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlElement.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
8 using System;
9 using System.Xml.Schema;
10 using System.Xml.XPath;
11 using System.Collections;
12 using System.Diagnostics;
13 using System.Globalization;
14
15 namespace System.Xml {
16     // Represents an element.
17     public class XmlElement : XmlLinkedNode {
18         XmlName name;
19         XmlAttributeCollection attributes;
20         XmlLinkedNode lastChild; // == this for empty elements otherwise it is the last child
21
22         internal XmlElement( XmlName name, bool empty, XmlDocument doc ): base( doc ) {
23             Debug.Assert(name!=null);
24             this.parentNode = null;
25             if ( !doc.IsLoading ) {
26                 XmlDocument.CheckName( name.Prefix );
27                 XmlDocument.CheckName( name.LocalName );
28             }
29             if (name.LocalName.Length == 0) 
30                 throw new ArgumentException(Res.GetString(Res.Xdom_Empty_LocalName));
31             this.name = name;
32             if (empty) {
33                 this.lastChild = this; 
34             }
35         }
36
37         protected internal XmlElement( string prefix, string localName, string namespaceURI, XmlDocument doc ) 
38         : this( doc.AddXmlName( prefix, localName, namespaceURI, null ), true, doc ) {
39         }
40
41         internal XmlName XmlName {
42             get { return name; }
43             set { name = value; }
44         }
45
46         // Creates a duplicate of this node.
47         public override XmlNode CloneNode(bool deep) {
48             Debug.Assert( OwnerDocument != null );
49             XmlDocument doc = OwnerDocument;
50             bool OrigLoadingStatus = doc.IsLoading;
51             doc.IsLoading = true;
52             XmlElement element = doc.CreateElement( Prefix, LocalName, NamespaceURI );
53             doc.IsLoading = OrigLoadingStatus;
54             if ( element.IsEmpty != this.IsEmpty )
55                 element.IsEmpty = this.IsEmpty;
56
57             if (HasAttributes) {
58                 foreach( XmlAttribute attr in Attributes ) {
59                     XmlAttribute newAttr = (XmlAttribute)(attr.CloneNode(true));
60                     if (attr is XmlUnspecifiedAttribute && attr.Specified == false)
61                         ( ( XmlUnspecifiedAttribute )newAttr).SetSpecified(false);
62                     element.Attributes.InternalAppendAttribute( newAttr );
63                 }
64             }
65             if (deep)
66                 element.CopyChildren( doc, this, deep );
67
68             return element;
69         }
70
71         // Gets the name of the node.
72         public override string Name { 
73             get { return name.Name;}
74         }
75
76         // Gets the name of the current node without the namespace prefix.
77         public override string LocalName { 
78             get { return name.LocalName;}
79         }
80
81         // Gets the namespace URI of this node.
82         public override string NamespaceURI { 
83             get { return name.NamespaceURI;} 
84         }
85
86         // Gets or sets the namespace prefix of this node.
87         public override string Prefix { 
88             get { return name.Prefix;}
89             set { name = name.OwnerDocument.AddXmlName( value, LocalName, NamespaceURI, SchemaInfo ); }
90         }
91
92         // Gets the type of the current node.
93         public override XmlNodeType NodeType {
94             get { return XmlNodeType.Element;}
95         }
96
97         public override XmlNode ParentNode {
98             get {
99                 return this.parentNode;
100             }
101         }
102
103         // Gets the XmlDocument that contains this node.
104         public override XmlDocument OwnerDocument { 
105             get { 
106                 return name.OwnerDocument;
107             }
108         }
109
110         internal override bool IsContainer {
111             get { return true;}
112         }
113
114         //the function is provided only at Load time to speed up Load process
115         internal override XmlNode AppendChildForLoad(XmlNode newChild, XmlDocument doc) {
116             XmlNodeChangedEventArgs args = doc.GetInsertEventArgsForLoad( newChild, this );
117
118             if (args != null)
119                 doc.BeforeEvent( args );
120
121             XmlLinkedNode newNode = (XmlLinkedNode)newChild;
122
123             if (lastChild == null 
124                 || lastChild == this) { // if LastNode == null 
125                 newNode.next = newNode;
126                 lastChild = newNode; // LastNode = newNode;
127                 newNode.SetParentForLoad(this);
128             }
129             else {
130                 XmlLinkedNode refNode = lastChild; // refNode = LastNode;
131                 newNode.next = refNode.next;
132                 refNode.next = newNode;
133                 lastChild = newNode; // LastNode = newNode;
134                 if (refNode.IsText
135                     && newNode.IsText) {
136                     NestTextNodes(refNode, newNode);
137                 }
138                 else {
139                     newNode.SetParentForLoad(this);
140                 }
141             }
142
143             if (args != null)
144                 doc.AfterEvent( args );
145
146             return newNode;
147         }
148
149         // Gets or sets whether the element does not have any children.
150         public bool IsEmpty {
151             get { 
152                 return lastChild == this;
153             }
154
155             set {
156                 if (value) {
157                     if (lastChild != this) {
158                         RemoveAllChildren();
159                         lastChild = this;
160                     }
161                 }
162                 else {
163                     if (lastChild == this) {
164                         lastChild = null;
165                     }
166                 }
167
168             }
169         }
170
171         internal override XmlLinkedNode LastNode {
172             get { 
173                 return lastChild == this ? null : lastChild; 
174             }
175
176             set { 
177                 lastChild = value;
178             }
179         }
180
181         internal override bool IsValidChildType( XmlNodeType type ) {
182             switch (type) {
183                 case XmlNodeType.Element:
184                 case XmlNodeType.Text:
185                 case XmlNodeType.EntityReference:
186                 case XmlNodeType.Comment:
187                 case XmlNodeType.Whitespace:
188                 case XmlNodeType.SignificantWhitespace:
189                 case XmlNodeType.ProcessingInstruction:
190                 case XmlNodeType.CDATA:
191                     return true;
192
193                 default:
194                     return false;
195             }
196         }
197
198
199         // Gets a XmlAttributeCollection containing the list of attributes for this node.
200         public override XmlAttributeCollection Attributes { 
201             get { 
202                 if (attributes == null) {
203                     lock ( OwnerDocument.objLock ) {
204                         if ( attributes == null ) {
205                             attributes = new XmlAttributeCollection(this);
206                         }
207                     }
208                 }
209
210                 return attributes; 
211             }
212         }
213
214         // Gets a value indicating whether the current node
215         // has any attributes.
216         public virtual bool HasAttributes {
217             get {
218                 if ( this.attributes == null )
219                     return false;
220                 else
221                     return this.attributes.Count > 0;
222             }
223         }
224
225         // Returns the value for the attribute with the specified name.
226         public virtual string GetAttribute(string name) {
227             XmlAttribute attr = GetAttributeNode(name);
228             if (attr != null)
229                 return attr.Value;
230             return String.Empty;
231         }
232
233         // Sets the value of the attribute
234         // with the specified name.
235         public virtual void SetAttribute(string name, string value) {
236             XmlAttribute attr = GetAttributeNode(name);
237             if (attr == null) {
238                 attr = OwnerDocument.CreateAttribute(name);
239                 attr.Value = value;
240                 Attributes.InternalAppendAttribute( attr );
241             }
242             else {
243                 attr.Value = value;
244             }
245         }
246
247         // Removes an attribute by name.
248         public virtual void RemoveAttribute(string name) {
249             if (HasAttributes)
250                 Attributes.RemoveNamedItem(name);
251         }
252
253         // Returns the XmlAttribute with the specified name.
254         public virtual XmlAttribute GetAttributeNode(string name) {
255             if (HasAttributes)
256                 return Attributes[name];
257             return null;
258         }
259
260         // Adds the specified XmlAttribute.
261         public virtual XmlAttribute SetAttributeNode(XmlAttribute newAttr) {
262             if ( newAttr.OwnerElement != null )
263                 throw new InvalidOperationException( Res.GetString(Res.Xdom_Attr_InUse) );
264             return(XmlAttribute) Attributes.SetNamedItem(newAttr);
265         }
266
267         // Removes the specified XmlAttribute.
268         public virtual XmlAttribute RemoveAttributeNode(XmlAttribute oldAttr) {
269             if (HasAttributes)
270                 return(XmlAttribute) Attributes.Remove(oldAttr);
271             return null;
272         }
273
274         // Returns a XmlNodeList containing
275         // a list of all descendant elements that match the specified name.
276         public virtual XmlNodeList GetElementsByTagName(string name) {
277             return new XmlElementList( this, name );
278         }
279
280         //
281         // DOM Level 2
282         //
283
284         // Returns the value for the attribute with the specified LocalName and NamespaceURI.
285         public virtual string GetAttribute(string localName, string namespaceURI) {
286             XmlAttribute attr = GetAttributeNode( localName, namespaceURI );
287             if (attr != null)
288                 return attr.Value;
289             return String.Empty;
290         }
291
292         // Sets the value of the attribute with the specified name
293         // and namespace.
294         public virtual string SetAttribute(string localName, string namespaceURI, string value) {
295             XmlAttribute attr = GetAttributeNode( localName, namespaceURI );
296             if (attr == null) {
297                 attr = OwnerDocument.CreateAttribute( string.Empty, localName, namespaceURI );
298                 attr.Value = value;
299                 Attributes.InternalAppendAttribute( attr );
300             }
301             else {
302                 attr.Value = value;
303             }
304
305             return value;
306         }
307
308         // Removes an attribute specified by LocalName and NamespaceURI.
309         public virtual void RemoveAttribute(string localName, string namespaceURI) {
310             //Debug.Assert(namespaceURI != null);
311             RemoveAttributeNode( localName, namespaceURI );
312         }
313
314         // Returns the XmlAttribute with the specified LocalName and NamespaceURI.
315         public virtual XmlAttribute GetAttributeNode(string localName, string namespaceURI) {
316             //Debug.Assert(namespaceURI != null);
317             if (HasAttributes)
318                 return Attributes[ localName, namespaceURI ];
319             return null;
320         }
321
322         // Adds the specified XmlAttribute.
323         public virtual XmlAttribute SetAttributeNode(string localName, string namespaceURI) {
324             XmlAttribute attr = GetAttributeNode( localName, namespaceURI );
325             if (attr == null) {
326                 attr = OwnerDocument.CreateAttribute( string.Empty, localName, namespaceURI );
327                 Attributes.InternalAppendAttribute( attr );
328             }
329             return attr;
330         }
331
332         // Removes the XmlAttribute specified by LocalName and NamespaceURI.
333         public virtual XmlAttribute RemoveAttributeNode(string localName, string namespaceURI) {
334             //Debug.Assert(namespaceURI != null);
335             if (HasAttributes) {
336                 XmlAttribute attr = GetAttributeNode( localName, namespaceURI );
337                 Attributes.Remove( attr );
338                 return attr;
339             }
340             return null;
341         }
342
343         // Returns a XmlNodeList containing 
344         // a list of all descendant elements that match the specified name.
345         public virtual XmlNodeList GetElementsByTagName(string localName, string namespaceURI) {
346             //Debug.Assert(namespaceURI != null);
347             return new XmlElementList( this, localName, namespaceURI );
348         }
349
350         // Determines whether the current node has the specified attribute.
351         public virtual bool HasAttribute(string name) {
352             return GetAttributeNode(name) != null;
353         }
354
355         // Determines whether the current node has the specified
356         // attribute from the specified namespace.
357         public virtual bool HasAttribute(string localName, string namespaceURI) {
358             return GetAttributeNode(localName, namespaceURI) != null;
359         }
360
361         // Saves the current node to the specified XmlWriter.
362         public override void WriteTo(XmlWriter w) {
363
364             if (GetType() == typeof(XmlElement)) {
365                 // Use the non-recursive version (for XmlElement only)
366                 WriteElementTo(w, this);
367             }
368             else {
369                 // Use the (potentially) recursive version
370                 WriteStartElement(w);
371
372                 if (IsEmpty) {
373                     w.WriteEndElement();
374                 }
375                 else {
376                     WriteContentTo(w);
377                     w.WriteFullEndElement();
378                 }
379             }
380         }
381
382         // This method is copied from System.Xml.Linq.ElementWriter.WriteElement but adapted to DOM
383         private static void WriteElementTo(XmlWriter writer, XmlElement e) {
384             XmlNode root = e;
385             XmlNode n = e;
386             while (true) {
387                 e = n as XmlElement;
388                 // Only use the inlined write logic for XmlElement, not for derived classes
389                 if (e != null && e.GetType() == typeof(XmlElement)) {
390                     // Write the element
391                     e.WriteStartElement(writer);
392                     // Write the element's content
393                     if (e.IsEmpty) {
394                         // No content; use a short end element <a />
395                         writer.WriteEndElement();
396                     }
397                     else if (e.lastChild == null) {
398                         // No actual content; use a full end element <a></a>
399                         writer.WriteFullEndElement();
400                     }
401                     else {
402                         // There are child node(s); move to first child
403                         n = e.FirstChild;
404                         Debug.Assert(n != null);
405                         continue;
406                     }
407                 }
408                 else {
409                     // Use virtual dispatch (might recurse)
410                     n.WriteTo(writer);
411                 }
412                 // Go back to the parent after writing the last child
413                 while (n != root && n == n.ParentNode.LastChild) {
414                     n = n.ParentNode;
415                     Debug.Assert(n != null);
416                     writer.WriteFullEndElement();
417                 }
418                 if (n == root)
419                     break;
420                 n = n.NextSibling;
421                 Debug.Assert(n != null);
422             }
423         }
424
425         // Writes the start of the element (and its attributes) to the specified writer
426         private void WriteStartElement(XmlWriter w) {
427             w.WriteStartElement(Prefix, LocalName, NamespaceURI);
428
429             if (HasAttributes) {
430                 XmlAttributeCollection attrs = Attributes;
431                 for (int i = 0; i < attrs.Count; i += 1) {
432                     XmlAttribute attr = attrs[i];
433                     attr.WriteTo(w);
434                 }
435             }
436         }
437
438         // Saves all the children of the node to the specified XmlWriter.
439         public override void WriteContentTo(XmlWriter w) {
440             for (XmlNode node = FirstChild; node != null; node = node.NextSibling) {
441                 node.WriteTo(w);
442             }
443         }
444
445         // Removes the attribute node with the specified index from the attribute collection.
446         public virtual XmlNode RemoveAttributeAt(int i) {
447             if (HasAttributes)
448                 return attributes.RemoveAt( i );
449             return null;
450         }
451
452         // Removes all attributes from the element.
453         public virtual void RemoveAllAttributes() {
454             if (HasAttributes) {
455                 attributes.RemoveAll();
456             }
457         }
458
459         // Removes all the children and/or attributes
460         // of the current node.
461         public override void RemoveAll() {
462             //remove all the children
463             base.RemoveAll(); 
464             //remove all the attributes
465             RemoveAllAttributes();
466         }
467         
468         internal void RemoveAllChildren() {
469             base.RemoveAll();
470         }
471
472         public override IXmlSchemaInfo SchemaInfo {
473             get {
474                 return name;
475             }
476         }
477
478         // Gets or sets the markup representing just
479         // the children of this node.
480         public override string InnerXml {
481             get {
482                 return base.InnerXml;
483             }
484             set {
485                 RemoveAllChildren();
486                 XmlLoader loader = new XmlLoader();
487                 loader.LoadInnerXmlElement( this, value );
488             }
489         }
490
491         // Gets or sets the concatenated values of the
492         // node and all its children.
493         public override string InnerText { 
494             get {
495                 return base.InnerText;
496             }
497             set {
498                 XmlLinkedNode linkedNode = LastNode;
499                 if (linkedNode != null && //there is one child
500                     linkedNode.NodeType == XmlNodeType.Text && //which is text node
501                     linkedNode.next == linkedNode ) // and it is the only child 
502                 {
503                     //this branch is for perf reason, event fired when TextNode.Value is changed.
504                     linkedNode.Value = value;
505                 } 
506                 else {
507                     RemoveAllChildren();
508                     AppendChild( OwnerDocument.CreateTextNode( value ) );
509                 }
510             }
511         }
512
513         public override XmlNode NextSibling { 
514             get { 
515                 if (this.parentNode != null
516                     && this.parentNode.LastNode != this)
517                     return next;
518                 return null; 
519             } 
520         }
521
522         internal override void SetParent(XmlNode node) {
523             this.parentNode = node;
524         }
525
526         internal override XPathNodeType XPNodeType { get { return XPathNodeType.Element; } }
527
528         internal override string XPLocalName { get { return LocalName; } }
529
530         internal override string GetXPAttribute( string localName, string ns ) {
531             if ( ns == OwnerDocument.strReservedXmlns )
532                 return null;
533             XmlAttribute attr = GetAttributeNode( localName, ns );
534             if ( attr != null )
535                 return attr.Value;
536             return string.Empty;
537         }
538     }
539 }