/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package com.cherry.lib.doc.office.fc.dom4j.io;

import java.util.List;

import org.w3c.dom.DOMImplementation;

import com.cherry.lib.doc.office.fc.dom4j.Attribute;
import com.cherry.lib.doc.office.fc.dom4j.CDATA;
import com.cherry.lib.doc.office.fc.dom4j.Comment;
import com.cherry.lib.doc.office.fc.dom4j.Document;
import com.cherry.lib.doc.office.fc.dom4j.DocumentException;
import com.cherry.lib.doc.office.fc.dom4j.Element;
import com.cherry.lib.doc.office.fc.dom4j.Entity;
import com.cherry.lib.doc.office.fc.dom4j.Namespace;
import com.cherry.lib.doc.office.fc.dom4j.ProcessingInstruction;
import com.cherry.lib.doc.office.fc.dom4j.Text;
import com.cherry.lib.doc.office.fc.dom4j.tree.NamespaceStack;


/**
 * <p>
 * <code>DOMWriter</code> takes a DOM4J tree and outputs it as a W3C DOM
 * object
 * </p>
 * 
 * @author <a href="mailto:james.strachan@metastuff.com">James Strachan </a>
 * @version $Revision: 1.17 $
 */
public class DOMWriter
{
    private static boolean loggedWarning = false;

    private static final String[] DEFAULT_DOM_DOCUMENT_CLASSES = {
        "org.apache.xerces.dom.DocumentImpl", // Xerces
        "gnu.xml.dom.DomDocument", // GNU JAXP
        "org.apache.crimson.tree.XmlDocument", // Crimson
        "com.sun.xml.tree.XmlDocument", // Sun's Project X
        "oracle.xml.parser.v2.XMLDocument", // Oracle V2
        "oracle.xml.parser.XMLDocument", // Oracle V1
        "org.dom4j.dom.DOMDocument" // Internal DOM implementation
    };

    // the Class used to create new DOM Document instances
    private Class domDocumentClass;

    /** stack of <code>Namespace</code> objects */
    private NamespaceStack namespaceStack = new NamespaceStack();

    public DOMWriter()
    {
    }

    public DOMWriter(Class domDocumentClass)
    {
        this.domDocumentClass = domDocumentClass;
    }

    public Class getDomDocumentClass() throws DocumentException
    {
        Class result = domDocumentClass;

        if (result == null)
        {
            // lets try and find one in the classpath
            int size = DEFAULT_DOM_DOCUMENT_CLASSES.length;

            for (int i = 0; i < size; i++)
            {
                try
                {
                    String name = DEFAULT_DOM_DOCUMENT_CLASSES[i];
                    result = Class.forName(name, true, DOMWriter.class.getClassLoader());

                    if (result != null)
                    {
                        break;
                    }
                }
                catch(Exception e)
                {
                    // could not load class correctly
                    // lets carry on to the next one
                }
            }
        }

        return result;
    }

    /**
     * Sets the DOM {@link org.w3c.dom.Document}implementation class used by
     * the writer when creating DOM documents.
     * 
     * @param domDocumentClass
     *            is the Class implementing the {@linkorg.w3c.dom.Document}
     *            interface
     */
    public void setDomDocumentClass(Class domDocumentClass)
    {
        this.domDocumentClass = domDocumentClass;
    }

    /**
     * Sets the DOM {@link org.w3c.dom.Document}implementation class name used
     * by the writer when creating DOM documents.
     * 
     * @param name
     *            is the name of the Class implementing the {@link
     *            org.w3c.dom.Document} interface
     * 
     * @throws DocumentException
     *             if the class could not be loaded
     */
    public void setDomDocumentClassName(String name) throws DocumentException
    {
        try
        {
            this.domDocumentClass = Class.forName(name, true, DOMWriter.class.getClassLoader());
        }
        catch(Exception e)
        {
            throw new DocumentException("Could not load the DOM Document " + "class: " + name, e);
        }
    }

