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