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