    public org.w3c.dom.Document write(Document document) throws DocumentException
    {
        if (document instanceof org.w3c.dom.Document)
        {
            return (org.w3c.dom.Document)document;
        }

        resetNamespaceStack();

        org.w3c.dom.Document domDocument = createDomDocument(document);
        appendDOMTree(domDocument, domDocument, document.content());
        namespaceStack.clear();

        return domDocument;
    }

    public org.w3c.dom.Document write(Document document, org.w3c.dom.DOMImplementation domImpl)
        throws DocumentException
    {
        if (document instanceof org.w3c.dom.Document)
        {
            return (org.w3c.dom.Document)document;
        }

        resetNamespaceStack();

        org.w3c.dom.Document domDocument = createDomDocument(document, domImpl);
        appendDOMTree(domDocument, domDocument, document.content());
        namespaceStack.clear();

        return domDocument;
    }

    protected void appendDOMTree(org.w3c.dom.Document domDocument, org.w3c.dom.Node domCurrent,
        List content)
    {
        int size = content.size();

        for (int i = 0; i < size; i++)
        {
            Object object = content.get(i);

            if (object instanceof Element)
            {
                appendDOMTree(domDocument, domCurrent, (Element)object);
            }
            else if (object instanceof String)
            {
                appendDOMTree(domDocument, domCurrent, (String)object);
            }
            else if (object instanceof Text)
            {
                Text text = (Text)object;
                appendDOMTree(domDocument, domCurrent, text.getText());
            }
            else if (object instanceof CDATA)
            {
                appendDOMTree(domDocument, domCurrent, (CDATA)object);
            }
            else if (object instanceof Comment)
            {
                appendDOMTree(domDocument, domCurrent, (Comment)object);
            }
            else if (object instanceof Entity)
            {
                appendDOMTree(domDocument, domCurrent, (Entity)object);
            }
            else if (object instanceof ProcessingInstruction)
            {
                appendDOMTree(domDocument, domCurrent, (ProcessingInstruction)object);
            }
        }
    }

    protected void appendDOMTree(org.w3c.dom.Document domDocument, org.w3c.dom.Node domCurrent,
        Element element)
    {
        String elUri = element.getNamespaceURI();
        String elName = element.getQualifiedName();
        org.w3c.dom.Element domElement = domDocument.createElementNS(elUri, elName);

        int stackSize = namespaceStack.size();

        // add the namespace of the element first
        Namespace elementNamespace = element.getNamespace();

        if (isNamespaceDeclaration(elementNamespace))
        {
            namespaceStack.push(elementNamespace);
            writeNamespace(domElement, elementNamespace);
        }

        // add the additional declared namespaces
        List declaredNamespaces = element.declaredNamespaces();

        for (int i = 0, size = declaredNamespaces.size(); i < size; i++)
        {
            Namespace namespace = (Namespace)declaredNamespaces.get(i);

            if (isNamespaceDeclaration(namespace))
            {
                namespaceStack.push(namespace);
                writeNamespace(domElement, namespace);
            }
        }

        // add the attributes
        for (int i = 0, size = element.attributeCount(); i < size; i++)
        {
            Attribute attribute = (Attribute)element.attribute(i);
            String attUri = attribute.getNamespaceURI();
            String attName = attribute.getQualifiedName();
            String value = attribute.getValue();
            domElement.setAttributeNS(attUri, attName, value);
        }

        // add content
        appendDOMTree(domDocument, domElement, element.content());

        domCurrent.appendChild(domElement);

        while (namespaceStack.size() > stackSize)
        {
            namespaceStack.pop();
        }
    }

    protected void appendDOMTree(org.w3c.dom.Document domDocument, org.w3c.dom.Node domCurrent,
        CDATA cdata)
    {
        org.w3c.dom.CDATASection domCDATA = domDocument.createCDATASection(cdata.getText());
        domCurrent.appendChild(domCDATA);
    }

    protected void appendDOMTree(org.w3c.dom.Document domDocument, org.w3c.dom.Node domCurrent,
        Comment comment)
    {
        org.w3c.dom.Comment domComment = domDocument.createComment(comment.getText());
        domCurrent.appendChild(domComment);
    }

