File: System\Xml\Dom\XmlAttributeCollection.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.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Xml
{
    // Represents a collection of attributes that can be accessed by name or index.
    public sealed class XmlAttributeCollection : XmlNamedNodeMap, ICollection
    {
        internal XmlAttributeCollection(XmlNode parent) : base(parent)
        {
        }
 
        // Gets the attribute with the specified index.
        [System.Runtime.CompilerServices.IndexerName("ItemOf")]
        public XmlAttribute this[int i]
        {
            get
            {
                try
                {
                    return (XmlAttribute)nodes[i];
                }
                catch (ArgumentOutOfRangeException)
                {
                    throw new IndexOutOfRangeException(SR.Xdom_IndexOutOfRange);
                }
            }
        }
 
        // Gets the attribute with the specified name.
        [System.Runtime.CompilerServices.IndexerName("ItemOf")]
        public XmlAttribute? this[string name]
        {
            get
            {
                int hash = XmlName.GetHashCode(name);
 
                for (int i = 0; i < nodes.Count; i++)
                {
                    XmlAttribute node = (XmlAttribute)nodes[i];
 
                    if (hash == node.LocalNameHash
                        && name == node.Name)
                    {
                        return node;
                    }
                }
 
                return null;
            }
        }
 
        // Gets the attribute with the specified LocalName and NamespaceUri.
        [System.Runtime.CompilerServices.IndexerName("ItemOf")]
        public XmlAttribute? this[string localName, string? namespaceURI]
        {
            get
            {
                int hash = XmlName.GetHashCode(localName);
 
                for (int i = 0; i < nodes.Count; i++)
                {
                    XmlAttribute node = (XmlAttribute)nodes[i];
 
                    if (hash == node.LocalNameHash
                        && localName == node.LocalName
                        && namespaceURI == node.NamespaceURI)
                    {
                        return node;
                    }
                }
 
                return null;
            }
        }
 
        internal int FindNodeOffset(XmlAttribute node)
        {
            for (int i = 0; i < nodes.Count; i++)
            {
                XmlAttribute tmp = (XmlAttribute)nodes[i];
 
                if (tmp.LocalNameHash == node.LocalNameHash
                    && tmp.Name == node.Name
                    && tmp.NamespaceURI == node.NamespaceURI)
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        internal int FindNodeOffsetNS(XmlAttribute node)
        {
            for (int i = 0; i < nodes.Count; i++)
            {
                XmlAttribute tmp = (XmlAttribute)nodes[i];
                if (tmp.LocalNameHash == node.LocalNameHash
                    && tmp.LocalName == node.LocalName
                    && tmp.NamespaceURI == node.NamespaceURI)
                {
                    return i;
                }
            }
            return -1;
        }
 
        // Adds a XmlNode using its Name property
        [return: NotNullIfNotNull(nameof(node))]
        public override XmlNode? SetNamedItem(XmlNode? node)
        {
            if (node == null)
                return null;
 
            if (!(node is XmlAttribute))
                throw new ArgumentException(SR.Xdom_AttrCol_Object);
 
            int offset = FindNodeOffset(node.LocalName, node.NamespaceURI);
            if (offset == -1)
            {
                return InternalAppendAttribute((XmlAttribute)node);
            }
            else
            {
                XmlNode oldNode = base.RemoveNodeAt(offset);
                InsertNodeAt(offset, node);
                return oldNode;
            }
        }
 
        // Inserts the specified node as the first node in the collection.
        public XmlAttribute Prepend(XmlAttribute node)
        {
            if (node.OwnerDocument != null && node.OwnerDocument != parent.OwnerDocument)
                throw new ArgumentException(SR.Xdom_NamedNode_Context);
 
            if (node.OwnerElement != null)
                Detach(node);
 
            RemoveDuplicateAttribute(node);
 
            InsertNodeAt(0, node);
            return node;
        }
 
        // Inserts the specified node as the last node in the collection.
        public XmlAttribute Append(XmlAttribute node)
        {
            XmlDocument doc = node.OwnerDocument;
            if (doc == null || doc.IsLoading == false)
            {
                if (doc != null && doc != parent.OwnerDocument)
                {
                    throw new ArgumentException(SR.Xdom_NamedNode_Context);
                }
                if (node.OwnerElement != null)
                {
                    Detach(node);
                }
                AddNode(node);
            }
            else
            {
                base.AddNodeForLoad(node, doc);
                InsertParentIntoElementIdAttrMap(node);
            }
            return node;
        }
 
        // Inserts the specified attribute immediately before the specified reference attribute.
        public XmlAttribute InsertBefore(XmlAttribute newNode, XmlAttribute? refNode)
        {
            if (newNode == refNode)
                return newNode;
 
            if (refNode == null)
                return Append(newNode);
 
            if (refNode.OwnerElement != parent)
                throw new ArgumentException(SR.Xdom_AttrCol_Insert);
 
            if (newNode.OwnerDocument != null && newNode.OwnerDocument != parent.OwnerDocument)
                throw new ArgumentException(SR.Xdom_NamedNode_Context);
 
            if (newNode.OwnerElement != null)
                Detach(newNode);
 
            int offset = FindNodeOffset(refNode.LocalName, refNode.NamespaceURI);
            Debug.Assert(offset != -1); // the if statement above guarantees that the ref node is in the collection
 
            int dupoff = RemoveDuplicateAttribute(newNode);
            if (dupoff >= 0 && dupoff < offset)
                offset--;
            InsertNodeAt(offset, newNode);
 
            return newNode;
        }
 
        // Inserts the specified attribute immediately after the specified reference attribute.
        public XmlAttribute InsertAfter(XmlAttribute newNode, XmlAttribute? refNode)
        {
            if (newNode == refNode)
                return newNode;
 
            if (refNode == null)
                return Prepend(newNode);
 
            if (refNode.OwnerElement != parent)
                throw new ArgumentException(SR.Xdom_AttrCol_Insert);
 
            if (newNode.OwnerDocument != null && newNode.OwnerDocument != parent.OwnerDocument)
                throw new ArgumentException(SR.Xdom_NamedNode_Context);
 
            if (newNode.OwnerElement != null)
                Detach(newNode);
 
            int offset = FindNodeOffset(refNode.LocalName, refNode.NamespaceURI);
            Debug.Assert(offset != -1); // the if statement above guarantees that the ref node is in the collection
 
            int dupoff = RemoveDuplicateAttribute(newNode);
            if (dupoff >= 0 && dupoff <= offset)
                offset--;
            InsertNodeAt(offset + 1, newNode);
 
            return newNode;
        }
 
        // Removes the specified attribute node from the map.
        public XmlAttribute? Remove(XmlAttribute? node)
        {
            int cNodes = nodes.Count;
            for (int offset = 0; offset < cNodes; offset++)
            {
                if (nodes[offset] == node)
                {
                    RemoveNodeAt(offset);
                    return node;
                }
            }
 
            return null;
        }
 
        // Removes the attribute node with the specified index from the map.
        public XmlAttribute? RemoveAt(int i)
        {
            if (i < 0 || i >= Count)
                return null;
 
            return (XmlAttribute)RemoveNodeAt(i);
        }
 
        // Removes all attributes from the map.
        public void RemoveAll()
        {
            int n = Count;
            while (n > 0)
            {
                n--;
                RemoveAt(n);
            }
        }
 
        void ICollection.CopyTo(Array array, int index)
        {
            for (int i = 0, max = Count; i < max; i++, index++)
            {
                array.SetValue(nodes[i], index);
            }
        }
 
        bool ICollection.IsSynchronized
        {
            get { return false; }
        }
 
        object ICollection.SyncRoot
        {
            get { return this; }
        }
 
        int ICollection.Count
        {
            get { return base.Count; }
        }
 
        public void CopyTo(XmlAttribute[] array, int index)
        {
            for (int i = 0, max = Count; i < max; i++, index++)
                array[index] = (XmlAttribute)(((XmlNode)nodes[i]).CloneNode(true));
        }
 
        internal override XmlNode AddNode(XmlNode node)
        {
            //should be sure by now that the node doesn't have the same name with an existing node in the collection
            RemoveDuplicateAttribute((XmlAttribute)node);
            XmlNode retNode = base.AddNode(node);
            Debug.Assert(retNode is XmlAttribute);
            InsertParentIntoElementIdAttrMap((XmlAttribute)node);
            return retNode;
        }
 
        internal override XmlNode InsertNodeAt(int i, XmlNode node)
        {
            XmlNode retNode = base.InsertNodeAt(i, node);
            InsertParentIntoElementIdAttrMap((XmlAttribute)node);
            return retNode;
        }
 
        internal override XmlNode RemoveNodeAt(int i)
        {
            //remove the node without checking replacement
            XmlNode retNode = base.RemoveNodeAt(i);
            Debug.Assert(retNode is XmlAttribute);
            RemoveParentFromElementIdAttrMap((XmlAttribute)retNode);
            // after remove the attribute, we need to check if a default attribute node should be created and inserted into the tree
            XmlAttribute? defattr = parent.OwnerDocument!.GetDefaultAttribute((XmlElement)parent, retNode.Prefix, retNode.LocalName, retNode.NamespaceURI);
            if (defattr != null)
                InsertNodeAt(i, defattr);
 
            return retNode;
        }
 
        internal static void Detach(XmlAttribute attr)
        {
            attr.OwnerElement!.Attributes.Remove(attr);
        }
 
        //insert the parent element node into the map
        internal void InsertParentIntoElementIdAttrMap(XmlAttribute attr)
        {
            XmlElement? parentElem = parent as XmlElement;
            if (parentElem != null)
            {
                if (parent.OwnerDocument == null)
                    return;
 
                XmlName? attrname = parent.OwnerDocument.GetIDInfoByElement(parentElem.XmlName);
                if (attrname != null && attrname.Prefix == attr.XmlName.Prefix && attrname.LocalName == attr.XmlName.LocalName)
                {
                    parent.OwnerDocument.AddElementWithId(attr.Value, parentElem); //add the element into the hashtable
                }
            }
        }
 
        //remove the parent element node from the map when the ID attribute is removed
        internal void RemoveParentFromElementIdAttrMap(XmlAttribute attr)
        {
            XmlElement? parentElem = parent as XmlElement;
            if (parentElem != null)
            {
                if (parent.OwnerDocument == null)
                    return;
 
                XmlName? attrname = parent.OwnerDocument.GetIDInfoByElement(parentElem.XmlName);
                if (attrname != null && attrname.Prefix == attr.XmlName.Prefix && attrname.LocalName == attr.XmlName.LocalName)
                {
                    parent.OwnerDocument.RemoveElementWithId(attr.Value, parentElem); //remove the element from the hashtable
                }
            }
        }
 
        //the function checks if there is already node with the same name existing in the collection
        // if so, remove it because the new one will be inserted to replace this one (could be in different position though )
        //  by the calling function later
        internal int RemoveDuplicateAttribute(XmlAttribute attr)
        {
            int ind = FindNodeOffset(attr.LocalName, attr.NamespaceURI);
            if (ind != -1)
            {
                XmlAttribute at = (XmlAttribute)nodes[ind];
                base.RemoveNodeAt(ind);
                RemoveParentFromElementIdAttrMap(at);
            }
            return ind;
        }
 
        internal bool PrepareParentInElementIdAttrMap(string attrPrefix, string attrLocalName)
        {
            XmlElement? parentElem = parent as XmlElement;
            Debug.Assert(parentElem != null);
            XmlDocument? doc = parent.OwnerDocument;
            Debug.Assert(doc != null);
            //The returned attrname if not null is the name with namespaceURI being set to string.Empty
            //Because DTD doesn't support namespaceURI so all comparisons are based on no namespaceURI (string.Empty);
            XmlName? attrname = doc.GetIDInfoByElement(parentElem.XmlName);
            if (attrname != null && attrname.Prefix == attrPrefix && attrname.LocalName == attrLocalName)
            {
                return true;
            }
 
            return false;
        }
 
        internal void ResetParentInElementIdAttrMap(string oldVal, string newVal)
        {
            XmlElement? parentElem = parent as XmlElement;
            Debug.Assert(parentElem != null);
            XmlDocument? doc = parent.OwnerDocument;
            Debug.Assert(doc != null);
            doc.RemoveElementWithId(oldVal, parentElem); //add the element into the hashtable
            doc.AddElementWithId(newVal, parentElem);
        }
 
        // WARNING:
        //  For performance reasons, this function does not check
        //  for xml attributes within the collection with the same full name.
        //  This means that any caller of this function must be sure that
        //  a duplicate attribute does not exist.
        internal XmlAttribute InternalAppendAttribute(XmlAttribute node)
        {
            // a duplicate node better not exist
            Debug.Assert(-1 == FindNodeOffset(node));
 
            XmlNode retNode = base.AddNode(node);
            Debug.Assert(retNode is XmlAttribute);
            InsertParentIntoElementIdAttrMap((XmlAttribute)node);
            return (XmlAttribute)retNode;
        }
    }
}