Subversion Repositories ES

Rev

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
}