blob: 32b9cd6953b4de7cf0b97b02249083dae5d4f993 [file] [log] [blame]
/*
* 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.SAXException;
/**
* This utility class is used to keep track namespaces declarations and resolve
* namespaces names.
*
* @author <a href="mailto:pier@apache.org">Pierpaolo Fumagalli</a>
* (Apache Software Foundation)
* @version CVS $Id: NamespacesTable.java,v 1.3 2004/03/05 13:03:01 bdelacretaz Exp $
*/
public class NamespacesTable {
/** The initial namespace declaration. */
private Entry entry=null;
/**
* Construct a new <code>NamespacesTable</code> instance.
*/
public NamespacesTable() {
super();
this.entry=Entry.create("","");
// Set the previous declaration of this namespace as self, so it will
// not be possible to remove it :)
this.entry.previousDeclaration=this.entry;
this.addDeclaration("xml", "http://www.w3.org/XML/1998/namespace");
}
/**
* Declare a new namespace prefix-uri mapping.
*
* @return The newly added <code>Declaration</code>.
*/
public Declaration addDeclaration(String prefix, String uri) {
Entry e=Entry.create(prefix,uri);
Entry previous=null;
Entry current=this.entry;
while (current!=null) {
if (current.prefixHash==e.prefixHash) {
// Set the current entry to be the previous declaration for the
// specified prefix and remove it from the chain.
e.previousDeclaration=current;
e.nextEntry=current.nextEntry;
current.nextEntry=null;
// Set the new entry in the chain
if (previous==null) this.entry=e;
else previous.nextEntry=e;
return(e);
} else {
previous=current;
current=current.nextEntry;
}
}
if (previous==null) this.entry=e;
else previous.nextEntry=e;
return(e);
}
/**
* Undeclare a namespace prefix-uri mapping.
* <br>
* If the prefix was previously declared mapping another URI, its value
* is restored.
*
* @return The removed <code>Declaration</code> or <b>null</b>.
*/
public Declaration removeDeclaration(String prefix) {
int hash=prefix.hashCode();
Entry previous=null;
Entry current=this.entry;
while (current!=null) {
if (current.prefixHash==hash) {
if (current.previousDeclaration==null) {
if (previous==null) this.entry=current.nextEntry;
else previous.nextEntry=current.nextEntry;
} else {
current.previousDeclaration.nextEntry=current.nextEntry;
if (previous==null) this.entry=current.previousDeclaration;
else previous.nextEntry=current.previousDeclaration;
}
return(current);
} else {
previous=current;
current=current.nextEntry;
}
}
return(null);
}
/**
* Return the URI associated with the given prefix or <b>null</b> if the
* prefix was not mapped.
*/
public String getUri(String prefix) {
int hash=prefix.hashCode();
Entry current=this.entry;
while (current!=null) {
if(current.prefixHash==hash) return(current.uri);
else current=current.nextEntry;
}
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) {
int hash=uri.hashCode();
Entry current=this.entry;
int count=0;
while (current!=null) {
if(current.uriHash==hash) count++;
current=current.nextEntry;
}
if (count==0) return(new String[0]);
String prefixes[]=new String[count];
count=0;
while (current!=null) {
if(current.uriHash==hash) prefixes[count++]=current.prefix;
current=current.nextEntry;
}
return(prefixes);
}
/**
* Return one of the prefixes currently mapped to the specified URI or
* <b>null</b>.
*/
public String getPrefix(String uri) {
int hash=uri.hashCode();
Entry current=this.entry;
while (current!=null) {
if(current.uriHash==hash) return(current.prefix);
current=current.nextEntry;
}
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 hashcode. */
protected int uriHash=0;
/** The prefix hashcode. */
protected int prefixHash=0;
/** The URI string. */
protected String uri="";
/** The prefix string. */
protected String prefix="";
/** The previous declaration for the same prefix. */
protected Entry previousDeclaration;
/** The declaration following this one in the table. */
protected Entry nextEntry;
/** 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 and hash code.
if (prefix!=null) e.prefix=prefix;
e.prefixHash=e.prefix.hashCode();
// Set the uri string and hash code.
if (uri!=null) e.uri=uri;
e.uriHash=e.uri.hashCode();
// 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();
}
}