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 }