    protected void appendDOMTree(org.w3c.dom.Document domDocument, org.w3c.dom.Node domCurrent,
        String text)
    {
        org.w3c.dom.Text domText = domDocument.createTextNode(text);
        domCurrent.appendChild(domText);
    }

    protected void appendDOMTree(org.w3c.dom.Document domDocument, org.w3c.dom.Node domCurrent,
        Entity entity)
    {
        org.w3c.dom.EntityReference domEntity = domDocument.createEntityReference(entity.getName());
        domCurrent.appendChild(domEntity);
    }

    protected void appendDOMTree(org.w3c.dom.Document domDoc, org.w3c.dom.Node domCurrent,
        ProcessingInstruction pi)
    {
        org.w3c.dom.ProcessingInstruction domPI = domDoc.createProcessingInstruction(
            pi.getTarget(), pi.getText());
        domCurrent.appendChild(domPI);
    }

    protected void writeNamespace(org.w3c.dom.Element domElement, Namespace namespace)
    {
        String attributeName = attributeNameForNamespace(namespace);

        // domElement.setAttributeNS("", attributeName, namespace.getURI());
        domElement.setAttribute(attributeName, namespace.getURI());
    }

    protected String attributeNameForNamespace(Namespace namespace)
    {
        String xmlns = "xmlns";
        String prefix = namespace.getPrefix();

        if (prefix.length() > 0)
        {
            return xmlns + ":" + prefix;
        }

        return xmlns;
    }

    protected org.w3c.dom.Document createDomDocument(Document document) throws DocumentException
    {
        org.w3c.dom.Document result = null;

        // use the given domDocumentClass (if not null)
        if (domDocumentClass != null)
        {
            try
            {
                result = (org.w3c.dom.Document)domDocumentClass.newInstance();
            }
            catch(Exception e)
            {
                throw new DocumentException("Could not instantiate an instance "
                    + "of DOM Document with class: " + domDocumentClass.getName(), e);
            }
        }
        else
        {
            // lets try JAXP first before using the hardcoded default parsers
            result = createDomDocumentViaJAXP();

            if (result == null)
            {
                Class theClass = getDomDocumentClass();

                try
                {
                    result = (org.w3c.dom.Document)theClass.newInstance();
                }
                catch(Exception e)
                {
                    throw new DocumentException("Could not instantiate an "
                        + "instance of DOM Document " + "with class: " + theClass.getName(), e);
                }
            }
        }

        return result;
    }

    protected org.w3c.dom.Document createDomDocumentViaJAXP() throws DocumentException
    {
        try
        {
            return JAXPHelper.createDocument(false, true);
        }
        catch(Throwable e)
        {
            if (!loggedWarning)
            {
                loggedWarning = true;

                if (SAXHelper.isVerboseErrorReporting())
                {
                    // log all exceptions as warnings and carry
                    e.printStackTrace();
                }
                else
                {
                }
            }
        }

        return null;
    }

    protected org.w3c.dom.Document createDomDocument(Document document, DOMImplementation domImpl)
        throws DocumentException
    {
        String namespaceURI = null;
        String qualifiedName = null;
        org.w3c.dom.DocumentType docType = null;

        return domImpl.createDocument(namespaceURI, qualifiedName, docType);
    }

    protected boolean isNamespaceDeclaration(Namespace ns)
    {
        if ((ns != null) && (ns != Namespace.NO_NAMESPACE) && (ns != Namespace.XML_NAMESPACE))
        {
            String uri = ns.getURI();

            if ((uri != null) && (uri.length() > 0))
            {
                if (!namespaceStack.contains(ns))
                {
                    return true;
                }
            }
        }

        return false;
    }

    protected void resetNamespaceStack()
    {
        namespaceStack.clear();
        namespaceStack.push(Namespace.XML_NAMESPACE);
    }
}

/*
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and
 * notices. Redistributions must also contain a copy of this document.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The name "DOM4J" must not be used to endorse or promote products derived
 * from this Software without prior written permission of MetaStuff, Ltd. For
 * written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may
 * "DOM4J" appear in their names without prior written permission of MetaStuff,
 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */
