1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * @LastModified: Nov 2017 4 */ 5 /* 6 * Licensed to the Apache Software Foundation (ASF) under one or more 7 * contributor license agreements. See the NOTICE file distributed with 8 * this work for additional information regarding copyright ownership. 9 * The ASF licenses this file to You under the Apache License, Version 2.0 10 * (the "License"); you may not use this file except in compliance with 11 * the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 */ 21 22 package com.sun.org.apache.xml.internal.serializer; 23 24 import java.util.HashMap; 25 import java.util.Iterator; 26 import java.util.Stack; 27 import org.xml.sax.ContentHandler; 28 import org.xml.sax.SAXException; 29 30 /** 31 * This class keeps track of the currently defined namespaces. Conceptually the 32 * prefix/uri/depth triplets are pushed on a stack pushed on a stack. The depth 33 * indicates the nesting depth of the element for which the mapping was made. 34 * 35 * <p>For example: 36 * <pre> 37 * <chapter xmlns:p1="def"> 38 * <paragraph xmlns:p2="ghi"> 39 * <sentance xmlns:p3="jkl"> 40 * </sentance> 41 * </paragraph> 42 * <paragraph xlmns:p4="mno"> 43 * </paragraph> 44 * </chapter> 45 * </pre> 46 * 47 * When the <chapter> element is encounted the prefix "p1" associated with uri 48 * "def" is pushed on the stack with depth 1. 49 * When the first <paragraph> is encountered "p2" and "ghi" are pushed with 50 * depth 2. 51 * When the <sentance> is encountered "p3" and "jkl" are pushed with depth 3. 52 * When </sentance> occurs the popNamespaces(3) will pop "p3"/"jkl" off the 53 * stack. Of course popNamespaces(2) would pop anything with depth 2 or 54 * greater. 55 * 56 * So prefix/uri pairs are pushed and poped off the stack as elements are 57 * processed. At any given moment of processing the currently visible prefixes 58 * are on the stack and a prefix can be found given a uri, or a uri can be found 59 * given a prefix. 60 * 61 * This class is public only because it is used by Xalan. It is not a public API 62 * 63 * @xsl.usage internal 64 */ 65 public class NamespaceMappings 66 { 67 /** 68 * This member is continually incremented when new prefixes need to be 69 * generated. ("ns0" "ns1" ...) 70 */ 71 private int count; 72 73 /** 74 * Each entry (prefix) in this hashmap points to a Stack of URIs 75 * This maps a prefix (String) to a Stack of prefix mappings. 76 * All mappings in that retrieved stack have the same prefix, 77 * though possibly different URI's or depths. Such a stack must have 78 * mappings at deeper depths push later on such a stack. Mappings pushed 79 * earlier on the stack will have smaller values for MappingRecord.m_declarationDepth. 80 */ 81 private HashMap<String, Stack<MappingRecord>> m_namespaces = new HashMap<>(); 82 83 /** 84 * The top of this stack contains the MapRecord 85 * of the last declared a namespace. 86 * Used to know how many prefix mappings to pop when leaving 87 * the current element depth. 88 * For every prefix mapping the current element depth is 89 * pushed on this stack. 90 * That way all prefixes pushed at the current depth can be 91 * removed at the same time. 92 * Used to ensure prefix/uri map scopes are closed correctly 93 * 94 */ 95 private Stack<MappingRecord> 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 */ 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 116 117 // Define the default namespace (initially maps to "" uri) 118 Stack<MappingRecord> stack; 119 m_namespaces.put(EMPTYSTRING, stack = new Stack<>()); 120 stack.push(new MappingRecord(EMPTYSTRING,EMPTYSTRING,0)); 121 122 m_namespaces.put(XML_PREFIX, stack = new Stack<>()); 123 stack.push(new MappingRecord( XML_PREFIX, 124 "http://www.w3.org/XML/1998/namespace",0)); 125 126 m_nodeStack.push(new MappingRecord(null,null,-1)); 127 128 } 129 130 /** 131 * Use a namespace prefix to lookup a namespace URI. 132 * 133 * @param prefix String the prefix of the namespace 134 * @return the URI corresponding to the prefix 135 */ 136 public String lookupNamespace(String prefix) 137 { 138 final Stack<MappingRecord> stack = m_namespaces.get(prefix); 139 return stack != null && !stack.isEmpty() ? (stack.peek()).m_uri : null; 140 } 141 142 MappingRecord getMappingFromPrefix(String prefix) { 143 final Stack<MappingRecord> stack = m_namespaces.get(prefix); 144 return stack != null && !stack.isEmpty() ? (stack.peek()) : null; 145 } 146 147 /** 148 * Given a namespace uri, and the namespaces mappings for the 149 * current element, return the current prefix for that uri. 150 * 151 * @param uri the namespace URI to be search for 152 * @return an existing prefix that maps to the given URI, null if no prefix 153 * maps to the given namespace URI. 154 */ 155 public String lookupPrefix(String uri) 156 { 157 String foundPrefix = null; 158 Iterator<String> itr = m_namespaces.keySet().iterator(); 159 while (itr.hasNext()) { 160 String prefix = itr.next(); 161 String uri2 = lookupNamespace(prefix); 162 if (uri2 != null && uri2.equals(uri)) 163 { 164 foundPrefix = prefix; 165 break; 166 } 167 } 168 return foundPrefix; 169 } 170 171 MappingRecord getMappingFromURI(String uri) 172 { 173 MappingRecord foundMap = null; 174 Iterator<String> itr = m_namespaces.keySet().iterator(); 175 while (itr.hasNext()) 176 { 177 String prefix = itr.next(); 178 MappingRecord map2 = getMappingFromPrefix(prefix); 179 if (map2 != null && (map2.m_uri).equals(uri)) 180 { 181 foundMap = map2; 182 break; 183 } 184 } 185 return foundMap; 186 } 187 188 /** 189 * Undeclare the namespace that is currently pointed to by a given prefix 190 */ 191 boolean popNamespace(String prefix) 192 { 193 // Prefixes "xml" and "xmlns" cannot be redefined 194 if (prefix.startsWith(XML_PREFIX)) 195 { 196 return false; 197 } 198 199 Stack<MappingRecord> stack; 200 if ((stack = m_namespaces.get(prefix)) != null) 201 { 202 stack.pop(); 203 return true; 204 } 205 return false; 206 } 207 208 /** 209 * Declare a mapping of a prefix to namespace URI at the given element depth. 210 * @param prefix a String with the prefix for a qualified name 211 * @param uri a String with the uri to which the prefix is to map 212 * @param elemDepth the depth of current declaration 213 */ 214 boolean pushNamespace(String prefix, String uri, int elemDepth) 215 { 216 // Prefixes "xml" and "xmlns" cannot be redefined 217 if (prefix.startsWith(XML_PREFIX)) 218 { 219 return false; 220 } 221 222 Stack<MappingRecord> stack; 223 // Get the stack that contains URIs for the specified prefix 224 if ((stack = m_namespaces.get(prefix)) == null) 225 { 226 m_namespaces.put(prefix, stack = new Stack<>()); 227 } 228 229 if (!stack.empty() && uri.equals((stack.peek()).m_uri)) 230 { 231 return false; 232 } 233 MappingRecord map = new MappingRecord(prefix,uri,elemDepth); 234 stack.push(map); 235 m_nodeStack.push(map); 236 return true; 237 } 238 239 /** 240 * Pop, or undeclare all namespace definitions that are currently 241 * declared at the given element depth, or deepter. 242 * @param elemDepth the element depth for which mappings declared at this 243 * depth or deeper will no longer be valid 244 * @param saxHandler The ContentHandler to notify of any endPrefixMapping() 245 * calls. This parameter can be null. 246 */ 247 void popNamespaces(int elemDepth, ContentHandler saxHandler) 248 { 249 while (true) 250 { 251 if (m_nodeStack.isEmpty()) 252 return; 253 MappingRecord map = m_nodeStack.peek(); 254 int depth = map.m_declarationDepth; 255 if (depth < elemDepth) 256 return; 257 /* the depth of the declared mapping is elemDepth or deeper 258 * so get rid of it 259 */ 260 261 map = m_nodeStack.pop(); 262 final String prefix = map.m_prefix; 263 popNamespace(prefix); 264 if (saxHandler != null) 265 { 266 try 267 { 268 saxHandler.endPrefixMapping(prefix); 269 } 270 catch (SAXException e) 271 { 272 // not much we can do if they aren't willing to listen 273 } 274 } 275 276 } 277 } 278 279 /** 280 * Generate a new namespace prefix ( ns0, ns1 ...) not used before 281 * @return String a new namespace prefix ( ns0, ns1, ns2 ...) 282 */ 283 public String generateNextPrefix() 284 { 285 return "ns" + (count++); 286 } 287 288 289 /** 290 * This method makes a clone of this object. 291 * 292 */ 293 @SuppressWarnings("unchecked") 294 public Object clone() throws CloneNotSupportedException { 295 NamespaceMappings clone = new NamespaceMappings(); 296 clone.m_nodeStack = (Stack<MappingRecord>) m_nodeStack.clone(); 297 clone.m_namespaces = (HashMap<String, Stack<MappingRecord>>) m_namespaces.clone(); 298 clone.count = count; 299 return clone; 300 301 } 302 303 final void reset() 304 { 305 this.count = 0; 306 this.m_namespaces.clear(); 307 this.m_nodeStack.clear(); 308 initNamespaces(); 309 } 310 311 class MappingRecord { 312 final String m_prefix; // the prefix 313 final String m_uri; // the uri 314 // the depth of the element where declartion was made 315 final int m_declarationDepth; 316 MappingRecord(String prefix, String uri, int depth) { 317 m_prefix = prefix; 318 m_uri = uri; 319 m_declarationDepth = depth; 320 321 } 322 } 323 324 }