001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: NamespaceMappings.java 1225444 2011-12-29 05:52:39Z mrglavas $
020 */
021 package org.apache.xml.serializer;
022
023 import java.util.Enumeration;
024 import java.util.Hashtable;
025
026 import org.xml.sax.ContentHandler;
027 import org.xml.sax.SAXException;
028
029 /**
030 * This class keeps track of the currently defined namespaces. Conceptually the
031 * prefix/uri/depth triplets are pushed on a stack pushed on a stack. The depth
032 * indicates the nesting depth of the element for which the mapping was made.
033 *
034 * <p>For example:
035 * <pre>
036 * <chapter xmlns:p1="def">
037 * <paragraph xmlns:p2="ghi">
038 * <sentance xmlns:p3="jkl">
039 * </sentance>
040 * </paragraph>
041 * <paragraph xlmns:p4="mno">
042 * </paragraph>
043 * </chapter>
044 * </pre>
045 *
046 * When the <chapter> element is encounted the prefix "p1" associated with uri
047 * "def" is pushed on the stack with depth 1.
048 * When the first <paragraph> is encountered "p2" and "ghi" are pushed with
049 * depth 2.
050 * When the <sentance> is encountered "p3" and "jkl" are pushed with depth 3.
051 * When </sentance> occurs the popNamespaces(3) will pop "p3"/"jkl" off the
052 * stack. Of course popNamespaces(2) would pop anything with depth 2 or
053 * greater.
054 *
055 * So prefix/uri pairs are pushed and poped off the stack as elements are
056 * processed. At any given moment of processing the currently visible prefixes
057 * are on the stack and a prefix can be found given a uri, or a uri can be found
058 * given a prefix.
059 *
060 * This class is intended for internal use only. However, it is made public because
061 * other packages require it.
062 * @xsl.usage internal
063 */
064 public class NamespaceMappings
065 {
066 /**
067 * This member is continually incremented when new prefixes need to be
068 * generated. ("ns0" "ns1" ...)
069 */
070 private int count = 0;
071
072 /**
073 * Each entry (prefix) in this hashtable points to a Stack of URIs
074 * This table maps a prefix (String) to a Stack of NamespaceNodes.
075 * All Namespace nodes in that retrieved stack have the same prefix,
076 * though possibly different URI's or depths. Such a stack must have
077 * mappings at deeper depths push later on such a stack. Mappings pushed
078 * earlier on the stack will have smaller values for MappingRecord.m_declarationDepth.
079 */
080 private Hashtable m_namespaces = new Hashtable();
081
082 /**
083 * This stack is used as a convenience.
084 * It contains the pushed NamespaceNodes (shallowest
085 * to deepest) and is used to delete NamespaceNodes
086 * when leaving the current element depth
087 * to returning to the parent. The mappings of the deepest
088 * depth can be popped of the top and the same node
089 * can be removed from the appropriate prefix stack.
090 *
091 * All prefixes pushed at the current depth can be
092 * removed at the same time by using this stack to
093 * ensure prefix/uri map scopes are closed correctly.
094 */
095 private Stack m_nodeStack = new Stack();
096
097 private static final String EMPTYSTRING = "";
098 private static final String XML_PREFIX = "xml"; // was "xmlns"
099
100 /**
101 * Default constructor
102 * @see java.lang.Object#Object()
103 */
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 */
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 */
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
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 */
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
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 */
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 */
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 */
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 */
321 public String generateNextPrefix()
322 {
323 return "ns" + (count++);
324 }
325
326
327 /**
328 * This method makes a clone of this object.
329 *
330 */
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
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 static 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;
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
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
392 public Stack()
393 {
394 }
395
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
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
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
431 public Object peek(int idx) {
432 return m_stack[idx];
433 }
434
435 public boolean isEmpty() {
436 return (top < 0);
437 }
438 public boolean empty() {
439 return (top < 0);
440 }
441
442 public void clear() {
443 for (int i=0; i<= top; i++)
444 m_stack[i] = null;
445 top = -1;
446 }
447
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
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 */
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 */
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 }