/*
* Copyright 2005 Philipp Erlacher
*
* 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.exolab.castor.xml.parsing;
import org.castor.core.util.StringUtil;
import org.exolab.castor.xml.AttributeSet;
import org.exolab.castor.xml.util.AttributeSetImpl;
import org.xml.sax.AttributeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* A helper class that takes SAX v1 AttributeList or SAX v2 attributes and
* converts those into Castor's internal {@link AttributeSet} representation.
*
* @author <a href="mailto:philipp DOT erlacher AT gmail DOT com">Philipp
* Erlacher</a>
*
* @since 1.3.2
*/
public class AttributeSetBuilder {
/**
* The built-in XML prefix used for xml:space, xml:lang and, as the XML 1.0
* Namespaces document specifies, are reserved for use by XML and XML
* related specs.
**/
private static final String XML_PREFIX = "xml";
/**
* Attribute name for default namespace declaration
**/
private static final String XMLNS = "xmlns";
/**
* Attribute prefix for prefixed namespace declaration.
**/
private final static String XMLNS_PREFIX = "xmlns:";
/**
* Length of the XMLNS prefix.
*/
private final static int XMLNS_PREFIX_LENGTH = XMLNS_PREFIX.length();
/**
* A "reusable" AttributeSet, for when using handling SAX 2 ContentHandler.
*/
private static AttributeSetImpl _reusableAtts = null;
/**
* Tool class to deal with XML name spaces.
*/
private NamespaceHandling _namespaceHandling = null;
/**
* Creates an instance of this class.
*
* @param namespaceHandling
* Instance of a tool class to handle XML name spaces.
*/
public AttributeSetBuilder(NamespaceHandling namespaceHandling) {
super();
_namespaceHandling = namespaceHandling;
}
/**
* Prepares a reusable {@link AttributeSet} object instance.
*
* @param atts
* Attributes to determine the length of the reusable attribute
* object, can be null
*/
private void prepareReusableAttributes(Attributes atts) {
if (_reusableAtts == null) {
if (atts != null) {
_reusableAtts = new AttributeSetImpl(atts.getLength());
} else {
// -- we can't pass a null AttributeSet to the
// -- startElement
_reusableAtts = new AttributeSetImpl();
}
} else {
_reusableAtts.clear();
}
}
/**
* Sets a reusable {@link AttributeSet} object instance.
*
* @param attName
* Name of the attribute.
* @param value
* Value of the attribute.
* @param uri
* Name space URI of the attribute.
*/
private void setReusableAttribute(String attName, String value, String uri) {
_reusableAtts.setAttribute(attName, value, uri);
}
/**
* Processes the attributes and XML name space declarations found in the
* given {@link Attributes} instance. XML namespace declarations are added
* to the set of name spaces in scope.
*
* @return AttributeSet,
* @throws SAXException
* If a name space associated with the prefix could not be
* resolved.
*/
public AttributeSet getAttributeSet(Attributes atts) throws SAXException {
prepareReusableAttributes(atts);
processAttributes(atts);
return _reusableAtts;
}
/**
* Processes the attributes and XML name space declarations found in the
* given SAX Attributes. The global {@link AttributeSet} is cleared and
* updated with the attributes. XML name space declarations are added to the
* set of name spaces in scope.
*
* @param atts
* the Attributes to process (can be null).
**/
private void processAttributes(Attributes atts) {
// -- process attributes
if (atts == null || atts.getLength() == 0) {
return;
}
boolean hasQNameAtts = false;
// -- look for any potential namespace declarations
// -- in case namespace processing was disable
// -- on the parser
for (int i = 0; i < atts.getLength(); i++) {
String attName = atts.getQName(i);
if (StringUtil.isNotEmpty(attName)) {
if (attName.equals(XMLNS)) {
_namespaceHandling.addDefaultNamespace(atts.getValue(i));
} else if (attName.startsWith(XMLNS_PREFIX)) {
String prefix = attName.substring(XMLNS_PREFIX.length());
_namespaceHandling.addNamespace(prefix, atts.getValue(i));
} else {
// -- check for prefix
if (attName.indexOf(':') < 0) {
setReusableAttribute(attName, atts.getValue(i), atts
.getURI(i));
} else
hasQNameAtts = true;
}
} else {
// -- if attName is null or empty, just process as a normal
// -- attribute
attName = atts.getLocalName(i);
if (XMLNS.equals(attName)) {
_namespaceHandling.addDefaultNamespace(atts.getValue(i));
} else {
setReusableAttribute(attName, atts.getValue(i), atts
.getURI(i));
}
}
}
// return if there are no qualified name attributes
if (!hasQNameAtts) {
return;
}
// -- if we found any qName-only atts, process those
for (int i = 0; i < atts.getLength(); i++) {
String attName = atts.getQName(i);
if (StringUtil.isNotEmpty(attName)) {
// -- process any non-namespace qName atts
if ((!attName.equals(XMLNS))
&& (!attName.startsWith(XMLNS_PREFIX))) {
int idx = attName.indexOf(':');
if (idx >= 0) {
String prefix = attName.substring(0, idx);
attName = attName.substring(idx + 1);
String nsURI = atts.getURI(i);
if (StringUtil.isEmpty(nsURI)) {
nsURI = _namespaceHandling.getNamespaceURI(prefix);
}
setReusableAttribute(attName, atts.getValue(i), nsURI);
}
}
}
// -- else skip already processed in previous loop
}
}
/**
* Processes the attributes and XML name space declarations found in the SAX
* v1 {@link AttributeList}. XML name space declarations are added to the
* set of XML name spaces in scope.
*
*
* @return AttributeSet An internal representation of XML attributes.
* @throws SAXException
* If the XML name space associated with the prefix could not be
* resolved.
*/
public AttributeSet getAttributeSet(AttributeList atts) throws SAXException {
prepareReusableAttributes(atts);
processAttributeList(atts);
return _reusableAtts;
}
/**
* Processes the attributes and XML name space declarations found in the
* given SAX v1 AttributeList. The global AttributeSet is cleared and
* updated with the attribute data. XML name space declarations are added to
* the set of XML name spaces in scope.
*
* @deprecated
* @param atts
* the {@link AttributeList} to process (can be null)
**/
private void processAttributeList(AttributeList atts) throws SAXException {
if (atts == null || atts.getLength() == 0)
return;
// -- process all namespaces first
int attCount = 0;
boolean[] validAtts = new boolean[atts.getLength()];
for (int i = 0; i < validAtts.length; i++) {
String attName = atts.getName(i);
if (attName.equals(XMLNS)) {
_namespaceHandling.addDefaultNamespace(atts.getValue(i));
} else if (attName.startsWith(XMLNS_PREFIX)) {
String prefix = attName.substring(XMLNS_PREFIX_LENGTH);
_namespaceHandling.addNamespace(prefix, atts.getValue(i));
} else {
validAtts[i] = true;
++attCount;
}
}
// -- process validAtts...if any exist
for (int i = 0; i < validAtts.length; i++) {
if (!validAtts[i])
continue;
String namespace = null;
String attName = atts.getName(i);
int idx = attName.indexOf(':');
if (idx > 0) {
String prefix = attName.substring(0, idx);
if (!prefix.equals(XML_PREFIX)) {
attName = attName.substring(idx + 1);
namespace = _namespaceHandling.getNamespaceURI(prefix);
if (namespace == null) {
String error = "The namespace associated with "
+ "the prefix '" + prefix
+ "' could not be resolved.";
throw new SAXException(error);
}
}
}
setReusableAttribute(attName, atts.getValue(i), namespace);
}
}
/**
* Prepares a reusable AttributeSet object.
*
* @deprecated
* @param atts
* {@link Attributes} to determine the length of the reusable
* {@link AttributeSet} object (can be null).
*/
private void prepareReusableAttributes(AttributeList atts) {
if (_reusableAtts == null) {
if (atts == null) {
_reusableAtts = new AttributeSetImpl();
} else {
_reusableAtts = new AttributeSetImpl(atts.getLength());
}
} else {
_reusableAtts.clear();
}
}
}