Merge pull request #93 from konrad-kruczynski/dispatcher_timer_fix
[mono.git] / mcs / class / System.XML / System.Xml / XmlAttributeCollection.cs
1 //
2 // System.Xml.XmlAttributeCollection
3 //
4 // Author:
5 //   Jason Diamond (jason@injektilo.org)
6 //   Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 //
8 // (C) 2002 Jason Diamond  http://injektilo.org/
9 // (C) 2002 Atsushi Enomoto
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.Collections;
35 using Mono.Xml;
36
37 namespace System.Xml
38 {
39 #if NET_2_0
40         public sealed class XmlAttributeCollection : XmlNamedNodeMap, ICollection
41 #else
42         public class XmlAttributeCollection : XmlNamedNodeMap, ICollection
43 #endif
44         {
45                 XmlElement ownerElement;
46                 XmlDocument ownerDocument;
47
48                 internal XmlAttributeCollection (XmlNode parent) : base (parent)
49                 {
50                         ownerElement = parent as XmlElement;
51                         ownerDocument = parent.OwnerDocument;
52                         if(ownerElement == null)
53                                 throw new XmlException ("invalid construction for XmlAttributeCollection.");
54                 }
55
56                 bool ICollection.IsSynchronized {
57                         get { return false; }
58                 }
59
60                 bool IsReadOnly {
61                         get { return ownerElement.IsReadOnly; }
62                 }
63
64                 [System.Runtime.CompilerServices.IndexerName ("ItemOf")]
65 #if NET_2_0
66                 public XmlAttribute this [string name] {
67 #else
68                 public virtual XmlAttribute this [string name] {
69 #endif
70                         get {
71                                 return (XmlAttribute) GetNamedItem (name);
72                         }
73                 }
74
75                 [System.Runtime.CompilerServices.IndexerName ("ItemOf")]
76 #if NET_2_0
77                 public XmlAttribute this [int i] {
78 #else
79                 public virtual XmlAttribute this [int i] {
80 #endif
81                         get {
82                                 return (XmlAttribute) Nodes [i];
83                         }
84                 }
85
86                 [System.Runtime.CompilerServices.IndexerName ("ItemOf")]
87 #if NET_2_0
88                 public XmlAttribute this [string localName, string namespaceURI] {
89 #else
90                 public virtual XmlAttribute this [string localName, string namespaceURI] {
91 #endif
92                         get {
93                                 return (XmlAttribute) GetNamedItem (localName, namespaceURI);
94                         }
95                 }
96
97                 object ICollection.SyncRoot {
98                         get { return this; }
99                 }
100
101 #if NET_2_0
102                 public XmlAttribute Append (XmlAttribute node) 
103 #else
104                 public virtual XmlAttribute Append (XmlAttribute node) 
105 #endif
106                 {
107                         SetNamedItem (node);
108                         return node;
109                 }
110
111                 public void CopyTo (XmlAttribute[] array, int index)
112                 {
113                         // assuming that Nodes is a correct collection.
114                         for(int i=0; i<Count; i++)
115                                 array [index + i] = Nodes [i] as XmlAttribute;
116                 }
117
118                 void ICollection.CopyTo (Array array, int index)
119                 {
120                         // assuming that Nodes is a correct collection.
121                         array.CopyTo (Nodes.ToArray (typeof(XmlAttribute)), index);
122                 }
123
124 #if NET_2_0
125                 public XmlAttribute InsertAfter (XmlAttribute newNode, XmlAttribute refNode)
126 #else
127                 public virtual XmlAttribute InsertAfter (XmlAttribute newNode, XmlAttribute refNode)
128 #endif
129                 {
130                         if (refNode == null) {
131                                 if (Count == 0)
132                                         return InsertBefore (newNode, null);
133                                 else
134                                         return InsertBefore (newNode, this [0]);
135                         }
136                         for (int i = 0; i < Count; i++)
137                                 if (refNode == Nodes [i])
138                                         return InsertBefore (newNode, Count == i + 1 ? null : this [i + 1]);
139
140                         throw new ArgumentException ("refNode not found in this collection.");
141                 }
142
143 #if NET_2_0
144                 public XmlAttribute InsertBefore (XmlAttribute newNode, XmlAttribute refNode)
145 #else
146                 public virtual XmlAttribute InsertBefore (XmlAttribute newNode, XmlAttribute refNode)
147 #endif
148                 {
149                         if (newNode.OwnerDocument != ownerDocument)
150                                 throw new ArgumentException ("different document created this newNode.");
151
152                         ownerDocument.onNodeInserting (newNode, null);
153
154                         int pos = Count;
155                         if (refNode != null) {
156                                 for (int i = 0; i < Count; i++) {
157                                         XmlNode n = Nodes [i] as XmlNode;
158                                         if (n == refNode) {
159                                                 pos = i;
160                                                 break;
161                                         }
162                                 }
163                                 if (pos == Count)
164                                         throw new ArgumentException ("refNode not found in this collection.");
165                         }
166                         SetNamedItem (newNode, pos, false);
167
168                         ownerDocument.onNodeInserted (newNode, null);
169
170                         return newNode;
171                 }
172
173 #if NET_2_0
174                 public XmlAttribute Prepend (XmlAttribute node) 
175 #else
176                 public virtual XmlAttribute Prepend (XmlAttribute node) 
177 #endif
178                 {
179                         return this.InsertAfter (node, null);
180                 }
181
182 #if NET_2_0
183                 public XmlAttribute Remove (XmlAttribute node) 
184 #else
185                 public virtual XmlAttribute Remove (XmlAttribute node) 
186 #endif
187                 {
188                         if (IsReadOnly)
189                                 throw new ArgumentException ("This attribute collection is read-only.");
190                         if (node == null)
191                                 throw new ArgumentException ("Specified node is null.");
192                         if (node.OwnerDocument != ownerDocument)
193                                 throw new ArgumentException ("Specified node is in a different document.");
194                         if (node.OwnerElement != this.ownerElement)
195                                 throw new ArgumentException ("The specified attribute is not contained in the element.");
196
197                         XmlAttribute retAttr = null;
198                         for (int i = 0; i < Count; i++) {
199                                 XmlAttribute attr = (XmlAttribute) Nodes [i];
200                                 if (attr == node) {
201                                         retAttr = attr;
202                                         break;
203                                 }
204                         }
205
206                         if(retAttr != null) {
207                                 ownerDocument.onNodeRemoving (node, ownerElement);
208                                 base.RemoveNamedItem (retAttr.LocalName, retAttr.NamespaceURI);
209                                 RemoveIdenticalAttribute (retAttr);
210                                 ownerDocument.onNodeRemoved (node, ownerElement);
211                         }
212                         // If it is default, then directly create new attribute.
213                         DTDAttributeDefinition def = retAttr.GetAttributeDefinition ();
214                         if (def != null && def.DefaultValue != null) {
215                                 XmlAttribute attr = ownerDocument.CreateAttribute (
216                                         retAttr.Prefix, retAttr.LocalName, retAttr.NamespaceURI, true, false);
217                                 attr.Value = def.DefaultValue;
218                                 attr.SetDefault ();
219                                 this.SetNamedItem (attr);
220                         }
221                         retAttr.AttributeOwnerElement = null;
222                         return retAttr;
223                 }
224
225 #if NET_2_0
226                 public void RemoveAll () 
227 #else
228                 public virtual void RemoveAll () 
229 #endif
230                 {
231                         int current = 0;
232                         while (current < Count) {
233                                 XmlAttribute attr = this [current];
234                                 if (!attr.Specified)
235                                         current++;
236                                 // It is called for the purpose of event support.
237                                 Remove (attr);
238                         }
239                 }
240
241 #if NET_2_0
242                 public XmlAttribute RemoveAt (int i) 
243 #else
244                 public virtual XmlAttribute RemoveAt (int i) 
245 #endif
246                 {
247                         if(Count <= i)
248                                 return null;
249                         return Remove ((XmlAttribute)Nodes [i]);
250                 }
251
252                 public override XmlNode SetNamedItem (XmlNode node)
253                 {
254                         if(IsReadOnly)
255                                 throw new ArgumentException ("this AttributeCollection is read only.");
256
257                         XmlAttribute attr = node as XmlAttribute;
258                         if (attr.OwnerElement == ownerElement)
259                                 return node; // do nothing
260                         if (attr.OwnerElement != null)
261                                 throw new ArgumentException ("This attribute is already set to another element.");
262
263                         ownerElement.OwnerDocument.onNodeInserting (node, ownerElement);
264
265                         attr.AttributeOwnerElement = ownerElement;
266                         XmlNode n = base.SetNamedItem (node, -1, false);
267                         AdjustIdenticalAttributes (node as XmlAttribute, n == node ? null : n);
268
269                         ownerElement.OwnerDocument.onNodeInserted (node, ownerElement);
270
271                         return n as XmlAttribute;
272                 }
273
274                 internal void AddIdenticalAttribute ()
275                 {
276                         SetIdenticalAttribute (false);
277                 }
278
279                 internal void RemoveIdenticalAttribute ()
280                 {
281                         SetIdenticalAttribute (true);
282                 }
283
284                 private void SetIdenticalAttribute (bool remove)
285                 {
286                         if (ownerElement == null)
287                                 return;
288
289                         // Check if new attribute's datatype is ID.
290                         XmlDocumentType doctype = ownerDocument.DocumentType;
291                         if (doctype == null || doctype.DTD == null)
292                                 return;
293                         DTDElementDeclaration elem = doctype.DTD.ElementDecls [ownerElement.Name];
294                         for (int i = 0; i < Count; i++) {
295                                 XmlAttribute node = (XmlAttribute) Nodes [i];
296                                 DTDAttributeDefinition attdef = elem == null ? null : elem.Attributes [node.Name];
297                                 if (attdef == null || attdef.Datatype.TokenizedType != XmlTokenizedType.ID)
298                                         continue;
299
300                                 if (remove) {
301                                         if (ownerDocument.GetIdenticalAttribute (node.Value) != null) {
302                                                 ownerDocument.RemoveIdenticalAttribute (node.Value);
303                                                 return;
304                                         }
305                                 } else {
306                                         // adding new identical attribute, but 
307                                         // MS.NET is pity for ID support, so I'm wondering how to correct it...
308                                         if (ownerDocument.GetIdenticalAttribute (node.Value) != null)
309                                                 throw new XmlException (String.Format (
310                                                         "ID value {0} already exists in this document.", node.Value));
311                                         ownerDocument.AddIdenticalAttribute (node);
312                                         return;
313                                 }
314                         }
315
316                 }
317
318                 private void AdjustIdenticalAttributes (XmlAttribute node, XmlNode existing)
319                 {
320                         // If owner element is not appended to the document,
321                         // ID table should not be filled.
322                         if (ownerElement == null)
323                                 return;
324
325                         if (existing != null)
326                                 RemoveIdenticalAttribute (existing);
327
328                         // Check if new attribute's datatype is ID.
329                         XmlDocumentType doctype = node.OwnerDocument.DocumentType;
330                         if (doctype == null || doctype.DTD == null)
331                                 return;
332                         DTDAttListDeclaration attList = doctype.DTD.AttListDecls [ownerElement.Name];
333                         DTDAttributeDefinition attdef = attList == null ? null : attList.Get (node.Name);
334                         if (attdef == null || attdef.Datatype.TokenizedType != XmlTokenizedType.ID)
335                                 return;
336
337                         ownerDocument.AddIdenticalAttribute (node);
338                 }
339
340                 private XmlNode RemoveIdenticalAttribute (XmlNode existing)
341                 {
342                         // If owner element is not appended to the document,
343                         // ID table should not be filled.
344                         if (ownerElement == null)
345                                 return existing;
346
347                         if (existing != null) {
348                                 // remove identical attribute (if it is).
349                                 if (ownerDocument.GetIdenticalAttribute (existing.Value) != null)
350                                         ownerDocument.RemoveIdenticalAttribute (existing.Value);
351                         }
352
353                         return existing;
354                 }
355         }
356 }