Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
16 | PointedEar | 1 | /* |
2 | * The contents of this file are subject to the terms |
||
3 | * of the Common Development and Distribution License |
||
4 | * (the "License"). You may not use this file except |
||
5 | * in compliance with the License. |
||
6 | * |
||
7 | * You can obtain a copy of the license at |
||
8 | * https://jaxp.dev.java.net/CDDLv1.0.html. |
||
9 | * See the License for the specific language governing |
||
10 | * permissions and limitations under the License. |
||
11 | * |
||
12 | * When distributing Covered Code, include this CDDL |
||
13 | * HEADER in each file and include the License file at |
||
14 | * https://jaxp.dev.java.net/CDDLv1.0.html |
||
15 | * If applicable add the following below this CDDL HEADER |
||
16 | * with the fields enclosed by brackets "[]" replaced with |
||
17 | * your own identifying information: Portions Copyright |
||
18 | * [year] [name of copyright owner] |
||
19 | */ |
||
20 | |||
21 | /* |
||
22 | * $Id: NamespaceContextHelper.java,v 1.2 2006-03-28 20:54:02 ndw Exp $ |
||
23 | * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved. |
||
24 | * Portions Copyright 2010 Thomas 'PointedEars' Lahn |
||
25 | */ |
||
26 | |||
27 | package com.nwalsh.namespace; |
||
28 | |||
29 | import java.util.Enumeration; |
||
30 | import java.util.Hashtable; |
||
31 | import java.util.Iterator; |
||
32 | |||
33 | import javax.xml.namespace.NamespaceContext; |
||
34 | |||
35 | /* |
||
36 | * NOTE: Skip XML 1.1 validity tests because we don't want to recompile |
||
37 | * org.apache.xerces for Android -- PointedEars |
||
38 | */ |
||
39 | // import org.apache.xerces.util.XML11Char; |
||
40 | |||
41 | /** |
||
42 | * Helper implementation of the javax.xml.namespace.NamespaceContext interface. |
||
43 | * |
||
44 | * <p> |
||
45 | * This class implements the JAXP (1.3+) |
||
46 | * {@link javax.xml.namespace.NamespaceContext} interface. This is the interface |
||
47 | * used by the JAXP XPath APIs to establish namespace bindings for XPath |
||
48 | * expressions. |
||
49 | * <p> |
||
50 | * |
||
51 | * <p> |
||
52 | * There are two errors (in retrospect) with respect to the namespace context in |
||
53 | * the XPath API. First, there's no way to construct a new one. Given an XPath |
||
54 | * you can find out what context it is using, but if you want to construct a new |
||
55 | * expression, there's no standard class that you can instantiate to build a new |
||
56 | * context. Second, the {@link javax.xml.namespace.NamespaceContext} interface |
||
57 | * is obviously (again, in retrospect) missing a method that returns an iterator |
||
58 | * that will allow you to find <emph>all</emph> the namespace URIs (and/or |
||
59 | * prefixes, which would be equivalent) in the context. |
||
60 | * </p> |
||
61 | * |
||
62 | * <p> |
||
63 | * This class addresses the first error by providing an object that you can |
||
64 | * instantiate that implements the {@link javax.xml.namespace.NamespaceContext} |
||
65 | * interface. It's not a <emph>standard</emph> class, but it at least saves you |
||
66 | * the trouble of writing it yourself. (Feel free to move it into your own |
||
67 | * package, of course.) |
||
68 | * </p> |
||
69 | * |
||
70 | * <p> |
||
71 | * There's really no way to address the second error. An interface, like |
||
72 | * {@link javax.xml.namespace.NamespaceContext}, is immutable once released into |
||
73 | * the wild in the Java platform. (This is a consequence of backwards |
||
74 | * compatibility rules.) To really address the problem, we'll have to invent a |
||
75 | * new interface or provide an alternative abstract class that implementations |
||
76 | * will be required to use, or something. However, as an experiment, this class |
||
77 | * implements a couple of extra methods that we might wish had been in the |
||
78 | * interface. These methods are carefully identified as non-standard. Having |
||
79 | * them here really isn't all that useful because your underlying XPath |
||
80 | * implementation isn't likely to return instances of this class. |
||
81 | * </p> |
||
82 | * |
||
83 | * <p> |
||
84 | * There are three ways to instantiate this class: |
||
85 | * </p> |
||
86 | * |
||
87 | * <ol> |
||
88 | * <li>The no-argument constructor produces an initially empty namespace |
||
89 | * context.</li> |
||
90 | * <li>Another constructor takes a prefix and URI and produces a namespace |
||
91 | * context with that binding.</li> |
||
92 | * <li>Finally, there's a constructor that takes a hash of namespace/uri pairs |
||
93 | * and produces a namespace context with those initial bindings.</li> |
||
94 | * <li>The obvious constructor, one that takes an existing |
||
95 | * {@link javax.xml.namespace.NamespaceContext} so that you can extend it, isn't |
||
96 | * there because you can't get the current bindings from that interface; see the |
||
97 | * aforementioned bug.</li> |
||
98 | * </ol> |
||
99 | * |
||
100 | * <p> |
||
101 | * After the object has been instantiated, you can call the |
||
102 | * {@link #add(String,String)} method to add additional bindings to the |
||
103 | * namespace context. Because I'm not sure how and where the XPath API |
||
104 | * implementations might save pointers to the context object, I've imposed a |
||
105 | * number of rules designed to make sure that the context remains coherent: |
||
106 | * </p> |
||
107 | * |
||
108 | * <ul> |
||
109 | * <li>Namespace bindings can only be added, not removed.</li> |
||
110 | * <li>Once a prefix is bound, its binding cannot be changed.</li> |
||
111 | * <li>The XML restrictions on the 'xml' prefix, the 'xmlns' prefix, and their |
||
112 | * respective namespace URIs are enforced.</li> |
||
113 | * <li>Namespace prefixes must be valid NCNames (or "" for the default |
||
114 | * namespace). Note that unprefixed element and attribute names in an XPath |
||
115 | * expression can <em>never</em> match a name that's in a namespace. In |
||
116 | * particular, setting the default namespace won't have that effect.</li> |
||
117 | * </ul> |
||
118 | * |
||
119 | * <p> |
||
120 | * Even with these rules, you can't assume that the context is thread safe. |
||
121 | * Don't allow it to be changed while someone else is reading it. |
||
122 | * </p> |
||
123 | * |
||
124 | * <p> |
||
125 | * <b>Other notes:</b> |
||
126 | * </p> |
||
127 | * |
||
128 | * <ul> |
||
129 | * <li>There's no <code>getNamespaceURIs(String prefix)</code> method because |
||
130 | * there can be at most one URI bound to any given prefix. Wrapping an interator |
||
131 | * around the mapping available with {@link #getNamespaceURI(String)} seemed |
||
132 | * silly.</li> |
||
133 | * <li>This class relies on {@link org.apache.xerces.util.XML11Char} to test |
||
134 | * that the prefixes are valid NCNames. Note that this means that they're valid |
||
135 | * XML 1.1 names. XML 1.1 names are a superset of XML 1.0 names and it didn't |
||
136 | * seem worth the extra effort that would be required to allow the user to |
||
137 | * choose XML 1.0 or XML 1.1. You might not think it's worth the effort to check |
||
138 | * at all. Fair enough.</li> |
||
139 | * <li>I've used generics here and there to make the JDK 1.5 compiler stop |
||
140 | * complaining. Just delete them for JDK 1.4 and everything should work fine.</li> |
||
141 | * </ul> |
||
142 | * |
||
143 | * @author <a href="mailto:Norman.Walsh@Sun.COM">Norman Walsh</a> |
||
144 | * @version $Revision: 1.2 $, $Date: 2006-03-28 20:54:02 $ |
||
145 | * @see <a href="http://jaxp.dev.java.net/">Java API for XML Processing</a> |
||
146 | * @see <a href="http://www.w3.org/TR/REC-xml-names/#ns-qualnames"> Namespaces |
||
147 | * in XML</a> |
||
148 | * @see <a href="http://www.w3.org/XML/xml-names-19990114-errata"> Namespaces in |
||
149 | * XML Errata</a> |
||
150 | */ |
||
151 | public class NamespaceContextHelper implements NamespaceContext |
||
152 | { |
||
153 | private final Hashtable<String, String> ns = new Hashtable<String, String>(); |
||
154 | |||
155 | /** |
||
156 | * Creates a new instance of NamespaceContextHelper. |
||
157 | * |
||
158 | * <p> |
||
159 | * Creates an empty namespace context. |
||
160 | * </p> |
||
161 | */ |
||
162 | public NamespaceContextHelper() |
||
163 | { |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Creates a new instance of NamespaceContextHelper. |
||
168 | * |
||
169 | * <p> |
||
170 | * Creates a namespace context with the bindings specified in |
||
171 | * <code>initialNamespaces</code>. |
||
172 | * </p> |
||
173 | */ |
||
174 | public NamespaceContextHelper(Hashtable initialNamespaces) |
||
175 | { |
||
176 | Enumeration keys = initialNamespaces.keys(); |
||
177 | while (keys.hasMoreElements()) |
||
178 | { |
||
179 | String prefix = (String) keys.nextElement(); |
||
180 | String uri = (String) initialNamespaces.get(prefix); |
||
181 | |||
182 | this.add(prefix, uri); |
||
183 | } |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Creates a new instance of NamespaceContextHelper. |
||
188 | * |
||
189 | * <p> |
||
190 | * Creates a namespace context with the specified <code>prefix</code> bound to |
||
191 | * <code>uri</code>. |
||
192 | * </p> |
||
193 | */ |
||
194 | public NamespaceContextHelper(String prefix, String uri) |
||
195 | { |
||
196 | this.add(prefix, uri); |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * Adds a new prefix/uri binding to the namespace context. |
||
201 | * |
||
202 | * @throws NullPointerException |
||
203 | * if the <code>prefix</code> or <code>uri</code> is |
||
204 | * <code>null</code>. |
||
205 | * @throws IllegalArgumentException |
||
206 | * if the caller attempts to change the binding of |
||
207 | * <code>prefix</code>, if the caller attempts to bind the prefix " |
||
208 | * <code>xml</code>" |
||
209 | * or the namespace " |
||
210 | * <code>http://www.w3.org/XML/1998/namespace</code>" incorrectly, |
||
211 | * if the caller attempts to bind the prefix "<code>xmlns</code>" or |
||
212 | * the namespace |
||
213 | * "<code>http://www.w3.org/2000/xmlns</code>", or if the |
||
214 | * <code>prefix</code> is |
||
215 | * not a valid NCName. |
||
216 | */ |
||
217 | public void add(String prefix, String uri) |
||
218 | { |
||
219 | if (prefix == null || uri == null) |
||
220 | { |
||
221 | throw new NullPointerException( |
||
222 | "Null prefix or uri passed to NamespaceContextHelper"); |
||
223 | } |
||
224 | |||
225 | if (this.ns.containsKey(prefix)) |
||
226 | { |
||
227 | String curURI = this.ns.get(prefix); |
||
228 | if (uri.equals(curURI)) |
||
229 | { |
||
230 | return; |
||
231 | } |
||
232 | throw new IllegalArgumentException( |
||
233 | "Attempt to change binding in NamespaceContextHelper"); |
||
234 | } |
||
235 | |||
236 | if ("xml".equals(prefix) |
||
237 | && !"http://www.w3.org/XML/1998/namespace".equals(uri)) |
||
238 | { |
||
239 | throw new IllegalArgumentException( |
||
240 | "The prefix 'xml' can only be bound to 'http://www.w3.org/XML/1998/namespace' in NamespaceContextHelper"); |
||
241 | } |
||
242 | |||
243 | if ("http://www.w3.org/XML/1998/namespace".equals(uri) |
||
244 | && !"xml".equals(prefix)) |
||
245 | { |
||
246 | throw new IllegalArgumentException( |
||
247 | "The namespace 'http://www.w3.org/XML/1998/namespace' can only have the prefix 'xml' in NamespaceContextHelper"); |
||
248 | } |
||
249 | |||
250 | if ("xmlns".equals(prefix) |
||
251 | || "http://www.w3.org/2000/xmlns".equals(uri)) |
||
252 | { |
||
253 | throw new IllegalArgumentException( |
||
254 | "Neither the prefix 'xmlns' nor the URI 'http://www.w3.org/2000/xmlns' can be bound in NamespaceContextHelper"); |
||
255 | } |
||
256 | |||
257 | if ("".equals(prefix)) |
||
258 | { |
||
259 | this.ns.put(prefix, uri); |
||
260 | } |
||
261 | else |
||
262 | { |
||
263 | /* |
||
264 | * NOTE: Skip XML 1.1 validity tests because we don't want to recompile |
||
265 | * org.apache.xerces for Android -- PointedEars |
||
266 | */ |
||
267 | // if (XML11Char.isXML11ValidNCName (prefix)) { |
||
268 | this.ns.put(prefix, uri); |
||
269 | // } else { |
||
270 | // throw new IllegalArgumentException |
||
271 | // ("Prefix is not a valid NCName in NamespaceContextHelper"); |
||
272 | // } |
||
273 | } |
||
274 | } |
||
275 | |||
276 | /** Implements the NamespaceContext getNamespaceURI method. */ |
||
277 | public String getNamespaceURI(String prefix) |
||
278 | { |
||
279 | return this.ns.get(prefix); |
||
280 | } |
||
281 | |||
282 | /** Implements the NamespaceContext getPrefix method. */ |
||
283 | public String getPrefix(String namespaceURI) |
||
284 | { |
||
285 | if (this.ns.containsValue(namespaceURI)) |
||
286 | { |
||
287 | Enumeration<String> keys = this.ns.keys(); |
||
288 | while (keys.hasMoreElements()) |
||
289 | { |
||
290 | String pfx = keys.nextElement(); |
||
291 | String uri = this.ns.get(pfx); |
||
292 | if (namespaceURI.equals(uri)) |
||
293 | { |
||
294 | return pfx; |
||
295 | } |
||
296 | } |
||
297 | } |
||
298 | return null; |
||
299 | } |
||
300 | |||
301 | /** |
||
302 | * Implements a <emph>NON STANDARD</emph> method for finding all of the |
||
303 | * prefixes |
||
304 | * in the namespace context. |
||
305 | * |
||
306 | * <p> |
||
307 | * Returns an iterator over all of the prefixes in the namespace context. Note |
||
308 | * that multiple prefixes may be bound to the same URI. |
||
309 | * </p> |
||
310 | */ |
||
311 | public Iterator getPrefixes() |
||
312 | { |
||
313 | return this.getPrefixes(null); |
||
314 | } |
||
315 | |||
316 | /** Implements the NamespaceContext getPrefixes method. */ |
||
317 | public Iterator getPrefixes(String namespaceURI) |
||
318 | { |
||
319 | return new NSIterator(this.ns, namespaceURI); |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Implements a <emph>NON STANDARD</emph> method for finding all of the |
||
324 | * namespace URIs |
||
325 | * in the namespace context. |
||
326 | * |
||
327 | * <p> |
||
328 | * Returns an iterator over all of the namespace URIs in the namespace |
||
329 | * context. Note that each namespace URI is returned exactly once, even if it |
||
330 | * is bound to several different prefixes. |
||
331 | * </p> |
||
332 | */ |
||
333 | public Iterator getNamespaceURIs() |
||
334 | { |
||
335 | // Make sure each URI is returned at most once... |
||
336 | Hashtable<String, String> uriHash = new Hashtable<String, String>(); |
||
337 | Enumeration<String> keys = this.ns.keys(); |
||
338 | while (keys.hasMoreElements()) |
||
339 | { |
||
340 | String pfx = keys.nextElement(); |
||
341 | String uri = this.ns.get(pfx); |
||
342 | if (!uriHash.containsKey(uri)) |
||
343 | { |
||
344 | uriHash.put(uri, pfx); |
||
345 | } |
||
346 | } |
||
347 | |||
348 | return new NSIterator(uriHash, null); |
||
349 | } |
||
350 | |||
351 | /** Implements the Iterator interface over namespace bindings. */ |
||
352 | private class NSIterator implements Iterator |
||
353 | { |
||
354 | private Enumeration<String> keys; |
||
355 | |||
356 | public NSIterator(Hashtable<String, String> hash, String value) |
||
357 | { |
||
358 | this.keys = hash.keys(); |
||
359 | if (value != null) |
||
360 | { |
||
361 | // We have to copy the hash to get only the keys that have the specified |
||
362 | // value |
||
363 | Hashtable<String, String> vHash = new Hashtable<String, String>(); |
||
364 | while (this.keys.hasMoreElements()) |
||
365 | { |
||
366 | String key = this.keys.nextElement(); |
||
367 | String val = hash.get(key); |
||
368 | if (val.equals(value)) |
||
369 | { |
||
370 | vHash.put(key, val); |
||
371 | } |
||
372 | } |
||
373 | this.keys = vHash.keys(); |
||
374 | } |
||
375 | } |
||
376 | |||
377 | public boolean hasNext() |
||
378 | { |
||
379 | return this.keys.hasMoreElements(); |
||
380 | } |
||
381 | |||
382 | public String next() |
||
383 | { |
||
384 | return this.keys.nextElement(); |
||
385 | } |
||
386 | |||
387 | public void remove() |
||
388 | { |
||
389 | throw new UnsupportedOperationException( |
||
390 | "Cannot remove prefix in NamespaceContextHelper"); |
||
391 | } |
||
392 | } |
||
393 | |||
394 | } |