| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * $Id: NamespaceMappings.java 469648 2006-10-31 20:52:27Z minchau $ |
| */ |
| package org.apache.xml.serializer; |
| |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * This class keeps track of the currently defined namespaces. Conceptually the |
| * prefix/uri/depth triplets are pushed on a stack pushed on a stack. The depth |
| * indicates the nesting depth of the element for which the mapping was made. |
| * |
| * <p>For example: |
| * <pre> |
| * <chapter xmlns:p1="def"> |
| * <paragraph xmlns:p2="ghi"> |
| * <sentance xmlns:p3="jkl"> |
| * </sentance> |
| * </paragraph> |
| * <paragraph xlmns:p4="mno"> |
| * </paragraph> |
| * </chapter> |
| * </pre> |
| * |
| * When the <chapter> element is encounted the prefix "p1" associated with uri |
| * "def" is pushed on the stack with depth 1. |
| * When the first <paragraph> is encountered "p2" and "ghi" are pushed with |
| * depth 2. |
| * When the <sentance> is encountered "p3" and "jkl" are pushed with depth 3. |
| * When </sentance> occurs the popNamespaces(3) will pop "p3"/"jkl" off the |
| * stack. Of course popNamespaces(2) would pop anything with depth 2 or |
| * greater. |
| * |
| * So prefix/uri pairs are pushed and poped off the stack as elements are |
| * processed. At any given moment of processing the currently visible prefixes |
| * are on the stack and a prefix can be found given a uri, or a uri can be found |
| * given a prefix. |
| * |
| * This class is intended for internal use only. However, it is made public because |
| * other packages require it. |
| * @xsl.usage internal |
| */ |
| public class NamespaceMappings |
| { |
| /** |
| * This member is continually incremented when new prefixes need to be |
| * generated. ("ns0" "ns1" ...) |
| */ |
| private int count = 0; |
| |
| /** |
| * Each entry (prefix) in this hashtable points to a Stack of URIs |
| * This table maps a prefix (String) to a Stack of NamespaceNodes. |
| * All Namespace nodes in that retrieved stack have the same prefix, |
| * though possibly different URI's or depths. Such a stack must have |
| * mappings at deeper depths push later on such a stack. Mappings pushed |
| * earlier on the stack will have smaller values for MappingRecord.m_declarationDepth. |
| */ |
| private Hashtable m_namespaces = new Hashtable(); |
| |
| /** |
| * This stack is used as a convenience. |
| * It contains the pushed NamespaceNodes (shallowest |
| * to deepest) and is used to delete NamespaceNodes |
| * when leaving the current element depth |
| * to returning to the parent. The mappings of the deepest |
| * depth can be popped of the top and the same node |
| * can be removed from the appropriate prefix stack. |
| * |
| * All prefixes pushed at the current depth can be |
| * removed at the same time by using this stack to |
| * ensure prefix/uri map scopes are closed correctly. |
| */ |
| private Stack m_nodeStack = new Stack(); |
| |
| private static final String EMPTYSTRING = ""; |
| private static final String XML_PREFIX = "xml"; // was "xmlns" |
| |
| /** |
| * Default constructor |
| * @see java.lang.Object#Object() |
| */ |
| public NamespaceMappings() |
| { |
| initNamespaces(); |
| } |
| |
| /** |
| * This method initializes the namespace object with appropriate stacks |
| * and predefines a few prefix/uri pairs which always exist. |
| */ |
| private void initNamespaces() |
| { |
| // The initial prefix mappings will never be deleted because they are at element depth -1 |
| // (a kludge) |
| |
| // Define the default namespace (initially maps to "" uri) |
| Stack stack; |
| MappingRecord nn; |
| nn = new MappingRecord(EMPTYSTRING, EMPTYSTRING, -1); |
| stack = createPrefixStack(EMPTYSTRING); |
| stack.push(nn); |
| |
| // define "xml" namespace |
| nn = new MappingRecord(XML_PREFIX, "http://www.w3.org/XML/1998/namespace", -1); |
| stack = createPrefixStack(XML_PREFIX); |
| stack.push(nn); |
| } |
| |
| /** |
| * Use a namespace prefix to lookup a namespace URI. |
| * |
| * @param prefix String the prefix of the namespace |
| * @return the URI corresponding to the prefix, returns "" |
| * if there is no visible mapping. |
| */ |
| public String lookupNamespace(String prefix) |
| { |
| String uri = null; |
| final Stack stack = getPrefixStack(prefix); |
| if (stack != null && !stack.isEmpty()) { |
| uri = ((MappingRecord) stack.peek()).m_uri; |
| } |
| if (uri == null) |
| uri = EMPTYSTRING; |
| return uri; |
| } |
| |
| |
| MappingRecord getMappingFromPrefix(String prefix) { |
| final Stack stack = (Stack) m_namespaces.get(prefix); |
| return stack != null && !stack.isEmpty() ? |
| ((MappingRecord) stack.peek()) : null; |
| } |
| |
| /** |
| * Given a namespace uri, and the namespaces mappings for the |
| * current element, return the current prefix for that uri. |
| * |
| * @param uri the namespace URI to be search for |
| * @return an existing prefix that maps to the given URI, null if no prefix |
| * maps to the given namespace URI. |
| */ |
| public String lookupPrefix(String uri) |
| { |
| String foundPrefix = null; |
| Enumeration prefixes = m_namespaces.keys(); |
| while (prefixes.hasMoreElements()) |
| { |
| String prefix = (String) prefixes.nextElement(); |
| String uri2 = lookupNamespace(prefix); |
| if (uri2 != null && uri2.equals(uri)) |
| { |
| foundPrefix = prefix; |
| break; |
| } |
| } |
| return foundPrefix; |
| } |
| |
| MappingRecord getMappingFromURI(String uri) |
| { |
| MappingRecord foundMap = null; |
| Enumeration prefixes = m_namespaces.keys(); |
| while (prefixes.hasMoreElements()) |
| { |
| String prefix = (String) prefixes.nextElement(); |
| MappingRecord map2 = getMappingFromPrefix(prefix); |
| if (map2 != null && (map2.m_uri).equals(uri)) |
| { |
| foundMap = map2; |
| break; |
| } |
| } |
| return foundMap; |
| } |
| |
| /** |
| * Undeclare the namespace that is currently pointed to by a given prefix |
| */ |
| boolean popNamespace(String prefix) |
| { |
| // Prefixes "xml" and "xmlns" cannot be redefined |
| if (prefix.startsWith(XML_PREFIX)) |
| { |
| return false; |
| } |
| |
| Stack stack; |
| if ((stack = getPrefixStack(prefix)) != null) |
| { |
| stack.pop(); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Declare a mapping of a prefix to namespace URI at the given element depth. |
| * @param prefix a String with the prefix for a qualified name |
| * @param uri a String with the uri to which the prefix is to map |
| * @param elemDepth the depth of current declaration |
| */ |
| public boolean pushNamespace(String prefix, String uri, int elemDepth) |
| { |
| // Prefixes "xml" and "xmlns" cannot be redefined |
| if (prefix.startsWith(XML_PREFIX)) |
| { |
| return false; |
| } |
| |
| Stack stack; |
| // Get the stack that contains URIs for the specified prefix |
| if ((stack = (Stack) m_namespaces.get(prefix)) == null) |
| { |
| m_namespaces.put(prefix, stack = new Stack()); |
| } |
| |
| if (!stack.empty()) |
| { |
| MappingRecord mr = (MappingRecord)stack.peek(); |
| if (uri.equals(mr.m_uri) || elemDepth == mr.m_declarationDepth) { |
| // If the same prefix/uri mapping is already on the stack |
| // don't push this one. |
| // Or if we have a mapping at the same depth |
| // don't replace by pushing this one. |
| return false; |
| } |
| } |
| MappingRecord map = new MappingRecord(prefix,uri,elemDepth); |
| stack.push(map); |
| m_nodeStack.push(map); |
| return true; |
| } |
| |
| /** |
| * Pop, or undeclare all namespace definitions that are currently |
| * declared at the given element depth, or deepter. |
| * @param elemDepth the element depth for which mappings declared at this |
| * depth or deeper will no longer be valid |
| * @param saxHandler The ContentHandler to notify of any endPrefixMapping() |
| * calls. This parameter can be null. |
| */ |
| void popNamespaces(int elemDepth, ContentHandler saxHandler) |
| { |
| while (true) |
| { |
| if (m_nodeStack.isEmpty()) |
| return; |
| MappingRecord map = (MappingRecord) (m_nodeStack.peek()); |
| int depth = map.m_declarationDepth; |
| if (elemDepth < 1 || map.m_declarationDepth < elemDepth) |
| break; |
| /* the depth of the declared mapping is elemDepth or deeper |
| * so get rid of it |
| */ |
| |
| MappingRecord nm1 = (MappingRecord) m_nodeStack.pop(); |
| // pop the node from the stack |
| String prefix = map.m_prefix; |
| |
| Stack prefixStack = getPrefixStack(prefix); |
| MappingRecord nm2 = (MappingRecord) prefixStack.peek(); |
| if (nm1 == nm2) |
| { |
| // It would be nice to always pop() but we |
| // need to check that the prefix stack still has |
| // the node we want to get rid of. This is because |
| // the optimization of essentially this situation: |
| // <a xmlns:x="abc"><b xmlns:x="" xmlns:x="abc" /></a> |
| // will remove both mappings in <b> because the |
| // new mapping is the same as the masked one and we get |
| // <a xmlns:x="abc"><b/></a> |
| // So we are only removing xmlns:x="" or |
| // xmlns:x="abc" from the depth of element <b> |
| // when going back to <a> if in fact they have |
| // not been optimized away. |
| // |
| prefixStack.pop(); |
| if (saxHandler != null) |
| { |
| try |
| { |
| saxHandler.endPrefixMapping(prefix); |
| } |
| catch (SAXException e) |
| { |
| // not much we can do if they aren't willing to listen |
| } |
| } |
| } |
| |
| } |
| } |
| |
| /** |
| * Generate a new namespace prefix ( ns0, ns1 ...) not used before |
| * @return String a new namespace prefix ( ns0, ns1, ns2 ...) |
| */ |
| public String generateNextPrefix() |
| { |
| return "ns" + (count++); |
| } |
| |
| |
| /** |
| * This method makes a clone of this object. |
| * |
| */ |
| public Object clone() throws CloneNotSupportedException { |
| NamespaceMappings clone = new NamespaceMappings(); |
| clone.m_nodeStack = (NamespaceMappings.Stack) m_nodeStack.clone(); |
| clone.count = this.count; |
| clone.m_namespaces = (Hashtable) m_namespaces.clone(); |
| |
| clone.count = count; |
| return clone; |
| |
| } |
| |
| final void reset() |
| { |
| this.count = 0; |
| this.m_namespaces.clear(); |
| this.m_nodeStack.clear(); |
| |
| initNamespaces(); |
| } |
| |
| /** |
| * Just a little class that ties the 3 fields together |
| * into one object, and this simplifies the pushing |
| * and popping of namespaces to one push or one pop on |
| * one stack rather than on 3 separate stacks. |
| */ |
| class MappingRecord { |
| final String m_prefix; // the prefix |
| final String m_uri; // the uri, possibly "" but never null |
| // the depth of the element where declartion was made |
| final int m_declarationDepth; |
| MappingRecord(String prefix, String uri, int depth) { |
| m_prefix = prefix; |
| m_uri = (uri==null)? EMPTYSTRING : uri; |
| m_declarationDepth = depth; |
| } |
| } |
| |
| /** |
| * Rather than using java.util.Stack, this private class |
| * provides a minimal subset of methods and is faster |
| * because it is not thread-safe. |
| */ |
| private class Stack { |
| private int top = -1; |
| private int max = 20; |
| Object[] m_stack = new Object[max]; |
| |
| public Object clone() throws CloneNotSupportedException { |
| NamespaceMappings.Stack clone = new NamespaceMappings.Stack(); |
| clone.max = this.max; |
| clone.top = this.top; |
| clone.m_stack = new Object[clone.max]; |
| for (int i=0; i <= top; i++) { |
| // We are just copying references to immutable MappingRecord objects here |
| // so it is OK if the clone has references to these. |
| clone.m_stack[i] = this.m_stack[i]; |
| } |
| return clone; |
| } |
| |
| public Stack() |
| { |
| } |
| |
| public Object push(Object o) { |
| top++; |
| if (max <= top) { |
| int newMax = 2*max + 1; |
| Object[] newArray = new Object[newMax]; |
| System.arraycopy(m_stack,0, newArray, 0, max); |
| max = newMax; |
| m_stack = newArray; |
| } |
| m_stack[top] = o; |
| return o; |
| } |
| |
| public Object pop() { |
| Object o; |
| if (0 <= top) { |
| o = m_stack[top]; |
| // m_stack[top] = null; do we really care? |
| top--; |
| } |
| else |
| o = null; |
| return o; |
| } |
| |
| public Object peek() { |
| Object o; |
| if (0 <= top) { |
| o = m_stack[top]; |
| } |
| else |
| o = null; |
| return o; |
| } |
| |
| public Object peek(int idx) { |
| return m_stack[idx]; |
| } |
| |
| public boolean isEmpty() { |
| return (top < 0); |
| } |
| public boolean empty() { |
| return (top < 0); |
| } |
| |
| public void clear() { |
| for (int i=0; i<= top; i++) |
| m_stack[i] = null; |
| top = -1; |
| } |
| |
| public Object getElement(int index) { |
| return m_stack[index]; |
| } |
| } |
| /** |
| * A more type-safe way to get a stack of prefix mappings |
| * from the Hashtable m_namespaces |
| * (this is the only method that does the type cast). |
| */ |
| |
| private Stack getPrefixStack(String prefix) { |
| Stack fs = (Stack) m_namespaces.get(prefix); |
| return fs; |
| } |
| |
| /** |
| * A more type-safe way of saving stacks under the |
| * m_namespaces Hashtable. |
| */ |
| private Stack createPrefixStack(String prefix) |
| { |
| Stack fs = new Stack(); |
| m_namespaces.put(prefix, fs); |
| return fs; |
| } |
| |
| /** |
| * Given a namespace uri, get all prefixes bound to the Namespace URI in the current scope. |
| * |
| * @param uri the namespace URI to be search for |
| * @return An array of Strings which are |
| * all prefixes bound to the namespace URI in the current scope. |
| * An array of zero elements is returned if no prefixes map to the given |
| * namespace URI. |
| */ |
| public String[] lookupAllPrefixes(String uri) |
| { |
| java.util.ArrayList foundPrefixes = new java.util.ArrayList(); |
| Enumeration prefixes = m_namespaces.keys(); |
| while (prefixes.hasMoreElements()) |
| { |
| String prefix = (String) prefixes.nextElement(); |
| String uri2 = lookupNamespace(prefix); |
| if (uri2 != null && uri2.equals(uri)) |
| { |
| foundPrefixes.add(prefix); |
| } |
| } |
| String[] prefixArray = new String[foundPrefixes.size()]; |
| foundPrefixes.toArray(prefixArray); |
| return prefixArray; |
| } |
| } |