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: NamespaceMappings.java 469648 2006-10-31 20:52:27Z minchau $
20  */
21 package org.apache.xml.serializer;
22 
23 import java.util.Enumeration;
24 import java.util.Hashtable;
25 
26 import org.xml.sax.ContentHandler;
27 import org.xml.sax.SAXException;
28 
29 /**
30  * This class keeps track of the currently defined namespaces. Conceptually the
31  * prefix/uri/depth triplets are pushed on a stack pushed on a stack. The depth
32  * indicates the nesting depth of the element for which the mapping was made.
33  *
34  * <p>For example:
35  * <pre>
36  * <chapter xmlns:p1="def">
37  *   <paragraph xmlns:p2="ghi">
38  *      <sentance xmlns:p3="jkl">
39  *      </sentance>
40  *    </paragraph>
41  *    <paragraph xlmns:p4="mno">
42  *    </paragraph>
43  * </chapter>
44  * </pre>
45  *
46  * When the <chapter> element is encounted the prefix "p1" associated with uri
47  * "def" is pushed on the stack with depth 1.
48  * When the first <paragraph> is encountered "p2" and "ghi" are pushed with
49  * depth 2.
50  * When the <sentance> is encountered "p3" and "jkl" are pushed with depth 3.
51  * When </sentance> occurs the popNamespaces(3) will pop "p3"/"jkl" off the
52  * stack.  Of course popNamespaces(2) would pop anything with depth 2 or
53  * greater.
54  *
55  * So prefix/uri pairs are pushed and poped off the stack as elements are
56  * processed.  At any given moment of processing the currently visible prefixes
57  * are on the stack and a prefix can be found given a uri, or a uri can be found
58  * given a prefix.
59  *
60  * This class is intended for internal use only.  However, it is made public because
61  * other packages require it.
62  * @xsl.usage internal
63  */
64 public class NamespaceMappings
65 {
66     /**
67      * This member is continually incremented when new prefixes need to be
68      * generated. ("ns0"  "ns1" ...)
69      */
70     private int count = 0;
71 
72     /**
73      * Each entry (prefix) in this hashtable points to a Stack of URIs
74      * This table maps a prefix (String) to a Stack of NamespaceNodes.
75      * All Namespace nodes in that retrieved stack have the same prefix,
76      * though possibly different URI's or depths. Such a stack must have
77      * mappings at deeper depths push later on such a stack.  Mappings pushed
78      * earlier on the stack will have smaller values for MappingRecord.m_declarationDepth.
79      */
80     private Hashtable m_namespaces = new Hashtable();
81 
82     /**
83      * This stack is used as a convenience.
84      * It contains the pushed NamespaceNodes (shallowest
85      * to deepest) and is used to delete NamespaceNodes
86      * when leaving the current element depth
87      * to returning to the parent. The mappings of the deepest
88      * depth can be popped of the top and the same node
89      * can be removed from the appropriate prefix stack.
90      *
91      * All prefixes pushed at the current depth can be
92      * removed at the same time by using this stack to
93      * ensure prefix/uri map scopes are closed correctly.
94      */
95     private Stack m_nodeStack = new Stack();
96 
97     private static final String EMPTYSTRING = "";
98     private static final String XML_PREFIX = "xml"; // was "xmlns"
99 
100     /**
101      * Default constructor
102      * @see java.lang.Object#Object()
103      */
NamespaceMappings()104     public NamespaceMappings()
105     {
106         initNamespaces();
107     }
108 
109     /**
110      * This method initializes the namespace object with appropriate stacks
111      * and predefines a few prefix/uri pairs which always exist.
112      */
initNamespaces()113     private void initNamespaces()
114     {
115         // The initial prefix mappings will never be deleted because they are at element depth -1
116         // (a kludge)
117 
118         // Define the default namespace (initially maps to "" uri)
119         Stack stack;
120         MappingRecord nn;
121         nn = new MappingRecord(EMPTYSTRING, EMPTYSTRING, -1);
122         stack = createPrefixStack(EMPTYSTRING);
123         stack.push(nn);
124 
125         // define "xml" namespace
126         nn = new MappingRecord(XML_PREFIX, "http://www.w3.org/XML/1998/namespace", -1);
127         stack = createPrefixStack(XML_PREFIX);
128         stack.push(nn);
129     }
130 
131     /**
132      * Use a namespace prefix to lookup a namespace URI.
133      *
134      * @param prefix String the prefix of the namespace
135      * @return the URI corresponding to the prefix, returns ""
136      * if there is no visible mapping.
137      */
lookupNamespace(String prefix)138     public String lookupNamespace(String prefix)
139     {
140         String uri = null;
141         final Stack stack = getPrefixStack(prefix);
142         if (stack != null && !stack.isEmpty()) {
143             uri = ((MappingRecord) stack.peek()).m_uri;
144         }
145         if (uri == null)
146             uri = EMPTYSTRING;
147         return uri;
148     }
149 
150 
getMappingFromPrefix(String prefix)151     MappingRecord getMappingFromPrefix(String prefix) {
152         final Stack stack = (Stack) m_namespaces.get(prefix);
153         return stack != null && !stack.isEmpty() ?
154             ((MappingRecord) stack.peek()) : null;
155     }
156 
157     /**
158      * Given a namespace uri, and the namespaces mappings for the
159      * current element, return the current prefix for that uri.
160      *
161      * @param uri the namespace URI to be search for
162      * @return an existing prefix that maps to the given URI, null if no prefix
163      * maps to the given namespace URI.
164      */
lookupPrefix(String uri)165     public String lookupPrefix(String uri)
166     {
167         String foundPrefix = null;
168         Enumeration prefixes = m_namespaces.keys();
169         while (prefixes.hasMoreElements())
170         {
171             String prefix = (String) prefixes.nextElement();
172             String uri2 = lookupNamespace(prefix);
173             if (uri2 != null && uri2.equals(uri))
174             {
175                 foundPrefix = prefix;
176                 break;
177             }
178         }
179         return foundPrefix;
180     }
181 
getMappingFromURI(String uri)182     MappingRecord getMappingFromURI(String uri)
183     {
184         MappingRecord foundMap = null;
185         Enumeration prefixes = m_namespaces.keys();
186         while (prefixes.hasMoreElements())
187         {
188             String prefix = (String) prefixes.nextElement();
189             MappingRecord map2 = getMappingFromPrefix(prefix);
190             if (map2 != null && (map2.m_uri).equals(uri))
191             {
192                 foundMap = map2;
193                 break;
194             }
195         }
196         return foundMap;
197     }
198 
199     /**
200      * Undeclare the namespace that is currently pointed to by a given prefix
201      */
popNamespace(String prefix)202     boolean popNamespace(String prefix)
203     {
204         // Prefixes "xml" and "xmlns" cannot be redefined
205         if (prefix.startsWith(XML_PREFIX))
206         {
207             return false;
208         }
209 
210         Stack stack;
211         if ((stack = getPrefixStack(prefix)) != null)
212         {
213             stack.pop();
214             return true;
215         }
216         return false;
217     }
218 
219     /**
220      * Declare a mapping of a prefix to namespace URI at the given element depth.
221      * @param prefix a String with the prefix for a qualified name
222      * @param uri a String with the uri to which the prefix is to map
223      * @param elemDepth the depth of current declaration
224      */
pushNamespace(String prefix, String uri, int elemDepth)225     public boolean pushNamespace(String prefix, String uri, int elemDepth)
226     {
227         // Prefixes "xml" and "xmlns" cannot be redefined
228         if (prefix.startsWith(XML_PREFIX))
229         {
230             return false;
231         }
232 
233         Stack stack;
234         // Get the stack that contains URIs for the specified prefix
235         if ((stack = (Stack) m_namespaces.get(prefix)) == null)
236         {
237             m_namespaces.put(prefix, stack = new Stack());
238         }
239 
240         if (!stack.empty())
241         {
242             MappingRecord mr = (MappingRecord)stack.peek();
243             if (uri.equals(mr.m_uri) || elemDepth == mr.m_declarationDepth) {
244                 // If the same prefix/uri mapping is already on the stack
245                 // don't push this one.
246                 // Or if we have a mapping at the same depth
247                 // don't replace by pushing this one.
248                 return false;
249             }
250         }
251         MappingRecord map = new MappingRecord(prefix,uri,elemDepth);
252         stack.push(map);
253         m_nodeStack.push(map);
254         return true;
255     }
256 
257     /**
258      * Pop, or undeclare all namespace definitions that are currently
259      * declared at the given element depth, or deepter.
260      * @param elemDepth the element depth for which mappings declared at this
261      * depth or deeper will no longer be valid
262      * @param saxHandler The ContentHandler to notify of any endPrefixMapping()
263      * calls.  This parameter can be null.
264      */
popNamespaces(int elemDepth, ContentHandler saxHandler)265     void popNamespaces(int elemDepth, ContentHandler saxHandler)
266     {
267         while (true)
268         {
269             if (m_nodeStack.isEmpty())
270                 return;
271             MappingRecord map = (MappingRecord) (m_nodeStack.peek());
272             int depth = map.m_declarationDepth;
273             if (elemDepth < 1 || map.m_declarationDepth < elemDepth)
274                 break;
275             /* the depth of the declared mapping is elemDepth or deeper
276              * so get rid of it
277              */
278 
279             MappingRecord nm1 = (MappingRecord) m_nodeStack.pop();
280             // pop the node from the stack
281             String prefix = map.m_prefix;
282 
283             Stack prefixStack = getPrefixStack(prefix);
284             MappingRecord nm2 = (MappingRecord) prefixStack.peek();
285             if (nm1 == nm2)
286             {
287                 // It would be nice to always pop() but we
288                 // need to check that the prefix stack still has
289                 // the node we want to get rid of. This is because
290                 // the optimization of essentially this situation:
291                 // <a xmlns:x="abc"><b xmlns:x="" xmlns:x="abc" /></a>
292                 // will remove both mappings in <b> because the
293                 // new mapping is the same as the masked one and we get
294                 // <a xmlns:x="abc"><b/></a>
295                 // So we are only removing xmlns:x="" or
296                 // xmlns:x="abc" from the depth of element <b>
297                 // when going back to <a> if in fact they have
298                 // not been optimized away.
299                 //
300                 prefixStack.pop();
301                 if (saxHandler != null)
302                 {
303                     try
304                     {
305                         saxHandler.endPrefixMapping(prefix);
306                     }
307                     catch (SAXException e)
308                     {
309                         // not much we can do if they aren't willing to listen
310                     }
311                 }
312             }
313 
314         }
315     }
316 
317     /**
318      * Generate a new namespace prefix ( ns0, ns1 ...) not used before
319      * @return String a new namespace prefix ( ns0, ns1, ns2 ...)
320      */
generateNextPrefix()321     public String generateNextPrefix()
322     {
323         return "ns" + (count++);
324     }
325 
326 
327     /**
328      * This method makes a clone of this object.
329      *
330      */
clone()331     public Object clone() throws CloneNotSupportedException {
332         NamespaceMappings clone = new NamespaceMappings();
333         clone.m_nodeStack = (NamespaceMappings.Stack) m_nodeStack.clone();
334         clone.count = this.count;
335         clone.m_namespaces = (Hashtable) m_namespaces.clone();
336 
337         clone.count = count;
338         return clone;
339 
340     }
341 
reset()342     final void reset()
343     {
344         this.count = 0;
345         this.m_namespaces.clear();
346         this.m_nodeStack.clear();
347 
348         initNamespaces();
349     }
350 
351     /**
352      * Just a little class that ties the 3 fields together
353      * into one object, and this simplifies the pushing
354      * and popping of namespaces to one push or one pop on
355      * one stack rather than on 3 separate stacks.
356      */
357     class MappingRecord {
358         final String m_prefix;  // the prefix
359         final String m_uri;     // the uri, possibly "" but never null
360         // the depth of the element where declartion was made
361         final int m_declarationDepth;
MappingRecord(String prefix, String uri, int depth)362         MappingRecord(String prefix, String uri, int depth) {
363             m_prefix = prefix;
364             m_uri = (uri==null)? EMPTYSTRING : uri;
365             m_declarationDepth = depth;
366         }
367     }
368 
369     /**
370      * Rather than using java.util.Stack, this private class
371      * provides a minimal subset of methods and is faster
372      * because it is not thread-safe.
373      */
374     private class Stack {
375         private int top = -1;
376         private int max = 20;
377         Object[] m_stack = new Object[max];
378 
clone()379         public Object clone() throws CloneNotSupportedException {
380             NamespaceMappings.Stack clone = new NamespaceMappings.Stack();
381             clone.max = this.max;
382             clone.top = this.top;
383             clone.m_stack = new Object[clone.max];
384             for (int i=0; i <= top; i++) {
385             	// We are just copying references to immutable MappingRecord objects here
386             	// so it is OK if the clone has references to these.
387             	clone.m_stack[i] = this.m_stack[i];
388             }
389             return clone;
390         }
391 
Stack()392         public Stack()
393         {
394         }
395 
push(Object o)396         public Object push(Object o) {
397             top++;
398             if (max <= top) {
399                 int newMax = 2*max + 1;
400                 Object[] newArray = new Object[newMax];
401                 System.arraycopy(m_stack,0, newArray, 0, max);
402                 max = newMax;
403                 m_stack = newArray;
404             }
405             m_stack[top] = o;
406             return o;
407         }
408 
pop()409         public Object pop() {
410             Object o;
411             if (0 <= top) {
412                 o = m_stack[top];
413                 // m_stack[top] = null;  do we really care?
414                 top--;
415             }
416             else
417                 o = null;
418             return o;
419         }
420 
peek()421         public Object peek() {
422             Object o;
423             if (0 <= top) {
424                 o = m_stack[top];
425             }
426             else
427                 o = null;
428             return o;
429         }
430 
peek(int idx)431         public Object peek(int idx) {
432             return m_stack[idx];
433         }
434 
isEmpty()435         public boolean isEmpty() {
436             return (top < 0);
437         }
empty()438         public boolean empty() {
439             return (top < 0);
440         }
441 
clear()442         public void clear() {
443             for (int i=0; i<= top; i++)
444                 m_stack[i] = null;
445             top = -1;
446         }
447 
getElement(int index)448         public Object getElement(int index) {
449             return m_stack[index];
450         }
451     }
452     /**
453      * A more type-safe way to get a stack of prefix mappings
454      * from the Hashtable m_namespaces
455      * (this is the only method that does the type cast).
456      */
457 
getPrefixStack(String prefix)458     private Stack getPrefixStack(String prefix) {
459         Stack fs = (Stack) m_namespaces.get(prefix);
460         return fs;
461     }
462 
463     /**
464      * A more type-safe way of saving stacks under the
465      * m_namespaces Hashtable.
466      */
createPrefixStack(String prefix)467     private Stack createPrefixStack(String prefix)
468     {
469         Stack fs = new Stack();
470         m_namespaces.put(prefix, fs);
471         return fs;
472     }
473 
474     /**
475      * Given a namespace uri, get all prefixes bound to the Namespace URI in the current scope.
476      *
477      * @param uri the namespace URI to be search for
478      * @return An array of Strings which are
479      * all prefixes bound to the namespace URI in the current scope.
480      * An array of zero elements is returned if no prefixes map to the given
481      * namespace URI.
482      */
lookupAllPrefixes(String uri)483     public String[] lookupAllPrefixes(String uri)
484     {
485         java.util.ArrayList foundPrefixes = new java.util.ArrayList();
486         Enumeration prefixes = m_namespaces.keys();
487         while (prefixes.hasMoreElements())
488         {
489             String prefix = (String) prefixes.nextElement();
490             String uri2 = lookupNamespace(prefix);
491             if (uri2 != null && uri2.equals(uri))
492             {
493                 foundPrefixes.add(prefix);
494             }
495         }
496         String[] prefixArray = new String[foundPrefixes.size()];
497         foundPrefixes.toArray(prefixArray);
498         return prefixArray;
499     }
500 }
501