All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cocoon.xml.sax.NamespacesTable Maven / Gradle / Ivy

/*
 * 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.
 */
package org.apache.cocoon.xml.sax;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

/**
 * Keeps track of namespaces declarations and resolve namespaces names.
 * 

* This class also provides a very convenient and safe way of handling * namespace declarations in SAX pipes. It also allows to filter duplicate namespace * declarations that too often clutter up XML documents that went through * several transformations, and avoid useless namespace declarations that aren't followed * by element events. *

* Usage example in a SAX pipe: *

 *   NamespacesTable namespaces = new NamespacesTable();
 *   ContentHandler nextHandler;
 *
 *   public void startPrefixMapping(String prefix, String uri) throws SAXException {
 *       namespaces.addDeclaration(prefix, uri);
 *   }
 *
 *   public void startElement(...) throws SAXException {
 *       // automatically start mappings for this scope
 *       namespaces.enterScope(nextHandler);
 *       nextHandler.startElement(...);
 *   }
 *
 *   public void endElement(...) throws SAXException {
 *       nextHandler.endElement(...);
 *       // automatically end mappings for this scope
 *       namespaces.leaveScope(nextHandler);
 *   }
 *
 *   public void endPrefixMapping(String prefix) throws SAXException {
 *       // Ignore, it is handled by leaveScope()
 *   }
 * 
* * @version $Id: NamespacesTable.java 729283 2008-12-24 09:25:21Z cziegeler $ */ public class NamespacesTable { /** The last namespace declaration. */ private Entry lastEntry; /** The entry that start the prefix mappings for the scope that's about to be entered * or was just left. */ private Entry lastDeclaredEntry; private boolean usesScopes = false; /** * Construct a new NamespacesTable instance. */ public NamespacesTable() { clear(); } /** * Clear and reinitialize this namespace table before reuse. * * @since 2.1.8 */ public void clear() { this.lastEntry = Entry.create("",""); this.addDeclaration("xml", "http://www.w3.org/XML/1998/namespace"); // Lock this scope this.lastEntry.closedScopes = 1; } /** * Declare a new namespace prefix-uri mapping. * * @return The newly added Declaration. */ public Declaration addDeclaration(String prefix, String uri) { // Find a previous declaration of the same prefix Entry dup = this.lastEntry; while (dup != null && !dup.prefix.equals(prefix)) { dup = dup.previous; } if (dup != null) { if (usesScopes && dup.uri.equals(uri)) { return dup; } dup.overriden = true; } Entry e = Entry.create(prefix, uri); e.previous = this.lastEntry; e.overrides = dup; this.lastEntry = e; // this always starts the declared prefix chain this.lastDeclaredEntry = e; return e; } /** * Undeclare a namespace prefix-uri mapping. If the prefix was previously declared * mapping another URI, its value is restored. *

* When using {@link #enterScope()}/{@link #leaveScope()}, this method does nothing and always * returns null, as declaration removal is handled in {@link #leaveScope()}. * * @return the removed Declaration or null. */ public Declaration removeDeclaration(String prefix) { if (usesScopes) { // Automatically handled in leaveScope return null; // or throw and IllegalStateException if enterScope(handler) was used? } Entry current = this.lastEntry; Entry afterCurrent = null; while(current != null) { if (current.closedScopes > 0) { // Don't undeclare mappings not declared in this scope return null; } if (current.prefix.equals(prefix)) { // Got it // Remove it from the chain if (afterCurrent != null) { afterCurrent.previous = current.previous; } // And report closed scopes on the previous entry current.previous.closedScopes += current.closedScopes; Entry overrides = current.overrides; if (overrides != null) { // No more overriden overrides.overriden = false; } if (this.lastDeclaredEntry == current) { if (current.previous.closedScopes == 0) { this.lastDeclaredEntry = current.previous; } else { this.lastDeclaredEntry = null; } } if (this.lastEntry == current) { this.lastEntry = current.previous; } return current; } afterCurrent = current; current = current.previous; } // Not found return null; } /** * Enter a new scope. This starts a new, empty list of declarations for the new scope. *

* Typically called in a SAX handler before sending a startElement() * event. * * @since 2.1.8 */ public void enterScope() { this.usesScopes = true; this.lastEntry.closedScopes++; this.lastDeclaredEntry = null; } /** * Start all declared mappings of the current scope and enter a new scope. This starts a new, * empty list of declarations for the new scope. *

* Typically called in a SAX handler before sending a startElement() * event. * * @param handler the handler that will receive startPrefixMapping events. * @throws SAXException * @since 2.1.8 */ public void enterScope(ContentHandler handler) throws SAXException { this.usesScopes = true; Entry current = this.lastEntry; while (current != null && current.closedScopes == 0) { handler.startPrefixMapping(current.prefix, current.uri); current = current.previous; } this.lastEntry.closedScopes++; this.lastDeclaredEntry = null; } /** * Leave a scope. The namespace declarations that occured before the corresponding * enterScope() are no more visible using the resolution methods, but * still available using {@link #getCurrentScopeDeclarations()} until the next call * to {@link #addDeclaration(String, String)} or {@link #enterScope()}. *

* Typically called in a SAX handler after sending a endElement() * event. * * @since 2.1.8 */ public void leaveScope() { Entry current = this.lastEntry; // Purge declarations that were added but not included in a scope while (current.closedScopes == 0) { current = current.previous; } current.closedScopes--; if (current.closedScopes == 0) { this.lastDeclaredEntry = current; } else { // More than one scope closed here: no local declarations this.lastDeclaredEntry = null; } while (current != null && current.closedScopes == 0) { Entry overrides = current.overrides; if (overrides != null) { // No more overriden overrides.overriden = false; } current = current.previous; } this.lastEntry = current; } /** * Leave a scope. The namespace declarations that occured before the corresponding * enterScope() are no more visible using the resolution methods, but * still available using {@link #getCurrentScopeDeclarations()} until the next call * to {@link #addDeclaration(String, String)} or {@link #enterScope()}. *

* Typically called in a SAX handler after sending a endElement() * event. * * @param handler the handler that will receive endPrefixMapping events. * @throws SAXException * @since 2.1.8 */ public void leaveScope(ContentHandler handler) throws SAXException { Entry current = this.lastEntry; // Purge declarations that were added but not included in a scope while (current.closedScopes == 0) { current = current.previous; } current.closedScopes--; if (current.closedScopes == 0) { this.lastDeclaredEntry = current; } else { // More than one scope closed here: no local declarations this.lastDeclaredEntry = null; } while (current != null && current.closedScopes == 0) { handler.endPrefixMapping(current.prefix); Entry overrides = current.overrides; if (overrides != null) { // No more overriden overrides.overriden = false; } current = current.previous; } this.lastEntry = current; } private static final Declaration[] NO_DECLS = new Declaration[0]; /** * Get the declarations that were declared within the current scope. * * @return the declarations (possibly empty, but never null) * @since 2.1.8 */ public Declaration[] getCurrentScopeDeclarations() { int count = 0; Entry current = this.lastDeclaredEntry; while (current != null && current.closedScopes == 0) { count++; current = current.previous; } if (count == 0) return NO_DECLS; Declaration[] decls = new Declaration[count]; count = 0; current = this.lastDeclaredEntry; while (current != null && current.closedScopes == 0) { decls[count++] = current; current = current.previous; } return decls; } /** * Return the URI associated with the given prefix or null if the * prefix was not mapped. */ public String getUri(String prefix) { Entry current = this.lastEntry; while (current != null) { if (current.prefix.equals(prefix)) { return current.uri; } current = current.previous; } // Not found return null; } /** * Return an array with all prefixes currently mapped to the specified URI. *
* The array length might be zero if no prefixes are associated with * the specified uri. * * @return A non-null String array. */ public String[] getPrefixes(String uri) { Entry current=this.lastEntry; int count=0; while (current!=null) { if(!current.overriden && current.uri.equals(uri)) count++; current=current.previous; } if (count==0) return(new String[0]); String prefixes[]=new String[count]; count=0; current = this.lastEntry; while (current!=null) { if(!current.overriden && current.uri.equals(uri)) prefixes[count++] = current.prefix; current = current.previous; } return prefixes; } /** * Return one of the prefixes currently mapped to the specified URI or * null. */ public String getPrefix(String uri) { Entry current = this.lastEntry; while (current != null) { if(!current.overriden && current.uri.equals(uri)) return current.prefix; current = current.previous; } return null; } /** * Resolve a namespace-aware name against the current namespaces * declarations. * * @param uri The namespace URI or null if not known. * @param raw The raw (complete) name or null if not known. * @param prefix The namespace prefix or null if not known. * @param local The local name or null if not known. * @return A non-null Name. * @exception SAXException If the name cannot be resolved. */ public Name resolve(String uri, String raw, String prefix, String local) throws SAXException { if (uri==null) uri=""; if (raw==null) raw=""; if (prefix==null) prefix=""; if (local==null) local=""; // Start examining the URI if (raw.length()>0) { // The raw name was specified int pos=raw.indexOf(':'); if (pos>0) { // We have a namespace prefix:local separator String pre=raw.substring(0,pos); String loc=raw.substring(pos+1); if (prefix.length()==0) prefix=pre; else if (!prefix.equals(pre)) throw new SAXException("Raw/Prefix mismatch"); if (local.length()==0) local=loc; else if (!local.equals(loc)) throw new SAXException("Raw/Local Name mismatch"); } else { // We don't have a prefix:local separator if (prefix.length()>0) throw new SAXException("Raw Name/Prefix mismatch"); if (local.length()==0) local=raw; else if (!local.equals(raw)) throw new SAXException("Raw Name/Local Name mismatch"); } } else { // The raw name was not specified if (local.length()==0) throw new SAXException("No Raw/Local Name"); if (prefix.length()==0) raw=local; else raw=prefix+':'+local; } // We have resolved and checked data between the raw, local, and // prefix... We have to doublecheck the namespaces. if (uri.length()>0) { // We have a URI and a prefix, check them if ((prefix.length()>0) && (!uri.equals(this.getUri(prefix)))) { throw new SAXException("URI/Prefix mismatch [" + prefix + "," + uri + "]"); } String temp=this.getPrefix(uri); if (temp==null) throw new SAXException("URI not declared"); else if (temp.length()>0) { prefix=temp; raw=prefix+':'+local; } } else { // We don't have a URI, check if we can find one from the prefix. String temp=this.getUri(prefix); if (temp==null) { throw new SAXException("Prefix not declared"); } uri=temp; } NameImpl name=new NameImpl(); if (uri.length() > 0) name.uri=uri; else name.uri=null; name.raw=raw; name.prefix=prefix; name.local=local; return(name); } /** The internal entry structure for this table. */ private static class Entry implements Declaration { /** The URI string. */ protected String uri=""; /** The prefix string. */ protected String prefix=""; /** The previous declaration. */ protected Entry previous; protected Entry overrides; protected int closedScopes = 0; protected boolean overriden = false; /** Create a new namespace declaration. */ protected static Entry create(String prefix, String uri) { // Create a new entry Entry e = new Entry(); // Set the prefix string. if (prefix != null) e.prefix=prefix; // Set the uri string. if (uri != null) e.uri=uri; // Return the entry return e; } /** Return the namespace URI. */ public String getUri() { return this.uri; } /** Return the namespace prefix. */ public String getPrefix() { return this.prefix; } } /** The default namespace-aware name declaration implementation */ private static class NameImpl implements Name { /** The namespace URI. */ protected String uri; /** The namespace prefix. */ protected String prefix; /** The namespace local name. */ protected String local; /** The namespace raw name. */ protected String raw; /** Return the namespace URI. */ public String getUri() { return this.uri; } /** Return the namespace prefix. */ public String getPrefix() { return this.prefix; } /** Return the namespace local name. */ public String getLocalName() { return this.local; } /** Return the namespace raw name. */ public String getQName() { return this.raw; } } /** * A namespace-aware name. (This interface is used in conjunction * with NamespacesTable). */ public interface Name { /** Return the namespace URI. */ String getUri(); /** Return the namespace prefix. */ String getPrefix(); /** Return the namespace local name. */ String getLocalName(); /** Return the namespace raw name. */ String getQName(); } /** * A namespace declaration. (This interface is used in conjunction * with NamespacesTable). */ public interface Declaration { /** Return the namespace URI. */ String getUri(); /** Return the namespace prefix. */ String getPrefix(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy