File: System\Xml\Dom\XmlElement.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Xml.Schema;
using System.Xml.XPath;
 
namespace System.Xml
{
    // Represents an element.
    public class XmlElement : XmlLinkedNode
    {
        private XmlName _name;
        private XmlAttributeCollection? _attributes;
        private XmlLinkedNode? _lastChild; // == this for empty elements otherwise it is the last child
 
        internal XmlElement(XmlName name, bool empty, XmlDocument doc) : base(doc)
        {
            Debug.Assert(name != null);
            this.parentNode = null;
            if (!doc.IsLoading)
            {
                XmlDocument.CheckName(name.Prefix);
                XmlDocument.CheckName(name.LocalName);
            }
            if (name.LocalName.Length == 0)
                throw new ArgumentException(SR.Xdom_Empty_LocalName);
 
            _name = name;
            if (empty)
            {
                _lastChild = this;
            }
        }
 
        protected internal XmlElement(string? prefix, string localName, string? namespaceURI, XmlDocument doc)
        : this(doc.AddXmlName(prefix, localName, namespaceURI, null), true, doc)
        {
        }
 
        internal XmlName XmlName
        {
            get { return _name; }
            set { _name = value; }
        }
 
        // Creates a duplicate of this node.
        public override XmlNode CloneNode(bool deep)
        {
            Debug.Assert(OwnerDocument != null);
            XmlDocument doc = OwnerDocument;
            bool OrigLoadingStatus = doc.IsLoading;
            doc.IsLoading = true;
            XmlElement element = doc.CreateElement(Prefix, LocalName, NamespaceURI);
            doc.IsLoading = OrigLoadingStatus;
            if (element.IsEmpty != this.IsEmpty)
                element.IsEmpty = this.IsEmpty;
 
            if (HasAttributes)
            {
                foreach (XmlAttribute attr in Attributes)
                {
                    XmlAttribute newAttr = (XmlAttribute)(attr.CloneNode(true));
                    XmlUnspecifiedAttribute? unspecAttr = newAttr as XmlUnspecifiedAttribute;
                    if (unspecAttr != null && attr.Specified == false)
                    {
                        unspecAttr.SetSpecified(false);
                    }
                    element.Attributes.InternalAppendAttribute(newAttr);
                }
            }
 
            if (deep)
                element.CopyChildren(doc, this, deep);
 
            return element;
        }
 
        // Gets the name of the node.
        public override string Name
        {
            get { return _name.Name; }
        }
 
        // Gets the name of the current node without the namespace prefix.
        public override string LocalName
        {
            get { return _name.LocalName; }
        }
 
        // Gets the namespace URI of this node.
        public override string NamespaceURI
        {
            get { return _name.NamespaceURI; }
        }
 
        // Gets or sets the namespace prefix of this node.
        [AllowNull]
        public override string Prefix
        {
            get { return _name.Prefix; }
            set { _name = _name.OwnerDocument.AddXmlName(value, LocalName, NamespaceURI, SchemaInfo); }
        }
 
        // Gets the type of the current node.
        public override XmlNodeType NodeType
        {
            get { return XmlNodeType.Element; }
        }
 
        public override XmlNode? ParentNode
        {
            get
            {
                return this.parentNode;
            }
        }
 
        // Gets the XmlDocument that contains this node.
        public override XmlDocument OwnerDocument
        {
            get
            {
                return _name.OwnerDocument;
            }
        }
 
        internal override bool IsContainer
        {
            get { return true; }
        }
 
        //the function is provided only at Load time to speed up Load process
        internal override XmlNode AppendChildForLoad(XmlNode newChild, XmlDocument doc)
        {
            XmlNodeChangedEventArgs? args = doc.GetInsertEventArgsForLoad(newChild, this);
 
            if (args != null)
                doc.BeforeEvent(args);
 
            XmlLinkedNode newNode = (XmlLinkedNode)newChild;
 
            if (_lastChild == null
                || _lastChild == this)
            { // if LastNode == null
                newNode.next = newNode;
                _lastChild = newNode; // LastNode = newNode;
                newNode.SetParentForLoad(this);
            }
            else
            {
                XmlLinkedNode refNode = _lastChild; // refNode = LastNode;
                newNode.next = refNode.next;
                refNode.next = newNode;
                _lastChild = newNode; // LastNode = newNode;
                if (refNode.IsText
                    && newNode.IsText)
                {
                    NestTextNodes(refNode, newNode);
                }
                else
                {
                    newNode.SetParentForLoad(this);
                }
            }
 
            if (args != null)
                doc.AfterEvent(args);
 
            return newNode;
        }
 
