/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

#include <com/sun/star/uno/Sequence.h>

#include "document.hxx"
#include "attr.hxx"
#include "element.hxx"
#include "cdatasection.hxx"
#include "documentfragment.hxx"
#include "text.hxx"
#include "cdatasection.hxx"
#include "comment.hxx"
#include "processinginstruction.hxx"
#include "entityreference.hxx"
#include "documenttype.hxx"
#include "elementlist.hxx"
#include "domimplementation.hxx"
#include <entity.hxx>
#include <notation.hxx>

#include "../events/event.hxx"
#include "../events/mutationevent.hxx"
#include "../events/uievent.hxx"
#include "../events/mouseevent.hxx"
#include "../events/eventdispatcher.hxx"

#include <string.h>

#include <com/sun/star/xml/sax/FastToken.hpp>
#include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp>

namespace DOM
{
    static xmlNodePtr lcl_getDocumentType(xmlDocPtr const i_pDocument)
    {
        // find the doc type
        xmlNodePtr cur = i_pDocument->children;
        while (cur != NULL)
        {
            if ((cur->type == XML_DOCUMENT_TYPE_NODE) ||
                (cur->type == XML_DTD_NODE)) {
                    return cur;
            }
        }
        return 0;
    }

    /// get the pointer to the root element node of the document
    static xmlNodePtr lcl_getDocumentRootPtr(xmlDocPtr const i_pDocument)
    {
        // find the document element
        xmlNodePtr cur = i_pDocument->children;
        while (cur != NULL)
        {
            if (cur->type == XML_ELEMENT_NODE)
                break;
            cur = cur->next;
        }
        return cur;
    }

    CDocument::CDocument(xmlDocPtr const pDoc)
        : CDocument_Base(*this, m_Mutex,
                NodeType_DOCUMENT_NODE, reinterpret_cast<xmlNodePtr>(pDoc))
        , m_aDocPtr(pDoc)
        , m_streamListeners()
        , m_pEventDispatcher(new events::CEventDispatcher())
    {
    }

    ::rtl::Reference<CDocument> CDocument::CreateCDocument(xmlDocPtr const pDoc)
    {
        ::rtl::Reference<CDocument> const xDoc(new CDocument(pDoc));
        // add the doc itself to its nodemap!
        xDoc->m_NodeMap.insert(
            nodemap_t::value_type(reinterpret_cast<xmlNodePtr>(pDoc),
                ::std::make_pair(
                    WeakReference<XNode>(static_cast<XDocument*>(xDoc.get())),
                    xDoc.get())));
        return xDoc;
    }

    CDocument::~CDocument()
    {
        ::osl::MutexGuard const g(m_Mutex);
#ifdef DBG_UTIL
        // node map must be empty now, otherwise CDocument must not die!
        for (nodemap_t::iterator i = m_NodeMap.begin();
                i != m_NodeMap.end(); ++i)
        {
            Reference<XNode> const xNode(i->second.first);
            OSL_ENSURE(!xNode.is(),
            "CDocument::~CDocument(): ERROR: live node in document node map!");
        }
#endif
        xmlFreeDoc(m_aDocPtr);
    }


    events::CEventDispatcher & CDocument::GetEventDispatcher()
    {
        return *m_pEventDispatcher;
    }

    ::rtl::Reference< CElement > CDocument::GetDocumentElement()
    {
        xmlNodePtr const pNode = lcl_getDocumentRootPtr(m_aDocPtr);
        ::rtl::Reference< CElement > const xRet(
            dynamic_cast<CElement*>(GetCNode(pNode).get()));
        return xRet;
    }

    void
    CDocument::RemoveCNode(xmlNodePtr const pNode, CNode const*const pCNode)
    {
        nodemap_t::iterator const i = m_NodeMap.find(pNode);
        if (i != m_NodeMap.end()) {
            // #i113681# consider this scenario:
            // T1 calls ~CNode
            // T2 calls getCNode:    lookup will find i->second->first invalid
            //                       so a new CNode is created and inserted
            // T1 calls removeCNode: i->second->second now points to a
            //                       different CNode instance!
            //
            // check that the CNode is the right one
            CNode *const pCurrent = i->second.second;
            if (pCurrent == pCNode) {
                m_NodeMap.erase(i);
            }
        }
    }

