001/* 002 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 003 * 004 * This software is open source. 005 * See the bottom of this file for the licence. 006 * 007 * $Id: NamespaceStack.java,v 1.6 2001/09/20 10:48:18 jstrachan Exp $ 008 */ 009 010package org.dom4j.tree; 011 012import java.util.ArrayList; 013import java.util.HashMap; 014import java.util.Map; 015import java.util.List; 016 017import org.dom4j.DocumentFactory; 018import org.dom4j.Namespace; 019import org.dom4j.QName; 020 021/** NamespaceStack implements a stack of namespaces and optionally 022 * maintains a cache of all the fully qualified names (<code>QName</code>) 023 * which are in scope. This is useful when building or navigating a <i>dom4j</i> 024 * document. 025 * 026 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 027 * @version $Revision: 1.6 $ 028 */ 029public class NamespaceStack { 030 031 /** The factory used to create new <code>Namespace</code> instances */ 032 private DocumentFactory documentFactory; 033 034 /** The Stack of namespaces */ 035 private ArrayList namespaceStack = new ArrayList(); 036 037 /** The cache of qualifiedNames to QNames per namespace context */ 038 private ArrayList namespaceCacheList = new ArrayList(); 039 040 /** A cache of current namespace context cache of mapping from qualifiedName to QName */ 041 private Map currentNamespaceCache; 042 043 /** A cache of mapping from qualifiedName to QName before any namespaces are declared */ 044 private Map rootNamespaceCache = new HashMap(); 045 046 047 /** Caches the default namespace defined via xmlns="" */ 048 private Namespace defaultNamespace; 049 050 051 public NamespaceStack() { 052 this.documentFactory = DocumentFactory.getInstance(); 053 } 054 055 public NamespaceStack(DocumentFactory documentFactory) { 056 this.documentFactory = documentFactory; 057 } 058 059 /** Pushes the given namespace onto the stack so that its prefix 060 * becomes available. 061 * 062 * @param namespace is the <code>Namespace</code> to add to the stack. 063 */ 064 public void push(Namespace namespace) { 065 namespaceStack.add( namespace ); 066 namespaceCacheList.add( null ); 067 currentNamespaceCache = null; 068 String prefix = namespace.getPrefix(); 069 if ( prefix == null || prefix.length() == 0 ) { 070 defaultNamespace = namespace; 071 } 072 } 073 074 /** Pops the most recently used <code>Namespace</code> from 075 * the stack 076 * 077 * @return Namespace popped from the stack 078 */ 079 public Namespace pop() { 080 return remove( namespaceStack.size() - 1 ); 081 } 082 083 /** @return the number of namespaces on the stackce stack. 084 */ 085 public int size() { 086 return namespaceStack.size(); 087 } 088 089 /** Clears the stack 090 */ 091 public void clear() { 092 namespaceStack.clear(); 093 namespaceCacheList.clear(); 094 rootNamespaceCache.clear(); 095 currentNamespaceCache = null; 096 } 097 098 /** @return the namespace at the specified index on the stack 099 */ 100 public Namespace getNamespace( int index ) { 101 return (Namespace) namespaceStack.get( index ); 102 } 103 104 /** @return the namespace for the given prefix or null 105 * if it could not be found. 106 */ 107 public Namespace getNamespaceForPrefix( String prefix ) { 108 if ( prefix == null ) { 109 prefix = ""; 110 } 111 for ( int i = namespaceStack.size() - 1; i >= 0; i-- ) { 112 Namespace namespace = (Namespace) namespaceStack.get(i); 113 if ( prefix.equals( namespace.getPrefix() ) ) { 114 return namespace; 115 } 116 } 117 return null; 118 } 119 120 /** @return the URI for the given prefix or null if it 121 * could not be found. 122 */ 123 public String getURI( String prefix ) { 124 Namespace namespace = getNamespaceForPrefix( prefix ); 125 return ( namespace != null ) ? namespace.getURI() : null; 126 } 127 128 /** @return true if the given prefix is in the stack. 129 */ 130 public boolean contains( Namespace namespace ) { 131 String prefix = namespace.getPrefix(); 132 Namespace current = null; 133 if ( prefix == null || prefix.length() == 0 ) { 134 current = getDefaultNamespace(); 135 } 136 else { 137 current = getNamespaceForPrefix( prefix ); 138 } 139 if ( current == null ) { 140 return false; 141 } 142 if ( current == namespace ) { 143 return true; 144 } 145 return namespace.getURI().equals( current.getURI() ); 146 } 147 148 public QName getQName( String namespaceURI, String localName, String qualifiedName ) { 149 if ( localName == null ) { 150 localName = qualifiedName; 151 } 152 else if ( qualifiedName == null ) { 153 qualifiedName = localName; 154 } 155 if ( namespaceURI == null ) { 156 namespaceURI = ""; 157 } 158 String prefix = ""; 159 int index = qualifiedName.indexOf(":"); 160 if (index > 0) { 161 prefix = qualifiedName.substring(0, index); 162 } 163 Namespace namespace = createNamespace( prefix, namespaceURI ); 164 return pushQName( localName, qualifiedName, namespace, prefix ); 165 } 166 167 public QName getAttributeQName( String namespaceURI, String localName, String qualifiedName ) { 168 if ( qualifiedName == null ) { 169 qualifiedName = localName; 170 } 171 Map map = getNamespaceCache(); 172 QName answer = (QName) map.get( qualifiedName ); 173 if ( answer != null ) { 174 return answer; 175 } 176 if ( localName == null ) { 177 localName = qualifiedName; 178 } 179 if ( namespaceURI == null ) { 180 namespaceURI = ""; 181 } 182 Namespace namespace = null; 183 String prefix = ""; 184 int index = qualifiedName.indexOf(":"); 185 if (index > 0) { 186 prefix = qualifiedName.substring(0, index); 187 namespace = createNamespace( prefix, namespaceURI ); 188 } 189 else { 190 // attributes with no prefix have no namespace 191 namespace = Namespace.NO_NAMESPACE; 192 } 193 answer = pushQName( localName, qualifiedName, namespace, prefix ); 194 map.put( qualifiedName, answer ); 195 return answer; 196 } 197 198 /** Adds a namepace to the stack with the given prefix and URI */ 199 public void push( String prefix, String uri ) { 200 if ( uri == null ) { 201 uri = ""; 202 } 203 Namespace namespace = createNamespace( prefix, uri ); 204 push( namespace ); 205 } 206 207 /** Adds a new namespace to the stack */ 208 public Namespace addNamespace( String prefix, String uri ) { 209 Namespace namespace = createNamespace( prefix, uri ); 210 push( namespace ); 211 return namespace; 212 } 213 214 /** Pops a namepace from the stack with the given prefix and URI */ 215 public Namespace pop( String prefix ) { 216 if ( prefix == null ) { 217 prefix = ""; 218 } 219 Namespace namespace = null; 220 for (int i = namespaceStack.size() - 1; i >= 0; i-- ) { 221 Namespace ns = (Namespace) namespaceStack.get(i); 222 if ( prefix.equals( ns.getPrefix() ) ) { 223 remove(i); 224 namespace = ns; 225 break; 226 } 227 } 228 if ( namespace == null ) { 229 System.out.println( "Warning: missing namespace prefix ignored: " + prefix ); 230 } 231 return namespace; 232 } 233 234 public String toString() { 235 return super.toString() + " Stack: " + namespaceStack.toString(); 236 } 237 238 public DocumentFactory getDocumentFactory() { 239 return documentFactory; 240 } 241 242 public void setDocumentFactory(DocumentFactory documentFactory) { 243 this.documentFactory = documentFactory; 244 } 245 246 public Namespace getDefaultNamespace() { 247 if ( defaultNamespace == null ) { 248 defaultNamespace = findDefaultNamespace(); 249 } 250 return defaultNamespace; 251 } 252 253 // Implementation methods 254 //------------------------------------------------------------------------- 255 256 /** Adds the QName to the stack of available QNames 257 */ 258 protected QName pushQName( String localName, String qualifiedName, Namespace namespace, String prefix ) { 259 if ( prefix == null || prefix.length() == 0 ) { 260 this.defaultNamespace = null; 261 } 262 return createQName( localName, qualifiedName, namespace ); 263 } 264 265 /** Factory method to creeate new QName instances. By default this method 266 * interns the QName 267 */ 268 protected QName createQName( String localName, String qualifiedName, Namespace namespace ) { 269 return documentFactory.createQName( localName, namespace ); 270 } 271 272 /** Factory method to creeate new Namespace instances. By default this method 273 * interns the Namespace 274 */ 275 protected Namespace createNamespace( String prefix, String namespaceURI ) { 276 return documentFactory.createNamespace( prefix, namespaceURI ); 277 } 278 279 /** Attempts to find the current default namespace on the stack right now or returns null if one 280 * could not be found 281 */ 282 protected Namespace findDefaultNamespace() { 283 for ( int i = namespaceStack.size() - 1; i >= 0; i-- ) { 284 Namespace namespace = (Namespace) namespaceStack.get(i); 285 if ( namespace != null ) { 286 String prefix = namespace.getPrefix(); 287 if ( prefix == null || namespace.getPrefix().length() == 0 ) { 288 return namespace; 289 } 290 } 291 } 292 return null; 293 } 294 295 /** Removes the namespace at the given index of the stack */ 296 protected Namespace remove(int index) { 297 Namespace namespace = (Namespace) namespaceStack.remove(index); 298 namespaceCacheList.remove(index); 299 defaultNamespace = null; 300 currentNamespaceCache = null; 301 return namespace; 302 } 303 304 protected Map getNamespaceCache() { 305 if ( currentNamespaceCache == null ) { 306 int index = namespaceStack.size() - 1; 307 if ( index < 0 ) { 308 currentNamespaceCache = rootNamespaceCache; 309 } 310 else { 311 currentNamespaceCache = (Map) namespaceCacheList.get(index); 312 if ( currentNamespaceCache == null ) { 313 currentNamespaceCache = new HashMap(); 314 namespaceCacheList.set(index, currentNamespaceCache); 315 } 316 } 317 } 318 return currentNamespaceCache; 319 } 320} 321 322 323 324 325/* 326 * Redistribution and use of this software and associated documentation 327 * ("Software"), with or without modification, are permitted provided 328 * that the following conditions are met: 329 * 330 * 1. Redistributions of source code must retain copyright 331 * statements and notices. Redistributions must also contain a 332 * copy of this document. 333 * 334 * 2. Redistributions in binary form must reproduce the 335 * above copyright notice, this list of conditions and the 336 * following disclaimer in the documentation and/or other 337 * materials provided with the distribution. 338 * 339 * 3. The name "DOM4J" must not be used to endorse or promote 340 * products derived from this Software without prior written 341 * permission of MetaStuff, Ltd. For written permission, 342 * please contact dom4j-info@metastuff.com. 343 * 344 * 4. Products derived from this Software may not be called "DOM4J" 345 * nor may "DOM4J" appear in their names without prior written 346 * permission of MetaStuff, Ltd. DOM4J is a registered 347 * trademark of MetaStuff, Ltd. 348 * 349 * 5. Due credit should be given to the DOM4J Project 350 * (http://dom4j.org/). 351 * 352 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS 353 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 354 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 355 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 356 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 357 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 358 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 359 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 360 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 361 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 362 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 363 * OF THE POSSIBILITY OF SUCH DAMAGE. 364 * 365 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 366 * 367 * $Id: NamespaceStack.java,v 1.6 2001/09/20 10:48:18 jstrachan Exp $ 368 */