        // Gets or sets whether the element does not have any children.
        public bool IsEmpty
        {
            get
            {
                return _lastChild == this;
            }
 
            set
            {
                if (value)
                {
                    if (_lastChild != this)
                    {
                        RemoveAllChildren();
                        _lastChild = this;
                    }
                }
                else
                {
                    if (_lastChild == this)
                    {
                        _lastChild = null;
                    }
                }
            }
        }
 
        internal override XmlLinkedNode? LastNode
        {
            get
            {
                return _lastChild == this ? null : _lastChild;
            }
            set
            {
                _lastChild = value;
            }
        }
 
        internal override bool IsValidChildType(XmlNodeType type)
        {
            switch (type)
            {
                case XmlNodeType.Element:
                case XmlNodeType.Text:
                case XmlNodeType.EntityReference:
                case XmlNodeType.Comment:
                case XmlNodeType.Whitespace:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.ProcessingInstruction:
                case XmlNodeType.CDATA:
                    return true;
 
                default:
                    return false;
            }
        }
 
 
        // Gets a XmlAttributeCollection containing the list of attributes for this node.
        public override XmlAttributeCollection Attributes
        {
            get
            {
                if (_attributes == null)
                {
                    lock (OwnerDocument.objLock)
                    {
                        _attributes ??= new XmlAttributeCollection(this);
                    }
                }
 
                return _attributes;
            }
        }
 
        // Gets a value indicating whether the current node
        // has any attributes.
        public virtual bool HasAttributes
        {
            get
            {
                if (_attributes == null)
                    return false;
                else
                    return _attributes.Count > 0;
            }
        }
 
        // Returns the value for the attribute with the specified name.
        public virtual string GetAttribute(string name)
        {
            XmlAttribute? attr = GetAttributeNode(name);
            if (attr != null)
                return attr.Value;
 
            return string.Empty;
        }
 
        // Sets the value of the attribute
        // with the specified name.
        public virtual void SetAttribute(string name, string? value)
        {
            XmlAttribute? attr = GetAttributeNode(name);
            if (attr == null)
            {
                attr = OwnerDocument.CreateAttribute(name);
                attr.Value = value;
                Attributes.InternalAppendAttribute(attr);
            }
            else
            {
                attr.Value = value;
            }
        }
 
        // Removes an attribute by name.
        public virtual void RemoveAttribute(string name)
        {
            if (HasAttributes)
                Attributes.RemoveNamedItem(name);
        }
 
        // Returns the XmlAttribute with the specified name.
        public virtual XmlAttribute? GetAttributeNode(string name)
        {
            if (HasAttributes)
                return Attributes[name];
            return null;
        }
 
        // Adds the specified XmlAttribute.
        public virtual XmlAttribute? SetAttributeNode(XmlAttribute newAttr)
        {
            if (newAttr.OwnerElement != null)
                throw new InvalidOperationException(SR.Xdom_Attr_InUse);
 
            return (XmlAttribute)Attributes.SetNamedItem(newAttr);
        }
 
        // Removes the specified XmlAttribute.
        public virtual XmlAttribute? RemoveAttributeNode(XmlAttribute oldAttr)
        {
            if (HasAttributes)
                return (XmlAttribute?)Attributes.Remove(oldAttr);
            return null;
        }
 
        // Returns a XmlNodeList containing
        // a list of all descendant elements that match the specified name.
        public virtual XmlNodeList GetElementsByTagName(string name)
        {
            return new XmlElementList(this, name);
        }
 
