2004-06-03 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml / XmlNamespaceManager.cs
1 //
2 // XmlNamespaceManager.cs
3 //
4 // Authors:
5 //   Jason Diamond (jason@injektilo.org)
6 //   Ben Maurer (bmaurer@users.sourceforge.net)
7 //
8 // (C) 2001 Jason Diamond  http://injektilo.org/
9 // (C) 2003 Ben Maurer
10 //
11
12 using System.Collections;
13 using System.Collections.Specialized;
14
15 namespace System.Xml
16 {
17         public class XmlNamespaceManager : IEnumerable
18         {
19                 #region Data
20                 struct NsDecl {
21                         public string Prefix, Uri;
22                 }
23                 
24                 struct NsScope {
25                         public int DeclCount;
26                         public string DefaultNamespace;
27                 }
28                 
29                 NsDecl [] decls;
30                 int declPos = -1;
31                 
32                 NsScope [] scopes;
33                 int scopePos = -1;
34                 
35                 string defaultNamespace;
36                 int count;
37                 
38                 void InitData ()
39                 {
40                         decls = new NsDecl [10];
41                         scopes = new NsScope [40];
42                 }
43                 
44                 // precondition declPos == nsDecl.Length
45                 void GrowDecls ()
46                 {
47                         NsDecl [] old = decls;
48                         decls = new NsDecl [declPos * 2 + 1];
49                         if (declPos > 0)
50                                 Array.Copy (old, 0, decls, 0, declPos);
51                 }
52                 
53                 // precondition scopePos == scopes.Length
54                 void GrowScopes ()
55                 {
56                         NsScope [] old = scopes;
57                         scopes = new NsScope [scopePos * 2 + 1];
58                         if (scopePos > 0)
59                                 Array.Copy (old, 0, scopes, 0, scopePos);
60                 }
61                 
62                 #endregion
63                 
64                 #region Fields
65
66                 private XmlNameTable nameTable;
67                 internal const string XmlnsXml = "http://www.w3.org/XML/1998/namespace";
68                 internal const string XmlnsXmlns = "http://www.w3.org/2000/xmlns/";
69                 internal const string PrefixXml = "xml";
70                 internal const string PrefixXmlns = "xmlns";
71
72                 #endregion
73
74                 #region Constructor
75
76                 public XmlNamespaceManager (XmlNameTable nameTable)
77                 {
78                         this.nameTable = nameTable;
79
80                         nameTable.Add (PrefixXmlns);
81                         nameTable.Add (PrefixXml);
82                         nameTable.Add (String.Empty);
83                         nameTable.Add (XmlnsXmlns);
84                         nameTable.Add (XmlnsXml);
85                         
86                         InitData ();
87                 }
88
89                 #endregion
90
91                 #region Properties
92
93                 public virtual string DefaultNamespace {
94                         get { return defaultNamespace == null ? string.Empty : defaultNamespace; }
95                 }
96
97                 public XmlNameTable NameTable {
98                         get { return nameTable; }
99                 }
100
101                 #endregion
102
103                 #region Methods
104
105                 public virtual void AddNamespace (string prefix, string uri)
106                 {
107                         AddNamespace (prefix, uri, false);
108                 }
109
110 #if NET_2_0
111                 public virtual void AddNamespace (string prefix, string uri, bool atomizedNames)
112 #else
113                 internal virtual void AddNamespace (string prefix, string uri, bool atomizedNames)
114 #endif
115                 {
116                         if (prefix == null)
117                                 throw new ArgumentNullException ("prefix", "Value cannot be null.");
118
119                         if (uri == null)
120                                 throw new ArgumentNullException ("uri", "Value cannot be null.");
121                         if (!atomizedNames) {
122                                 prefix = nameTable.Add (prefix);
123                                 uri = nameTable.Add (uri);
124                         }
125
126                         IsValidDeclaration (prefix, uri, true);
127
128                         if (prefix.Length == 0)
129                                 defaultNamespace = uri;
130                         
131                         for (int i = declPos; i > declPos - count; i--) {
132                                 if (object.ReferenceEquals (decls [i].Prefix, prefix)) {
133                                         decls [i].Uri = uri;
134                                         return;
135                                 }
136                         }
137                         
138                         declPos ++;
139                         count ++;
140                         
141                         if (declPos == decls.Length)
142                                 GrowDecls ();
143                         decls [declPos].Prefix = prefix;
144                         decls [declPos].Uri = uri;
145                 }
146
147                 internal static string IsValidDeclaration (string prefix, string uri, bool throwException)
148                 {
149                         string message = null;
150                         if (prefix == PrefixXml && uri != XmlnsXml)
151                                 message = String.Format ("Prefix \"xml\" is only allowed to the fixed uri \"{0}\"", XmlnsXml);
152                         else if (uri == XmlnsXml)
153                                 message = String.Format ("Namespace URI \"{0}\" can only be declared with the fixed prefix \"xml\"", XmlnsXml);
154                         if (message == null && prefix == "xmlns")
155                                 message = "Declaring prefix named \"xmlns\" is not allowed to any namespace.";
156                         if (message == null && uri == XmlnsXmlns)
157                                 message = String.Format ("Namespace URI \"{0}\" cannot be declared with any namespace.", XmlnsXmlns);
158                         if (message != null && throwException)
159                                 throw new ArgumentException (message);
160                         else
161                                 return message;
162                 }
163
164                 public virtual IEnumerator GetEnumerator ()
165                 {
166                         // In fact it returns such table's enumerator that contains all the namespaces.
167                         // while HasNamespace() ignores pushed namespaces.
168                         
169                         Hashtable ht = new Hashtable ();
170                         for (int i = 0; i <= declPos; i++) {
171                                 if (decls [i].Prefix != string.Empty && decls [i].Uri != null) {
172                                         ht [decls [i].Prefix] = decls [i].Uri;
173                                 }
174                         }
175                         
176                         ht [string.Empty] = DefaultNamespace;
177                         ht [PrefixXml] = XmlnsXml;
178                         ht [PrefixXmlns] = XmlnsXmlns;
179                         
180                         return ht.Keys.GetEnumerator ();
181                 }
182 #if NET_2_0
183                 [MonoTODO]
184                 public virtual StringDictionary GetNamespacesInScope (XmlNamespaceScope scope)
185                 {
186                         throw new NotImplementedException ();
187                 }
188 #endif
189
190                 public virtual bool HasNamespace (string prefix)
191                 {
192                         if (prefix == null || count == 0)
193                                 return false;
194
195                         for (int i = declPos; i > declPos - count; i--) {
196                                 if (decls [i].Prefix == prefix)
197                                         return true;
198                         }
199                         
200                         return false;
201                 }
202
203                 public virtual string LookupNamespace (string prefix)
204                 {
205                         return LookupNamespace (prefix, true);
206                 }
207
208 #if NET_2_0
209                 public virtual string LookupNamespace (string prefix, bool atomizedName)
210 #else
211                 internal virtual string LookupNamespace (string prefix, bool atomizedName)
212 #endif
213                 {
214                         switch (prefix) {
215                         case PrefixXmlns:
216                                 return nameTable.Get (XmlnsXmlns);
217                         case PrefixXml:
218                                 return nameTable.Get (XmlnsXml);
219                         case "":
220                                 return DefaultNamespace;
221                         case null:
222                                 return null;
223                         }
224
225                         for (int i = declPos; i >= 0; i--) {
226                                 if (CompareString (decls [i].Prefix, prefix, atomizedName) && decls [i].Uri != null /* null == flag for removed */)
227                                         return decls [i].Uri;
228                         }
229                         
230                         return null;
231                 }
232
233                 public virtual string LookupPrefix (string uri)
234                 {
235                         return LookupPrefix (uri, true);
236                 }
237
238                 private bool CompareString (string s1, string s2, bool atomizedNames)
239                 {
240                         if (atomizedNames)
241                                 return object.ReferenceEquals (s1, s2);
242                         else
243                                 return s1 == s2;
244                 }
245
246 #if NET_2_0
247                 public string LookupPrefix (string uri, bool atomizedName)
248 #else
249                 internal string LookupPrefix (string uri, bool atomizedName)
250 #endif
251                 {
252                         if (uri == null)
253                                 return null;
254
255                         if (CompareString (uri, DefaultNamespace, atomizedName))
256                                 return string.Empty;
257
258                         if (CompareString (uri, XmlnsXml, atomizedName))
259                                 return PrefixXml;
260                         
261                         if (CompareString (uri, XmlnsXmlns, atomizedName))
262                                 return PrefixXmlns;
263
264                         for (int i = declPos; i >= 0; i--) {
265                                 if (CompareString (decls [i].Uri, uri, atomizedName) && decls [i].Prefix.Length > 0) // we already looked for ""
266                                         return decls [i].Prefix;
267                         }
268
269                         // ECMA specifies that this method returns String.Empty
270                         // in case of no match. But actually MS.NET returns null.
271                         // For more information,see
272                         //  http://lists.ximian.com/archives/public/mono-list/2003-January/005071.html
273                         //return String.Empty;
274                         return null;
275                 }
276
277                 public virtual bool PopScope ()
278                 {
279                         if (scopePos == -1)
280                                 return false;
281
282                         declPos -= count;
283                         defaultNamespace = scopes [scopePos].DefaultNamespace;
284                         count = scopes [scopePos].DeclCount;
285                         scopePos --;
286                         return true;
287                 }
288
289                 public virtual void PushScope ()
290                 {
291                         scopePos ++;
292                         if (scopePos == scopes.Length)
293                                 GrowScopes ();
294                         
295                         scopes [scopePos].DefaultNamespace = defaultNamespace;
296                         scopes [scopePos].DeclCount = count;
297                         count = 0;
298                 }
299
300                 // It is rarely used, so we don't need NameTable optimization on it.
301                 public virtual void RemoveNamespace (string prefix, string uri)
302                 {
303                         RemoveNamespace (prefix, uri, false);
304                 }
305
306 #if NET_2_0
307                 public virtual void RemoveNamespace (string prefix, string uri, bool atomizedNames)
308 #else
309                 internal virtual void RemoveNamespace (string prefix, string uri, bool atomizedNames)
310 #endif
311                 {
312                         if (prefix == null)
313                                 throw new ArgumentNullException ("prefix");
314
315                         if (uri == null)
316                                 throw new ArgumentNullException ("uri");
317                         
318                         if (count == 0)
319                                 return;
320
321                         for (int i = declPos; i > declPos - count; i--) {
322                                 if (CompareString (decls [i].Prefix, prefix, atomizedNames) && CompareString (decls [i].Uri, uri, atomizedNames))
323                                         decls [i].Uri = null;
324                         }
325                 }
326
327                 #endregion
328         }
329 }