You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by cz...@apache.org on 2008/12/22 15:50:00 UTC

svn commit: r728704 [2/2] - in /cocoon/whiteboard/xml-utils: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/cocoon/ src/main/java/org/apache/cocoon/xml/ src/main/java/org/apache/cocoon/xml/util/ s...

Added: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/SaxBuffer.java
URL: http://svn.apache.org/viewvc/cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/SaxBuffer.java?rev=728704&view=auto
==============================================================================
--- cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/SaxBuffer.java (added)
+++ cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/SaxBuffer.java Mon Dec 22 06:49:59 2008
@@ -0,0 +1,563 @@
+/*
+ * 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.cocoon.xml.util;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * A class that can record SAX events and replay them later.
+ *
+ * <p>Compared to the old Cocoon.XMLByteStreamCompiler,
+ * this class is many times faster at sending out the recorded SAX events since
+ * it doesn't need to convert between byte and char representations etc.
+ * On the other hand, its data structure is more complex then a simple byte array,
+ * making XMLByteStreamCompiler better in case the recorded SAX should be stored long-term.
+ *
+ * <p>Use this class if you need to frequently generate smaller amounts of SAX events,
+ * or replay a set of recorded start events immediately.</p>
+ *
+ * <p>Both {@link ContentHandler} and {@link LexicalHandler} are supported, the only
+ * exception is that the setDocumentLocator event is not recorded.</p>
+ *
+ * @version $Id$
+ */
+public class SaxBuffer implements Serializable {
+
+    /**
+     * Stores list of {@link SaxBit} objects.
+     */
+    protected List<SaxBit> saxbits;
+
+    /**
+     * Creates empty SaxBuffer
+     */
+    public SaxBuffer() {
+        this.saxbits = new ArrayList<SaxBit>();
+    }
+
+    /**
+     * Creates SaxBuffer based on the provided bits list.
+     */
+    public SaxBuffer(List<SaxBit> bits) {
+        this.saxbits = bits;
+    }
+
+    /**
+     * Creates copy of another SaxBuffer
+     */
+    public SaxBuffer(SaxBuffer saxBuffer) {
+        this.saxbits = new ArrayList<SaxBit>(saxBuffer.saxbits);
+    }
+
+    //
+    // ContentHandler Interface
+    //
+
+    public void skippedEntity(String name) throws SAXException {
+        saxbits.add(new SkippedEntity(name));
+    }
+
+    public void setDocumentLocator(Locator locator) {
+        // Don't record this event
+    }
+
+    public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
+        saxbits.add(new IgnorableWhitespace(ch, start, length));
+    }
+
+    public void processingInstruction(String target, String data) throws SAXException {
+        saxbits.add(new PI(target, data));
+    }
+
+    public void startDocument() throws SAXException {
+        saxbits.add(StartDocument.SINGLETON);
+    }
+
+    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+        saxbits.add(new StartElement(namespaceURI, localName, qName, atts));
+    }
+
+    public void endPrefixMapping(String prefix) throws SAXException {
+        saxbits.add(new EndPrefixMapping(prefix));
+    }
+
+    public void characters(char ch[], int start, int length) throws SAXException {
+        saxbits.add(new Characters(ch, start, length));
+    }
+
+    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+        saxbits.add(new EndElement(namespaceURI, localName, qName));
+    }
+
+    public void endDocument() throws SAXException {
+        saxbits.add(EndDocument.SINGLETON);
+    }
+
+    public void startPrefixMapping(String prefix, String uri) throws SAXException {
+        saxbits.add(new StartPrefixMapping(prefix, uri));
+    }
+
+    //
+    // LexicalHandler Interface
+    //
+
+    public void endCDATA() throws SAXException {
+        saxbits.add(EndCDATA.SINGLETON);
+    }
+
+    public void comment(char ch[], int start, int length) throws SAXException {
+        saxbits.add(new Comment(ch, start, length));
+    }
+
+    public void startEntity(String name) throws SAXException {
+        saxbits.add(new StartEntity(name));
+    }
+
+    public void endDTD() throws SAXException {
+        saxbits.add(EndDTD.SINGLETON);
+    }
+
+    public void startDTD(String name, String publicId, String systemId) throws SAXException {
+        saxbits.add(new StartDTD(name, publicId, systemId));
+    }
+
+    public void startCDATA() throws SAXException {
+        saxbits.add(StartCDATA.SINGLETON);
+    }
+
+    public void endEntity(String name) throws SAXException {
+        saxbits.add(new EndEntity(name));
+    }
+
+    //
+    // Public Methods
+    //
+
+    /**
+     * Add a another buffer
+     */
+    public void saxBuffer(SaxBuffer xml) {
+        saxbits.add(new XMLizableBit(xml));
+    }
+
+    /**
+     * @return true if buffer is empty
+     */
+    public boolean isEmpty() {
+        return saxbits.isEmpty();
+    }
+
+    /**
+     * @return unmodifiable list of SAX bits
+     */
+    public List<SaxBit> getBits() {
+        return Collections.unmodifiableList(saxbits);
+    }
+
+    /**
+     * Stream this buffer into the provided content handler.
+     * If contentHandler object implements LexicalHandler, it will get lexical
+     * events as well.
+     */
+    public void toSAX(ContentHandler contentHandler) throws SAXException {
+        for (Iterator<SaxBit> i = saxbits.iterator(); i.hasNext();) {
+            SaxBit saxbit = i.next();
+            saxbit.send(contentHandler);
+        }
+    }
+
+    /**
+     * @return String value of the buffer
+     */
+    public String toString() {
+        // NOTE: This method is used in i18n XML bundle implementation
+        final StringBuffer value = new StringBuffer();
+        for (Iterator<SaxBit> i = saxbits.iterator(); i.hasNext();) {
+            final SaxBit saxbit = i.next();
+            if (saxbit instanceof Characters) {
+                ((Characters) saxbit).toString(value);
+            }
+        }
+
+        return value.toString();
+    }
+
+    /**
+     * Clear this buffer
+     */
+    public void recycle() {
+        saxbits.clear();
+    }
+
+    /**
+     * Dump buffer contents into the provided writer.
+     */
+    public void dump(Writer writer) throws IOException {
+        Iterator<SaxBit> i = saxbits.iterator();
+        while (i.hasNext()) {
+            final SaxBit saxbit = i.next();
+            saxbit.dump(writer);
+        }
+        writer.flush();
+    }
+
+    //
+    // Implementation Methods
+    //
+
+    /**
+     * Adds a SaxBit to the bits list
+     */
+    protected final void addBit(SaxBit bit) {
+        saxbits.add(bit);
+    }
+
+    /**
+     * Iterates through the bits list
+     */
+    protected final Iterator<SaxBit> bits() {
+        return saxbits.iterator();
+    }
+
+    /**
+     * SaxBit is a representation of the SAX event. Every SaxBit is immutable object.
+     */
+    interface SaxBit {
+        public void send(ContentHandler contentHandler) throws SAXException;
+        public void dump(Writer writer) throws IOException;
+    }
+
+    public final static class StartDocument implements SaxBit, Serializable {
+        public static final StartDocument SINGLETON = new StartDocument();
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.startDocument();
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[StartDocument]\n");
+        }
+    }
+
+    public final static class EndDocument implements SaxBit, Serializable {
+        public static final EndDocument SINGLETON = new EndDocument();
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.endDocument();
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[EndDocument]\n");
+        }
+    }
+
+    public final static class PI implements SaxBit, Serializable {
+        public final String target;
+        public final String data;
+
+        public PI(String target, String data) {
+            this.target = target;
+            this.data = data;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.processingInstruction(target, data);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[ProcessingInstruction] target=" + target + ",data=" + data + "\n");
+        }
+    }
+
+    public final static class StartDTD implements SaxBit, Serializable {
+        public final String name;
+        public final String publicId;
+        public final String systemId;
+
+        public StartDTD(String name, String publicId, String systemId) {
+            this.name = name;
+            this.publicId = publicId;
+            this.systemId = systemId;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            if (contentHandler instanceof LexicalHandler)
+                ((LexicalHandler)contentHandler).startDTD(name, publicId, systemId);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[StartDTD] name=" + name + ",publicId=" + publicId + ",systemId=" + systemId + "\n");
+        }
+    }
+
+    public final static class EndDTD implements SaxBit, Serializable {
+        public static final EndDTD SINGLETON = new EndDTD();
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            if (contentHandler instanceof LexicalHandler)
+                ((LexicalHandler)contentHandler).endDTD();
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[EndDTD]\n");
+        }
+    }
+
+    public final static class StartEntity implements SaxBit, Serializable {
+        public final String name;
+
+        public StartEntity(String name) {
+            this.name = name;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            if (contentHandler instanceof LexicalHandler)
+                ((LexicalHandler)contentHandler).startEntity(name);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[StartEntity] name=" + name + "\n");
+        }
+    }
+
+    public final static class EndEntity implements SaxBit, Serializable {
+        public final String name;
+
+        public EndEntity(String name) {
+            this.name = name;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            if (contentHandler instanceof LexicalHandler)
+                ((LexicalHandler)contentHandler).endEntity(name);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[EndEntity] name=" + name + "\n");
+        }
+    }
+
+    public final static class SkippedEntity implements SaxBit, Serializable {
+        public final String name;
+
+        public SkippedEntity(String name) {
+            this.name = name;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.skippedEntity(name);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[SkippedEntity] name=" + name + "\n");
+        }
+    }
+
+    public final static class StartPrefixMapping implements SaxBit, Serializable {
+        public final String prefix;
+        public final String uri;
+
+        public StartPrefixMapping(String prefix, String uri) {
+            this.prefix = prefix;
+            this.uri = uri;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.startPrefixMapping(prefix, uri);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[StartPrefixMapping] prefix=" + prefix + ",uri=" + uri + "\n");
+        }
+    }
+
+    public final static class EndPrefixMapping implements SaxBit, Serializable {
+        public final String prefix;
+
+        public EndPrefixMapping(String prefix) {
+            this.prefix = prefix;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.endPrefixMapping(prefix);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[EndPrefixMapping] prefix=" + prefix + "\n");
+        }
+    }
+
+    public final static class StartElement implements SaxBit, Serializable {
+        public final String namespaceURI;
+        public final String localName;
+        public final String qName;
+        public final Attributes attrs;
+
+        public StartElement(String namespaceURI, String localName, String qName, Attributes attrs) {
+            this.namespaceURI = namespaceURI;
+            this.localName = localName;
+            this.qName = qName;
+            this.attrs = new org.xml.sax.helpers.AttributesImpl(attrs);
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.startElement(namespaceURI, localName, qName, attrs);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[StartElement] namespaceURI=" + namespaceURI + ",localName=" + localName + ",qName=" + qName + "\n");
+            for (int i = 0; i < attrs.getLength(); i++) {
+                writer.write("      [Attribute] namespaceURI=" + attrs.getURI(i) + ",localName=" + attrs.getLocalName(i) + ",qName=" + attrs.getQName(i) + ",type=" + attrs.getType(i) + ",value=" + attrs.getValue(i) + "\n");
+            }
+        }
+    }
+
+    public final static class EndElement implements SaxBit, Serializable {
+        public final String namespaceURI;
+        public final String localName;
+        public final String qName;
+
+        public EndElement(String namespaceURI, String localName, String qName) {
+            this.namespaceURI = namespaceURI;
+            this.localName = localName;
+            this.qName = qName;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.endElement(namespaceURI, localName, qName);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[EndElement] namespaceURI=" + namespaceURI + ",localName=" + localName + ",qName=" + qName + "\n");
+        }
+    }
+
+    public final static class Characters implements SaxBit, Serializable {
+        public final char[] ch;
+
+        public Characters(char[] ch, int start, int length) {
+            // make a copy so that we don't hold references to a potentially large array we don't control
+            this.ch = new char[length];
+            System.arraycopy(ch, start, this.ch, 0, length);
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.characters(ch, 0, ch.length);
+        }
+
+        public void toString(StringBuffer value) {
+            value.append(ch);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[Characters] ch=" + new String(ch) + "\n");
+        }
+    }
+
+    public final static class Comment implements SaxBit, Serializable {
+        public final char[] ch;
+
+        public Comment(char[] ch, int start, int length) {
+            // make a copy so that we don't hold references to a potentially large array we don't control
+            this.ch = new char[length];
+            System.arraycopy(ch, start, this.ch, 0, length);
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            if (contentHandler instanceof LexicalHandler)
+                ((LexicalHandler)contentHandler).comment(ch, 0, ch.length);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[Comment] ch=" + new String(ch) + "\n");
+        }
+    }
+
+    public final static class StartCDATA implements SaxBit, Serializable {
+        public static final StartCDATA SINGLETON = new StartCDATA();
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            if (contentHandler instanceof LexicalHandler)
+                ((LexicalHandler)contentHandler).startCDATA();
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[StartCDATA]\n");
+        }
+    }
+
+    public final static class EndCDATA implements SaxBit, Serializable {
+        public static final EndCDATA SINGLETON = new EndCDATA();
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            if (contentHandler instanceof LexicalHandler)
+                ((LexicalHandler)contentHandler).endCDATA();
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[EndCDATA]\n");
+        }
+    }
+
+    public final static class IgnorableWhitespace implements SaxBit, Serializable {
+        public final char[] ch;
+
+        public IgnorableWhitespace(char[] ch, int start, int length) {
+            // make a copy so that we don't hold references to a potentially large array we don't control
+            this.ch = new char[length];
+            System.arraycopy(ch, start, this.ch, 0, length);
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            contentHandler.ignorableWhitespace(ch, 0, ch.length);
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[IgnorableWhitespace] ch=" + new String(ch) + "\n");
+        }
+    }
+
+    public final static class XMLizableBit implements SaxBit, Serializable {
+        public final SaxBuffer xml;
+
+        public XMLizableBit(SaxBuffer xml) {
+            this.xml = xml;
+        }
+
+        public void send(ContentHandler contentHandler) throws SAXException {
+            this.xml.toSAX(new EmbeddedXMLPipe(contentHandler));
+        }
+
+        public void dump(Writer writer) throws IOException {
+            writer.write("[XMLizable] Begin nested SaxBuffer\n");
+            xml.dump(writer);
+            writer.write("[XMLizable] End nested SaxBuffer\n");
+        }
+    }
+}

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/SaxBuffer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/SaxBuffer.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/SaxBuffer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/XMLUtils.java
URL: http://svn.apache.org/viewvc/cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/XMLUtils.java?rev=728704&view=auto
==============================================================================
--- cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/XMLUtils.java (added)
+++ cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/XMLUtils.java Mon Dec 22 06:49:59 2008
@@ -0,0 +1,617 @@
+/*
+ * 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.cocoon.xml.util;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Properties;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.cocoon.xml.util.dom.DOMStreamer;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * XML utility methods.
+ *
+ * @version $Id$
+ */
+public class XMLUtils {
+
+    /**
+     * Empty attributes immutable object.
+     */
+    public static final Attributes EMPTY_ATTRIBUTES = new ImmutableAttributesImpl();
+
+    private static final SAXTransformerFactory FACTORY = (SAXTransformerFactory) TransformerFactory.newInstance();
+    private static final Properties XML_FORMAT = createDefaultPropertiesForXML(false);
+    private static final Properties XML_FORMAT_NODECL = createDefaultPropertiesForXML(true);
+
+
+    // FIXME: parent parameter not used anymore
+    // Using parent because some dom implementations like jtidy are bugged,
+    // cannot get parent or delete child
+    public static void stripDuplicateAttributes(Node node, Node parent) {
+
+        // The output depends on the type of the node
+        switch(node.getNodeType()) {
+        case Node.DOCUMENT_NODE: {
+            Document doc = (Document)node;
+            Node child = doc.getFirstChild();
+            while(child != null) {
+                stripDuplicateAttributes(child, node);
+                child = child.getNextSibling();
+            }
+            break;
+        }
+
+        case Node.ELEMENT_NODE: {
+            Element elt = (Element) node;
+            NamedNodeMap attrs = elt.getAttributes();
+
+            ArrayList<Node> nodesToRemove = new ArrayList<Node>();
+            int nodesToRemoveNum = 0;
+
+            for (int i = 0; i < attrs.getLength(); i++) {
+                final Node a = attrs.item(i);
+
+                for (int j = 0; j < attrs.getLength(); j++) {
+                    final Node b = attrs.item(j);
+
+                    //if there are two attributes with same name
+                    if (i != j && (a.getNodeName().equals(b.getNodeName()))) {
+                        nodesToRemove.add(b);
+                        nodesToRemoveNum++;
+                    }
+                }
+            }
+
+            for (int i = 0; i < nodesToRemoveNum; i++) {
+                Attr nodeToDelete = (Attr) nodesToRemove.get(i);
+                Element nodeToDeleteParent = (Element) node; // nodeToDelete.getParentNode();
+                nodeToDeleteParent.removeAttributeNode(nodeToDelete);
+            }
+
+            nodesToRemove.clear();
+
+            Node child = elt.getFirstChild();
+            while (child != null) {
+                stripDuplicateAttributes(child, node);
+                child = child.getNextSibling();
+            }
+
+            break;
+        }
+
+        default:
+            //do nothing
+            break;
+        }
+    }
+
+    /**
+     * Method for static initializer
+     */
+    private static Properties createDefaultPropertiesForXML(boolean omitXMLDeclaration) {
+        final Properties format = new Properties();
+        format.put(OutputKeys.METHOD, "xml");
+        format.put(OutputKeys.OMIT_XML_DECLARATION, (omitXMLDeclaration ? "yes" : "no"));
+        format.put(OutputKeys.INDENT, "yes");
+        return format;
+    }
+
+    /**
+     * Create a new properties set for serializing xml.
+     * The omit xml declaration property can be controlled by the flag.
+     *
+     * <ul>
+     * <li>Method: xml
+     * <li>Omit xml declaration: according to the flag
+     * <li>Indent: yes
+     * </ul>
+     */
+    public static Properties createPropertiesForXML(boolean omitXMLDeclaration) {
+        /* Properties passed as parameters to the Properties constructor become "default properties".
+            But Xalan does not use the default properties, so they are lost.
+            Therefore, we must promote them to "set properties".
+         */
+         Properties propertiesForXML = new Properties(omitXMLDeclaration? XML_FORMAT_NODECL: XML_FORMAT);
+         for (Enumeration e = propertiesForXML.propertyNames(); e.hasMoreElements(); ) {
+             String propertyName = (String)e.nextElement();
+             propertiesForXML.setProperty(propertyName, propertiesForXML.getProperty(propertyName, ""));
+         }
+         return propertiesForXML;
+    }
+
+    /**
+     * Serialize a DOM node into a string using format created by
+     * <code>createPropertiesForXML(false)</code>.
+     *
+     * @see #createPropertiesForXML
+     */
+    public static String serializeNode(Node node)
+    throws TransformerException {
+        // Don't create new properties as we do not intend to modify defaults.
+        return serializeNode(node, XML_FORMAT);
+    }
+
+    /**
+     * Serialize a DOM node into a string.
+     * If the node is null the empty string is returned.
+     *
+     * @param format The format of the output to be used by SAX transformer.
+     * @see OutputKeys
+     */
+    public static String serializeNode(Node node, Properties format)
+    throws TransformerException {
+
+        try {
+            if (node == null) {
+                return "";
+            }
+
+            StringWriter writer = new StringWriter();
+            TransformerHandler transformerHandler;
+            transformerHandler = FACTORY.newTransformerHandler();
+            transformerHandler.getTransformer().setOutputProperties(format);
+            transformerHandler.setResult(new StreamResult(writer));
+            if (node.getNodeType() != Node.DOCUMENT_NODE) {
+                transformerHandler.startDocument();
+            }
+            DOMStreamer domStreamer = new DOMStreamer(transformerHandler, transformerHandler);
+            domStreamer.stream(node);
+            if (node.getNodeType() != Node.DOCUMENT_NODE) {
+                transformerHandler.endDocument();
+            }
+
+            return writer.toString();
+        } catch (TransformerException e) {
+            throw e;
+        } catch (SAXException e) {
+            throw new TransformerException("SAXException while streaming DOM node to SAX: " + e, e);
+        }
+    }
+
+    /**
+     * Add string data
+     *
+     * @param contentHandler The SAX content handler
+     * @param data The string data
+     */
+    public static void data(ContentHandler contentHandler,
+                            String data)
+    throws SAXException {
+
+        contentHandler.characters(data.toCharArray(), 0, data.length());
+    }
+
+    /**
+     * Implementation of &lt;xsp:expr&gt; for <code>String</code> :
+     * outputs characters representing the value.
+     *
+     * @param contentHandler the SAX content handler
+     * @param text the value
+     */
+    public static void valueOf(ContentHandler contentHandler, String text)
+    throws SAXException {
+
+        if (text != null) {
+            data(contentHandler, text);
+        }
+    }
+
+    /**
+     * Implementation of &lt;xsp:expr&gt; for <code>SaxBuffer</code> :
+     * outputs the value by calling <code>v.toSax(contentHandler)</code>.
+     *
+     * @param contentHandler the SAX content handler
+     * @param v the XML fragment
+     */
+    public static void valueOf(ContentHandler contentHandler,
+                               SaxBuffer v)
+    throws SAXException {
+
+        if (v != null) {
+            v.toSAX(contentHandler);
+        }
+    }
+
+    /**
+     * Implementation of &lt;xsp:expr&gt; for <code>org.w3c.dom.Node</code> :
+     * converts the Node to a SAX event stream.
+     *
+     * @param contentHandler the SAX content handler
+     * @param v the value
+     */
+    public static void valueOf(ContentHandler contentHandler, Node v)
+    throws SAXException {
+
+        if (v != null) {
+            DOMStreamer streamer = new DOMStreamer(contentHandler);
+            if (contentHandler instanceof LexicalHandler) {
+                streamer.setLexicalHandler((LexicalHandler)contentHandler);
+            }
+            streamer.stream(v);
+        }
+    }
+
+    /**
+     * Implementation of &lt;xsp:expr&gt; for <code>java.util.Collection</code> :
+     * outputs the value by calling <code>xspExpr()</code> on each element of the
+     * collection.
+     *
+     * @param contentHandler the SAX content handler
+     * @param v the XML fragment
+     */
+    public static void valueOf(ContentHandler contentHandler,
+                               Collection v)
+    throws SAXException {
+
+        if (v != null) {
+            Iterator iterator = v.iterator();
+            while (iterator.hasNext()) {
+                valueOf(contentHandler, iterator.next());
+            }
+        }
+     }
+
+    /**
+     * Implementation of &lt;xsp:expr&gt; for <code>Object</code> depending on its class :
+     * <ul>
+     * <li>if it's an array, call <code>xspExpr()</code> on all its elements,</li>
+     * <li>if it's class has a specific <code>xspExpr()</code>implementation, use it,</li>
+     * <li>else, output it's string representation.</li>
+     * </ul>
+     *
+     * @param contentHandler the SAX content handler
+     * @param v the value
+     */
+    public static void valueOf(ContentHandler contentHandler, Object v)
+    throws SAXException {
+
+        if (v == null) {
+            return;
+        }
+
+        // Array: recurse over each element
+        if (v.getClass().isArray()) {
+            Object[] elements = (Object[]) v;
+
+            for (int i = 0; i < elements.length; i++) {
+                valueOf(contentHandler, elements[i]);
+            }
+            return;
+         }
+
+
+         // XMLizable
+         if (v instanceof SaxBuffer) {
+             valueOf(contentHandler, (SaxBuffer)v);
+             return;
+         }
+
+         // Node
+         if (v instanceof Node) {
+             valueOf(contentHandler, (Node)v);
+             return;
+         }
+
+         // Collection
+         if (v instanceof Collection) {
+             valueOf(contentHandler, (Collection)v);
+             return;
+         }
+
+         // Give up: hope it's a string or has a meaningful string representation
+         data(contentHandler, String.valueOf(v));
+    }
+
+    /**
+     * Create a start and endElement with a empty Namespace and without Attributes
+     *
+     * @param localName The local name (without prefix)
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     */
+    public static void createElement(ContentHandler contentHandler,
+                                     String localName)
+    throws SAXException {
+
+        startElement(contentHandler, localName);
+        endElement(contentHandler, localName);
+    }
+
+    /**
+     * Create a start and endElement with a empty Namespace and without Attributes
+     * The content of the Element is set to the stringValue parameter
+     *
+     * @param localName The local name (without prefix)
+     * @param stringValue The content of the Element
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     */
+    public static void createElement(ContentHandler contentHandler,
+                                     String localName,
+                                     String stringValue)
+    throws SAXException {
+
+        startElement(contentHandler, localName);
+        data(contentHandler, stringValue);
+        endElement(contentHandler, localName);
+    }
+
+    /**
+     * Create a start and endElement with a empty Namespace
+     *
+     * @param localName The local name (without prefix)
+     * @param atts The attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     * @see org.xml.sax.Attributes
+     */
+    public static void createElement(ContentHandler contentHandler,
+                                     String localName,
+                                     Attributes atts)
+    throws SAXException {
+
+        startElement(contentHandler, localName, atts);
+        endElement(contentHandler, localName);
+    }
+
+    /**
+     * Create a start and endElement with a empty Namespace
+     * The content of the Element is set to the stringValue parameter
+     *
+     * @param localName The local name (without prefix)
+     * @param atts The attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.
+     * @param stringValue The content of the Element
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     * @see org.xml.sax.Attributes
+     */
+    public static void createElement(ContentHandler contentHandler,
+                                     String localName,
+                                     Attributes atts,
+                                     String stringValue)
+    throws SAXException {
+
+        startElement(contentHandler, localName, atts);
+        data(contentHandler, stringValue);
+        endElement(contentHandler, localName);
+    }
+
+    /**
+     * Create a start and endElement without Attributes
+     *
+     * @param localName The local name (without prefix)
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     */
+    public static void createElementNS(ContentHandler contentHandler,
+                                       String namespaceURI,
+                                       String localName)
+    throws SAXException {
+
+        startElement(contentHandler, namespaceURI, localName);
+        endElement(contentHandler, namespaceURI, localName);
+    }
+
+    /**
+     * Create a start and endElement without Attributes
+     * The content of the Element is set to the stringValue parameter
+     *
+     * @param localName The local name (without prefix)
+     * @param stringValue The content of the Element
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     */
+    public static void createElementNS(ContentHandler contentHandler,
+                                       String namespaceURI,
+                                       String localName,
+                                       String stringValue)
+    throws SAXException {
+
+        startElement(contentHandler, namespaceURI, localName);
+        data(contentHandler, stringValue);
+        endElement(contentHandler, namespaceURI, localName);
+    }
+
+    /**
+     * Create a start and endElement
+     *
+     * @param localName The local name (without prefix)
+     * @param atts The attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     * @see org.xml.sax.Attributes
+     */
+    public static void createElementNS(ContentHandler contentHandler,
+                                       String namespaceURI,
+                                       String localName,
+                                       Attributes atts)
+    throws SAXException {
+
+        startElement(contentHandler, namespaceURI, localName, atts);
+        endElement(contentHandler, namespaceURI, localName);
+    }
+
+    /**
+     * Create a start and endElement with a empty Namespace
+     * The content of the Element is set to the stringValue parameter
+     *
+     * @param localName The local name (without prefix)
+     * @param atts The attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.
+     * @param stringValue The content of the Element
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     * @see org.xml.sax.Attributes
+     */
+    public static void createElementNS(ContentHandler contentHandler,
+                                       String namespaceURI,
+                                       String localName,
+                                       Attributes atts,
+                                       String stringValue)
+    throws SAXException {
+
+        startElement(contentHandler, namespaceURI, localName, atts);
+        data(contentHandler, stringValue);
+        endElement(contentHandler, namespaceURI, localName);
+    }
+
+
+    /**
+     * Create endElement with empty Namespace
+     *
+     * <p>For information on the names, see startElement.</p>
+     *
+     * @param localName The local name (without prefix)
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     */
+    public static void endElement(ContentHandler contentHandler,
+                                  String localName)
+    throws SAXException {
+
+        contentHandler.endElement("", localName, localName);
+    }
+
+    /**
+     * Create endElement
+     * Prefix must be mapped to empty String
+     *
+     * <p>For information on the names, see startElement.</p>
+     *
+     * @param localName The local name (without prefix)
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     */
+    public static void endElement(ContentHandler contentHandler,
+                                  String namespaceURI,
+                                  String localName)
+    throws SAXException {
+
+        contentHandler.endElement(namespaceURI, localName, localName);
+    }
+
+    /**
+     * Create a startElement with a empty Namespace and without Attributes
+     *
+     * @param localName The local name (without prefix)
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     */
+    public static void startElement(ContentHandler contentHandler,
+                                    String localName)
+    throws SAXException {
+
+        contentHandler.startElement("", localName, localName, EMPTY_ATTRIBUTES);
+    }
+
+    /**
+     * Create a startElement without Attributes
+     * Prefix must be mapped to empty String
+     *
+     * @param namespaceURI The Namespace URI
+     * @param localName The local name (without prefix)
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     */
+    public static void startElement(ContentHandler contentHandler,
+                                    String namespaceURI,
+                                    String localName)
+    throws SAXException {
+
+        contentHandler.startElement(namespaceURI, localName, localName, EMPTY_ATTRIBUTES);
+    }
+
+    /**
+     * Create a startElement with a empty Namespace
+     *
+     * @param localName The local name (without prefix)
+     * @param atts The attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     * @see org.xml.sax.Attributes
+     */
+    public static void startElement(ContentHandler contentHandler,
+                                    String localName,
+                                    Attributes atts)
+    throws SAXException {
+
+        contentHandler.startElement("", localName, localName, atts);
+    }
+
+    /**
+     * Create a startElement with a empty Namespace
+     * Prefix must be mapped to empty String
+     *
+     * @param namespaceURI The Namespace URI
+     * @param localName The local name (without prefix)
+     * @param atts The attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.
+     * @exception org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement(ContentHandler, String)
+     * @see org.xml.sax.Attributes
+     */
+    public static void startElement(ContentHandler contentHandler,
+                                    String namespaceURI,
+                                    String localName,
+                                    Attributes atts)
+    throws SAXException {
+
+        contentHandler.startElement(namespaceURI, localName, localName, atts);
+    }
+}

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/XMLUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/XMLUtils.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/XMLUtils.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMBuilder.java
URL: http://svn.apache.org/viewvc/cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMBuilder.java?rev=728704&view=auto
==============================================================================
--- cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMBuilder.java (added)
+++ cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMBuilder.java Mon Dec 22 06:49:59 2008
@@ -0,0 +1,173 @@
+/*
+ * 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.cocoon.xml.util.dom;
+
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.apache.cocoon.xml.util.AbstractXMLPipe;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+/**
+ * The <code>DOMBuilder</code> is a utility class that will generate a W3C
+ * DOM Document from SAX events.
+ *
+ * @version $Id$
+ */
+public class DOMBuilder extends AbstractXMLPipe {
+
+    /** The default transformer factory shared by all instances */
+    protected static final SAXTransformerFactory FACTORY = (SAXTransformerFactory) TransformerFactory.newInstance();
+
+    /** The transformer factory */
+    protected SAXTransformerFactory factory;
+
+    /** The listener */
+    protected Listener listener;
+
+    /** The result */
+    protected DOMResult result;
+
+    /** The parentNode */
+    protected Node parentNode;
+
+    /**
+     * Construct a new instance of this DOMBuilder.
+     */
+    public DOMBuilder() {
+        this((Listener) null, (Node) null);
+    }
+
+    /**
+     * Construct a new instance of this DOMBuilder.
+     */
+    public DOMBuilder(SAXTransformerFactory factory) {
+        this(factory, null, null);
+    }
+
+    /**
+     * Construct a new instance of this DOMBuilder.
+     */
+    public DOMBuilder(Listener listener) {
+        this(listener, null);
+    }
+
+    /**
+     * Constructs a new instance that appends nodes to the given parent node.
+     * <br/>
+     * <strong>Note:</strong> You cannot use a <code>Listener<code> when appending to a
+     * <code>Node</code>, because the notification occurs at <code>endDocument()</code>
+     * which does not happen here.
+     */
+    public DOMBuilder(Node parentNode) {
+        this(null, parentNode);
+    }
+
+    /**
+     * Construct a new instance of this DOMBuilder.
+     */
+    public DOMBuilder(Listener listener, Node parentNode) {
+        this((SAXTransformerFactory) null, listener, parentNode);
+    }
+
+    /**
+     * Construct a new instance of this DOMBuilder.
+     */
+    public DOMBuilder(SAXTransformerFactory factory, Listener listener, Node parentNode) {
+        super();
+        this.factory = factory == null? FACTORY: factory;
+        this.listener = listener;
+        this.parentNode = parentNode;
+        setup();
+    }
+
+    /**
+     * Setup this instance transformer and result objects.
+     */
+    private void setup() {
+        try {
+            TransformerHandler handler = this.factory.newTransformerHandler();
+            setContentHandler(handler);
+            setLexicalHandler(handler);
+            if (this.parentNode != null) {
+                this.result = new DOMResult(this.parentNode);
+            } else {
+                this.result = new DOMResult();
+            }
+            handler.setResult(this.result);
+        } catch (javax.xml.transform.TransformerException local) {
+            throw new RuntimeException("Fatal-Error: Unable to get transformer handler", local);
+        }
+    }
+
+    /**
+     * Recycle this builder, prepare for re-use.
+     */
+    public void recycle() {
+        super.recycle();
+        setup();
+    }
+
+    /**
+     * Return the newly built Document.
+     */
+    public Document getDocument() {
+        if (this.result == null || this.result.getNode() == null) {
+            return null;
+        } else if (this.result.getNode().getNodeType() == Node.DOCUMENT_NODE) {
+            return (Document) this.result.getNode();
+        } else {
+            return this.result.getNode().getOwnerDocument();
+        }
+    }
+
+    /**
+     * Receive notification of the end of a document.
+     *
+     * @exception SAXException If this method was not called appropriately.
+     */
+    public void endDocument() throws SAXException {
+        super.endDocument();
+        // Notify the listener
+        notifyListener();
+    }
+
+    /**
+     * Receive notification of a successfully completed DOM tree generation.
+     */
+    protected void notifyListener() throws SAXException {
+        if (this.listener != null) {
+            this.listener.notify(getDocument());
+        }
+    }
+
+    /**
+     * The Listener interface must be implemented by those objects willing to
+     * be notified of a successful DOM tree generation.
+     */
+    public interface Listener {
+
+        /**
+         * Receive notification of a successfully completed DOM tree generation.
+         */
+        void notify(Document doc) throws SAXException;
+    }
+}

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMBuilder.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMBuilder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMStreamer.java
URL: http://svn.apache.org/viewvc/cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMStreamer.java?rev=728704&view=auto
==============================================================================
--- cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMStreamer.java (added)
+++ cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMStreamer.java Mon Dec 22 06:49:59 2008
@@ -0,0 +1,689 @@
+/*
+ * 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.cocoon.xml.util.dom;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXResult;
+
+import org.apache.cocoon.xml.util.AbstractXMLProducer;
+import org.apache.cocoon.xml.util.EmbeddedXMLPipe;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Element;
+import org.w3c.dom.EntityReference;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * The <code>DOMStreamer</code> is a utility class that will generate SAX
+ * events from a W3C DOM Document.
+ *
+ * <p>The DOMStreamer uses a different strategy based on the value of the
+ * normalizeNamespaces property:
+ * <ul>
+ * <li>if true (the default), the DOMStreamer will normalize namespace
+ * declarations (i.e. add missing xmlns attributes or correct them). See
+ * also {@link NamespaceNormalizingDOMStreamer}.
+ * <li>if false, the standard JAXP identity transformer is used.
+ * </ul>
+ *
+ * @version $Id$
+ */
+public class DOMStreamer {
+
+    /** The transformer factory shared by all instances (only used by DefaultDOMStreamer) */
+    private static final TransformerFactory FACTORY = TransformerFactory.newInstance();
+
+    /** Default value for normalizeNamespaces. */
+    private static final boolean DEFAULT_NORMALIZE_NAMESPACES = true;
+
+    /** Indicates whether namespace normalization should happen. */
+    protected boolean normalizeNamespaces = DEFAULT_NORMALIZE_NAMESPACES;
+
+    /** DOMStreamer used in case of namespace normalization. */
+    protected NamespaceNormalizingDOMStreamer namespaceNormalizingDOMStreamer = new NamespaceNormalizingDOMStreamer();
+
+    /** DOMStreamer used when namespace normalization should not explicitely happen. */
+    protected DefaultDOMStreamer defaultDOMStreamer = new DefaultDOMStreamer();
+
+    /**
+     * Create a new <code>DOMStreamer</code> instance.
+     */
+    public DOMStreamer() {
+        super();
+    }
+
+    /**
+     * Create a new <code>DOMStreamer</code> instance.
+     */
+    public DOMStreamer(ContentHandler content, LexicalHandler lexical) {
+        this();
+        setContentHandler(content);
+        setLexicalHandler(lexical);
+    }
+
+    /**
+     * Create a new <code>DOMStreamer</code> instance.
+     */
+    public DOMStreamer(ContentHandler content) {
+        this(content, content instanceof LexicalHandler ? (LexicalHandler) content : null);
+    }
+
+    /**
+     * Set the <code>ContentHandler</code> that will receive XML data.
+     */
+    public void setContentHandler(ContentHandler handler) {
+        defaultDOMStreamer.setContentHandler(handler);
+        namespaceNormalizingDOMStreamer.setContentHandler(handler);
+    }
+
+    /**
+     * Set the <code>LexicalHandler</code> that will receive XML data.
+     */
+    public void setLexicalHandler(LexicalHandler handler) {
+        defaultDOMStreamer.setLexicalHandler(handler);
+        namespaceNormalizingDOMStreamer.setLexicalHandler(handler);
+    }
+
+    /**
+     * Start the production of SAX events.
+     */
+    public void stream(Node node) throws SAXException {
+        if (normalizeNamespaces) {
+            namespaceNormalizingDOMStreamer.stream(node);
+        } else {
+            defaultDOMStreamer.stream(node);
+        }
+    }
+
+    public boolean isNormalizeNamespaces() {
+        return normalizeNamespaces;
+    }
+
+    public void setNormalizeNamespaces(boolean normalizeNamespaces) {
+        this.normalizeNamespaces = normalizeNamespaces;
+    }
+
+    public void recycle() {
+        defaultDOMStreamer.recycle();
+        namespaceNormalizingDOMStreamer.recycle();
+        normalizeNamespaces = DEFAULT_NORMALIZE_NAMESPACES;
+    }
+
+    /**
+     * Streams a DOM tree to SAX events and normalizes namespace declarations on the way.
+     *
+     * <p>The code in this class is based on the org.apache.xml.utils.TreeWalker class from Xalan,
+     * though it differs in some important ways.
+     *
+     * <p>This class will automatically fix up ("normalize") namespace declarations
+     * while streaming to SAX. The original DOM-tree is not modified. The algorithm
+     * used is described in
+     * <a href="http://www.w3.org/TR/2002/WD-DOM-Level-3-Core-20021022/namespaces-algorithms.html#normalizeDocumentAlgo">an appendix of the DOM Level 3 spec</a>.
+     *
+     * <p>This class will NOT check the correctness of namespaces, e.g. it will not
+     * check that the "xml" prefix is not misused etc.
+     *
+     */
+    public static class NamespaceNormalizingDOMStreamer extends AbstractXMLProducer {
+        /**
+         * Information about the current element. Used to remember the localName, qName
+         * and namespaceURI for generating the endElement event, and holds the namespaces
+         * declared on the element. This extra class is needed because we don't want to
+         * modify the DOM-tree itself. The currentElementInfo has a pointer to its parent
+         * elementInfo.
+         */
+        protected NamespaceNormalizingDOMStreamer.ElementInfo currentElementInfo;
+
+        /** Counter used when generating new namespace prefixes. */
+        protected int newPrefixCounter;
+
+        public void recycle() {
+            super.recycle();
+            currentElementInfo = null;
+            newPrefixCounter = 0;
+        }
+
+        /**
+         * Start the production of SAX events.
+         *
+         * <p>Perform a pre-order traversal non-recursive style.
+         *
+         * <p>Note that TreeWalker assumes that the subtree is intended to represent
+         * a complete (though not necessarily well-formed) document and, during a
+         * traversal, startDocument and endDocument will always be issued to the
+         * SAX listener.
+         *
+         * @param pos Node in the tree where to start traversal
+         */
+        protected void stream(Node pos) throws SAXException {
+
+            // Start document only if we're streaming a document
+            boolean isDoc = (pos.getNodeType() == Node.DOCUMENT_NODE);
+            if (isDoc) {
+              contentHandler.startDocument();
+            }
+
+            Node top = pos;
+            while (null != pos) {
+                startNode(pos);
+
+                Node nextNode = pos.getFirstChild();
+                while (null == nextNode) {
+                    endNode(pos);
+
+                    if (top.equals(pos)) {
+                        break;
+                    }
+
+                    nextNode = pos.getNextSibling();
+                    if (null == nextNode) {
+                        pos = pos.getParentNode();
+
+                        if ((null == pos) || (top.equals(pos))) {
+                            if (null != pos) {
+                                endNode(pos);
+                            }
+
+                            break;
+                        }
+                    }
+                }
+
+                pos = nextNode;
+            }
+
+            if (isDoc) {
+            	contentHandler.endDocument();
+            }
+        }
+
+        private final void dispatchChars(Node node) throws SAXException {
+            final String data = ((Text) node).getData();
+            if ( data != null ) {
+                contentHandler.characters(data.toCharArray(), 0, data.length());
+            }
+        }
+
+        /**
+         * Start processing given node
+         *
+         * @param node Node to process
+         */
+        protected void startNode(Node node) throws SAXException {
+
+            switch (node.getNodeType()) {
+                case Node.COMMENT_NODE:
+                    {
+                        if (lexicalHandler != null) {
+                            final String data = ((Comment) node).getData();
+                            if ( data != null ) {
+                                lexicalHandler.comment(data.toCharArray(), 0, data.length());
+                            }
+                        }
+                    }
+                    break;
+                case Node.DOCUMENT_FRAGMENT_NODE:
+                    // ??;
+                case Node.DOCUMENT_NODE:
+                    break;
+                case Node.ELEMENT_NODE:
+                    NamedNodeMap atts = node.getAttributes();
+                    int nAttrs = atts.getLength();
+
+                    // create a list of localy declared namespace prefixes
+                    currentElementInfo = new NamespaceNormalizingDOMStreamer.ElementInfo(currentElementInfo);
+                    for (int i = 0; i < nAttrs; i++) {
+                        Node attr = atts.item(i);
+                        String attrName = attr.getNodeName();
+
+                        if (attrName.equals("xmlns") || attrName.startsWith("xmlns:")) {
+                            int index;
+                            String prefix = (index = attrName.indexOf(":")) < 0
+                                    ? "" : attrName.substring(index + 1);
+
+                            currentElementInfo.put(prefix, attr.getNodeValue());
+                        }
+                    }
+
+                    String namespaceURI = node.getNamespaceURI();
+                    String prefix = node.getPrefix();
+                    String localName = node.getLocalName();
+
+                    if (localName == null) {
+                        // this is an element created with createElement instead of createElementNS
+                        String[] prefixAndLocalName = getPrefixAndLocalName(node.getNodeName());
+                        prefix = prefixAndLocalName[0];
+                        localName = prefixAndLocalName[1];
+                        // note: if prefix is null, there can still be a default namespace...
+                        namespaceURI = getNamespaceForPrefix(prefix, (Element)node);
+                    }
+
+                    if (namespaceURI != null) {
+                        // no prefix means: make this the default namespace
+                        if (prefix == null) {
+                            prefix = "";
+                        }
+                        // check that is declared
+                        String uri = currentElementInfo.findNamespaceURI(prefix);
+                        if ( uri != null && uri.equals(namespaceURI) ) {
+                            // System.out.println("namespace is declared");
+                            // prefix is declared correctly, do nothing
+                            //} else if (uri != null) {
+                            // System.out.println("prefix is declared with other namespace, overwriting it");
+                            // prefix exists but is bound to another namespace, overwrite it
+                            // currentElementInfo.put(prefix, namespaceURI);
+                        } else {
+                            // System.out.println("prefix is not yet declared, declaring it now");
+                            currentElementInfo.put(prefix, namespaceURI);
+                        }
+                    } else {
+                        // element has no namespace
+                        // check if there is a default namespace, if so undeclare it
+                        String uri = currentElementInfo.findNamespaceURI("");
+                        if (uri != null && uri.trim().length() > 0 ) {
+                            // System.out.println("undeclaring default namespace");
+                            currentElementInfo.put("", "");
+                        }
+                    }
+
+                    // SAX uses empty string to denote no namespace, while DOM uses null.
+                    if (namespaceURI == null)
+                        namespaceURI = "";
+
+                    String qName;
+                    if (prefix != null && prefix.trim().length() > 0 ) {
+                        qName = prefix + ":" + localName;
+                    } else {
+                        qName = localName;
+                    }
+
+                    // make the attributes
+                    AttributesImpl newAttrs = new AttributesImpl();
+                    for (int i = 0; i < nAttrs; i++) {
+                        Node attr = atts.item(i);
+                        String attrName = attr.getNodeName();
+                        String assignedAttrPrefix = null;
+
+                        // only do non-namespace attributes
+                        if (!(attrName.equals("xmlns") || attrName.startsWith("xmlns:"))) {
+                            String attrPrefix;
+                            String attrLocalName;
+                            String attrNsURI;
+
+                            if (attr.getLocalName() == null) {
+                                // this is an attribute created with setAttribute instead of setAttributeNS
+                                String[] prefixAndLocalName = getPrefixAndLocalName(attrName);
+                                attrPrefix = prefixAndLocalName[0];
+                                // the statement below causes the attribute to keep its prefix even if it is not
+                                // bound to a namespace (to support pre-namespace XML).
+                                assignedAttrPrefix = attrPrefix;
+                                attrLocalName = prefixAndLocalName[1];
+                                // note: if prefix is null, the attribute has no namespace (namespace defaulting
+                                // does not apply to attributes)
+                                if (attrPrefix != null)
+                                    attrNsURI = getNamespaceForPrefix(attrPrefix, (Element)node);
+                                else
+                                    attrNsURI = null;
+                            } else {
+                                attrLocalName = attr.getLocalName();
+                                attrPrefix = attr.getPrefix();
+                                attrNsURI = attr.getNamespaceURI();
+                            }
+
+                            if (attrNsURI != null) {
+                                String declaredUri = currentElementInfo.findNamespaceURI(attrPrefix);
+                                // if the prefix is null, or the prefix has not been declared, or conflicts with an in-scope binding
+                                if (declaredUri == null || !declaredUri.equals(attrNsURI)) {
+                                    String availablePrefix = currentElementInfo.findPrefix(attrNsURI);
+                                    if (availablePrefix != null && !availablePrefix.equals(""))
+                                        assignedAttrPrefix = availablePrefix;
+                                    else {
+                                        if (attrPrefix != null && declaredUri == null) {
+                                            // prefix is not null and is not yet declared: declare it
+                                            assignedAttrPrefix = attrPrefix;
+                                            currentElementInfo.put(assignedAttrPrefix, attrNsURI);
+                                        } else {
+                                            // attribute has no prefix (which is not allowed for namespaced attributes) or
+                                            // the prefix is already bound to something else: generate a new prefix
+                                            newPrefixCounter++;
+                                            assignedAttrPrefix = "NS" + newPrefixCounter;
+                                            currentElementInfo.put(assignedAttrPrefix, attrNsURI);
+                                        }
+                                    }
+                                } else {
+                                    assignedAttrPrefix = attrPrefix;
+                                }
+                            }
+
+                            String assignedAttrNsURI = attrNsURI != null ? attrNsURI : "";
+                            String attrQName;
+                            if (assignedAttrPrefix != null) {
+                                attrQName = assignedAttrPrefix + ":" + attrLocalName;
+                            } else {
+                                attrQName = attrLocalName;
+                            }
+                            newAttrs.addAttribute(assignedAttrNsURI, attrLocalName, attrQName, "CDATA", attr.getNodeValue());
+                        }
+                    }
+
+                    // add local namespace declaration and fire startPrefixMapping events
+                    if (currentElementInfo.namespaceDeclarations != null && currentElementInfo.namespaceDeclarations.size() > 0) {
+                        Iterator<Map.Entry<String, String>> localNsDeclIt = currentElementInfo.namespaceDeclarations.entrySet().iterator();
+                        while (localNsDeclIt.hasNext()) {
+                            Map.Entry<String, String> entry = localNsDeclIt.next();
+                            String pr = entry.getKey();
+                            String ns = entry.getValue();
+                            // the following lines enable the creation of explicit xmlns attributes
+                            //String pr1 = pr.equals("") ? "xmlns" : pr;
+                            //String qn = pr.equals("") ? "xmlns" : "xmlns:" + pr;
+                            //newAttrs.addAttribute("", pr1, qn, "CDATA", ns);
+                            // System.out.println("starting prefix mapping  for prefix " + pr + " for " + ns);
+                            contentHandler.startPrefixMapping(pr, ns);
+                        }
+                    }
+
+                    contentHandler.startElement(namespaceURI, localName, qName, newAttrs);
+
+                    currentElementInfo.localName = localName;
+                    currentElementInfo.namespaceURI = namespaceURI;
+                    currentElementInfo.qName = qName;
+                    break;
+                case Node.PROCESSING_INSTRUCTION_NODE:
+                    {
+                        ProcessingInstruction pi = (ProcessingInstruction) node;
+                        contentHandler.processingInstruction(pi.getNodeName(), pi.getData());
+                    }
+                    break;
+                case Node.CDATA_SECTION_NODE:
+                    {
+                        if (lexicalHandler != null)
+                            lexicalHandler.startCDATA();
+
+                        dispatchChars(node);
+
+                        if (lexicalHandler != null)
+                            lexicalHandler.endCDATA();
+                    }
+                    break;
+                case Node.TEXT_NODE:
+                    {
+                        dispatchChars(node);
+                    }
+                    break;
+                case Node.ENTITY_REFERENCE_NODE:
+                    {
+                        EntityReference eref = (EntityReference) node;
+
+                        if (lexicalHandler != null) {
+                            lexicalHandler.startEntity(eref.getNodeName());
+                        } else {
+                            // warning("Can not output entity to a pure SAX ContentHandler");
+                        }
+                    }
+                    break;
+                default :
+            }
+        }
+
+        /**
+         * Searches the namespace for a given namespace prefix starting from a
+         * given Element.
+         *
+         * <p>Note that this resolves the prefix in the orginal DOM-tree, not in
+         * the {@link ElementInfo} objects. This is used to resolve prefixes
+         * of elements or attributes created with createElement or setAttribute
+         * instead of createElementNS or setAttributeNS.
+         *
+         * <p>The code in this method is largely based on
+         * org.apache.xml.utils.DOMHelper.getNamespaceForPrefix() (from Xalan).
+         *
+         * @param prefix the prefix to look for, can be empty or null to find the
+         * default namespace
+         *
+         * @return the namespace, or null if not found.
+         */
+        public String getNamespaceForPrefix(String prefix, Element namespaceContext) {
+            int type;
+            Node parent = namespaceContext;
+            String namespace = null;
+
+            if (prefix == null)
+                prefix = "";
+
+            if (prefix.equals("xml")) {
+                namespace = "http://www.w3.org/XML/1998/namespace";
+            } else if(prefix.equals("xmlns")) {
+                namespace = "http://www.w3.org/2000/xmlns/";
+            } else {
+                // Attribute name for this prefix's declaration
+                String declname = (prefix.length() == 0) ? "xmlns" : "xmlns:" + prefix;
+
+                // Scan until we run out of Elements or have resolved the namespace
+                while ((null != parent)
+                   && (((type = parent.getNodeType()) == Node.ELEMENT_NODE)
+                       || (type == Node.ENTITY_REFERENCE_NODE))) {
+                    if (type == Node.ELEMENT_NODE) {
+                        Attr attr=((Element)parent).getAttributeNode(declname);
+                        if (attr != null) {
+                            namespace = attr.getNodeValue();
+                            break;
+                        }
+                    }
+                    parent = parent.getParentNode();
+                }
+            }
+            return namespace;
+        }
+
+        /**
+         * Splits a nodeName into a prefix and a localName
+         *
+         * @return an array containing two elements, the first one is the prefix (can be null), the
+         * second one is the localName
+         */
+        private String[] getPrefixAndLocalName(String nodeName) {
+            String prefix, localName;
+            int colonPos = nodeName.indexOf(":");
+            if (colonPos != -1) {
+                prefix = nodeName.substring(0, colonPos);
+                localName = nodeName.substring(colonPos + 1, nodeName.length());
+            } else {
+                prefix = null;
+                localName = nodeName;
+            }
+            return new String[] {prefix, localName};
+        }
+
+        /**
+         * End processing of given node
+         *
+         * @param node Node we just finished processing
+         */
+        protected void endNode(Node node) throws org.xml.sax.SAXException {
+
+            switch (node.getNodeType()) {
+                case Node.ELEMENT_NODE:
+                    contentHandler.endElement(currentElementInfo.namespaceURI,
+                            currentElementInfo.localName, currentElementInfo.qName);
+
+                    // generate endPrefixMapping events if needed
+                    if (currentElementInfo.namespaceDeclarations != null && currentElementInfo.namespaceDeclarations.size() > 0) {
+                        Iterator<Map.Entry<String, String>> namespaceIt = currentElementInfo.namespaceDeclarations.entrySet().iterator();
+                        while (namespaceIt.hasNext()) {
+                            Map.Entry<String, String> entry = namespaceIt.next();
+                            contentHandler.endPrefixMapping(entry.getKey());
+                            //System.out.println("ending prefix mapping " + (String) entry.getKey());
+                        }
+                    }
+                    currentElementInfo = currentElementInfo.parent;
+                    break;
+                case Node.DOCUMENT_NODE:
+                case Node.CDATA_SECTION_NODE:
+                    break;
+                case Node.ENTITY_REFERENCE_NODE:
+                    {
+                        EntityReference eref = (EntityReference) node;
+
+                        if (lexicalHandler != null) {
+                            lexicalHandler.endEntity(eref.getNodeName());
+                        }
+                    }
+                    break;
+                default :
+            }
+        }
+
+        public static class ElementInfo {
+            public String localName;
+            public String namespaceURI;
+            public String qName;
+            public Map<String, String> namespaceDeclarations;
+            public ElementInfo parent;
+
+            public ElementInfo(ElementInfo parent) {
+                this.parent = parent;
+            }
+
+            /**
+             * Declare a new namespace prefix on this element, possibly overriding
+             * an existing one.
+             */
+            public void put(String prefix, String namespaceURI) {
+                if (namespaceDeclarations == null)
+                    namespaceDeclarations = new HashMap<String, String>();
+                namespaceDeclarations.put(prefix, namespaceURI);
+            }
+
+            /**
+             * Finds a prefix declared on this element.
+             */
+            public String getPrefix(String namespaceURI) {
+                if (namespaceDeclarations == null || namespaceDeclarations.size() == 0)
+                    return null;
+                // note: there could be more than one prefix for the same namespaceURI, but
+                // we return the first found one.
+                Iterator<Map.Entry<String, String>> it = namespaceDeclarations.entrySet().iterator();
+                while (it.hasNext()) {
+                    Map.Entry<String, String> entry = it.next();
+                    if (entry.getValue().equals(namespaceURI))
+                        return entry.getKey();
+                }
+                return null;
+            }
+
+            /**
+             * Finds a namespace URI declared on this element.
+             */
+            public String getNamespaceURI(String prefix) {
+                if (namespaceDeclarations == null || namespaceDeclarations.size() == 0)
+                    return null;
+
+                return namespaceDeclarations.get(prefix);
+            }
+
+            /**
+             * Finds a prefix declaration on this element or containing elements.
+             */
+            public String findPrefix(String namespaceURI) {
+                if (namespaceDeclarations != null && namespaceDeclarations.size() != 0) {
+                    String prefix = getPrefix(namespaceURI);
+                    if (prefix != null) {
+                        return prefix;
+                    }
+                }
+                if (parent != null) {
+                    return parent.findPrefix(namespaceURI);
+                }
+                return null;
+            }
+
+            /**
+             * Finds a namespace declaration on this element or containing elements.
+             */
+            public String findNamespaceURI(String prefix) {
+                if (namespaceDeclarations != null && namespaceDeclarations.size() != 0) {
+                    String uri = namespaceDeclarations.get(prefix);
+                    if (uri != null) {
+                        return uri;
+                    }
+                }
+                if (parent != null) {
+                    return parent.findNamespaceURI(prefix);
+                }
+                return null;
+            }
+        }
+    }
+
+    /**
+     * The <code>DefaultDOMStreamer</code> is a utility class that will generate SAX
+     * events from a W3C DOM Document.
+     */
+    public static class DefaultDOMStreamer extends AbstractXMLProducer {
+
+        /** The private transformer for this instance */
+        protected Transformer transformer;
+
+        /**
+         * Start the production of SAX events.
+         */
+        public void stream(Node node)
+        throws SAXException {
+            if (this.transformer == null) {
+                try {
+                    this.transformer = FACTORY.newTransformer();
+                } catch (TransformerConfigurationException e) {
+                    throw new SAXException(e);
+                }
+            }
+            DOMSource source = new DOMSource(node);
+
+            ContentHandler handler;
+            if (node.getNodeType() == Node.DOCUMENT_NODE) {
+                // Pass all SAX events
+                handler = contentHandler;
+            } else {
+                // Strip start/endDocument
+                handler = new EmbeddedXMLPipe(contentHandler);
+            }
+
+            SAXResult result = new SAXResult(handler);
+            result.setLexicalHandler(lexicalHandler);
+
+            try {
+                transformer.transform(source, result);
+            } catch (TransformerException e) {
+                throw new SAXException(e);
+            }
+        }
+    }
+}

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMStreamer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMStreamer.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: cocoon/whiteboard/xml-utils/src/main/java/org/apache/cocoon/xml/util/dom/DOMStreamer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain