1 /*
2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
3  */
4 /*
5  * Licensed to the Apache Software Foundation (ASF) under one or more
6  * contributor license agreements.  See the NOTICE file distributed with
7  * this work for additional information regarding copyright ownership.
8  * The ASF licenses this file to You under the Apache License, Version 2.0
9  * (the "License"); you may not use this file except in compliance with
10  * the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 package com.sun.org.apache.xml.internal.serializer;
22 
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.Stack;
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 public only because it is used by Xalan. It is not a public API
61  *
62  * @xsl.usage internal
63  * @LastModified: Nov 2017
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      */
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 
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      */
lookupNamespace(String prefix)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 
getMappingFromPrefix(String prefix)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      */
lookupPrefix(String uri)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 
getMappingFromURI(String uri)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      */
popNamespace(String prefix)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      */
pushNamespace(String prefix, String uri, int elemDepth)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      */
popNamespaces(int elemDepth, ContentHandler saxHandler)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      */
generateNextPrefix()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")
clone()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 
reset()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;
MappingRecord(String prefix, String uri, int depth)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 }
325