You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by fm...@apache.org on 2008/04/02 22:58:55 UTC

svn commit: r644053 - in /jackrabbit/branches/1.4/jackrabbit-jcr-rmi: ./ src/main/java/org/apache/jackrabbit/rmi/client/ src/main/java/org/apache/jackrabbit/rmi/xml/

Author: fmeschbe
Date: Wed Apr  2 13:58:54 2008
New Revision: 644053

URL: http://svn.apache.org/viewvc?rev=644053&view=rev
Log:
JCR-1343 Replace xerces for serialization by JAXP
  - Implement Jukka's proposal to copy the jackrabbit jcr commons
    1.4.2 xml *ContentHandler classes and remove the dependency
    to jackrabbit jcr commons 1.4.2 to have the rmi library be
    drop-in compatible with the Jackrabbit 1.4 release.

Added:
    jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/
    jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/DefaultContentHandler.java
    jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/ParsingContentHandler.java
    jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/SerializingContentHandler.java
Modified:
    jackrabbit/branches/1.4/jackrabbit-jcr-rmi/pom.xml
    jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java
    jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java

Modified: jackrabbit/branches/1.4/jackrabbit-jcr-rmi/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.4/jackrabbit-jcr-rmi/pom.xml?rev=644053&r1=644052&r2=644053&view=diff
==============================================================================
--- jackrabbit/branches/1.4/jackrabbit-jcr-rmi/pom.xml (original)
+++ jackrabbit/branches/1.4/jackrabbit-jcr-rmi/pom.xml Wed Apr  2 13:58:54 2008
@@ -94,7 +94,6 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>1.4.2</version>
     </dependency>
     <dependency>
       <groupId>xerces</groupId>

Modified: jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java?rev=644053&r1=644052&r2=644053&view=diff
==============================================================================
--- jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java (original)
+++ jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java Wed Apr  2 13:58:54 2008
@@ -41,10 +41,10 @@
 import javax.xml.transform.sax.SAXResult;
 import javax.xml.transform.stream.StreamSource;
 
-import org.apache.jackrabbit.commons.xml.DefaultContentHandler;
-import org.apache.jackrabbit.commons.xml.SerializingContentHandler;
 import org.apache.jackrabbit.rmi.remote.RemoteSession;
 import org.apache.jackrabbit.rmi.value.SerialValueFactory;
+import org.apache.jackrabbit.rmi.xml.DefaultContentHandler;
+import org.apache.jackrabbit.rmi.xml.SerializingContentHandler;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 

Modified: jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java?rev=644053&r1=644052&r2=644053&view=diff
==============================================================================
--- jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java (original)
+++ jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java Wed Apr  2 13:58:54 2008
@@ -31,12 +31,12 @@
 import javax.jcr.query.QueryManager;
 import javax.jcr.version.Version;
 
-import org.apache.jackrabbit.commons.xml.DefaultContentHandler;
-import org.apache.jackrabbit.commons.xml.SerializingContentHandler;
 import org.apache.jackrabbit.rmi.remote.RemoteNamespaceRegistry;
 import org.apache.jackrabbit.rmi.remote.RemoteNodeTypeManager;
 import org.apache.jackrabbit.rmi.remote.RemoteQueryManager;
 import org.apache.jackrabbit.rmi.remote.RemoteWorkspace;
+import org.apache.jackrabbit.rmi.xml.DefaultContentHandler;
+import org.apache.jackrabbit.rmi.xml.SerializingContentHandler;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 

