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