    /** NB: this is the CNode factory.
        it is the only place where CNodes may be instantiated.
        all CNodes must be registered at the m_NodeMap.
     */
    ::rtl::Reference<CNode>
    CDocument::GetCNode(xmlNodePtr const pNode, bool const bCreate)
    {
        if (0 == pNode) {
            return 0;
        }
        //check whether there is already an instance for this node
        nodemap_t::const_iterator const i = m_NodeMap.find(pNode);
        if (i != m_NodeMap.end()) {
            // #i113681# check that the CNode is still alive
            uno::Reference<XNode> const xNode(i->second.first);
            if (xNode.is())
            {
                ::rtl::Reference<CNode> ret(i->second.second);
                OSL_ASSERT(ret.is());
                return ret;
            }
        }

        if (!bCreate) { return 0; }

        // there is not yet an instance wrapping this node,
        // create it and store it in the map

        ::rtl::Reference<CNode> pCNode;
        switch (pNode->type)
        {
            case XML_ELEMENT_NODE:
                // m_aNodeType = NodeType::ELEMENT_NODE;
                pCNode = static_cast< CNode* >(
                        new CElement(*this, m_Mutex, pNode));
            break;
            case XML_TEXT_NODE:
                // m_aNodeType = NodeType::TEXT_NODE;
                pCNode = static_cast< CNode* >(
                        new CText(*this, m_Mutex, pNode));
            break;
            case XML_CDATA_SECTION_NODE:
                // m_aNodeType = NodeType::CDATA_SECTION_NODE;
                pCNode = static_cast< CNode* >(
                        new CCDATASection(*this, m_Mutex, pNode));
            break;
            case XML_ENTITY_REF_NODE:
                // m_aNodeType = NodeType::ENTITY_REFERENCE_NODE;
                pCNode = static_cast< CNode* >(
                        new CEntityReference(*this, m_Mutex, pNode));
            break;
            case XML_ENTITY_NODE:
                // m_aNodeType = NodeType::ENTITY_NODE;
                pCNode = static_cast< CNode* >(new CEntity(*this, m_Mutex,
                            reinterpret_cast<xmlEntityPtr>(pNode)));
            break;
            case XML_PI_NODE:
                // m_aNodeType = NodeType::PROCESSING_INSTRUCTION_NODE;
                pCNode = static_cast< CNode* >(
                        new CProcessingInstruction(*this, m_Mutex, pNode));
            break;
            case XML_COMMENT_NODE:
                // m_aNodeType = NodeType::COMMENT_NODE;
                pCNode = static_cast< CNode* >(
                        new CComment(*this, m_Mutex, pNode));
            break;
            case XML_DOCUMENT_NODE:
                // m_aNodeType = NodeType::DOCUMENT_NODE;
                OSL_ENSURE(false, "CDocument::GetCNode is not supposed to"
                        " create a CDocument!!!");
                pCNode = static_cast< CNode* >(new CDocument(
                            reinterpret_cast<xmlDocPtr>(pNode)));
            break;
            case XML_DOCUMENT_TYPE_NODE:
            case XML_DTD_NODE:
                // m_aNodeType = NodeType::DOCUMENT_TYPE_NODE;
                pCNode = static_cast< CNode* >(new CDocumentType(*this, m_Mutex,
                            reinterpret_cast<xmlDtdPtr>(pNode)));
            break;
            case XML_DOCUMENT_FRAG_NODE:
                // m_aNodeType = NodeType::DOCUMENT_FRAGMENT_NODE;
                pCNode = static_cast< CNode* >(
                        new CDocumentFragment(*this, m_Mutex, pNode));
            break;
            case XML_NOTATION_NODE:
                // m_aNodeType = NodeType::NOTATION_NODE;
                pCNode = static_cast< CNode* >(new CNotation(*this, m_Mutex,
                            reinterpret_cast<xmlNotationPtr>(pNode)));
            break;
            case XML_ATTRIBUTE_NODE:
                // m_aNodeType = NodeType::ATTRIBUTE_NODE;
                pCNode = static_cast< CNode* >(new CAttr(*this, m_Mutex,
                            reinterpret_cast<xmlAttrPtr>(pNode)));
            break;
            // unsupported node types
            case XML_HTML_DOCUMENT_NODE:
            case XML_ELEMENT_DECL:
            case XML_ATTRIBUTE_DECL:
            case XML_ENTITY_DECL:
            case XML_NAMESPACE_DECL:
            default:
            break;
        }

        if (pCNode != 0) {
            bool const bInserted = m_NodeMap.insert(
                    nodemap_t::value_type(pNode,
                        ::std::make_pair(WeakReference<XNode>(pCNode.get()),
                        pCNode.get()))
                ).second;
            OSL_ASSERT(bInserted);
            if (!bInserted) {
                // if insertion failed, delete new instance and return null
                return 0;
            }
        }

        OSL_ENSURE(pCNode.is(), "no node produced during CDocument::GetCNode!");
        return pCNode;
    }


    CDocument & CDocument::GetOwnerDocument()
    {
        return *this;
    }

    void CDocument::saxify(const Reference< XDocumentHandler >& i_xHandler)
    {
        i_xHandler->startDocument();
        for (xmlNodePtr pChild = m_aNodePtr->children;
                        pChild != 0; pChild = pChild->next) {
            ::rtl::Reference<CNode> const pNode = GetCNode(pChild);
            OSL_ENSURE(pNode != 0, "CNode::get returned 0");
            pNode->saxify(i_xHandler);
        }
        i_xHandler->endDocument();
    }

