/*
* Copyright 2004-2008 Andy Clark
*
* 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.cyberneko.html.filters;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Vector;
import mf.org.apache.xerces.xni.*;
import mf.org.apache.xerces.xni.parser.XMLComponentManager;
import mf.org.apache.xerces.xni.parser.XMLConfigurationException;
import org.cyberneko.html.HTMLElements;
import org.cyberneko.html.xercesbridge.XercesBridge;
/**
* This filter binds namespaces if namespace processing is turned on
* by setting the feature "http://xml.org/sax/features/namespaces" is
* set to <code>true</code>.
* <p>
* This configuration recognizes the following features:
* <ul>
* <li>http://xml.org/sax/features/namespaces
* </ul>
*
* @author Andy Clark
*
* @version $Id: NamespaceBinder.java,v 1.8 2005/05/30 00:19:28 andyc Exp $
*/
public class NamespaceBinder
extends DefaultFilter {
//
// Constants
//
// namespace uris
/** XHTML 1.0 namespace URI (http://www.w3.org/1999/xhtml). */
public static final String XHTML_1_0_URI = "http://www.w3.org/1999/xhtml";
/** XML namespace URI (http://www.w3.org/XML/1998/namespace). */
public static final String XML_URI = "http://www.w3.org/XML/1998/namespace";
/** XMLNS namespace URI (http://www.w3.org/2000/xmlns/). */
public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/";
// features
/** Namespaces. */
protected static final String NAMESPACES = "http://xml.org/sax/features/namespaces";
/** Override namespace binding URI. */
protected static final String OVERRIDE_NAMESPACES = "http://cyberneko.org/html/features/override-namespaces";
/** Insert namespace binding URIs. */
protected static final String INSERT_NAMESPACES = "http://cyberneko.org/html/features/insert-namespaces";
/** Recognized features. */
private static final String[] RECOGNIZED_FEATURES = {
NAMESPACES,
OVERRIDE_NAMESPACES,
INSERT_NAMESPACES,
};
/** Feature defaults. */
private static final Boolean[] FEATURE_DEFAULTS = {
null,
Boolean.FALSE,
Boolean.FALSE,
};
// properties
/** Modify HTML element names: { "upper", "lower", "default" }. */
protected static final String NAMES_ELEMS = "http://cyberneko.org/html/properties/names/elems";
/** Modify HTML attribute names: { "upper", "lower", "default" }. */
protected static final String NAMES_ATTRS = "http://cyberneko.org/html/properties/names/attrs";
/** Namespaces URI. */
protected static final String NAMESPACES_URI = "http://cyberneko.org/html/properties/namespaces-uri";
/** Recognized properties. */
private static final String[] RECOGNIZED_PROPERTIES = new String[] {
NAMES_ELEMS,
NAMES_ATTRS,
NAMESPACES_URI,
};
/** Property defaults. */
private static final Object[] PROPERTY_DEFAULTS = {
null,
null,
XHTML_1_0_URI,
};
// modify HTML names
/** Don't modify HTML names. */
protected static final short NAMES_NO_CHANGE = 0;
/** Uppercase HTML names. */
protected static final short NAMES_UPPERCASE = 1;
/** Lowercase HTML names. */
protected static final short NAMES_LOWERCASE = 2;
//
// Data
//
// features
/** Namespaces. */
protected boolean fNamespaces;
/** Namespace prefixes. */
protected boolean fNamespacePrefixes;
/** Override namespaces. */
protected boolean fOverrideNamespaces;
/** Insert namespaces. */
protected boolean fInsertNamespaces;
// properties
/** Modify HTML element names. */
protected short fNamesElems;
/** Modify HTML attribute names. */
protected short fNamesAttrs;
/** Namespaces URI. */
protected String fNamespacesURI;
// state
/** Namespace context. */
protected final NamespaceSupport fNamespaceContext = new NamespaceSupport();
// temp vars
/** QName. */
private final QName fQName = new QName();
//
// HTMLComponent methods
//
/**
* Returns a list of feature identifiers that are recognized by
* this component. This method may return null if no features
* are recognized by this component.
*/
public String[] getRecognizedFeatures() {
return merge(super.getRecognizedFeatures(), RECOGNIZED_FEATURES);
} // getRecognizedFeatures():String[]
/**
* Returns the default state for a feature, or null if this
* component does not want to report a default value for this
* feature.
*/
public Boolean getFeatureDefault(String featureId) {
for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
if (RECOGNIZED_FEATURES[i].equals(featureId)) {
return FEATURE_DEFAULTS[i];
}
}
return super.getFeatureDefault(featureId);
} // getFeatureDefault(String):Boolean
/**
* Returns a list of property identifiers that are recognized by
* this component. This method may return null if no properties
* are recognized by this component.
*/
public String[] getRecognizedProperties() {
return merge(super.getRecognizedProperties(), RECOGNIZED_PROPERTIES);
} // getRecognizedProperties():String[]
/**
* Returns the default value for a property, or null if this
* component does not want to report a default value for this
* property.
*/
public Object getPropertyDefault(String propertyId) {
for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
return PROPERTY_DEFAULTS[i];
}
}
return super.getPropertyDefault(propertyId);
} // getPropertyDefault(String):Object
/**
* Resets the component. The component can query the component manager
* about any features and properties that affect the operation of the
* component.
*
* @param manager The component manager.
*
* @throws XNIException Thrown by component on initialization error.
*/
public void reset(XMLComponentManager manager)
throws XMLConfigurationException {
super.reset(manager);
// features
fNamespaces = manager.getFeature(NAMESPACES);
fOverrideNamespaces = manager.getFeature(OVERRIDE_NAMESPACES);
fInsertNamespaces = manager.getFeature(INSERT_NAMESPACES);
// get properties
fNamesElems = getNamesValue(String.valueOf(manager.getProperty(NAMES_ELEMS)));
fNamesAttrs = getNamesValue(String.valueOf(manager.getProperty(NAMES_ATTRS)));
fNamespacesURI = String.valueOf(manager.getProperty(NAMESPACES_URI));
// initialize state
fNamespaceContext.reset();
} // reset(XMLComponentManager)
//
// XMLDocumentHandler methods
//
/** Start document. */
public void startDocument(XMLLocator locator, String encoding,
NamespaceContext nscontext, Augmentations augs)
throws XNIException {
// perform default handling
// NOTE: using own namespace context
super.startDocument(locator,encoding,fNamespaceContext,augs);
} // startDocument(XMLLocator,String,NamespaceContext,Augmentations)
/** Start element. */
public void startElement(QName element, XMLAttributes attrs,
Augmentations augs) throws XNIException {
// bind namespaces, if needed
if (fNamespaces) {
fNamespaceContext.pushContext();
bindNamespaces(element, attrs);
int dcount = fNamespaceContext.getDeclaredPrefixCount();
if (fDocumentHandler != null && dcount > 0) {
for (int i = 0; i < dcount; i++) {
String prefix = fNamespaceContext.getDeclaredPrefixAt(i);
String uri = fNamespaceContext.getURI(prefix);
XercesBridge.getInstance().XMLDocumentHandler_startPrefixMapping(fDocumentHandler, prefix, uri, augs);
}
}
}
// perform default handling
super.startElement(element, attrs, augs);
} // startElement(QName,XMLAttributes,Augmentations)
/** Empty element. */
public void emptyElement(QName element, XMLAttributes attrs,
Augmentations augs) throws XNIException {
// bind namespaces, if needed
if (fNamespaces) {
fNamespaceContext.pushContext();
bindNamespaces(element, attrs);
int dcount = fNamespaceContext.getDeclaredPrefixCount();
if (fDocumentHandler != null && dcount > 0) {
for (int i = 0; i < dcount; i++) {
String prefix = fNamespaceContext.getDeclaredPrefixAt(i);
String uri = fNamespaceContext.getURI(prefix);
XercesBridge.getInstance().XMLDocumentHandler_startPrefixMapping(fDocumentHandler, prefix, uri, augs);
}
}
}
// perform default handling
super.emptyElement(element, attrs, augs);
// pop context
if (fNamespaces) {
int dcount = fNamespaceContext.getDeclaredPrefixCount();
if (fDocumentHandler != null && dcount > 0) {
for (int i = dcount-1; i >= 0; i--) {
String prefix = fNamespaceContext.getDeclaredPrefixAt(i);
XercesBridge.getInstance().XMLDocumentHandler_endPrefixMapping(fDocumentHandler, prefix, augs);
}
}
fNamespaceContext.popContext();
}
} // startElement(QName,XMLAttributes,Augmentations)
/** End element. */
public void endElement(QName element, Augmentations augs)
throws XNIException {
// bind namespaces, if needed
if (fNamespaces) {
bindNamespaces(element, null);
}
// perform default handling
super.endElement(element, augs);
// pop context
if (fNamespaces) {
int dcount = fNamespaceContext.getDeclaredPrefixCount();
if (fDocumentHandler != null && dcount > 0) {
for (int i = dcount-1; i >= 0; i--) {
String prefix = fNamespaceContext.getDeclaredPrefixAt(i);
XercesBridge.getInstance().XMLDocumentHandler_endPrefixMapping(fDocumentHandler, prefix, augs);
}
}
fNamespaceContext.popContext();
}
} // endElement(QName,Augmentations)
//
// Protected static methods
//
/** Splits a qualified name. */
protected static void splitQName(QName qname) {
int index = qname.rawname.indexOf(':');
if (index != -1) {
qname.prefix = qname.rawname.substring(0,index);
qname.localpart = qname.rawname.substring(index+1);
}
} // splitQName(QName)
/**
* Converts HTML names string value to constant value.
*
* @see #NAMES_NO_CHANGE
* @see #NAMES_LOWERCASE
* @see #NAMES_UPPERCASE
*/
protected static final short getNamesValue(String value) {
if (value.equals("lower")) { return NAMES_LOWERCASE; }
if (value.equals("upper")) { return NAMES_UPPERCASE; }
return NAMES_NO_CHANGE;
} // getNamesValue(String):short
/** Modifies the given name based on the specified mode. */
protected static final String modifyName(String name, short mode) {
switch (mode) {
case NAMES_UPPERCASE: return name.toUpperCase(Locale.ENGLISH);
case NAMES_LOWERCASE: return name.toLowerCase(Locale.ENGLISH);
}
return name;
} // modifyName(String,short):String
//
// Protected methods
//
/** Binds namespaces. */
protected void bindNamespaces(QName element, XMLAttributes attrs) {
// split element qname
splitQName(element);
// declare namespace prefixes
int attrCount = attrs != null ? attrs.getLength() : 0;
for (int i = attrCount - 1; i >= 0; i--) {
attrs.getName(i, fQName);
String aname = fQName.rawname;
String ANAME = aname.toUpperCase(Locale.ENGLISH);
if (ANAME.startsWith("XMLNS:") || ANAME.equals("XMLNS")) {
int anamelen = aname.length();
// get parts
String aprefix = anamelen > 5 ? aname.substring(0,5) : null;
String alocal = anamelen > 5 ? aname.substring(6) : aname;
String avalue = attrs.getValue(i);
// re-case parts and set them back into attributes
if (anamelen > 5) {
aprefix = modifyName(aprefix, NAMES_LOWERCASE);
alocal = modifyName(alocal, fNamesElems);
aname = aprefix + ':' + alocal;
}
else {
alocal = modifyName(alocal, NAMES_LOWERCASE);
aname = alocal;
}
fQName.setValues(aprefix, alocal, aname, null);
attrs.setName(i, fQName);
// declare prefix
String prefix = alocal != aname ? alocal : "";
String uri = avalue.length() > 0 ? avalue : null;
if (fOverrideNamespaces &&
prefix.equals(element.prefix) &&
HTMLElements.getElement(element.localpart, null) != null) {
uri = fNamespacesURI;
}
fNamespaceContext.declarePrefix(prefix, uri);
}
}
// bind element
String prefix = element.prefix != null ? element.prefix : "";
element.uri = fNamespaceContext.getURI(prefix);
// REVISIT: The prefix of a qualified element name that is
// bound to a namespace is passed (as recent as
// Xerces 2.4.0) as "" for start elements and null
// for end elements. Why? One of them is a bug,
// clearly. -Ac
if (element.uri != null && element.prefix == null) {
element.prefix = "";
}
// do we need to insert namespace bindings?
if (fInsertNamespaces && attrs != null &&
HTMLElements.getElement(element.localpart,null) != null) {
if (element.prefix == null ||
fNamespaceContext.getURI(element.prefix) == null) {
String xmlns = "xmlns" + ((element.prefix != null)
? ":"+element.prefix : "");
fQName.setValues(null, xmlns, xmlns, null);
attrs.addAttribute(fQName, "CDATA", fNamespacesURI);
bindNamespaces(element, attrs);
return;
}
}
// bind attributes
attrCount = attrs != null ? attrs.getLength() : 0;
for (int i = 0; i < attrCount; i++) {
attrs.getName(i, fQName);
splitQName(fQName);
prefix = !fQName.rawname.equals("xmlns")
? (fQName.prefix != null ? fQName.prefix : "") : "xmlns";
// PATCH: Joseph Walton
if (!prefix.equals("")) {
fQName.uri = prefix.equals("xml") ? XML_URI : fNamespaceContext.getURI(prefix);
}
// NOTE: You would think the xmlns namespace would be handled
// by NamespaceSupport but it's not. -Ac
if (prefix.equals("xmlns") && fQName.uri == null) {
fQName.uri = XMLNS_URI;
}
attrs.setName(i, fQName);
}
} // bindNamespaces(QName,XMLAttributes)
//
// Classes
//
/**
* This namespace context object implements the old and new XNI
* <code>NamespaceContext</code> interface methods so that it can
* be used across all versions of Xerces2.
*/
public static class NamespaceSupport
implements NamespaceContext {
//
// Data
//
/** Top of the levels list. */
protected int fTop = 0;
/** The levels of the entries. */
protected int[] fLevels = new int[10];
/** The entries. */
protected Entry[] fEntries = new Entry[10];
//
// Constructors
//
/** Default constructor. */
public NamespaceSupport() {
pushContext();
declarePrefix("xml", NamespaceContext.XML_URI);
declarePrefix("xmlns", NamespaceContext.XMLNS_URI);
} // <init>()
//
// NamespaceContext methods
//
// since Xerces 2.0.0-beta2 (old XNI namespaces)
/** Get URI. */
public String getURI(String prefix) {
for (int i = fLevels[fTop]-1; i >= 0; i--) {
final Entry entry = fEntries[i];
if (entry.prefix.equals(prefix)) {
return entry.uri;
}
}
return null;
} // getURI(String):String
/** Get declared prefix count. */
public int getDeclaredPrefixCount() {
return fLevels[fTop] - fLevels[fTop-1];
} // getDeclaredPrefixCount():int
/** Get declared prefix at. */
public String getDeclaredPrefixAt(int index) {
return fEntries[fLevels[fTop-1] + index].prefix;
} // getDeclaredPrefixAt(int):String
/** Get parent context. */
public NamespaceContext getParentContext() {
return this;
} // getParentContext():NamespaceContext
// since Xerces #.#.# (new XNI namespaces)
/** Reset. */
public void reset() {
fLevels[fTop = 1] = fLevels[fTop-1];
} // reset()
/** Push context. */
public void pushContext() {
if (++fTop == fLevels.length) {
int[] iarray = new int[fLevels.length + 10];
System.arraycopy(fLevels, 0, iarray, 0, fLevels.length);
fLevels = iarray;
}
fLevels[fTop] = fLevels[fTop-1];
} // pushContext()
/** Pop context. */
public void popContext() {
if (fTop > 1) {
fTop--;
}
} // popContext()
/** Declare prefix. */
public boolean declarePrefix(String prefix, String uri) {
int count = getDeclaredPrefixCount();
for (int i = 0; i < count; i++) {
String dprefix = getDeclaredPrefixAt(i);
if (dprefix.equals(prefix)) {
return false;
}
}
Entry entry = new Entry(prefix, uri);
if (fLevels[fTop] == fEntries.length) {
Entry[] earray = new Entry[fEntries.length + 10];
System.arraycopy(fEntries, 0, earray, 0, fEntries.length);
fEntries = earray;
}
fEntries[fLevels[fTop]++] = entry;
return true;
} // declarePrefix(String,String):boolean
/** Get prefix. */
public String getPrefix(String uri) {
for (int i = fLevels[fTop]-1; i >= 0; i--) {
final Entry entry = fEntries[i];
if (entry.uri.equals(uri)) {
return entry.prefix;
}
}
return null;
} // getPrefix(String):String
/** Get all prefixes. */
public Enumeration getAllPrefixes() {
Vector prefixes = new Vector();
for (int i = fLevels[1]; i < fLevels[fTop]; i++) {
String prefix = fEntries[i].prefix;
if (!prefixes.contains(prefix)) {
prefixes.addElement(prefix);
}
}
return prefixes.elements();
} // getAllPrefixes():Enumeration
//
// Classes
//
/** A namespace binding entry. */
static class Entry {
//
// Data
//
/** Prefix. */
public String prefix;
/** URI. */
public String uri;
//
// Constructors
//
/** Constructs an entry. */
public Entry(String prefix, String uri) {
this.prefix = prefix;
this.uri = uri;
} // <init>(String,String)
} // class Entry
} // class NamespaceSupport
} // class NamespaceBinder