2004-01-07 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
14 namespace System.Xml
15 {
16         public class XmlNamespaceManager : IEnumerable
17         {
18                 #region Data
19                 struct NsDecl {
20                         public string Prefix, Uri;
21                 }
22                 
23                 struct NsScope {
24                         public int DeclCount;
25                         public string DefaultNamespace;
26                 }
27                 
28                 NsDecl [] decls;
29                 int declPos = -1;
30                 
31                 NsScope [] scopes;
32                 int scopePos = -1;
33                 
34                 string defaultNamespace;
35                 int count;
36                 
37                 void InitData ()
38                 {
39                         decls = new NsDecl [10];
40                         scopes = new NsScope [40];
41                 }
42                 
43                 // precondition declPos == nsDecl.Length
44                 void GrowDecls ()
45                 {
46                         NsDecl [] old = decls;
47                         decls = new NsDecl [declPos * 2 + 1];
48                         if (declPos > 0)
49                                 Array.Copy (old, 0, decls, 0, declPos);
50                 }
51                 
52                 // precondition scopePos == scopes.Length
53                 void GrowScopes ()
54                 {
55                         NsScope [] old = scopes;
56                         scopes = new NsScope [scopePos * 2 + 1];
57                         if (scopePos > 0)
58                                 Array.Copy (old, 0, scopes, 0, scopePos);
59                 }
60                 
61                 #endregion
62                 
63                 #region Fields
64
65                 private XmlNameTable nameTable;
66                 internal const string XmlnsXml = "http://www.w3.org/XML/1998/namespace";
67                 internal const string XmlnsXmlns = "http://www.w3.org/2000/xmlns/";
68                 internal const string PrefixXml = "xml";
69                 internal const string PrefixXmlns = "xmlns";
70
71                 #endregion
72
73                 #region Constructor
74
75                 internal XmlNamespaceManager () {}
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_1_2
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
183                 public virtual bool HasNamespace (string prefix)
184                 {
185                         if (prefix == null || count == 0)
186                                 return false;
187
188                         for (int i = declPos; i > declPos - count; i--) {
189                                 if (decls [i].Prefix == prefix)
190                                         return true;
191                         }
192                         
193                         return false;
194                 }
195
196                 public virtual string LookupNamespace (string prefix)
197                 {
198                         return LookupNamespace (prefix, false);
199                 }
200
201 #if NET_1_2
202                 public string LookupNamespace (string prefix, bool atomizedName)
203 #else
204                 internal string LookupNamespace (string prefix, bool atomizedName)
205 #endif
206                 {
207                         switch (prefix) {
208                         case PrefixXmlns:
209                                 return nameTable.Get (XmlnsXmlns);
210                         case PrefixXml:
211                                 return nameTable.Get (XmlnsXml);
212                         case "":
213                                 return DefaultNamespace;
214                         case null:
215                                 return null;
216                         }
217
218                         for (int i = declPos; i >= 0; i--) {
219                                 if (CompareString (decls [i].Prefix, prefix, atomizedName) && decls [i].Uri != null /* null == flag for removed */)
220                                         return decls [i].Uri;
221                         }
222                         
223                         return null;
224                 }
225
226                 public virtual string LookupPrefix (string uri)
227                 {
228                         return LookupPrefix (uri, false);
229                 }
230
231                 private bool CompareString (string s1, string s2, bool atomizedNames)
232                 {
233                         if (atomizedNames)
234                                 return object.ReferenceEquals (s1, s2);
235                         else
236                                 return s1 == s2;
237                 }
238
239 #if NET_1_2
240                 public string LookupPrefix (string uri, bool atomizedName)
241 #else
242                 internal string LookupPrefix (string uri, bool atomizedName)
243 #endif
244                 {
245                         if (uri == null)
246                                 return null;
247
248                         if (CompareString (uri, DefaultNamespace, atomizedName))
249                                 return string.Empty;
250
251                         if (CompareString (uri, XmlnsXml, atomizedName))
252                                 return PrefixXml;
253                         
254                         if (CompareString (uri, XmlnsXmlns, atomizedName))
255                                 return PrefixXmlns;
256
257                         for (int i = declPos; i >= 0; i--) {
258                                 if (CompareString (decls [i].Uri, uri, atomizedName) && decls [i].Prefix.Length > 0) // we already looked for ""
259                                         return decls [i].Prefix;
260                         }
261
262                         // ECMA specifies that this method returns String.Empty
263                         // in case of no match. But actually MS.NET returns null.
264                         // For more information,see
265                         //  http://lists.ximian.com/archives/public/mono-list/2003-January/005071.html
266                         //return String.Empty;
267                         return null;
268                 }
269
270                 public virtual bool PopScope ()
271                 {
272                         if (scopePos == -1)
273                                 return false;
274
275                         declPos -= count;
276                         defaultNamespace = scopes [scopePos].DefaultNamespace;
277                         count = scopes [scopePos].DeclCount;
278                         scopePos --;
279                         return true;
280                 }
281
282                 public virtual void PushScope ()
283                 {
284                         scopePos ++;
285                         if (scopePos == scopes.Length)
286                                 GrowScopes ();
287                         
288                         scopes [scopePos].DefaultNamespace = defaultNamespace;
289                         scopes [scopePos].DeclCount = count;
290                         count = 0;
291                 }
292
293                 // It is rarely used, so we don't need NameTable optimization on it.
294                 public virtual void RemoveNamespace (string prefix, string uri)
295                 {
296                         RemoveNamespace (prefix, uri, false);
297                 }
298
299 #if NET_1_2
300                 public virtual void RemoveNamespace (string prefix, string uri, bool atomizedNames)
301 #else
302                 internal virtual void RemoveNamespace (string prefix, string uri, bool atomizedNames)
303 #endif
304                 {
305                         if (prefix == null)
306                                 throw new ArgumentNullException ("prefix");
307
308                         if (uri == null)
309                                 throw new ArgumentNullException ("uri");
310                         
311                         if (count == 0)
312                                 return;
313
314                         for (int i = declPos; i > declPos - count; i--) {
315                                 if (CompareString (decls [i].Prefix, prefix, atomizedNames) && CompareString (decls [i].Uri, uri, atomizedNames))
316                                         decls [i].Uri = null;
317                         }
318                 }
319
320                 #endregion
321         }
322 }