    void CDocument::fastSaxify( Context& rContext )
    {
        rContext.mxDocHandler->startDocument();
        for (xmlNodePtr pChild = m_aNodePtr->children;
                        pChild != 0; pChild = pChild->next) {
            ::rtl::Reference<CNode> const pNode = GetCNode(pChild);
            OSL_ENSURE(pNode != 0, "CNode::get returned 0");
            pNode->fastSaxify(rContext);
        }
        rContext.mxDocHandler->endDocument();
    }

    bool CDocument::IsChildTypeAllowed(NodeType const nodeType)
    {
        switch (nodeType) {
            case NodeType_PROCESSING_INSTRUCTION_NODE:
            case NodeType_COMMENT_NODE:
                return true;
            case NodeType_ELEMENT_NODE:
                 // there may be only one!
                return 0 == lcl_getDocumentRootPtr(m_aDocPtr);
            case NodeType_DOCUMENT_TYPE_NODE:
                 // there may be only one!
                return 0 == lcl_getDocumentType(m_aDocPtr);
            default:
                return false;
        }
    }


	void SAL_CALL CDocument::addListener(const Reference< XStreamListener >& aListener )
		throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

		m_streamListeners.insert(aListener);
	}

	void SAL_CALL CDocument::removeListener(const Reference< XStreamListener >& aListener )
		throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

		m_streamListeners.erase(aListener);
	}

	// IO context functions for libxml2 interaction
	typedef struct {
		Reference< XOutputStream > stream;
		bool allowClose;
	} IOContext;

    extern "C" {
	// write callback
	// int xmlOutputWriteCallback (void * context, const char * buffer, int len)
    static int writeCallback(void *context, const char* buffer, int len){
		// create a sequence and write it to the stream
		IOContext *pContext = static_cast<IOContext*>(context);
		Sequence<sal_Int8> bs(reinterpret_cast<const sal_Int8*>(buffer), len);
		pContext->stream->writeBytes(bs);
		return len;
	}

	// clsoe callback
	//int xmlOutputCloseCallback (void * context)
	static int closeCallback(void *context)
	{
		IOContext *pContext = static_cast<IOContext*>(context);
		if (pContext->allowClose) {
			pContext->stream->closeOutput();
		}
		return 0;
	}
    } // extern "C"

	void SAL_CALL CDocument::start()
		throw (RuntimeException)
	{
        listenerlist_t streamListeners;
        {
            ::osl::MutexGuard const g(m_Mutex);

            if (! m_rOutputStream.is()) { throw RuntimeException(); }
            streamListeners = m_streamListeners;
        }

		// notify listeners about start
		listenerlist_t::const_iterator iter1 = streamListeners.begin();
        while (iter1 != streamListeners.end()) {
			Reference< XStreamListener > aListener = *iter1;
			aListener->started();
			iter1++;
		}

        {
            ::osl::MutexGuard const g(m_Mutex);

            // check again! could have been reset...
            if (! m_rOutputStream.is()) { throw RuntimeException(); }

            // setup libxml IO and write data to output stream
            IOContext ioctx = {m_rOutputStream, false};
            xmlOutputBufferPtr pOut = xmlOutputBufferCreateIO(
                writeCallback, closeCallback, &ioctx, NULL);
            xmlSaveFileTo(pOut, m_aNodePtr->doc, NULL);
        }

		// call listeners
        listenerlist_t::const_iterator iter2 = streamListeners.begin();
        while (iter2 != streamListeners.end()) {
			Reference< XStreamListener > aListener = *iter2;
			aListener->closed();
			iter2++;
		}
	}

	void SAL_CALL CDocument::terminate()
		throw (RuntimeException)
	{
		// not supported
	}

	void SAL_CALL CDocument::setOutputStream( const Reference< XOutputStream >& aStream )
		throw (RuntimeException)
	{
        ::osl::MutexGuard const g(m_Mutex);

		m_rOutputStream = aStream;
	}

	Reference< XOutputStream > SAL_CALL  CDocument::getOutputStream() throw (RuntimeException)
	{
        ::osl::MutexGuard const g(m_Mutex);

		return m_rOutputStream;
	}

    // Creates an Attr of the given name.
    Reference< XAttr > SAL_CALL CDocument::createAttribute(const OUString& name)
        throw (RuntimeException, DOMException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        OString o1 = OUStringToOString(name, RTL_TEXTENCODING_UTF8);
        xmlChar *xName = (xmlChar*)o1.getStr();
        xmlAttrPtr const pAttr = xmlNewDocProp(m_aDocPtr, xName, NULL);
        ::rtl::Reference< CAttr > const pCAttr(
            dynamic_cast< CAttr* >(GetCNode(
                    reinterpret_cast<xmlNodePtr>(pAttr)).get()));
        pCAttr->m_bUnlinked = true;
        return pCAttr.get();
    };

    // Creates an attribute of the given qualified name and namespace URI.
    Reference< XAttr > SAL_CALL CDocument::createAttributeNS(
            const OUString& ns, const OUString& qname)
        throw (RuntimeException, DOMException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        // libxml does not allow a NS definition to be attached to an
        // attribute node - which is a good thing, since namespaces are
        // only defined as parts of element nodes
        // thus the namespace data is stored in CAttr::m_pNamespace
        sal_Int32 i = qname.indexOf(':');
        OString oPrefix, oName, oUri;
        if (i != -1)
        {
            oPrefix = OUStringToOString(qname.copy(0, i), RTL_TEXTENCODING_UTF8);
            oName = OUStringToOString(qname.copy(i+1, qname.getLength()-i-1), RTL_TEXTENCODING_UTF8);
        }
        else
        {
            oName = OUStringToOString(qname, RTL_TEXTENCODING_UTF8);
        }
        oUri = OUStringToOString(ns, RTL_TEXTENCODING_UTF8);
        xmlAttrPtr const pAttr = xmlNewDocProp(m_aDocPtr,
                reinterpret_cast<xmlChar const*>(oName.getStr()), 0);
        ::rtl::Reference< CAttr > const pCAttr(
            dynamic_cast< CAttr* >(GetCNode(
                    reinterpret_cast<xmlNodePtr>(pAttr)).get()));
        if (!pCAttr.is()) { throw RuntimeException(); }
        // store the namespace data!
        pCAttr->m_pNamespace.reset( new stringpair_t(oUri, oPrefix) );
        pCAttr->m_bUnlinked = true;

        return pCAttr.get();
    };

    // Creates a CDATASection node whose value is the specified string.
    Reference< XCDATASection > SAL_CALL CDocument::createCDATASection(const OUString& data)
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        OString const oData(
                ::rtl::OUStringToOString(data, RTL_TEXTENCODING_UTF8));
        xmlChar const*const pData =
            reinterpret_cast<xmlChar const*>(oData.getStr());
        xmlNodePtr const pText =
            xmlNewCDataBlock(m_aDocPtr, pData, strlen(oData.getStr()));
        Reference< XCDATASection > const xRet(
            static_cast< XNode* >(GetCNode(pText).get()),
            UNO_QUERY_THROW);
        return xRet;
    }

    // Creates a Comment node given the specified string.
    Reference< XComment > SAL_CALL CDocument::createComment(const OUString& data)
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        OString o1 = OUStringToOString(data, RTL_TEXTENCODING_UTF8);
        xmlChar *xData = (xmlChar*)o1.getStr();
        xmlNodePtr pComment = xmlNewDocComment(m_aDocPtr, xData);
        Reference< XComment > const xRet(
            static_cast< XNode* >(GetCNode(pComment).get()),
            UNO_QUERY_THROW);
        return xRet;
    }

    //Creates an empty DocumentFragment object.
    Reference< XDocumentFragment > SAL_CALL CDocument::createDocumentFragment()
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        xmlNodePtr pFrag = xmlNewDocFragment(m_aDocPtr);
        Reference< XDocumentFragment > const xRet(
            static_cast< XNode* >(GetCNode(pFrag).get()),
            UNO_QUERY_THROW);
        return xRet;
    }

    // Creates an element of the type specified.
    Reference< XElement > SAL_CALL CDocument::createElement(const OUString& tagName)
        throw (RuntimeException, DOMException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        OString o1 = OUStringToOString(tagName, RTL_TEXTENCODING_UTF8);
        xmlChar *xName = (xmlChar*)o1.getStr();
        xmlNodePtr const pNode = xmlNewDocNode(m_aDocPtr, NULL, xName, NULL);
        Reference< XElement > const xRet(
            static_cast< XNode* >(GetCNode(pNode).get()),
            UNO_QUERY_THROW);
        return xRet;
    }

    // Creates an element of the given qualified name and namespace URI.
    Reference< XElement > SAL_CALL CDocument::createElementNS(
            const OUString& ns, const OUString& qname)
        throw (RuntimeException, DOMException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        sal_Int32 i = qname.indexOf(':');
        if (ns.getLength() == 0) throw RuntimeException();
        xmlChar *xPrefix;
        xmlChar *xName;
        OString o1, o2, o3;
        if ( i != -1) {
            o1 = OUStringToOString(qname.copy(0, i), RTL_TEXTENCODING_UTF8);
            xPrefix = (xmlChar*)o1.getStr();
            o2 = OUStringToOString(qname.copy(i+1, qname.getLength()-i-1), RTL_TEXTENCODING_UTF8);
            xName = (xmlChar*)o2.getStr();
        } else {
            // default prefix
            xPrefix = (xmlChar*)"";
            o2 = OUStringToOString(qname, RTL_TEXTENCODING_UTF8);
            xName = (xmlChar*)o2.getStr();
        }
        o3 = OUStringToOString(ns, RTL_TEXTENCODING_UTF8);
        xmlChar *xUri = (xmlChar*)o3.getStr();

        // xmlNsPtr aNsPtr = xmlNewReconciledNs?
        // xmlNsPtr aNsPtr = xmlNewGlobalNs?
        xmlNodePtr const pNode = xmlNewDocNode(m_aDocPtr, NULL, xName, NULL);
        xmlNsPtr const pNs = xmlNewNs(pNode, xUri, xPrefix);
        xmlSetNs(pNode, pNs);
        Reference< XElement > const xRet(
            static_cast< XNode* >(GetCNode(pNode).get()),
            UNO_QUERY_THROW);
        return xRet;
    }

    //Creates an EntityReference object.
    Reference< XEntityReference > SAL_CALL CDocument::createEntityReference(const OUString& name)
        throw (RuntimeException, DOMException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        OString o1 = OUStringToOString(name, RTL_TEXTENCODING_UTF8);
        xmlChar *xName = (xmlChar*)o1.getStr();
        xmlNodePtr const pNode = xmlNewReference(m_aDocPtr, xName);
        Reference< XEntityReference > const xRet(
            static_cast< XNode* >(GetCNode(pNode).get()),
            UNO_QUERY_THROW);
        return xRet;
    }

    // Creates a ProcessingInstruction node given the specified name and
    // data strings.
    Reference< XProcessingInstruction > SAL_CALL CDocument::createProcessingInstruction(
            const OUString& target, const OUString& data)
        throw (RuntimeException, DOMException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        OString o1 = OUStringToOString(target, RTL_TEXTENCODING_UTF8);
        xmlChar *xTarget = (xmlChar*)o1.getStr();
        OString o2 = OUStringToOString(data, RTL_TEXTENCODING_UTF8);
        xmlChar *xData = (xmlChar*)o2.getStr();
        xmlNodePtr const pNode = xmlNewDocPI(m_aDocPtr, xTarget, xData);
        pNode->doc = m_aDocPtr;
        Reference< XProcessingInstruction > const xRet(
            static_cast< XNode* >(GetCNode(pNode).get()),
            UNO_QUERY_THROW);
        return xRet;
    }

    // Creates a Text node given the specified string.
    Reference< XText > SAL_CALL CDocument::createTextNode(const OUString& data)
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        OString o1 = OUStringToOString(data, RTL_TEXTENCODING_UTF8);
        xmlChar *xData = (xmlChar*)o1.getStr();
        xmlNodePtr const pNode = xmlNewDocText(m_aDocPtr, xData);
        Reference< XText > const xRet(
            static_cast< XNode* >(GetCNode(pNode).get()),
            UNO_QUERY_THROW);
        return xRet;
    }

    // The Document Type Declaration (see DocumentType) associated with this
    // document.
    Reference< XDocumentType > SAL_CALL CDocument::getDoctype()
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        xmlNodePtr const pDocType(lcl_getDocumentType(m_aDocPtr));
        Reference< XDocumentType > const xRet(
            static_cast< XNode* >(GetCNode(pDocType).get()),
            UNO_QUERY);
        return xRet;
    }

    // This is a convenience attribute that allows direct access to the child
    // node that is the root element of the document.
    Reference< XElement > SAL_CALL CDocument::getDocumentElement()
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        xmlNodePtr const pNode = lcl_getDocumentRootPtr(m_aDocPtr);
        if (!pNode) { return 0; }
        Reference< XElement > const xRet(
            static_cast< XNode* >(GetCNode(pNode).get()),
            UNO_QUERY);
        return xRet;
    }

    static xmlNodePtr
    lcl_search_element_by_id(const xmlNodePtr cur, const xmlChar* id)
    {
        if (cur == NULL)
            return NULL;
        // look in current node
        if (cur->type == XML_ELEMENT_NODE)
        {
            xmlAttrPtr a = cur->properties;
            while (a != NULL)
            {
                if (a->atype == XML_ATTRIBUTE_ID) {
                    if (strcmp((char*)a->children->content, (char*)id) == 0)
                        return cur;
                }
                a = a->next;
            }
        }
        // look in children
        xmlNodePtr result = lcl_search_element_by_id(cur->children, id);
        if (result != NULL)
            return result;
        result = lcl_search_element_by_id(cur->next, id);
            return result;
    }

    // Returns the Element whose ID is given by elementId.
    Reference< XElement > SAL_CALL
    CDocument::getElementById(const OUString& elementId)
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        // search the tree for an element with the given ID
        OString o1 = OUStringToOString(elementId, RTL_TEXTENCODING_UTF8);
        xmlChar *xId = (xmlChar*)o1.getStr();
        xmlNodePtr const pStart = lcl_getDocumentRootPtr(m_aDocPtr);
        if (!pStart) { return 0; }
        xmlNodePtr const pNode = lcl_search_element_by_id(pStart, xId);
        Reference< XElement > const xRet(
            static_cast< XNode* >(GetCNode(pNode).get()),
            UNO_QUERY);
        return xRet;
    }


    Reference< XNodeList > SAL_CALL
    CDocument::getElementsByTagName(OUString const& rTagname)
            throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        Reference< XNodeList > const xRet(
            new CElementList(this->GetDocumentElement(), m_Mutex, rTagname));
        return xRet;
    }

    Reference< XNodeList > SAL_CALL CDocument::getElementsByTagNameNS(
            OUString const& rNamespaceURI, OUString const& rLocalName)
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        Reference< XNodeList > const xRet(
            new CElementList(this->GetDocumentElement(), m_Mutex,
                rLocalName, &rNamespaceURI));
        return xRet;
    }

    Reference< XDOMImplementation > SAL_CALL CDocument::getImplementation()
        throw (RuntimeException)
    {
        // does not need mutex currently
        return Reference< XDOMImplementation >(CDOMImplementation::get());
    }

    // helper function to recursively import siblings
    static void lcl_ImportSiblings(
        Reference< XDocument > const& xTargetDocument,
        Reference< XNode > const& xTargetParent,
        Reference< XNode > const& xChild)
    {
        Reference< XNode > xSibling = xChild;
        while (xSibling.is())
        {
            Reference< XNode > const xTmp(
                    xTargetDocument->importNode(xSibling, sal_True));
            xTargetParent->appendChild(xTmp);
            xSibling = xSibling->getNextSibling();
        }
    }

    static Reference< XNode >
    lcl_ImportNode( Reference< XDocument > const& xDocument,
            Reference< XNode > const& xImportedNode, sal_Bool deep)
    {
        Reference< XNode > xNode;
        NodeType aNodeType = xImportedNode->getNodeType();
        switch (aNodeType)
        {
        case NodeType_ATTRIBUTE_NODE:
        {
            Reference< XAttr > const xAttr(xImportedNode, UNO_QUERY_THROW);
            Reference< XAttr > const xNew =
                xDocument->createAttribute(xAttr->getName());
            xNew->setValue(xAttr->getValue());
            xNode.set(xNew, UNO_QUERY);
            break;
        }
        case NodeType_CDATA_SECTION_NODE:
        {
            Reference< XCDATASection > const xCData(xImportedNode,
                    UNO_QUERY_THROW);
            Reference< XCDATASection > const xNewCData =
                xDocument->createCDATASection(xCData->getData());
            xNode.set(xNewCData, UNO_QUERY);
            break;
        }
        case NodeType_COMMENT_NODE:
        {
            Reference< XComment > const xComment(xImportedNode,
                    UNO_QUERY_THROW);
            Reference< XComment > const xNewComment =
                xDocument->createComment(xComment->getData());
            xNode.set(xNewComment, UNO_QUERY);
            break;
        }
        case NodeType_DOCUMENT_FRAGMENT_NODE:
        {
            Reference< XDocumentFragment > const xFrag(xImportedNode,
                    UNO_QUERY_THROW);
            Reference< XDocumentFragment > const xNewFrag =
                xDocument->createDocumentFragment();
            xNode.set(xNewFrag, UNO_QUERY);
            break;
        }
        case NodeType_ELEMENT_NODE:
        {
            Reference< XElement > const xElement(xImportedNode,
                    UNO_QUERY_THROW);
            OUString const aNsUri = xImportedNode->getNamespaceURI();
            OUString const aNsPrefix = xImportedNode->getPrefix();
            OUString aQName = xElement->getTagName();
            Reference< XElement > xNewElement;
            if (aNsUri.getLength() > 0)
            {
                if (aNsPrefix.getLength() > 0) {
                    aQName = aNsPrefix + OUString::createFromAscii(":")
                                + aQName;
                }
                xNewElement = xDocument->createElementNS(aNsUri, aQName);
            } else {
                xNewElement = xDocument->createElement(aQName);
            }

            // get attributes
            if (xElement->hasAttributes())
            {
                Reference< XNamedNodeMap > attribs = xElement->getAttributes();
                for (sal_Int32 i = 0; i < attribs->getLength(); i++)
                {
                    Reference< XAttr > const curAttr(attribs->item(i),
                            UNO_QUERY_THROW);
                    OUString const aAttrUri = curAttr->getNamespaceURI();
                    OUString const aAttrPrefix = curAttr->getPrefix();
                    OUString aAttrName = curAttr->getName();
                    OUString const sValue = curAttr->getValue();
                    if (aAttrUri.getLength() > 0)
                    {
                        if (aAttrPrefix.getLength() > 0) {
                            aAttrName = aAttrPrefix +
                                OUString::createFromAscii(":") + aAttrName;
                        }
                        xNewElement->setAttributeNS(
                                aAttrUri, aAttrName, sValue);
                    } else {
                        xNewElement->setAttribute(aAttrName, sValue);
                    }
                }
            }
            xNode.set(xNewElement, UNO_QUERY);
            break;
        }
        case NodeType_ENTITY_REFERENCE_NODE:
        {
            Reference< XEntityReference > const xRef(xImportedNode,
                    UNO_QUERY_THROW);
            Reference< XEntityReference > const xNewRef(
                xDocument->createEntityReference(xRef->getNodeName()));
            xNode.set(xNewRef, UNO_QUERY);
            break;
        }
        case NodeType_PROCESSING_INSTRUCTION_NODE:
        {
            Reference< XProcessingInstruction > const xPi(xImportedNode,
                    UNO_QUERY_THROW);
            Reference< XProcessingInstruction > const xNewPi(
                xDocument->createProcessingInstruction(
                    xPi->getTarget(), xPi->getData()));
            xNode.set(xNewPi, UNO_QUERY);
            break;
        }
        case NodeType_TEXT_NODE:
        {
            Reference< XText > const xText(xImportedNode, UNO_QUERY_THROW);
            Reference< XText > const xNewText(
                xDocument->createTextNode(xText->getData()));
            xNode.set(xNewText, UNO_QUERY);
            break;
        }
        case NodeType_ENTITY_NODE:
        case NodeType_DOCUMENT_NODE:
        case NodeType_DOCUMENT_TYPE_NODE:
        case NodeType_NOTATION_NODE:
        default:
            // can't be imported
            throw RuntimeException();

        }
        if (deep)
        {
            // get children and import them
            Reference< XNode > const xChild = xImportedNode->getFirstChild();
            if (xChild.is())
            {
                lcl_ImportSiblings(xDocument, xNode, xChild);
            }
        }

        /* DOMNodeInsertedIntoDocument
         * Fired when a node is being inserted into a document,
         * either through direct insertion of the Node or insertion of a
         * subtree in which it is contained. This event is dispatched after
         * the insertion has taken place. The target of this event is the node
         * being inserted. If the Node is being directly inserted the DOMNodeInserted
         * event will fire before the DOMNodeInsertedIntoDocument event.
         *   Bubbles: No
         *   Cancelable: No
         *   Context Info: None
         */
        if (xNode.is())
        {
            Reference< XDocumentEvent > const xDocevent(xDocument, UNO_QUERY);
            Reference< XMutationEvent > const event(xDocevent->createEvent(
                OUString::createFromAscii("DOMNodeInsertedIntoDocument")),
                UNO_QUERY_THROW);
            event->initMutationEvent(
                OUString::createFromAscii("DOMNodeInsertedIntoDocument")
                , sal_True, sal_False, Reference< XNode >(),
                OUString(), OUString(), OUString(), (AttrChangeType)0 );
            Reference< XEventTarget > const xDocET(xDocument, UNO_QUERY);
            xDocET->dispatchEvent(Reference< XEvent >(event, UNO_QUERY));
        }

        return xNode;
    }

    Reference< XNode > SAL_CALL CDocument::importNode(
            Reference< XNode > const& xImportedNode, sal_Bool deep)
        throw (RuntimeException, DOMException)
    {
        if (!xImportedNode.is()) { throw RuntimeException(); }

        // NB: this whole operation inherently accesses 2 distinct documents.
        // The imported node could even be from a different DOM implementation,
        // so this implementation cannot make any assumptions about the
        // locking strategy of the imported node.
        // So the import takes no lock on this document;
        // it only calls UNO methods on this document that temporarily
        // lock the document, and UNO methods on the imported node that
        // may temporarily lock the other document.
        // As a consequence, the import is not atomic with regard to
        // concurrent modifications of either document, but it should not
        // deadlock.
        // To ensure that no members are accessed, the implementation is in
        // static non-member functions.

        Reference< XDocument > const xDocument(this);
        // already in doc?
        if (xImportedNode->getOwnerDocument() == xDocument) {
            return xImportedNode;
        }

        Reference< XNode > const xNode(
            lcl_ImportNode(xDocument, xImportedNode, deep) );
        return xNode;
    }


    OUString SAL_CALL CDocument::getNodeName()throw (RuntimeException)
    {
        // does not need mutex currently
        return OUString::createFromAscii("#document");
    }

    OUString SAL_CALL CDocument::getNodeValue() throw (RuntimeException)
    {
        // does not need mutex currently
        return OUString();
    }

    Reference< XNode > SAL_CALL CDocument::cloneNode(sal_Bool bDeep)
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_rMutex);

        OSL_ASSERT(0 != m_aNodePtr);
        if (0 == m_aNodePtr) {
            return 0;
        }
        xmlDocPtr const pClone(xmlCopyDoc(m_aDocPtr, (bDeep) ? 1 : 0));
        if (0 == pClone) { return 0; }
        Reference< XNode > const xRet(
            static_cast<CNode*>(CDocument::CreateCDocument(pClone).get()));
        return xRet;
    }

    Reference< XEvent > SAL_CALL CDocument::createEvent(const OUString& aType) throw (RuntimeException)
    {
        // does not need mutex currently
        events::CEvent *pEvent = 0;
        if (
            aType.compareToAscii("DOMSubtreeModified")          == 0||
            aType.compareToAscii("DOMNodeInserted")             == 0||
            aType.compareToAscii("DOMNodeRemoved")              == 0||
            aType.compareToAscii("DOMNodeRemovedFromDocument")  == 0||
            aType.compareToAscii("DOMNodeInsertedIntoDocument") == 0||
            aType.compareToAscii("DOMAttrModified")             == 0||
            aType.compareToAscii("DOMCharacterDataModified")    == 0)
        {
            pEvent = new events::CMutationEvent;

        } else if (
            aType.compareToAscii("DOMFocusIn")  == 0||
            aType.compareToAscii("DOMFocusOut") == 0||
            aType.compareToAscii("DOMActivate") == 0)
        {
            pEvent = new events::CUIEvent;
        } else if (
            aType.compareToAscii("click")     == 0||
            aType.compareToAscii("mousedown") == 0||
            aType.compareToAscii("mouseup")   == 0||
            aType.compareToAscii("mouseover") == 0||
            aType.compareToAscii("mousemove") == 0||
            aType.compareToAscii("mouseout")  == 0 )
        {
            pEvent = new events::CMouseEvent;
        }
        else // generic event
        {
            pEvent = new events::CEvent;
        }
        return Reference< XEvent >(pEvent);
    }

    // ::com::sun::star::xml::sax::XSAXSerializable
    void SAL_CALL CDocument::serialize(
            const Reference< XDocumentHandler >& i_xHandler,
            const Sequence< beans::StringPair >& i_rNamespaces)
        throw (RuntimeException, SAXException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        // add new namespaces to root node
        xmlNodePtr const pRoot = lcl_getDocumentRootPtr(m_aDocPtr);
        if (0 != pRoot) {
            const beans::StringPair * pSeq = i_rNamespaces.getConstArray();
            for (const beans::StringPair *pNsDef = pSeq;
                 pNsDef < pSeq + i_rNamespaces.getLength(); ++pNsDef) {
                OString prefix = OUStringToOString(pNsDef->First,
                                    RTL_TEXTENCODING_UTF8);
                OString href   = OUStringToOString(pNsDef->Second,
                                    RTL_TEXTENCODING_UTF8);
                // this will only add the ns if it does not exist already
                xmlNewNs(pRoot, reinterpret_cast<const xmlChar*>(href.getStr()),
                         reinterpret_cast<const xmlChar*>(prefix.getStr()));
            }
            // eliminate duplicate namespace declarations
            nscleanup(pRoot->children, pRoot);
        }
        saxify(i_xHandler);
    }

    // ::com::sun::star::xml::sax::XFastSAXSerializable
    void SAL_CALL CDocument::fastSerialize( const Reference< XFastDocumentHandler >& i_xHandler,
                                            const Reference< XFastTokenHandler >& i_xTokenHandler,
                                            const Sequence< beans::StringPair >& i_rNamespaces,
                                            const Sequence< beans::Pair< rtl::OUString, sal_Int32 > >& i_rRegisterNamespaces )
        throw (SAXException, RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        // add new namespaces to root node
        xmlNodePtr const pRoot = lcl_getDocumentRootPtr(m_aDocPtr);
        if (0 != pRoot) {
            const beans::StringPair * pSeq = i_rNamespaces.getConstArray();
            for (const beans::StringPair *pNsDef = pSeq;
                 pNsDef < pSeq + i_rNamespaces.getLength(); ++pNsDef) {
                OString prefix = OUStringToOString(pNsDef->First,
                                    RTL_TEXTENCODING_UTF8);
                OString href   = OUStringToOString(pNsDef->Second,
                                    RTL_TEXTENCODING_UTF8);
                // this will only add the ns if it does not exist already
                xmlNewNs(pRoot, reinterpret_cast<const xmlChar*>(href.getStr()),
                         reinterpret_cast<const xmlChar*>(prefix.getStr()));
            }
            // eliminate duplicate namespace declarations
            nscleanup(pRoot->children, pRoot);
        }

        Context aContext(i_xHandler,
                         i_xTokenHandler);

        // register namespace ids
        const beans::Pair<OUString,sal_Int32>* pSeq = i_rRegisterNamespaces.getConstArray();
        for (const beans::Pair<OUString,sal_Int32>* pNs = pSeq;
             pNs < pSeq + i_rRegisterNamespaces.getLength(); ++pNs)
        {
            OSL_ENSURE(pNs->Second >= FastToken::NAMESPACE,
                       "CDocument::fastSerialize(): invalid NS token id");
            aContext.maNamespaceMap[ pNs->First ] = pNs->Second;
        }

        fastSaxify(aContext);
    }
}