        //
        // DOM Level 2
        //
 
        // Returns the value for the attribute with the specified LocalName and NamespaceURI.
        public virtual string GetAttribute(string localName, string? namespaceURI)
        {
            XmlAttribute? attr = GetAttributeNode(localName, namespaceURI);
            if (attr != null)
                return attr.Value;
 
            return string.Empty;
        }
 
        // Sets the value of the attribute with the specified name
        // and namespace.
        [return: NotNullIfNotNull(nameof(value))]
        public virtual string? SetAttribute(string localName, string? namespaceURI, string? value)
        {
            XmlAttribute? attr = GetAttributeNode(localName, namespaceURI);
            if (attr == null)
            {
                attr = OwnerDocument.CreateAttribute(string.Empty, localName, namespaceURI);
                attr.Value = value;
                Attributes.InternalAppendAttribute(attr);
            }
            else
            {
                attr.Value = value;
            }
 
            return value;
        }
 
        // Removes an attribute specified by LocalName and NamespaceURI.
        public virtual void RemoveAttribute(string localName, string? namespaceURI)
        {
            RemoveAttributeNode(localName, namespaceURI);
        }
 
        // Returns the XmlAttribute with the specified LocalName and NamespaceURI.
        public virtual XmlAttribute? GetAttributeNode(string localName, string? namespaceURI)
        {
            if (HasAttributes)
                return Attributes[localName, namespaceURI];
            return null;
        }
 
        // Adds the specified XmlAttribute.
        public virtual XmlAttribute SetAttributeNode(string localName, string? namespaceURI)
        {
            XmlAttribute? attr = GetAttributeNode(localName, namespaceURI);
            if (attr == null)
            {
                attr = OwnerDocument.CreateAttribute(string.Empty, localName, namespaceURI);
                Attributes.InternalAppendAttribute(attr);
            }
 
            return attr;
        }
 
        // Removes the XmlAttribute specified by LocalName and NamespaceURI.
        public virtual XmlAttribute? RemoveAttributeNode(string localName, string? namespaceURI)
        {
            if (HasAttributes)
            {
                XmlAttribute? attr = GetAttributeNode(localName, namespaceURI);
                Attributes.Remove(attr);
                return attr;
            }
 
            return null;
        }
 
        // Returns a XmlNodeList containing
        // a list of all descendant elements that match the specified name.
        public virtual XmlNodeList GetElementsByTagName(string localName, string namespaceURI)
        {
            return new XmlElementList(this, localName, namespaceURI);
        }
 
        // Determines whether the current node has the specified attribute.
        public virtual bool HasAttribute(string name)
        {
            return GetAttributeNode(name) != null;
        }
 
        // Determines whether the current node has the specified
        // attribute from the specified namespace.
        public virtual bool HasAttribute(string localName, string? namespaceURI)
        {
            return GetAttributeNode(localName, namespaceURI) != null;
        }
 
        // Saves the current node to the specified XmlWriter.
        public override void WriteTo(XmlWriter w)
        {
            if (GetType() == typeof(XmlElement))
            {
                // Use the non-recursive version (for XmlElement only)
                WriteElementTo(w, this);
            }
            else
            {
                // Use the (potentially) recursive version
                WriteStartElement(w);
 
                if (IsEmpty)
                {
                    w.WriteEndElement();
                }
                else
                {
                    WriteContentTo(w);
                    w.WriteFullEndElement();
                }
            }
        }
 
