2003-10-25 Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
[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
69                 #endregion
70
71                 #region Constructor
72
73                 internal XmlNamespaceManager () {}
74                 public XmlNamespaceManager (XmlNameTable nameTable)
75                 {
76                         this.nameTable = nameTable;
77
78                         nameTable.Add ("xmlns");
79                         nameTable.Add ("xml");
80                         nameTable.Add (String.Empty);
81                         nameTable.Add (XmlnsXmlns);
82                         nameTable.Add (XmlnsXml);
83                         
84                         InitData ();
85                 }
86
87                 #endregion
88
89                 #region Properties
90
91                 public virtual string DefaultNamespace {
92                         get { return defaultNamespace == null ? string.Empty : defaultNamespace; }
93                 }
94
95                 public XmlNameTable NameTable {
96                         get { return nameTable; }
97                 }
98
99                 #endregion
100
101                 #region Methods
102
103                 public virtual void AddNamespace (string prefix, string uri)
104                 {
105                         if (prefix == null)
106                                 throw new ArgumentNullException ("prefix", "Value cannot be null.");
107
108                         if (uri == null)
109                                 throw new ArgumentNullException ("uri", "Value cannot be null.");
110                         
111                         prefix = nameTable.Add (prefix);
112                         uri = nameTable.Add (uri);
113
114                         IsValidDeclaration (prefix, uri, true);
115
116                         if (prefix == string.Empty)
117                                 defaultNamespace = uri;
118                         
119                         for (int i = declPos; i > declPos - count; i--) {
120                                 if (decls [i].Prefix == prefix) {
121                                         decls [i].Uri = uri;
122                                         return;
123                                 }
124                         }
125                         
126                         declPos ++;
127                         count ++;
128                         
129                         if (declPos == decls.Length)
130                                 GrowDecls ();
131                         decls [declPos].Prefix = prefix;
132                         decls [declPos].Uri = uri;
133                 }
134
135                 internal static string IsValidDeclaration (string prefix, string uri, bool throwException)
136                 {
137                         string message = null;
138                         if (prefix == "xml" && uri != XmlnsXml)
139                                 message = String.Format ("Prefix \"xml\" is only allowed to the fixed uri \"{0}\"", XmlnsXml);
140                         else if (uri == XmlnsXml)
141                                 message = String.Format ("Namespace URI \"{0}\" can only be declared with the fixed prefix \"xml\"", XmlnsXml);
142                         if (message == null && prefix == "xmlns")
143                                 message = "Declaring prefix named \"xmlns\" is not allowed to any namespace.";
144                         if (message == null && uri == XmlnsXmlns)
145                                 message = String.Format ("Namespace URI \"{0}\" cannot be declared with any namespace.", XmlnsXmlns);
146                         if (message != null && throwException)
147                                 throw new ArgumentException (message);
148                         else
149                                 return message;
150                 }
151
152                 public virtual IEnumerator GetEnumerator ()
153                 {
154                         // In fact it returns such table's enumerator that contains all the namespaces.
155                         // while HasNamespace() ignores pushed namespaces.
156                         
157                         Hashtable ht = new Hashtable ();
158                         for (int i = 0; i <= declPos; i++) {
159                                 if (decls [i].Prefix != string.Empty && decls [i].Uri != null) {
160                                         ht [decls [i].Prefix] = decls [i].Uri;
161                                 }
162                         }
163                         
164                         ht [string.Empty] = DefaultNamespace;
165                         ht ["xml"] = XmlnsXml;
166                         ht ["xmlns"] = XmlnsXmlns;
167                         
168                         return ht.Keys.GetEnumerator ();
169                 }
170
171                 public virtual bool HasNamespace (string prefix)
172                 {
173                         if (prefix == null || count == 0)
174                                 return false;
175
176                         for (int i = declPos; i > declPos - count; i--) {
177                                 if (decls [i].Prefix == prefix)
178                                         return true;
179                         }
180                         
181                         return false;
182                 }
183
184                 public virtual string LookupNamespace (string prefix)
185                 {
186                         switch (prefix) {
187                         case "xmlns":
188                                 return nameTable.Get (XmlnsXmlns);
189                         case "xml":
190                                 return nameTable.Get (XmlnsXml);
191                         case "":
192                                 return DefaultNamespace;
193                         }
194                         
195                         for (int i = declPos; i >= 0; i--) {
196                                 if (decls [i].Prefix == prefix && decls [i].Uri != null /* null == flag for removed */)
197                                         return decls [i].Uri;
198                         }
199                         
200                         return null;
201                 }
202
203                 public virtual string LookupPrefix (string uri)
204                 {
205                         if (uri == null)
206                                 return null;
207                         
208                         if (uri == DefaultNamespace)
209                                 return string.Empty;
210                                 
211                         if (uri == XmlnsXml)
212                                 return nameTable.Add ("xml");
213                         
214                         if (uri == XmlnsXmlns)
215                                 return nameTable.Add ("xmlns");
216                         
217                         
218                         for (int i = declPos; i >= 0; i--) {
219                                 if (decls [i].Uri == uri && decls [i].Prefix != string.Empty) // we already looked for ""
220                                         return decls [i].Prefix;
221                         }
222
223                         // ECMA specifies that this method returns String.Empty
224                         // in case of no match. But actually MS.NET returns null.
225                         // For more information,see
226                         //  http://lists.ximian.com/archives/public/mono-list/2003-January/005071.html
227                         //return String.Empty;
228                         return null;
229                 }
230
231                 public virtual bool PopScope ()
232                 {
233                         if (scopePos == -1)
234                                 return false;
235
236                         declPos -= count;
237                         defaultNamespace = scopes [scopePos].DefaultNamespace;
238                         count = scopes [scopePos].DeclCount;
239                         scopePos --;
240                         return true;
241                 }
242
243                 public virtual void PushScope ()
244                 {
245                         scopePos ++;
246                         if (scopePos == scopes.Length)
247                                 GrowScopes ();
248                         
249                         scopes [scopePos].DefaultNamespace = defaultNamespace;
250                         scopes [scopePos].DeclCount = count;
251                         count = 0;
252                 }
253
254                 public virtual void RemoveNamespace (string prefix, string uri)
255                 {
256                         if (prefix == null)
257                                 throw new ArgumentNullException ("prefix");
258
259                         if (uri == null)
260                                 throw new ArgumentNullException ("uri");
261                         
262                         if (count == 0)
263                                 return;         
264
265                         for (int i = declPos; i > declPos - count; i--) {
266                                 if (decls [i].Prefix == prefix && decls [i].Uri == uri)
267                                         decls [i].Uri = null;
268                         }
269                 }
270
271                 #endregion
272         }
273 }