sylvain 01/12/20 09:16:29 Modified: src/org/apache/cocoon/serialization Tag: cocoon_20_branch AbstractTextSerializer.java Log: Make the serializer smart about whether the SAXTransformerFactory needs namespaces as xmlns:xxx attributes or not. This automatically suppresses this costly processing for Saxon while keeping it for Xalan. Revision Changes Path No revision No revision 1.2.2.8 +286 -157 xml-cocoon2/src/org/apache/cocoon/serialization/AbstractTextSerializer.java Index: AbstractTextSerializer.java =================================================================== RCS file: /home/cvs/xml-cocoon2/src/org/apache/cocoon/serialization/AbstractTextSerializer.java,v retrieving revision 1.2.2.7 retrieving revision 1.2.2.8 diff -u -r1.2.2.7 -r1.2.2.8 --- AbstractTextSerializer.java 2001/11/01 09:44:33 1.2.2.7 +++ AbstractTextSerializer.java 2001/12/20 17:16:28 1.2.2.8 @@ -17,13 +17,19 @@ import org.apache.cocoon.caching.Cacheable; import org.apache.cocoon.caching.NOPCacheValidity; import org.apache.cocoon.util.TraxErrorHandler; +import org.apache.cocoon.xml.AbstractXMLPipe; +import org.apache.cocoon.xml.XMLConsumer; import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.ext.LexicalHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import javax.xml.transform.OutputKeys; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -31,13 +37,14 @@ import java.util.Properties; import java.io.OutputStream; import java.io.BufferedOutputStream; +import java.io.StringWriter; /** * @author <a href="mailto:[EMAIL PROTECTED]">Pierpaolo Fumagalli</a> * (Apache Software Foundation, Exoffice Technologies) * @author <a href="mailto:[EMAIL PROTECTED]">Stefano Mazzocchi</a> * @author <a href="mailto:[EMAIL PROTECTED]">Sylvain Wallez</a> - * @version CVS $Revision: 1.2.2.7 $ $Date: 2001/11/01 09:44:33 $ + * @version CVS $Revision: 1.2.2.8 $ $Date: 2001/12/20 17:16:28 $ */ public abstract class AbstractTextSerializer extends AbstractSerializer implements Configurable, Cacheable, Poolable { @@ -52,37 +59,62 @@ protected Properties format = new Properties(); /** - * The prefixes of startPreficMapping() declarations for the coming element. + * The default output buffer size. */ - private List prefixList = new ArrayList(); - + private static final int DEFAULT_BUFFER_SIZE = 8192; + /** - * The URIs of startPrefixMapping() declarations for the coming element. + * The output buffer size to use. */ - private List uriList = new ArrayList(); - + private int outputBufferSize = DEFAULT_BUFFER_SIZE; + /** - * Maps of URI<->prefix mappings. Used to work around a bug in the Xalan - * serializer. + * Cache for avoiding unnecessary checks of namespaces abilities. + * It associates a Boolean to the transformer class name. */ - private Map uriToPrefixMap = new HashMap(); - private Map prefixToUriMap = new HashMap(); - + private static Map needsNamespaceCache = new HashMap(); + + /** + * The pipe that adds namespaces as xmlns attributes. + */ + private NamespaceAsAttributes namespacePipe; + /** - * True if there has been some startPrefixMapping() for the coming element. + * Interpose namespace pipe if needed. */ - private boolean hasMappings = false; + public void setConsumer(XMLConsumer consumer) { + if (this.namespacePipe == null) { + super.setConsumer(consumer); + } else { + this.namespacePipe.setConsumer(consumer); + super.setConsumer(this.namespacePipe); + } + } /** - * The default output buffer size. + * Interpose namespace pipe if needed. */ - private static final int DEFAULT_BUFFER_SIZE = 8192; - + public void setContentHandler(ContentHandler handler) { + if (this.namespacePipe == null) { + super.setContentHandler(handler); + } else { + this.namespacePipe.setContentHandler(handler); + super.setContentHandler(this.namespacePipe); + } + } + /** - * The output buffer size to use. + * Interpose namespace pipe if needed. */ - private int outputBufferSize = DEFAULT_BUFFER_SIZE; - + public void setLexicalHandler(LexicalHandler handler) { + if (this.namespacePipe == null) { + super.setLexicalHandler(handler); + } else { + this.namespacePipe.setLexicalHandler(handler); + super.setLexicalHandler(this.namespacePipe); + } + } + /** * Helper for TransformerFactory. */ @@ -162,6 +194,17 @@ if (! version.getLocation().equals("-")) { format.put(OutputKeys.VERSION,version.getValue()); } + + // Check if we need namespace as attributes. + try { + if (needsNamespacesAsAttributes()) { + // Setup a correction pipe + this.namespacePipe = new NamespaceAsAttributes(); + this.namespacePipe.setLogger(getLogger()); + } + } catch(Exception e) { + getLogger().warn("Cannot know if transformer needs namespaces attributes - assuming NO.", e); + } } /** @@ -186,159 +229,245 @@ */ public CacheValidity generateValidity() { return new NOPCacheValidity(); - } - - /** - * Recycle serializer by removing references - */ - public void recycle() { - clearMappings(); - this.uriToPrefixMap.clear(); - this.prefixToUriMap.clear(); - super.recycle(); - } - - /** - * - */ - public void startDocument() - throws SAXException { - // Cleanup - clearMappings(); - super.startDocument(); } - + /** - * Add tracking of mappings to be able to add <code>xmlns:</code> attributes - * in <code>startElement()</code>. + * Checks if the used Trax implementation correctly handles namespaces set using + * <code>startPrefixMapping()</code>, but wants them also as 'xmlns:' attributes. + * <p> + * The check consists in sending SAX events representing a minimal namespaced document + * with namespaces defined only with calls to <code>startPrefixMapping</code> (no + * xmlns:xxx attributes) and check if they are present in the resulting text. */ - public void startPrefixMapping(String prefix, String uri) - throws SAXException { - // Store the mappings to reconstitute xmlns:attributes - this.hasMappings = true; - this.prefixList.add(prefix); - this.uriList.add(uri); - - // store mappings for xalan-bug-workaround. - // append the prefix colon now, in order to save concatenations later, but - // only for non-empty prefixes. - if(prefix.length() > 0) - this.uriToPrefixMap.put(uri, prefix + ":"); - else - this.uriToPrefixMap.put(uri, prefix); - this.prefixToUriMap.put(prefix, uri); + protected boolean needsNamespacesAsAttributes() throws Exception { - super.startPrefixMapping(prefix, uri); - } - - /** - * End the scope of a prefix-URI mapping: - * remove entry from mapping tables. - */ - public void endPrefixMapping(String prefix) - throws SAXException { - // remove mappings for xalan-bug-workaround. - // Unfortunately, we're not passed the uri, but the prefix here, - // so we need to maintain maps in both directions. - if(this.prefixToUriMap.containsKey(prefix)) { - this.uriToPrefixMap.remove((String) this.prefixToUriMap.get(prefix)); - this.prefixToUriMap.remove(prefix); - } + SAXTransformerFactory factory = getTransformerFactory(); - super.endPrefixMapping(prefix); + Boolean cacheValue = (Boolean)this.needsNamespaceCache.get(factory.getClass().getName()); + if (cacheValue != null) { + return cacheValue.booleanValue(); + + } else { + // Serialize a minimal document to check how namespaces are handled. + StringWriter writer = new StringWriter(); + + String uri = "namespaceuri"; + String prefix = "nsp"; + String check = "xmlns:" + prefix + "='" + uri + "'"; + + TransformerHandler handler = factory.newTransformerHandler(); + + handler.setResult(new StreamResult(writer)); + handler.getTransformer().setOutputProperties(format); + + // Output a single element + handler.startDocument(); + handler.startPrefixMapping(prefix, uri); + handler.startElement(uri, "element", "", new AttributesImpl()); + handler.endPrefixMapping(prefix); + handler.endDocument(); + + String text = writer.toString(); + + // Check if the namespace is there (replace " by ' to be sure of what we search in) + boolean needsIt = (text.replace('"', '\'').indexOf(check) == -1); + + String msg = needsIt ? " needs namespace attributes (will be slower)." : + " handles correctly namespaces."; + + getLogger().debug("Trax handler " + handler.getClass().getName() + msg); + + this.needsNamespaceCache.put(factory.getClass().getName(), new Boolean(needsIt)); + + return needsIt; + } } + //-------------------------------------------------------------------------------------------- + /** - * Ensure all namespace declarations are present as <code>xmlns:</code> attributes - * and add those needed before calling superclass. This is a workaround for a Xalan bug - * (at least in version 2.0.1) : <code>org.apache.xalan.serialize.SerializerToXML</code> - * ignores <code>start/endPrefixMapping()</code>. - */ - public void startElement(String eltUri, String eltLocalName, String eltQName, Attributes attrs) - throws SAXException { - - // try to restore the qName. The map already contains the colon - if(null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri) ) - eltQName = (String) this.uriToPrefixMap.get(eltUri) + eltLocalName; - - if (this.hasMappings) { - // Add xmlns* attributes where needed - - // New Attributes if we have to add some. - AttributesImpl newAttrs = null; + * A pipe that ensures that all namespace prefixes are also present as + * 'xmlns:' attributes. This used to circumvent Xalan's serialization behaviour + * which is to ignore namespaces if they're not present as 'xmlns:xxx' attributes. + */ + public static class NamespaceAsAttributes extends AbstractXMLPipe { + + /** + * The prefixes of startPreficMapping() declarations for the coming element. + */ + private List prefixList = new ArrayList(); + + /** + * The URIs of startPrefixMapping() declarations for the coming element. + */ + private List uriList = new ArrayList(); + + /** + * Maps of URI<->prefix mappings. Used to work around a bug in the Xalan + * serializer. + */ + private Map uriToPrefixMap = new HashMap(); + private Map prefixToUriMap = new HashMap(); + + /** + * True if there has been some startPrefixMapping() for the coming element. + */ + private boolean hasMappings = false; - int mappingCount = this.prefixList.size(); - int attrCount = attrs.getLength(); + public void startDocument() + throws SAXException { + // Cleanup + this.uriToPrefixMap.clear(); + this.prefixToUriMap.clear(); + clearMappings(); + super.startDocument(); + } - for(int mapping = 0; mapping < mappingCount; mapping++) { - - // Build infos for this namespace - String uri = (String)this.uriList.get(mapping); - String prefix = (String)this.prefixList.get(mapping); - String qName = prefix.equals("") ? "xmlns" : ("xmlns:" + prefix); - - // Search for the corresponding xmlns* attribute - boolean found = false; - find : for (int attr = 0; attr < attrCount; attr++) { - if (qName.equals(attrs.getQName(attr))) { - // Check if mapping and attribute URI match - if (! uri.equals(attrs.getValue(attr))) { - getLogger().error("AbstractTextSerializer:URI in prefix mapping and attribute do not match : '" + uri + "' - '" + attrs.getURI(attr) + "'"); - throw new SAXException("URI in prefix mapping and attribute do not match"); + /** + * Track mappings to be able to add <code>xmlns:</code> attributes + * in <code>startElement()</code>. + */ + public void startPrefixMapping(String prefix, String uri) + throws SAXException { + // Store the mappings to reconstitute xmlns:attributes + this.hasMappings = true; + this.prefixList.add(prefix); + this.uriList.add(uri); + + // append the prefix colon now, in order to save concatenations later, but + // only for non-empty prefixes. + if(prefix.length() > 0) { + this.uriToPrefixMap.put(uri, prefix + ":"); + } else { + this.uriToPrefixMap.put(uri, prefix); + } + + this.prefixToUriMap.put(prefix, uri); + + super.startPrefixMapping(prefix, uri); + } + + /** + * Ensure all namespace declarations are present as <code>xmlns:</code> attributes + * and add those needed before calling superclass. This is a workaround for a Xalan bug + * (at least in version 2.0.1) : <code>org.apache.xalan.serialize.SerializerToXML</code> + * ignores <code>start/endPrefixMapping()</code>. + */ + public void startElement(String eltUri, String eltLocalName, String eltQName, Attributes attrs) + throws SAXException { + + // try to restore the qName. The map already contains the colon + if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri) ) + eltQName = (String) this.uriToPrefixMap.get(eltUri) + eltLocalName; + + if (this.hasMappings) { + // Add xmlns* attributes where needed + + // New Attributes if we have to add some. + AttributesImpl newAttrs = null; + + int mappingCount = this.prefixList.size(); + int attrCount = attrs.getLength(); + + for(int mapping = 0; mapping < mappingCount; mapping++) { + + // Build infos for this namespace + String uri = (String)this.uriList.get(mapping); + String prefix = (String)this.prefixList.get(mapping); + String qName = prefix.equals("") ? "xmlns" : ("xmlns:" + prefix); + + // Search for the corresponding xmlns* attribute + boolean found = false; + find : for (int attr = 0; attr < attrCount; attr++) { + if (qName.equals(attrs.getQName(attr))) { + // Check if mapping and attribute URI match + if (! uri.equals(attrs.getValue(attr))) { + getLogger().error("AbstractTextSerializer:URI in prefix mapping and attribute do not match : '" + uri + "' - '" + attrs.getURI(attr) + "'"); + throw new SAXException("URI in prefix mapping and attribute do not match"); + } + found = true; + break find; } - found = true; - break find; - } - } - - if (!found) { - // Need to add this namespace - if (newAttrs == null) { - // Need to test if attrs is empty or we go into an infinite loop... - // Well know SAX bug which I spent 3 hours to remind of :-( - if (attrCount == 0) - newAttrs = new AttributesImpl(); - else - newAttrs = new AttributesImpl(attrs); } - - if (prefix.equals("")) { - newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, "xmlns", "xmlns", "CDATA", uri); - } else { - newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, prefix, qName, "CDATA", uri); + + if (!found) { + // Need to add this namespace + if (newAttrs == null) { + // Need to test if attrs is empty or we go into an infinite loop... + // Well know SAX bug which I spent 3 hours to remind of :-( + if (attrCount == 0) + newAttrs = new AttributesImpl(); + else + newAttrs = new AttributesImpl(attrs); + } + + if (prefix.equals("")) { + newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, "xmlns", "xmlns", "CDATA", uri); + } else { + newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, prefix, qName, "CDATA", uri); + } } - } - } // end for mapping - - // Cleanup for the next element - clearMappings(); - - // Start element with new attributes, if any - super.startElement(eltUri, eltLocalName, eltQName, newAttrs == null ? attrs : newAttrs); + } // end for mapping + + // Cleanup for the next element + clearMappings(); + + // Start element with new attributes, if any + super.startElement(eltUri, eltLocalName, eltQName, newAttrs == null ? attrs : newAttrs); + } + else { + // Normal job + super.startElement(eltUri, eltLocalName, eltQName, attrs); + } } - else { - // Normal job - super.startElement(eltUri, eltLocalName, eltQName, attrs); + + + /** + * Receive notification of the end of an element. + * Try to restore the element qName. + */ + public void endElement(String eltUri, String eltLocalName, String eltQName) + throws SAXException { + // try to restore the qName. The map already contains the colon + if(null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri) ) + eltQName = (String) this.uriToPrefixMap.get(eltUri) + eltLocalName; + + super.endElement(eltUri, eltLocalName, eltQName); } - } - /** - * Receive notification of the end of an element. - * Try to restore the element qName. - */ - public void endElement(String eltUri, String eltLocalName, String eltQName) - throws SAXException { - // try to restore the qName. The map already contains the colon - if(null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri) ) - eltQName = (String) this.uriToPrefixMap.get(eltUri) + eltLocalName; + /** + * End the scope of a prefix-URI mapping: + * remove entry from mapping tables. + */ + public void endPrefixMapping(String prefix) + throws SAXException { + // remove mappings for xalan-bug-workaround. + // Unfortunately, we're not passed the uri, but the prefix here, + // so we need to maintain maps in both directions. + if(this.prefixToUriMap.containsKey(prefix)) { + this.uriToPrefixMap.remove((String) this.prefixToUriMap.get(prefix)); + this.prefixToUriMap.remove(prefix); + } + + super.endPrefixMapping(prefix); + } - super.endElement(eltUri, eltLocalName, eltQName); - } - - private void clearMappings() - { - this.hasMappings = false; - this.prefixList.clear(); - this.uriList.clear(); + /** + * + */ + public void endDocument() throws SAXException { + // Cleanup + this.uriToPrefixMap.clear(); + this.prefixToUriMap.clear(); + clearMappings(); + super.endDocument(); + } + + private void clearMappings() + { + this.hasMappings = false; + this.prefixList.clear(); + this.uriList.clear(); + } } }
---------------------------------------------------------------------- In case of troubles, e-mail: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]