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