| /* |
| * Copyright 1999-2004 The Apache Software Foundation. |
| * |
| * Licensed 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; |
| |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Keeps track of namespaces declarations and resolve namespaces names. |
| * <p> |
| * 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. |
| * <p> |
| * Usage example in a SAX pipe: |
| * <pre> |
| * 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() |
| * } |
| * </pre> |
| * |
| * @version $Id$ |
| */ |
| 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 <code>NamespacesTable</code> 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 <code>Declaration</code>. |
| */ |
| 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. |
| * <p> |
| * When using {@link #enterScope()}/{@link #leaveScope()}, this method does nothing and always |
| * returns <code>null</code>, as declaration removal is handled in {@link #leaveScope()}. |
| * |
| * @return the removed <code>Declaration</code> or <b>null</b>. |
| */ |
| 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. |
| * <p> |
| * Typically called in a SAX handler <em>before</em> sending a <code>startElement()</code> |
| * 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. |
| * <p> |
| * Typically called in a SAX handler <em>before</em> sending a <code>startElement()</code> |
| * 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 |
| * <code>enterScope()</code> 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()}. |
| * <p> |
| * Typically called in a SAX handler <em>after</em> sending a <code>endElement()</code> |
| * 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 |
| * <code>enterScope()</code> 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()}. |
| * <p> |
| * Typically called in a SAX handler <em>after</em> sending a <code>endElement()</code> |
| * 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 <b>null</b> 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. |
| * <br> |
| * The array length might be <b>zero</b> if no prefixes are associated with |
| * the specified uri. |
| * |
| * @return A <b>non-null</b> <code>String</code> 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 |
| * <b>null</b>. |
| */ |
| 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 <b>null</b> if not known. |
| * @param raw The raw (complete) name or <b>null</b> if not known. |
| * @param prefix The namespace prefix or <b>null</b> if not known. |
| * @param local The local name or <b>null</b> if not known. |
| * @return A <b>non-null</b> <code>Name</code>. |
| * @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 + "]"); |
| } else { |
| 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"); |
| else 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 <code>NamespacesTable</code>). |
| */ |
| 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 <code>NamespacesTable</code>). |
| */ |
| public interface Declaration { |
| /** Return the namespace URI. */ |
| String getUri(); |
| /** Return the namespace prefix. */ |
| String getPrefix(); |
| } |
| } |