You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by sy...@apache.org on 2001/12/20 18:16:29 UTC
cvs commit: xml-cocoon2/src/org/apache/cocoon/serialization AbstractTextSerializer.java
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:fumagalli@exoffice.com">Pierpaolo Fumagalli</a>
* (Apache Software Foundation, Exoffice Technologies)
* @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
* @author <a href="mailto:sylvain.wallez@anyware-tech.com">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: webmaster@xml.apache.org
To unsubscribe, e-mail: cocoon-cvs-unsubscribe@xml.apache.org
For additional commands, e-mail: cocoon-cvs-help@xml.apache.org