1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections;
6 using System.Diagnostics;
7 
8 namespace System.Xml
9 {
10     // Represents a collection of attributes that can be accessed by name or index.
11     public sealed class XmlAttributeCollection : XmlNamedNodeMap, ICollection
12     {
XmlAttributeCollection(XmlNode parent)13         internal XmlAttributeCollection(XmlNode parent) : base(parent)
14         {
15         }
16 
17         // Gets the attribute with the specified index.
18         [System.Runtime.CompilerServices.IndexerName("ItemOf")]
19         public XmlAttribute this[int i]
20         {
21             get
22             {
23                 try
24                 {
25                     return (XmlAttribute)nodes[i];
26                 }
27                 catch (ArgumentOutOfRangeException)
28                 {
29                     throw new IndexOutOfRangeException(SR.Xdom_IndexOutOfRange);
30                 }
31             }
32         }
33 
34         // Gets the attribute with the specified name.
35         [System.Runtime.CompilerServices.IndexerName("ItemOf")]
36         public XmlAttribute this[string name]
37         {
38             get
39             {
40                 int hash = XmlName.GetHashCode(name);
41 
42                 for (int i = 0; i < nodes.Count; i++)
43                 {
44                     XmlAttribute node = (XmlAttribute)nodes[i];
45 
46                     if (hash == node.LocalNameHash
47                         && name == node.Name)
48                     {
49                         return node;
50                     }
51                 }
52 
53                 return null;
54             }
55         }
56 
57         // Gets the attribute with the specified LocalName and NamespaceUri.
58         [System.Runtime.CompilerServices.IndexerName("ItemOf")]
59         public XmlAttribute this[string localName, string namespaceURI]
60         {
61             get
62             {
63                 int hash = XmlName.GetHashCode(localName);
64 
65                 for (int i = 0; i < nodes.Count; i++)
66                 {
67                     XmlAttribute node = (XmlAttribute)nodes[i];
68 
69                     if (hash == node.LocalNameHash
70                         && localName == node.LocalName
71                         && namespaceURI == node.NamespaceURI)
72                     {
73                         return node;
74                     }
75                 }
76 
77                 return null;
78             }
79         }
80 
FindNodeOffset(XmlAttribute node)81         internal int FindNodeOffset(XmlAttribute node)
82         {
83             for (int i = 0; i < nodes.Count; i++)
84             {
85                 XmlAttribute tmp = (XmlAttribute)nodes[i];
86 
87                 if (tmp.LocalNameHash == node.LocalNameHash
88                     && tmp.Name == node.Name
89                     && tmp.NamespaceURI == node.NamespaceURI)
90                 {
91                     return i;
92                 }
93             }
94             return -1;
95         }
96 
FindNodeOffsetNS(XmlAttribute node)97         internal int FindNodeOffsetNS(XmlAttribute node)
98         {
99             for (int i = 0; i < nodes.Count; i++)
100             {
101                 XmlAttribute tmp = (XmlAttribute)nodes[i];
102                 if (tmp.LocalNameHash == node.LocalNameHash
103                     && tmp.LocalName == node.LocalName
104                     && tmp.NamespaceURI == node.NamespaceURI)
105                 {
106                     return i;
107                 }
108             }
109             return -1;
110         }
111 
112         // Adds a XmlNode using its Name property
SetNamedItem(XmlNode node)113         public override XmlNode SetNamedItem(XmlNode node)
114         {
115             if (node == null)
116                 return null;
117 
118             if (!(node is XmlAttribute))
119                 throw new ArgumentException(SR.Xdom_AttrCol_Object);
120 
121             int offset = FindNodeOffset(node.LocalName, node.NamespaceURI);
122             if (offset == -1)
123             {
124                 return InternalAppendAttribute((XmlAttribute)node);
125             }
126             else
127             {
128                 XmlNode oldNode = base.RemoveNodeAt(offset);
129                 InsertNodeAt(offset, node);
130                 return oldNode;
131             }
132         }
133 
134         // Inserts the specified node as the first node in the collection.
Prepend(XmlAttribute node)135         public XmlAttribute Prepend(XmlAttribute node)
136         {
137             if (node.OwnerDocument != null && node.OwnerDocument != parent.OwnerDocument)
138                 throw new ArgumentException(SR.Xdom_NamedNode_Context);
139 
140             if (node.OwnerElement != null)
141                 Detach(node);
142 
143             RemoveDuplicateAttribute(node);
144 
145             InsertNodeAt(0, node);
146             return node;
147         }
148 
149         // Inserts the specified node as the last node in the collection.
Append(XmlAttribute node)150         public XmlAttribute Append(XmlAttribute node)
151         {
152             XmlDocument doc = node.OwnerDocument;
153             if (doc == null || doc.IsLoading == false)
154             {
155                 if (doc != null && doc != parent.OwnerDocument)
156                 {
157                     throw new ArgumentException(SR.Xdom_NamedNode_Context);
158                 }
159                 if (node.OwnerElement != null)
160                 {
161                     Detach(node);
162                 }
163                 AddNode(node);
164             }
165             else
166             {
167                 base.AddNodeForLoad(node, doc);
168                 InsertParentIntoElementIdAttrMap(node);
169             }
170             return node;
171         }
172 
173         // Inserts the specified attribute immediately before the specified reference attribute.
InsertBefore(XmlAttribute newNode, XmlAttribute refNode)174         public XmlAttribute InsertBefore(XmlAttribute newNode, XmlAttribute refNode)
175         {
176             if (newNode == refNode)
177                 return newNode;
178 
179             if (refNode == null)
180                 return Append(newNode);
181 
182             if (refNode.OwnerElement != parent)
183                 throw new ArgumentException(SR.Xdom_AttrCol_Insert);
184 
185             if (newNode.OwnerDocument != null && newNode.OwnerDocument != parent.OwnerDocument)
186                 throw new ArgumentException(SR.Xdom_NamedNode_Context);
187 
188             if (newNode.OwnerElement != null)
189                 Detach(newNode);
190 
191             int offset = FindNodeOffset(refNode.LocalName, refNode.NamespaceURI);
192             Debug.Assert(offset != -1); // the if statement above guarantees that the ref node is in the collection
193 
194             int dupoff = RemoveDuplicateAttribute(newNode);
195             if (dupoff >= 0 && dupoff < offset)
196                 offset--;
197             InsertNodeAt(offset, newNode);
198 
199             return newNode;
200         }
201 
202         // Inserts the specified attribute immediately after the specified reference attribute.
InsertAfter(XmlAttribute newNode, XmlAttribute refNode)203         public XmlAttribute InsertAfter(XmlAttribute newNode, XmlAttribute refNode)
204         {
205             if (newNode == refNode)
206                 return newNode;
207 
208             if (refNode == null)
209                 return Prepend(newNode);
210 
211             if (refNode.OwnerElement != parent)
212                 throw new ArgumentException(SR.Xdom_AttrCol_Insert);
213 
214             if (newNode.OwnerDocument != null && newNode.OwnerDocument != parent.OwnerDocument)
215                 throw new ArgumentException(SR.Xdom_NamedNode_Context);
216 
217             if (newNode.OwnerElement != null)
218                 Detach(newNode);
219 
220             int offset = FindNodeOffset(refNode.LocalName, refNode.NamespaceURI);
221             Debug.Assert(offset != -1); // the if statement above guarantees that the ref node is in the collection
222 
223             int dupoff = RemoveDuplicateAttribute(newNode);
224             if (dupoff >= 0 && dupoff <= offset)
225                 offset--;
226             InsertNodeAt(offset + 1, newNode);
227 
228             return newNode;
229         }
230 
231         // Removes the specified attribute node from the map.
Remove(XmlAttribute node)232         public XmlAttribute Remove(XmlAttribute node)
233         {
234             int cNodes = nodes.Count;
235             for (int offset = 0; offset < cNodes; offset++)
236             {
237                 if (nodes[offset] == node)
238                 {
239                     RemoveNodeAt(offset);
240                     return node;
241                 }
242             }
243             return null;
244         }
245 
246         // Removes the attribute node with the specified index from the map.
RemoveAt(int i)247         public XmlAttribute RemoveAt(int i)
248         {
249             if (i < 0 || i >= Count)
250                 return null;
251 
252             return (XmlAttribute)RemoveNodeAt(i);
253         }
254 
255         // Removes all attributes from the map.
RemoveAll()256         public void RemoveAll()
257         {
258             int n = Count;
259             while (n > 0)
260             {
261                 n--;
262                 RemoveAt(n);
263             }
264         }
265 
ICollection.CopyTo(Array array, int index)266         void ICollection.CopyTo(Array array, int index)
267         {
268             for (int i = 0, max = Count; i < max; i++, index++)
269             {
270                 array.SetValue(nodes[i], index);
271             }
272         }
273 
274         bool ICollection.IsSynchronized
275         {
276             get { return false; }
277         }
278 
279         object ICollection.SyncRoot
280         {
281             get { return this; }
282         }
283 
284         int ICollection.Count
285         {
286             get { return base.Count; }
287         }
288 
CopyTo(XmlAttribute[] array, int index)289         public void CopyTo(XmlAttribute[] array, int index)
290         {
291             for (int i = 0, max = Count; i < max; i++, index++)
292                 array[index] = (XmlAttribute)(((XmlNode)nodes[i]).CloneNode(true));
293         }
294 
AddNode(XmlNode node)295         internal override XmlNode AddNode(XmlNode node)
296         {
297             //should be sure by now that the node doesn't have the same name with an existing node in the collection
298             RemoveDuplicateAttribute((XmlAttribute)node);
299             XmlNode retNode = base.AddNode(node);
300             Debug.Assert(retNode is XmlAttribute);
301             InsertParentIntoElementIdAttrMap((XmlAttribute)node);
302             return retNode;
303         }
304 
InsertNodeAt(int i, XmlNode node)305         internal override XmlNode InsertNodeAt(int i, XmlNode node)
306         {
307             XmlNode retNode = base.InsertNodeAt(i, node);
308             InsertParentIntoElementIdAttrMap((XmlAttribute)node);
309             return retNode;
310         }
311 
RemoveNodeAt(int i)312         internal override XmlNode RemoveNodeAt(int i)
313         {
314             //remove the node without checking replacement
315             XmlNode retNode = base.RemoveNodeAt(i);
316             Debug.Assert(retNode is XmlAttribute);
317             RemoveParentFromElementIdAttrMap((XmlAttribute)retNode);
318             // after remove the attribute, we need to check if a default attribute node should be created and inserted into the tree
319             XmlAttribute defattr = parent.OwnerDocument.GetDefaultAttribute((XmlElement)parent, retNode.Prefix, retNode.LocalName, retNode.NamespaceURI);
320             if (defattr != null)
321                 InsertNodeAt(i, defattr);
322             return retNode;
323         }
324 
Detach(XmlAttribute attr)325         internal void Detach(XmlAttribute attr)
326         {
327             attr.OwnerElement.Attributes.Remove(attr);
328         }
329 
330         //insert the parent element node into the map
InsertParentIntoElementIdAttrMap(XmlAttribute attr)331         internal void InsertParentIntoElementIdAttrMap(XmlAttribute attr)
332         {
333             XmlElement parentElem = parent as XmlElement;
334             if (parentElem != null)
335             {
336                 if (parent.OwnerDocument == null)
337                     return;
338                 XmlName attrname = parent.OwnerDocument.GetIDInfoByElement(parentElem.XmlName);
339                 if (attrname != null && attrname.Prefix == attr.XmlName.Prefix && attrname.LocalName == attr.XmlName.LocalName)
340                 {
341                     parent.OwnerDocument.AddElementWithId(attr.Value, parentElem); //add the element into the hashtable
342                 }
343             }
344         }
345 
346         //remove the parent element node from the map when the ID attribute is removed
RemoveParentFromElementIdAttrMap(XmlAttribute attr)347         internal void RemoveParentFromElementIdAttrMap(XmlAttribute attr)
348         {
349             XmlElement parentElem = parent as XmlElement;
350             if (parentElem != null)
351             {
352                 if (parent.OwnerDocument == null)
353                     return;
354                 XmlName attrname = parent.OwnerDocument.GetIDInfoByElement(parentElem.XmlName);
355                 if (attrname != null && attrname.Prefix == attr.XmlName.Prefix && attrname.LocalName == attr.XmlName.LocalName)
356                 {
357                     parent.OwnerDocument.RemoveElementWithId(attr.Value, parentElem); //remove the element from the hashtable
358                 }
359             }
360         }
361 
362         //the function checks if there is already node with the same name existing in the collection
363         // if so, remove it because the new one will be inserted to replace this one (could be in different position though )
364         //  by the calling function later
RemoveDuplicateAttribute(XmlAttribute attr)365         internal int RemoveDuplicateAttribute(XmlAttribute attr)
366         {
367             int ind = FindNodeOffset(attr.LocalName, attr.NamespaceURI);
368             if (ind != -1)
369             {
370                 XmlAttribute at = (XmlAttribute)nodes[ind];
371                 base.RemoveNodeAt(ind);
372                 RemoveParentFromElementIdAttrMap(at);
373             }
374             return ind;
375         }
376 
PrepareParentInElementIdAttrMap(string attrPrefix, string attrLocalName)377         internal bool PrepareParentInElementIdAttrMap(string attrPrefix, string attrLocalName)
378         {
379             XmlElement parentElem = parent as XmlElement;
380             Debug.Assert(parentElem != null);
381             XmlDocument doc = parent.OwnerDocument;
382             Debug.Assert(doc != null);
383             //The returned attrname if not null is the name with namespaceURI being set to string.Empty
384             //Because DTD doesn't support namespaceURI so all comparisons are based on no namespaceURI (string.Empty);
385             XmlName attrname = doc.GetIDInfoByElement(parentElem.XmlName);
386             if (attrname != null && attrname.Prefix == attrPrefix && attrname.LocalName == attrLocalName)
387             {
388                 return true;
389             }
390             return false;
391         }
392 
ResetParentInElementIdAttrMap(string oldVal, string newVal)393         internal void ResetParentInElementIdAttrMap(string oldVal, string newVal)
394         {
395             XmlElement parentElem = parent as XmlElement;
396             Debug.Assert(parentElem != null);
397             XmlDocument doc = parent.OwnerDocument;
398             Debug.Assert(doc != null);
399             doc.RemoveElementWithId(oldVal, parentElem); //add the element into the hashtable
400             doc.AddElementWithId(newVal, parentElem);
401         }
402 
403         // WARNING:
404         //  For performance reasons, this function does not check
405         //  for xml attributes within the collection with the same full name.
406         //  This means that any caller of this function must be sure that
407         //  a duplicate attribute does not exist.
InternalAppendAttribute(XmlAttribute node)408         internal XmlAttribute InternalAppendAttribute(XmlAttribute node)
409         {
410             // a duplicate node better not exist
411             Debug.Assert(-1 == FindNodeOffset(node));
412 
413             XmlNode retNode = base.AddNode(node);
414             Debug.Assert(retNode is XmlAttribute);
415             InsertParentIntoElementIdAttrMap((XmlAttribute)node);
416             return (XmlAttribute)retNode;
417         }
418     }
419 }
420