        // This method is copied from System.Xml.Linq.ElementWriter.WriteElement but adapted to DOM
        private static void WriteElementTo(XmlWriter writer, XmlElement el)
        {
            XmlElement? e = el;
            XmlNode root = e;
            XmlNode? n = e;
            while (true)
            {
                e = n as XmlElement;
                // Only use the inlined write logic for XmlElement, not for derived classes
                if (e != null && e.GetType() == typeof(XmlElement))
                {
                    // Write the element
                    e.WriteStartElement(writer);
                    // Write the element's content
                    if (e.IsEmpty)
                    {
                        // No content; use a short end element <a />
                        writer.WriteEndElement();
                    }
                    else if (e._lastChild == null)
                    {
                        // No actual content; use a full end element <a></a>
                        writer.WriteFullEndElement();
                    }
                    else
                    {
                        // There are child node(s); move to first child
                        n = e.FirstChild;
                        Debug.Assert(n != null);
                        continue;
                    }
                }
                else
                {
                    // Use virtual dispatch (might recurse)
                    n.WriteTo(writer);
                }
 
                // Go back to the parent after writing the last child
                while (n != root && n == n.ParentNode!.LastChild)
                {
                    n = n.ParentNode;
                    Debug.Assert(n != null);
                    writer.WriteFullEndElement();
                }
 
                if (n == root)
                    break;
 
                n = n.NextSibling;
                Debug.Assert(n != null);
            }
        }
 
        // Writes the start of the element (and its attributes) to the specified writer
        private void WriteStartElement(XmlWriter w)
        {
            w.WriteStartElement(Prefix, LocalName, NamespaceURI);
 
            if (HasAttributes)
            {
                XmlAttributeCollection attrs = Attributes;
                for (int i = 0; i < attrs.Count; i += 1)
                {
                    XmlAttribute attr = attrs[i];
                    attr.WriteTo(w);
                }
            }
        }
 
        // Saves all the children of the node to the specified XmlWriter.
        public override void WriteContentTo(XmlWriter w)
        {
            for (XmlNode? node = FirstChild; node != null; node = node.NextSibling)
            {
                node.WriteTo(w);
            }
        }
 
        // Removes the attribute node with the specified index from the attribute collection.
        public virtual XmlNode? RemoveAttributeAt(int i)
        {
            if (HasAttributes)
                return _attributes!.RemoveAt(i);
 
            return null;
        }
 
        // Removes all attributes from the element.
        public virtual void RemoveAllAttributes()
        {
            if (HasAttributes)
            {
                _attributes!.RemoveAll();
            }
        }
 
        // Removes all the children and/or attributes
        // of the current node.
        public override void RemoveAll()
        {
            //remove all the children
            base.RemoveAll();
            //remove all the attributes
            RemoveAllAttributes();
        }
 
        internal void RemoveAllChildren()
        {
            base.RemoveAll();
        }
 
        public override IXmlSchemaInfo SchemaInfo
        {
            get
            {
                return _name;
            }
        }
 
        // Gets or sets the markup representing just
        // the children of this node.
        public override string InnerXml
        {
            get
            {
                return base.InnerXml;
            }
            set
            {
                RemoveAllChildren();
                XmlLoader loader = new XmlLoader();
                loader.LoadInnerXmlElement(this, value);
            }
        }
 
        // Gets or sets the concatenated values of the
        // node and all its children.
        public override string InnerText
        {
            get
            {
                return base.InnerText;
            }
            set
            {
                XmlLinkedNode? linkedNode = LastNode;
                if (linkedNode != null && //there is one child
                    linkedNode.NodeType == XmlNodeType.Text && //which is text node
                    linkedNode.next == linkedNode) // and it is the only child
                {
                    //this branch is for perf reason, event fired when TextNode.Value is changed.
                    linkedNode.Value = value;
                }
                else
                {
                    RemoveAllChildren();
                    AppendChild(OwnerDocument.CreateTextNode(value));
                }
            }
        }
 
        public override XmlNode? NextSibling
        {
            get
            {
                if (this.parentNode != null
                    && this.parentNode.LastNode != this)
                    return next;
 
                return null;
            }
        }
 
        internal override void SetParent(XmlNode? node)
        {
            this.parentNode = node;
        }
 
        internal override XPathNodeType XPNodeType { get { return XPathNodeType.Element; } }
 
        internal override string XPLocalName { get { return LocalName; } }
 
        internal override string GetXPAttribute(string localName, string ns)
        {
            if (ns == OwnerDocument.strReservedXmlns)
                return string.Empty;
 
            XmlAttribute? attr = GetAttributeNode(localName, ns);
            if (attr != null)
                return attr.Value;
 
            return string.Empty;
        }
    }
}