• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id: NamespaceSupport2.java 468655 2006-10-28 07:12:06Z minchau $
20  */
21 package org.apache.xml.utils;
22 
23 import java.util.EmptyStackException;
24 import java.util.Enumeration;
25 import java.util.Hashtable;
26 import java.util.Vector;
27 
28 /**
29  * Encapsulate Namespace tracking logic for use by SAX drivers.
30  *
31  * <p>This class is an attempt to rewrite the SAX NamespaceSupport
32  * "helper" class for improved efficiency. It can be used to track the
33  * namespace declarations currently in scope, providing lookup
34  * routines to map prefixes to URIs and vice versa.</p>
35  *
36  * <p>ISSUE: For testing purposes, I've extended NamespaceSupport even
37  * though I'm completely reasserting all behaviors and fields.
38  * Wasteful.... But SAX did not put an interface under that object and
39  * we seem to have written that SAX class into our APIs... and I don't
40  * want to argue with it right now. </p>
41  *
42  * @see org.xml.sax.helpers.NamespaceSupport
43  * */
44 public class NamespaceSupport2
45     extends org.xml.sax.helpers.NamespaceSupport
46 {
47     ////////////////////////////////////////////////////////////////////
48     // Internal state.
49     ////////////////////////////////////////////////////////////////////
50 
51     private Context2 currentContext; // Current point on the double-linked stack
52 
53 
54     ////////////////////////////////////////////////////////////////////
55     // Constants.
56     ////////////////////////////////////////////////////////////////////
57 
58 
59     /**
60      * The XML Namespace as a constant.
61      *
62      * <p>This is the Namespace URI that is automatically mapped
63      * to the "xml" prefix.</p>
64      */
65     public final static String XMLNS =
66         "http://www.w3.org/XML/1998/namespace";
67 
68 
69     ////////////////////////////////////////////////////////////////////
70     // Constructor.
71     ////////////////////////////////////////////////////////////////////
72 
73 
74     /**
75      * Create a new Namespace support object.
76      */
NamespaceSupport2()77     public NamespaceSupport2 ()
78     {
79         reset();
80     }
81 
82 
83     ////////////////////////////////////////////////////////////////////
84     // Context management.
85     ////////////////////////////////////////////////////////////////////
86 
87 
88     /**
89      * Reset this Namespace support object for reuse.
90      *
91      * <p>It is necessary to invoke this method before reusing the
92      * Namespace support object for a new session.</p>
93      */
reset()94     public void reset ()
95     {
96         // Discarding the whole stack doesn't save us a lot versus
97         // creating a new NamespaceSupport. Do we care, or should we
98         // change this to just reset the root context?
99         currentContext = new Context2(null);
100         currentContext.declarePrefix("xml", XMLNS);
101     }
102 
103 
104     /**
105      * Start a new Namespace context.
106      *
107      * <p>Normally, you should push a new context at the beginning
108      * of each XML element: the new context will automatically inherit
109      * the declarations of its parent context, but it will also keep
110      * track of which declarations were made within this context.</p>
111      *
112      * <p>The Namespace support object always starts with a base context
113      * already in force: in this context, only the "xml" prefix is
114      * declared.</p>
115      *
116      * @see #popContext
117      */
pushContext()118     public void pushContext ()
119     {
120         // JJK: Context has a parent pointer.
121         // That means we don't need a stack to pop.
122         // We may want to retain for reuse, but that can be done via
123         // a child pointer.
124 
125         Context2 parentContext=currentContext;
126         currentContext = parentContext.getChild();
127         if (currentContext == null){
128                 currentContext = new Context2(parentContext);
129             }
130         else{
131             // JJK: This will wipe out any leftover data
132             // if we're reusing a previously allocated Context.
133             currentContext.setParent(parentContext);
134         }
135     }
136 
137 
138     /**
139      * Revert to the previous Namespace context.
140      *
141      * <p>Normally, you should pop the context at the end of each
142      * XML element.  After popping the context, all Namespace prefix
143      * mappings that were previously in force are restored.</p>
144      *
145      * <p>You must not attempt to declare additional Namespace
146      * prefixes after popping a context, unless you push another
147      * context first.</p>
148      *
149      * @see #pushContext
150      */
popContext()151     public void popContext ()
152     {
153         Context2 parentContext=currentContext.getParent();
154         if(parentContext==null)
155             throw new EmptyStackException();
156         else
157             currentContext = parentContext;
158     }
159 
160 
161 
162     ////////////////////////////////////////////////////////////////////
163     // Operations within a context.
164     ////////////////////////////////////////////////////////////////////
165 
166 
167     /**
168      * Declare a Namespace prefix.
169      *
170      * <p>This method declares a prefix in the current Namespace
171      * context; the prefix will remain in force until this context
172      * is popped, unless it is shadowed in a descendant context.</p>
173      *
174      * <p>To declare a default Namespace, use the empty string.  The
175      * prefix must not be "xml" or "xmlns".</p>
176      *
177      * <p>Note that you must <em>not</em> declare a prefix after
178      * you've pushed and popped another Namespace.</p>
179      *
180      * <p>Note that there is an asymmetry in this library: while {@link
181      * #getPrefix getPrefix} will not return the default "" prefix,
182      * even if you have declared one; to check for a default prefix,
183      * you have to look it up explicitly using {@link #getURI getURI}.
184      * This asymmetry exists to make it easier to look up prefixes
185      * for attribute names, where the default prefix is not allowed.</p>
186      *
187      * @param prefix The prefix to declare, or null for the empty
188      *        string.
189      * @param uri The Namespace URI to associate with the prefix.
190      * @return true if the prefix was legal, false otherwise
191      * @see #processName
192      * @see #getURI
193      * @see #getPrefix
194      */
declarePrefix(String prefix, String uri)195     public boolean declarePrefix (String prefix, String uri)
196     {
197         if (prefix.equals("xml") || prefix.equals("xmlns")) {
198             return false;
199         } else {
200             currentContext.declarePrefix(prefix, uri);
201             return true;
202         }
203     }
204 
205 
206     /**
207      * Process a raw XML 1.0 name.
208      *
209      * <p>This method processes a raw XML 1.0 name in the current
210      * context by removing the prefix and looking it up among the
211      * prefixes currently declared.  The return value will be the
212      * array supplied by the caller, filled in as follows:</p>
213      *
214      * <dl>
215      * <dt>parts[0]</dt>
216      * <dd>The Namespace URI, or an empty string if none is
217      *  in use.</dd>
218      * <dt>parts[1]</dt>
219      * <dd>The local name (without prefix).</dd>
220      * <dt>parts[2]</dt>
221      * <dd>The original raw name.</dd>
222      * </dl>
223      *
224      * <p>All of the strings in the array will be internalized.  If
225      * the raw name has a prefix that has not been declared, then
226      * the return value will be null.</p>
227      *
228      * <p>Note that attribute names are processed differently than
229      * element names: an unprefixed element name will received the
230      * default Namespace (if any), while an unprefixed element name
231      * will not.</p>
232      *
233      * @param qName The raw XML 1.0 name to be processed.
234      * @param parts A string array supplied by the caller, capable of
235      *        holding at least three members.
236      * @param isAttribute A flag indicating whether this is an
237      *        attribute name (true) or an element name (false).
238      * @return The supplied array holding three internalized strings
239      *        representing the Namespace URI (or empty string), the
240      *        local name, and the raw XML 1.0 name; or null if there
241      *        is an undeclared prefix.
242      * @see #declarePrefix
243      * @see java.lang.String#intern */
processName(String qName, String[] parts, boolean isAttribute)244     public String [] processName (String qName, String[] parts,
245                                   boolean isAttribute)
246     {
247         String[] name=currentContext.processName(qName, isAttribute);
248         if(name==null)
249             return null;
250 
251         // JJK: This recopying is required because processName may return
252         // a cached result. I Don't Like It. *****
253         System.arraycopy(name,0,parts,0,3);
254         return parts;
255     }
256 
257 
258     /**
259      * Look up a prefix and get the currently-mapped Namespace URI.
260      *
261      * <p>This method looks up the prefix in the current context.
262      * Use the empty string ("") for the default Namespace.</p>
263      *
264      * @param prefix The prefix to look up.
265      * @return The associated Namespace URI, or null if the prefix
266      *         is undeclared in this context.
267      * @see #getPrefix
268      * @see #getPrefixes
269      */
getURI(String prefix)270     public String getURI (String prefix)
271     {
272         return currentContext.getURI(prefix);
273     }
274 
275 
276     /**
277      * Return an enumeration of all prefixes currently declared.
278      *
279      * <p><strong>Note:</strong> if there is a default prefix, it will not be
280      * returned in this enumeration; check for the default prefix
281      * using the {@link #getURI getURI} with an argument of "".</p>
282      *
283      * @return An enumeration of all prefixes declared in the
284      *         current context except for the empty (default)
285      *         prefix.
286      * @see #getDeclaredPrefixes
287      * @see #getURI
288      */
getPrefixes()289     public Enumeration getPrefixes ()
290     {
291         return currentContext.getPrefixes();
292     }
293 
294 
295     /**
296      * Return one of the prefixes mapped to a Namespace URI.
297      *
298      * <p>If more than one prefix is currently mapped to the same
299      * URI, this method will make an arbitrary selection; if you
300      * want all of the prefixes, use the {@link #getPrefixes}
301      * method instead.</p>
302      *
303      * <p><strong>Note:</strong> this will never return the empty
304      * (default) prefix; to check for a default prefix, use the {@link
305      * #getURI getURI} method with an argument of "".</p>
306      *
307      * @param uri The Namespace URI.
308      * @return One of the prefixes currently mapped to the URI supplied,
309      *         or null if none is mapped or if the URI is assigned to
310      *         the default Namespace.
311      * @see #getPrefixes(java.lang.String)
312      * @see #getURI */
getPrefix(String uri)313     public String getPrefix (String uri)
314     {
315         return currentContext.getPrefix(uri);
316     }
317 
318 
319     /**
320      * Return an enumeration of all prefixes currently declared for a URI.
321      *
322      * <p>This method returns prefixes mapped to a specific Namespace
323      * URI.  The xml: prefix will be included.  If you want only one
324      * prefix that's mapped to the Namespace URI, and you don't care
325      * which one you get, use the {@link #getPrefix getPrefix}
326      *  method instead.</p>
327      *
328      * <p><strong>Note:</strong> the empty (default) prefix is
329      * <em>never</em> included in this enumeration; to check for the
330      * presence of a default Namespace, use the {@link #getURI getURI}
331      * method with an argument of "".</p>
332      *
333      * @param uri The Namespace URI.
334      * @return An enumeration of all prefixes declared in the
335      *         current context.
336      * @see #getPrefix
337      * @see #getDeclaredPrefixes
338      * @see #getURI */
getPrefixes(String uri)339     public Enumeration getPrefixes (String uri)
340     {
341         // JJK: The old code involved creating a vector, filling it
342         // with all the matching prefixes, and then getting its
343         // elements enumerator. Wastes storage, wastes cycles if we
344         // don't actually need them all. Better to either implement
345         // a specific enumerator for these prefixes... or a filter
346         // around the all-prefixes enumerator, which comes out to
347         // roughly the same thing.
348         //
349         // **** Currently a filter. That may not be most efficient
350         // when I'm done restructuring storage!
351         return new PrefixForUriEnumerator(this,uri,getPrefixes());
352     }
353 
354 
355     /**
356      * Return an enumeration of all prefixes declared in this context.
357      *
358      * <p>The empty (default) prefix will be included in this
359      * enumeration; note that this behaviour differs from that of
360      * {@link #getPrefix} and {@link #getPrefixes}.</p>
361      *
362      * @return An enumeration of all prefixes declared in this
363      *         context.
364      * @see #getPrefixes
365      * @see #getURI
366      */
getDeclaredPrefixes()367     public Enumeration getDeclaredPrefixes ()
368     {
369         return currentContext.getDeclaredPrefixes();
370     }
371 
372 
373 
374 }
375 
376 ////////////////////////////////////////////////////////////////////
377 // Local classes.
378 // These were _internal_ classes... but in fact they don't have to be,
379 // and may be more efficient if they aren't.
380 ////////////////////////////////////////////////////////////////////
381 
382 /**
383  * Implementation of Enumeration filter, wrapped
384  * aroung the get-all-prefixes version of the operation. This is NOT
385  * necessarily the most efficient approach; finding the URI and then asking
386  * what prefixes apply to it might make much more sense.
387  */
388 class PrefixForUriEnumerator implements Enumeration
389 {
390     private Enumeration allPrefixes;
391     private String uri;
392     private String lookahead=null;
393     private NamespaceSupport2 nsup;
394 
395     // Kluge: Since one can't do a constructor on an
396     // anonymous class (as far as I know)...
PrefixForUriEnumerator(NamespaceSupport2 nsup,String uri, Enumeration allPrefixes)397     PrefixForUriEnumerator(NamespaceSupport2 nsup,String uri, Enumeration allPrefixes)
398     {
399 	this.nsup=nsup;
400         this.uri=uri;
401         this.allPrefixes=allPrefixes;
402     }
403 
hasMoreElements()404     public boolean hasMoreElements()
405     {
406         if(lookahead!=null)
407             return true;
408 
409         while(allPrefixes.hasMoreElements())
410             {
411                 String prefix=(String)allPrefixes.nextElement();
412                 if(uri.equals(nsup.getURI(prefix)))
413                     {
414                         lookahead=prefix;
415                         return true;
416                     }
417             }
418         return false;
419     }
420 
nextElement()421     public Object nextElement()
422     {
423         if(hasMoreElements())
424             {
425                 String tmp=lookahead;
426                 lookahead=null;
427                 return tmp;
428             }
429         else
430             throw new java.util.NoSuchElementException();
431     }
432 }
433 
434 /**
435  * Internal class for a single Namespace context.
436  *
437  * <p>This module caches and reuses Namespace contexts, so the number allocated
438  * will be equal to the element depth of the document, not to the total
439  * number of elements (i.e. 5-10 rather than tens of thousands).</p>
440  */
441 final class Context2 {
442 
443     ////////////////////////////////////////////////////////////////
444     // Manefest Constants
445     ////////////////////////////////////////////////////////////////
446 
447     /**
448      * An empty enumeration.
449      */
450     private final static Enumeration EMPTY_ENUMERATION =
451         new Vector().elements();
452 
453     ////////////////////////////////////////////////////////////////
454     // Protected state.
455     ////////////////////////////////////////////////////////////////
456 
457     Hashtable prefixTable;
458     Hashtable uriTable;
459     Hashtable elementNameTable;
460     Hashtable attributeNameTable;
461     String defaultNS = null;
462 
463     ////////////////////////////////////////////////////////////////
464     // Internal state.
465     ////////////////////////////////////////////////////////////////
466 
467     private Vector declarations = null;
468     private boolean tablesDirty = false;
469     private Context2 parent = null;
470     private Context2 child = null;
471 
472     /**
473      * Create a new Namespace context.
474      */
Context2(Context2 parent)475     Context2 (Context2 parent)
476     {
477         if(parent==null)
478             {
479                 prefixTable = new Hashtable();
480                 uriTable = new Hashtable();
481                 elementNameTable=null;
482                 attributeNameTable=null;
483             }
484         else
485             setParent(parent);
486     }
487 
488 
489     /**
490      * @returns The child Namespace context object, or null if this
491      * is the last currently on the chain.
492      */
getChild()493     Context2 getChild()
494     {
495         return child;
496     }
497 
498     /**
499      * @returns The parent Namespace context object, or null if this
500      * is the root.
501      */
getParent()502     Context2 getParent()
503     {
504         return parent;
505     }
506 
507     /**
508      * (Re)set the parent of this Namespace context.
509      * This is separate from the c'tor because it's re-applied
510      * when a Context2 is reused by push-after-pop.
511      *
512      * @param context The parent Namespace context object.
513      */
setParent(Context2 parent)514     void setParent (Context2 parent)
515     {
516         this.parent = parent;
517         parent.child = this;        // JJK: Doubly-linked
518         declarations = null;
519         prefixTable = parent.prefixTable;
520         uriTable = parent.uriTable;
521         elementNameTable = parent.elementNameTable;
522         attributeNameTable = parent.attributeNameTable;
523         defaultNS = parent.defaultNS;
524         tablesDirty = false;
525     }
526 
527 
528     /**
529      * Declare a Namespace prefix for this context.
530      *
531      * @param prefix The prefix to declare.
532      * @param uri The associated Namespace URI.
533      * @see org.xml.sax.helpers.NamespaceSupport2#declarePrefix
534      */
declarePrefix(String prefix, String uri)535     void declarePrefix (String prefix, String uri)
536     {
537                                 // Lazy processing...
538         if (!tablesDirty) {
539             copyTables();
540         }
541         if (declarations == null) {
542             declarations = new Vector();
543         }
544 
545         prefix = prefix.intern();
546         uri = uri.intern();
547         if ("".equals(prefix)) {
548             if ("".equals(uri)) {
549                 defaultNS = null;
550             } else {
551                 defaultNS = uri;
552             }
553         } else {
554             prefixTable.put(prefix, uri);
555             uriTable.put(uri, prefix); // may wipe out another prefix
556         }
557         declarations.addElement(prefix);
558     }
559 
560 
561     /**
562      * Process a raw XML 1.0 name in this context.
563      *
564      * @param qName The raw XML 1.0 name.
565      * @param isAttribute true if this is an attribute name.
566      * @return An array of three strings containing the
567      *         URI part (or empty string), the local part,
568      *         and the raw name, all internalized, or null
569      *         if there is an undeclared prefix.
570      * @see org.xml.sax.helpers.NamespaceSupport2#processName
571      */
processName(String qName, boolean isAttribute)572     String [] processName (String qName, boolean isAttribute)
573     {
574         String name[];
575         Hashtable table;
576 
577                                 // Select the appropriate table.
578         if (isAttribute) {
579             if(elementNameTable==null)
580                 elementNameTable=new Hashtable();
581             table = elementNameTable;
582         } else {
583             if(attributeNameTable==null)
584                 attributeNameTable=new Hashtable();
585             table = attributeNameTable;
586         }
587 
588                                 // Start by looking in the cache, and
589                                 // return immediately if the name
590                                 // is already known in this content
591         name = (String[])table.get(qName);
592         if (name != null) {
593             return name;
594         }
595 
596                                 // We haven't seen this name in this
597                                 // context before.
598         name = new String[3];
599         int index = qName.indexOf(':');
600 
601 
602                                 // No prefix.
603         if (index == -1) {
604             if (isAttribute || defaultNS == null) {
605                 name[0] = "";
606             } else {
607                 name[0] = defaultNS;
608             }
609             name[1] = qName.intern();
610             name[2] = name[1];
611         }
612 
613                                 // Prefix
614         else {
615             String prefix = qName.substring(0, index);
616             String local = qName.substring(index+1);
617             String uri;
618             if ("".equals(prefix)) {
619                 uri = defaultNS;
620             } else {
621                 uri = (String)prefixTable.get(prefix);
622             }
623             if (uri == null) {
624                 return null;
625             }
626             name[0] = uri;
627             name[1] = local.intern();
628             name[2] = qName.intern();
629         }
630 
631                                 // Save in the cache for future use.
632         table.put(name[2], name);
633         tablesDirty = true;
634         return name;
635     }
636 
637 
638     /**
639      * Look up the URI associated with a prefix in this context.
640      *
641      * @param prefix The prefix to look up.
642      * @return The associated Namespace URI, or null if none is
643      *         declared.
644      * @see org.xml.sax.helpers.NamespaceSupport2#getURI
645      */
getURI(String prefix)646     String getURI (String prefix)
647     {
648         if ("".equals(prefix)) {
649             return defaultNS;
650         } else if (prefixTable == null) {
651             return null;
652         } else {
653             return (String)prefixTable.get(prefix);
654         }
655     }
656 
657 
658     /**
659      * Look up one of the prefixes associated with a URI in this context.
660      *
661      * <p>Since many prefixes may be mapped to the same URI,
662      * the return value may be unreliable.</p>
663      *
664      * @param uri The URI to look up.
665      * @return The associated prefix, or null if none is declared.
666      * @see org.xml.sax.helpers.NamespaceSupport2#getPrefix
667      */
getPrefix(String uri)668     String getPrefix (String uri)
669     {
670         if (uriTable == null) {
671             return null;
672         } else {
673             return (String)uriTable.get(uri);
674         }
675     }
676 
677 
678     /**
679      * Return an enumeration of prefixes declared in this context.
680      *
681      * @return An enumeration of prefixes (possibly empty).
682      * @see org.xml.sax.helpers.NamespaceSupport2#getDeclaredPrefixes
683      */
getDeclaredPrefixes()684     Enumeration getDeclaredPrefixes ()
685     {
686         if (declarations == null) {
687             return EMPTY_ENUMERATION;
688         } else {
689             return declarations.elements();
690         }
691     }
692 
693 
694     /**
695      * Return an enumeration of all prefixes currently in force.
696      *
697      * <p>The default prefix, if in force, is <em>not</em>
698      * returned, and will have to be checked for separately.</p>
699      *
700      * @return An enumeration of prefixes (never empty).
701      * @see org.xml.sax.helpers.NamespaceSupport2#getPrefixes
702      */
getPrefixes()703     Enumeration getPrefixes ()
704     {
705         if (prefixTable == null) {
706             return EMPTY_ENUMERATION;
707         } else {
708             return prefixTable.keys();
709         }
710     }
711 
712     ////////////////////////////////////////////////////////////////
713     // Internal methods.
714     ////////////////////////////////////////////////////////////////
715 
716     /**
717      * Copy on write for the internal tables in this context.
718      *
719      * <p>This class is optimized for the normal case where most
720      * elements do not contain Namespace declarations. In that case,
721      * the Context2 will share data structures with its parent.
722      * New tables are obtained only when new declarations are issued,
723      * so they can be popped off the stack.</p>
724      *
725      * <p> JJK: **** Alternative: each Context2 might declare
726      *  _only_ its local bindings, and delegate upward if not found.</p>
727      */
copyTables()728     private void copyTables ()
729     {
730         // Start by copying our parent's bindings
731         prefixTable = (Hashtable)prefixTable.clone();
732         uriTable = (Hashtable)uriTable.clone();
733 
734         // Replace the caches with empty ones, rather than
735         // trying to determine which bindings should be flushed.
736         // As far as I can tell, these caches are never actually
737         // used in Xalan... More efficient to remove the whole
738         // cache system? ****
739         if(elementNameTable!=null)
740             elementNameTable=new Hashtable();
741         if(attributeNameTable!=null)
742             attributeNameTable=new Hashtable();
743         tablesDirty = true;
744     }
745 
746 }
747 
748 
749 // end of NamespaceSupport2.java
750