1 /*
2 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
3 *
4 * This software is open source.
5 * See the bottom of this file for the licence.
6 */
7
8 package org.dom4j.tree;
9
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.Map;
13
14 import org.dom4j.DocumentFactory;
15 import org.dom4j.Namespace;
16 import org.dom4j.QName;
17
18 /**
19 * NamespaceStack implements a stack of namespaces and optionally maintains a
20 * cache of all the fully qualified names (<code>QName</code>) which are in
21 * scope. This is useful when building or navigating a <i>dom4j </i> document.
22 *
23 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
24 * @version $Revision: 1.13 $
25 */
26 public class NamespaceStack {
27 /** The factory used to create new <code>Namespace</code> instances */
28 private DocumentFactory documentFactory;
29
30 /** The Stack of namespaces */
31 private ArrayList namespaceStack = new ArrayList();
32
33 /** The cache of qualifiedNames to QNames per namespace context */
34 private ArrayList namespaceCacheList = new ArrayList();
35
36 /**
37 * A cache of current namespace context cache of mapping from qualifiedName
38 * to QName
39 */
40 private Map currentNamespaceCache;
41
42 /**
43 * A cache of mapping from qualifiedName to QName before any namespaces are
44 * declared
45 */
46 private Map rootNamespaceCache = new HashMap();
47
48 /** Caches the default namespace defined via xmlns="" */
49 private Namespace defaultNamespace;
50
51 public NamespaceStack() {
52 this.documentFactory = DocumentFactory.getInstance();
53 }
54
55 public NamespaceStack(DocumentFactory documentFactory) {
56 this.documentFactory = documentFactory;
57 }
58
59 /**
60 * Pushes the given namespace onto the stack so that its prefix becomes
61 * available.
62 *
63 * @param namespace
64 * is the <code>Namespace</code> to add to the stack.
65 */
66 public void push(Namespace namespace) {
67 namespaceStack.add(namespace);
68 namespaceCacheList.add(null);
69 currentNamespaceCache = null;
70
71 String prefix = namespace.getPrefix();
72
73 if ((prefix == null) || (prefix.length() == 0)) {
74 defaultNamespace = namespace;
75 }
76 }
77
78 /**
79 * Pops the most recently used <code>Namespace</code> from the stack
80 *
81 * @return Namespace popped from the stack
82 */
83 public Namespace pop() {
84 return remove(namespaceStack.size() - 1);
85 }
86
87 /**
88 * DOCUMENT ME!
89 *
90 * @return the number of namespaces on the stackce stack.
91 */
92 public int size() {
93 return namespaceStack.size();
94 }
95
96 /**
97 * Clears the stack
98 */
99 public void clear() {
100 namespaceStack.clear();
101 namespaceCacheList.clear();
102 rootNamespaceCache.clear();
103 currentNamespaceCache = null;
104 }
105
106 /**
107 * DOCUMENT ME!
108 *
109 * @param index
110 * DOCUMENT ME!
111 *
112 * @return the namespace at the specified index on the stack
113 */
114 public Namespace getNamespace(int index) {
115 return (Namespace) namespaceStack.get(index);
116 }
117
118 /**
119 * DOCUMENT ME!
120 *
121 * @param prefix
122 * DOCUMENT ME!
123 *
124 * @return the namespace for the given prefix or null if it could not be
125 * found.
126 */
127 public Namespace getNamespaceForPrefix(String prefix) {
128 if (prefix == null) {
129 prefix = "";
130 }
131
132 for (int i = namespaceStack.size() - 1; i >= 0; i--) {
133 Namespace namespace = (Namespace) namespaceStack.get(i);
134
135 if (prefix.equals(namespace.getPrefix())) {
136 return namespace;
137 }
138 }
139
140 return null;
141 }
142
143 /**
144 * DOCUMENT ME!
145 *
146 * @param prefix
147 * DOCUMENT ME!
148 *
149 * @return the URI for the given prefix or null if it could not be found.
150 */
151 public String getURI(String prefix) {
152 Namespace namespace = getNamespaceForPrefix(prefix);
153
154 return (namespace != null) ? namespace.getURI() : null;
155 }
156
157 /**
158 * DOCUMENT ME!
159 *
160 * @param namespace
161 * DOCUMENT ME!
162 *
163 * @return true if the given prefix is in the stack.
164 */
165 public boolean contains(Namespace namespace) {
166 String prefix = namespace.getPrefix();
167 Namespace current = null;
168
169 if ((prefix == null) || (prefix.length() == 0)) {
170 current = getDefaultNamespace();
171 } else {
172 current = getNamespaceForPrefix(prefix);
173 }
174
175 if (current == null) {
176 return false;
177 }
178
179 if (current == namespace) {
180 return true;
181 }
182
183 return namespace.getURI().equals(current.getURI());
184 }
185
186 public QName getQName(String namespaceURI, String localName,
187 String qualifiedName) {
188 if (localName == null) {
189 localName = qualifiedName;
190 } else if (qualifiedName == null) {
191 qualifiedName = localName;
192 }
193
194 if (namespaceURI == null) {
195 namespaceURI = "";
196 }
197
198 String prefix = "";
199 int index = qualifiedName.indexOf(":");
200
201 if (index > 0) {
202 prefix = qualifiedName.substring(0, index);
203
204 if (localName.trim().length() == 0) {
205 localName = qualifiedName.substring(index + 1);
206 }
207 } else if (localName.trim().length() == 0) {
208 localName = qualifiedName;
209 }
210
211 Namespace namespace = createNamespace(prefix, namespaceURI);
212
213 return pushQName(localName, qualifiedName, namespace, prefix);
214 }
215
216 public QName getAttributeQName(String namespaceURI, String localName,
217 String qualifiedName) {
218 if (qualifiedName == null) {
219 qualifiedName = localName;
220 }
221
222 Map map = getNamespaceCache();
223 QName answer = (QName) map.get(qualifiedName);
224
225 if (answer != null) {
226 return answer;
227 }
228
229 if (localName == null) {
230 localName = qualifiedName;
231 }
232
233 if (namespaceURI == null) {
234 namespaceURI = "";
235 }
236
237 Namespace namespace = null;
238 String prefix = "";
239 int index = qualifiedName.indexOf(":");
240
241 if (index > 0) {
242 prefix = qualifiedName.substring(0, index);
243 namespace = createNamespace(prefix, namespaceURI);
244
245 if (localName.trim().length() == 0) {
246 localName = qualifiedName.substring(index + 1);
247 }
248 } else {
249 // attributes with no prefix have no namespace
250 namespace = Namespace.NO_NAMESPACE;
251
252 if (localName.trim().length() == 0) {
253 localName = qualifiedName;
254 }
255 }
256
257 answer = pushQName(localName, qualifiedName, namespace, prefix);
258 map.put(qualifiedName, answer);
259
260 return answer;
261 }
262
263 /**
264 * Adds a namepace to the stack with the given prefix and URI
265 *
266 * @param prefix
267 * DOCUMENT ME!
268 * @param uri
269 * DOCUMENT ME!
270 */
271 public void push(String prefix, String uri) {
272 if (uri == null) {
273 uri = "";
274 }
275
276 Namespace namespace = createNamespace(prefix, uri);
277 push(namespace);
278 }
279
280 /**
281 * Adds a new namespace to the stack
282 *
283 * @param prefix
284 * DOCUMENT ME!
285 * @param uri
286 * DOCUMENT ME!
287 *
288 * @return DOCUMENT ME!
289 */
290 public Namespace addNamespace(String prefix, String uri) {
291 Namespace namespace = createNamespace(prefix, uri);
292 push(namespace);
293
294 return namespace;
295 }
296
297 /**
298 * Pops a namepace from the stack with the given prefix and URI
299 *
300 * @param prefix
301 * DOCUMENT ME!
302 *
303 * @return DOCUMENT ME!
304 */
305 public Namespace pop(String prefix) {
306 if (prefix == null) {
307 prefix = "";
308 }
309
310 Namespace namespace = null;
311
312 for (int i = namespaceStack.size() - 1; i >= 0; i--) {
313 Namespace ns = (Namespace) namespaceStack.get(i);
314
315 if (prefix.equals(ns.getPrefix())) {
316 remove(i);
317 namespace = ns;
318
319 break;
320 }
321 }
322
323 if (namespace == null) {
324 System.out.println("Warning: missing namespace prefix ignored: "
325 + prefix);
326 }
327
328 return namespace;
329 }
330
331 public String toString() {
332 return super.toString() + " Stack: " + namespaceStack.toString();
333 }
334
335 public DocumentFactory getDocumentFactory() {
336 return documentFactory;
337 }
338
339 public void setDocumentFactory(DocumentFactory documentFactory) {
340 this.documentFactory = documentFactory;
341 }
342
343 public Namespace getDefaultNamespace() {
344 if (defaultNamespace == null) {
345 defaultNamespace = findDefaultNamespace();
346 }
347
348 return defaultNamespace;
349 }
350
351 // Implementation methods
352 // -------------------------------------------------------------------------
353
354 /**
355 * Adds the QName to the stack of available QNames
356 *
357 * @param localName
358 * DOCUMENT ME!
359 * @param qualifiedName
360 * DOCUMENT ME!
361 * @param namespace
362 * DOCUMENT ME!
363 * @param prefix
364 * DOCUMENT ME!
365 *
366 * @return DOCUMENT ME!
367 */
368 protected QName pushQName(String localName, String qualifiedName,
369 Namespace namespace, String prefix) {
370 if ((prefix == null) || (prefix.length() == 0)) {
371 this.defaultNamespace = null;
372 }
373
374 return createQName(localName, qualifiedName, namespace);
375 }
376
377 /**
378 * Factory method to creeate new QName instances. By default this method
379 * interns the QName
380 *
381 * @param localName
382 * DOCUMENT ME!
383 * @param qualifiedName
384 * DOCUMENT ME!
385 * @param namespace
386 * DOCUMENT ME!
387 *
388 * @return DOCUMENT ME!
389 */
390 protected QName createQName(String localName, String qualifiedName,
391 Namespace namespace) {
392 return documentFactory.createQName(localName, namespace);
393 }
394
395 /**
396 * Factory method to creeate new Namespace instances. By default this method
397 * interns the Namespace
398 *
399 * @param prefix
400 * DOCUMENT ME!
401 * @param namespaceURI
402 * DOCUMENT ME!
403 *
404 * @return DOCUMENT ME!
405 */
406 protected Namespace createNamespace(String prefix, String namespaceURI) {
407 return documentFactory.createNamespace(prefix, namespaceURI);
408 }
409
410 /**
411 * Attempts to find the current default namespace on the stack right now or
412 * returns null if one could not be found
413 *
414 * @return DOCUMENT ME!
415 */
416 protected Namespace findDefaultNamespace() {
417 for (int i = namespaceStack.size() - 1; i >= 0; i--) {
418 Namespace namespace = (Namespace) namespaceStack.get(i);
419
420 if (namespace != null) {
421 String prefix = namespace.getPrefix();
422
423 if ((prefix == null) || (namespace.getPrefix().length() == 0)) {
424 return namespace;
425 }
426 }
427 }
428
429 return null;
430 }
431
432 /**
433 * Removes the namespace at the given index of the stack
434 *
435 * @param index
436 * DOCUMENT ME!
437 *
438 * @return DOCUMENT ME!
439 */
440 protected Namespace remove(int index) {
441 Namespace namespace = (Namespace) namespaceStack.remove(index);
442 namespaceCacheList.remove(index);
443 defaultNamespace = null;
444 currentNamespaceCache = null;
445
446 return namespace;
447 }
448
449 protected Map getNamespaceCache() {
450 if (currentNamespaceCache == null) {
451 int index = namespaceStack.size() - 1;
452
453 if (index < 0) {
454 currentNamespaceCache = rootNamespaceCache;
455 } else {
456 currentNamespaceCache = (Map) namespaceCacheList.get(index);
457
458 if (currentNamespaceCache == null) {
459 currentNamespaceCache = new HashMap();
460 namespaceCacheList.set(index, currentNamespaceCache);
461 }
462 }
463 }
464
465 return currentNamespaceCache;
466 }
467 }
468
469 /*
470 * Redistribution and use of this software and associated documentation
471 * ("Software"), with or without modification, are permitted provided that the
472 * following conditions are met:
473 *
474 * 1. Redistributions of source code must retain copyright statements and
475 * notices. Redistributions must also contain a copy of this document.
476 *
477 * 2. Redistributions in binary form must reproduce the above copyright notice,
478 * this list of conditions and the following disclaimer in the documentation
479 * and/or other materials provided with the distribution.
480 *
481 * 3. The name "DOM4J" must not be used to endorse or promote products derived
482 * from this Software without prior written permission of MetaStuff, Ltd. For
483 * written permission, please contact dom4j-info@metastuff.com.
484 *
485 * 4. Products derived from this Software may not be called "DOM4J" nor may
486 * "DOM4J" appear in their names without prior written permission of MetaStuff,
487 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
488 *
489 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
490 *
491 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
492 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
493 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
494 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
495 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
496 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
497 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
498 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
499 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
500 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
501 * POSSIBILITY OF SUCH DAMAGE.
502 *
503 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
504 */