Added: jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/DefaultContentHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/DefaultContentHandler.java?rev=644053&view=auto
==============================================================================
--- jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/DefaultContentHandler.java (added)
+++ jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/DefaultContentHandler.java Wed Apr  2 13:58:54 2008
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.rmi.xml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Adapter class for exposing a {@link ContentHandler} instance as
+ * {@link DefaultHandler} object.
+ * 
+ * @deprecated This class will be replaced by
+ *             org.apache.jackrabbit.commons.xml.DefaultContenthandler starting
+ *             with Jackrabbit JCR-RMI 1.5.
+ */
+public class DefaultContentHandler extends DefaultHandler {
+
+    /**
+     * The adapted content handler instance.
+     */
+    private final ContentHandler handler;
+
+    /**
+     * Creates a {@link DefaultHandler} adapter for the given content
+     * handler.
+     *
+     * @param handler content handler
+     */
+    public DefaultContentHandler(ContentHandler handler) {
+        this.handler = handler;
+    }
+
+    //------------------------------------------------------< ContentHandler >
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param ch passed through
+     * @param start passed through
+     * @param length passed through
+     * @throws SAXException if an error occurs
+     */
+    public void characters(char[] ch, int start, int length)
+            throws SAXException {
+        handler.characters(ch, start, length);
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @throws SAXException if an error occurs
+     */
+    public void endDocument() throws SAXException {
+        handler.endDocument();
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param namespaceURI passed through
+     * @param localName passed through
+     * @param qName passed through
+     * @throws SAXException if an error occurs
+     */
+    public void endElement(
+            String namespaceURI, String localName, String qName)
+            throws SAXException {
+        handler.endElement(namespaceURI, localName, qName);
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param prefix passed through
+     * @throws SAXException if an error occurs
+     */
+    public void endPrefixMapping(String prefix) throws SAXException {
+        handler.endPrefixMapping(prefix);
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param ch passed through
+     * @param start passed through
+     * @param length passed through
+     * @throws SAXException if an error occurs
+     */
+    public void ignorableWhitespace(char[] ch, int start, int length)
+            throws SAXException {
+        handler.ignorableWhitespace(ch, start, length);
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param target passed through
+     * @param data passed through
+     * @throws SAXException if an error occurs
+     */
+    public void processingInstruction(String target, String data)
+            throws SAXException {
+        handler.processingInstruction(target, data);
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param locator passed through
+     */
+    public void setDocumentLocator(Locator locator) {
+        handler.setDocumentLocator(locator);
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param name passed through
+     * @throws SAXException if an error occurs
+     */
+    public void skippedEntity(String name) throws SAXException {
+        handler.skippedEntity(name);
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @throws SAXException if an error occurs
+     */
+    public void startDocument() throws SAXException {
+        handler.startDocument();
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param namespaceURI passed through
+     * @param localName passed through
+     * @param qName passed through
+     * @param atts passed through
+     * @throws SAXException if an error occurs
+     */
+    public void startElement(
+            String namespaceURI, String localName, String qName,
+            Attributes atts) throws SAXException {
+        handler.startElement(namespaceURI, localName, qName, atts);
+    }
+
+    /**
+     * Delegated to {@link #handler}.
+     *
+     * @param prefix passed through
+     * @param uri passed through
+     * @throws SAXException if an error occurs
+     */
+    public void startPrefixMapping(String prefix, String uri)
+            throws SAXException {
+        handler.startPrefixMapping(prefix, uri);
+    }
+
+}

Added: jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/ParsingContentHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/ParsingContentHandler.java?rev=644053&view=auto
==============================================================================
--- jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/ParsingContentHandler.java (added)
+++ jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/ParsingContentHandler.java Wed Apr  2 13:58:54 2008
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.rmi.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Utility class that decorates a {@link ContentHandler} instance with
+ * simple XML parsing capability.
+ * 
+ * @deprecated This class will be replaced by
+ *             org.apache.jackrabbit.commons.xml.ParsingContenthandler starting
+ *             with Jackrabbit JCR-RMI 1.5.
+ */
+public class ParsingContentHandler extends DefaultContentHandler {
+
+    /**
+     * Creates a {@link DefaultHandler} adapter for the given content
+     * handler.
+     *
+     * @param handler content handler
+     */
+    public ParsingContentHandler(ContentHandler handler) {
+        super(handler);
+    }
+
+    /**
+     * Utility method that parses the given input stream using this handler.
+     * The parser is namespace-aware and will not resolve external entity
+     * references.
+     *
+     * @param in XML input stream
+     * @throws IOException if an I/O error occurs
+     * @throws SAXException if an XML parsing error occurs
+     */
+    public void parse(InputStream in) throws IOException, SAXException {
+        try {
+            SAXParserFactory factory = SAXParserFactory.newInstance();
+            factory.setNamespaceAware(true);
+            factory.newSAXParser().parse(new InputSource(in), this);
+        } catch (ParserConfigurationException e) {
+            throw new SAXException("SAX parser configuration error", e);
+        }
+    }
+
+    /**
+     * Returns an empty stream to prevent the XML parser from attempting
+     * to resolve external entity references.
+     */
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException {
+        return new InputSource(new ByteArrayInputStream(new byte[0]));
+    }
+
+}

Added: jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/SerializingContentHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/SerializingContentHandler.java?rev=644053&view=auto
==============================================================================
--- jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/SerializingContentHandler.java (added)
+++ jackrabbit/branches/1.4/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/xml/SerializingContentHandler.java Wed Apr  2 13:58:54 2008
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.rmi.xml;
+
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * A {@link ContentHandler} that serializes SAX events to a given {@link Result}
+ * instance. The JAXP {@link SAXTransformerFactory} facility is used for the
+ * serialization.
+ * <p>
+ * This class explicitly ensures that all namespace prefixes are also present as
+ * xmlns attributes in the serialized XML document. This avoids problems with
+ * Xalan's serialization behaviour which was (at least during JDK 1.4) to ignore
+ * namespaces if they were not present as xmlns attributes.
+ * <p>
+ * NOTE: The code in this class was originally written for Apache Cocoon and is
+ * included with some modifications here in Apache Jackrabbit. See the
+ * org.apache.cocoon.serialization.AbstractTextSerializer class in the
+ * cocoon-pipeline-impl component for the original code.
+ * 
+ * @deprecated This class will be replaced by
+ *             org.apache.jackrabbit.commons.xml.SerializingContenthandler
+ *             starting with Jackrabbit JCR-RMI 1.5.
+ */
+public class SerializingContentHandler extends DefaultContentHandler {
+
+    /** The URI for xml namespaces */
+    private static final String XML = "http://www.w3.org/XML/1998/namespace";
+
+    /**
+     * Creates a serializing content handler that writes to the given stream.
+     *
+     * @param stream serialization target
+     * @return serializing content handler
+     * @throws SAXException if the content handler could not be initialized
+     */
+    public static DefaultHandler getSerializer(OutputStream output)
+            throws SAXException {
+        return getSerializer(new StreamResult(output));
+    }
+
+    /**
+     * Creates a serializing content handler that writes to the given writer.
+     *
+     * @param writer serialization target
+     * @return serializing content handler
+     * @throws SAXException if the content handler could not be initialized
+     */
+    public static DefaultHandler getSerializer(Writer writer)
+            throws SAXException {
+        return getSerializer(new StreamResult(writer));
+    }
+
+    /**
+     * Creates a serializing content handler that writes to the given result.
+     *
+     * @param result serialization target
+     * @return serializing content handler
+     * @throws SAXException if the content handler could not be initialized
+     */
+    public static DefaultHandler getSerializer(Result result)
+            throws SAXException {
+        try {
+            SAXTransformerFactory factory = (SAXTransformerFactory)
+            SAXTransformerFactory.newInstance();
+
+            TransformerHandler handler = factory.newTransformerHandler();
+            handler.setResult(result);
+
+            // Test whether the NamespaceAsAttributes wrapper is needed
+            StringWriter writer = new StringWriter();
+            TransformerHandler probe = factory.newTransformerHandler();
+            probe.setResult(new StreamResult(writer));
+            probe.startDocument();
+            probe.startPrefixMapping("p", "uri");
+            probe.startElement("uri", "e", "p:e", new AttributesImpl());
+            probe.endElement("uri", "e", "p:e");
+            probe.endPrefixMapping("p");
+            probe.endDocument();
+            if (writer.toString().indexOf("xmlns") == -1) {
+                // The serializer does not output xmlns declarations,
+                // so we need to do it explicitly with this wrapper
+                return new SerializingContentHandler(handler);
+            } else {
+                return new DefaultContentHandler(handler);
+            }
+        } catch (TransformerConfigurationException e) {
+            throw new SAXException("Failed to initialize XML serializer", e);
+        }
+    }
+
+    /**
+     * The prefixes of startPrefixMapping() 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;
+
+    private SerializingContentHandler(ContentHandler handler) {
+        super(handler);
+    }
+
+    public void startDocument() throws SAXException {
+        // Cleanup
+        this.uriToPrefixMap.clear();
+        this.prefixToUriMap.clear();
+        clearMappings();
+        super.startDocument();
+    }
+
+    /**
+     * 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
+        // except prefixes starting with "xml": these are reserved
+        // VG: (uri != null) fixes NPE in startElement
+        if (uri != null && !prefix.startsWith("xml")) {
+            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 = 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;
+                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))) {
+                            throw new SAXException("URI in prefix mapping and attribute do not match");
+                        }
+                        found = true;
+                        break;
+                    }
+                }
+
+                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(XML, qName, qName, "CDATA", uri);
+                    } else {
+                        newAttrs.addAttribute(XML, 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);
+        } 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 = this.uriToPrefixMap.get(eltUri) + eltLocalName;
+        }
+        super.endElement(eltUri, eltLocalName, eltQName);
+    }
+
+    /**
+     * 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(this.prefixToUriMap.get(prefix));
+            this.prefixToUriMap.remove(prefix);
+        }
+
+        if (hasMappings) {
+            // most of the time, start/endPrefixMapping calls have an element event between them,
+            // which will clear the hasMapping flag and so this code will only be executed in the
+            // rather rare occasion when there are start/endPrefixMapping calls with no element
+            // event in between. If we wouldn't remove the items from the prefixList and uriList here,
+            // the namespace would be incorrectly declared on the next element following the
+            // endPrefixMapping call.
+            int pos = prefixList.lastIndexOf(prefix);
+            if (pos != -1) {
+                prefixList.remove(pos);
+                uriList.remove(pos);
+            }
+        }
+
+        super.endPrefixMapping(prefix);
+    }
+
+    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();
+    }
+
+}