Index: src/main/java/org/apache/jackrabbit/core/xml/AbstractSAXEventGenerator.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/xml/AbstractSAXEventGenerator.java (revision 428418) +++ src/main/java/org/apache/jackrabbit/core/xml/AbstractSAXEventGenerator.java (working copy) @@ -16,6 +16,8 @@ */ package org.apache.jackrabbit.core.xml; +import java.util.HashMap; + import org.apache.jackrabbit.name.NameException; import org.apache.jackrabbit.name.QName; import org.apache.jackrabbit.name.SessionNamespaceResolver; @@ -71,6 +73,11 @@ protected final Node startNode; protected final boolean skipBinary; protected final boolean noRecurse; + + /** + * the set of namespace declarations that have already been serialized + */ + protected NamespaceStack namespaces; /** * The jcr:primaryType property name (allowed for session-local prefix mappings) @@ -119,6 +126,8 @@ this.contentHandler = contentHandler; this.skipBinary = skipBinary; this.noRecurse = noRecurse; + // start with an empty set of known prefixes + this.namespaces = new NamespaceStack(null); // resolve the names of some wellknown properties // allowing for session-local prefix mappings @@ -196,7 +205,8 @@ /** * Adds explicit xmlns:prefix="uri" attributes to the - * XML top-level element. The effect is the same as setting the + * XML element as required (e.g., normally just on the root + * element). The effect is the same as setting the * "http://xml.org/sax/features/namespace-prefixes" * property on an SAX parser. * @@ -206,18 +216,38 @@ */ protected void addNamespacePrefixes(int level, AttributesImpl attributes) throws RepositoryException { - if (level == 0) { - String[] prefixes = session.getNamespacePrefixes(); - for (int i = 0; i < prefixes.length; i++) { - if (prefixes[i].length() > 0 - && !QName.NS_XML_PREFIX.equals(prefixes[i])) { + String[] prefixes = session.getNamespacePrefixes(); + NamespaceStack newNamespaces = null; + + for (int i = 0; i < prefixes.length; i++) { + String prefix = prefixes[i]; + + if (prefix.length() > 0 + && !QName.NS_XML_PREFIX.equals(prefix)) { + String uri = session.getNamespaceURI(prefix); + + // get the matching namespace from previous declarations + String mappedToNs = this.namespaces.getNamespaceURI(prefix); + + if (! uri.equals(mappedToNs)) { + // when not the same, add a declaration attributes.addAttribute( - QName.NS_XMLNS_URI, - prefixes[i], - QName.NS_XMLNS_PREFIX + ":" + prefixes[i], - "CDATA", - session.getNamespaceURI(prefixes[i])); + QName.NS_XMLNS_URI, + prefix, + QName.NS_XMLNS_PREFIX + ":" + prefix, + "CDATA", + uri); + + if (newNamespaces == null) { + // replace current namespace stack when needed + newNamespaces = new NamespaceStack(this.namespaces); + this.namespaces = newNamespaces; + } + + // remember the new declaration + newNamespaces.setNamespacePrefix(prefix, uri); } + } } } @@ -276,10 +306,19 @@ // child nodes NodeIterator nodeIter = node.getNodes(); while (nodeIter.hasNext()) { + // recurse Node childNode = nodeIter.nextNode(); - // recurse + + // remember the current namespace declarations + NamespaceStack previousNamespaces = this.namespaces; + process(childNode, level + 1); + + // restore the effective namespace declarations + // (from before visiting the child node) + this.namespaces = previousNamespaces; } + } // leaving node @@ -353,4 +392,59 @@ protected abstract void leaving(Property prop, int level) throws RepositoryException, SAXException; + + + /** + * Implements a simple stack of namespace + * declarations. + */ + + private static class NamespaceStack extends HashMap { + + /** + * Parent stack (may be null) + */ + private final NamespaceStack parent; + + /** + * Instantiate a new stack + * @param parent parent stack (may be null for the initial stack) + */ + public NamespaceStack(NamespaceStack parent) { + this.parent = parent; + } + + /** + * Obtain namespace URI for a prefix + * @param prefix prefix + * @return namespace URI (or null when unknown) + */ + public String getNamespaceURI(String prefix) { + + String namespace = (String)super.get(prefix); + + if (namespace != null) { + // found in this element, return right away + return namespace; + } + else { + // ask parent, when present + if (this.parent == null) { + return null; + } + else { + return this.parent.getNamespaceURI(prefix); + } + } + } + + /** + * Add a new prefix mapping + * @param prefix namespace prefix + * @param uri namespace URI + */ + public void setNamespacePrefix(String prefix, String uri) { + super.put(prefix, uri); + } + } }