/*
 *    DOM Document implementation
 *
 * Copyright 2005 Mike McCormack
 * Copyright 2010-2011 Adam Martinson for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS
#define NONAMELESSUNION

#include "config.h"

#include <stdarg.h>
#include <assert.h>
#ifdef HAVE_LIBXML2
# include <libxml/parser.h>
# include <libxml/xmlerror.h>
# include <libxml/xpathInternals.h>
# include <libxml/xmlsave.h>
# include <libxml/SAX2.h>
# include <libxml/parserInternals.h>
#endif

#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "winnls.h"
#include "ole2.h"
#include "olectl.h"
#include "msxml6.h"
#include "wininet.h"
#include "winreg.h"
#include "shlwapi.h"
#include "ocidl.h"
#include "objsafe.h"

#include "wine/debug.h"
#include "wine/list.h"

#include "msxml_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(msxml);

#ifdef HAVE_LIBXML2

/* not defined in older versions */
#define XML_SAVE_FORMAT     1
#define XML_SAVE_NO_DECL    2
#define XML_SAVE_NO_EMPTY   4
#define XML_SAVE_NO_XHTML   8
#define XML_SAVE_XHTML     16
#define XML_SAVE_AS_XML    32
#define XML_SAVE_AS_HTML   64

static const WCHAR PropertySelectionLanguageW[] = {'S','e','l','e','c','t','i','o','n','L','a','n','g','u','a','g','e',0};
static const WCHAR PropertySelectionNamespacesW[] = {'S','e','l','e','c','t','i','o','n','N','a','m','e','s','p','a','c','e','s',0};
static const WCHAR PropertyProhibitDTDW[] = {'P','r','o','h','i','b','i','t','D','T','D',0};
static const WCHAR PropertyNewParserW[] = {'N','e','w','P','a','r','s','e','r',0};
static const WCHAR PropValueXPathW[] = {'X','P','a','t','h',0};
static const WCHAR PropValueXSLPatternW[] = {'X','S','L','P','a','t','t','e','r','n',0};
static const WCHAR PropertyResolveExternalsW[] = {'R','e','s','o','l','v','e','E','x','t','e','r','n','a','l','s',0};

/* Anything that passes the test_get_ownerDocument()
 * tests can go here (data shared between all instances).
 * We need to preserve this when reloading a document,
 * and also need access to it from the libxml backend. */
typedef struct {
    MSXML_VERSION version;
    VARIANT_BOOL preserving;
    IXMLDOMSchemaCollection2* schemaCache;
    struct list selectNsList;
    xmlChar const* selectNsStr;
    LONG selectNsStr_len;
    BOOL XPath;
} domdoc_properties;

typedef struct ConnectionPoint ConnectionPoint;
typedef struct domdoc domdoc;

struct ConnectionPoint
{
    IConnectionPoint IConnectionPoint_iface;
    const IID *iid;

    ConnectionPoint *next;
    IConnectionPointContainer *container;
    domdoc *doc;

    union
    {
        IUnknown *unk;
        IDispatch *disp;
        IPropertyNotifySink *propnotif;
    } *sinks;
    DWORD sinks_size;
};

typedef enum {
    EVENTID_READYSTATECHANGE = 0,
    EVENTID_DATAAVAILABLE,
    EVENTID_TRANSFORMNODE,
    EVENTID_LAST
} eventid_t;

struct domdoc
{
    xmlnode node;
    IXMLDOMDocument3          IXMLDOMDocument3_iface;
    IPersistStreamInit        IPersistStreamInit_iface;
    IObjectWithSite           IObjectWithSite_iface;
    IObjectSafety             IObjectSafety_iface;
    IConnectionPointContainer IConnectionPointContainer_iface;
    LONG ref;
    VARIANT_BOOL async;
    VARIANT_BOOL validating;
    VARIANT_BOOL resolving;
    domdoc_properties* properties;
    bsc_t *bsc;
    HRESULT error;

    /* IObjectWithSite*/
    IUnknown *site;

    /* IObjectSafety */
    DWORD safeopt;

    /* connection list */
    ConnectionPoint *cp_list;
    ConnectionPoint cp_domdocevents;
    ConnectionPoint cp_propnotif;
    ConnectionPoint cp_dispatch;

    /* events */
    IDispatch *events[EVENTID_LAST];

    IXMLDOMSchemaCollection2 *namespaces;
};

static HRESULT set_doc_event(domdoc *doc, eventid_t eid, const VARIANT *v)
{
    IDispatch *disp;

    switch (V_VT(v))
    {
    case VT_UNKNOWN:
        if (V_UNKNOWN(v))
            IUnknown_QueryInterface(V_UNKNOWN(v), &IID_IDispatch, (void**)&disp);
        else
            disp = NULL;
        break;
    case VT_DISPATCH:
        disp = V_DISPATCH(v);
        if (disp) IDispatch_AddRef(disp);
        break;
    default:
        return DISP_E_TYPEMISMATCH;
    }

    if (doc->events[eid]) IDispatch_Release(doc->events[eid]);
    doc->events[eid] = disp;

    return S_OK;
}

static inline ConnectionPoint *impl_from_IConnectionPoint(IConnectionPoint *iface)
{
    return CONTAINING_RECORD(iface, ConnectionPoint, IConnectionPoint_iface);
}

/*
  In native windows, the whole lifetime management of XMLDOMNodes is
  managed automatically using reference counts. Wine emulates that by
  maintaining a reference count to the document that is increased for
  each IXMLDOMNode pointer passed out for this document. If all these
  pointers are gone, the document is unreachable and gets freed, that
  is, all nodes in the tree of the document get freed.

  You are able to create nodes that are associated to a document (in
  fact, in msxml's XMLDOM model, all nodes are associated to a document),
  but not in the tree of that document, for example using the createFoo
  functions from IXMLDOMDocument. These nodes do not get cleaned up
  by libxml, so we have to do it ourselves.

  To catch these nodes, a list of "orphan nodes" is introduced.
  It contains pointers to all roots of node trees that are
  associated with the document without being part of the document
  tree. All nodes with parent==NULL (except for the document root nodes)
  should be in the orphan node list of their document. All orphan nodes
  get freed together with the document itself.
 */

typedef struct _xmldoc_priv {
    LONG refs;
    struct list orphans;
    domdoc_properties* properties;
} xmldoc_priv;

typedef struct _orphan_entry {
    struct list entry;
    xmlNode * node;
} orphan_entry;

typedef struct _select_ns_entry {
    struct list entry;
    xmlChar const* prefix;
    xmlChar prefix_end;
    xmlChar const* href;
    xmlChar href_end;
} select_ns_entry;

static inline xmldoc_priv * priv_from_xmlDocPtr(const xmlDocPtr doc)
{
    return doc->_private;
}

static inline domdoc_properties * properties_from_xmlDocPtr(xmlDocPtr doc)
{
    return priv_from_xmlDocPtr(doc)->properties;
}

BOOL is_xpathmode(const xmlDocPtr doc)
{
    return properties_from_xmlDocPtr(doc)->XPath;
}

void set_xpathmode(xmlDocPtr doc, BOOL xpath)
{
    properties_from_xmlDocPtr(doc)->XPath = xpath;
}

int registerNamespaces(xmlXPathContextPtr ctxt)
{
    int n = 0;
    const select_ns_entry* ns = NULL;
    const struct list* pNsList = &properties_from_xmlDocPtr(ctxt->doc)->selectNsList;

    TRACE("(%p)\n", ctxt);

    LIST_FOR_EACH_ENTRY( ns, pNsList, select_ns_entry, entry )
    {
        xmlXPathRegisterNs(ctxt, ns->prefix, ns->href);
        ++n;
    }

    return n;
}

static inline void clear_selectNsList(struct list* pNsList)
{
    select_ns_entry *ns, *ns2;
    LIST_FOR_EACH_ENTRY_SAFE( ns, ns2, pNsList, select_ns_entry, entry )
    {
        heap_free( ns );
    }
    list_init(pNsList);
}

static xmldoc_priv * create_priv(void)
{
    xmldoc_priv *priv;
    priv = heap_alloc( sizeof (*priv) );

    if (priv)
    {
        priv->refs = 0;
        list_init( &priv->orphans );
        priv->properties = NULL;
    }

    return priv;
}

static domdoc_properties *create_properties(MSXML_VERSION version)
{
    domdoc_properties *properties = heap_alloc(sizeof(domdoc_properties));

    list_init(&properties->selectNsList);
    properties->preserving = VARIANT_FALSE;
    properties->schemaCache = NULL;
    properties->selectNsStr = heap_alloc_zero(sizeof(xmlChar));
    properties->selectNsStr_len = 0;

    /* properties that are dependent on object versions */
    properties->version = version;
    properties->XPath = (version == MSXML4 || version == MSXML6);

    return properties;
}

static domdoc_properties* copy_properties(domdoc_properties const* properties)
{
    domdoc_properties* pcopy = heap_alloc(sizeof(domdoc_properties));
    select_ns_entry const* ns = NULL;
    select_ns_entry* new_ns = NULL;
    int len = (properties->selectNsStr_len+1)*sizeof(xmlChar);
    ptrdiff_t offset;

    if (pcopy)
    {
        pcopy->version = properties->version;
        pcopy->preserving = properties->preserving;
        pcopy->schemaCache = properties->schemaCache;
        if (pcopy->schemaCache)
            IXMLDOMSchemaCollection2_AddRef(pcopy->schemaCache);
        pcopy->XPath = properties->XPath;
        pcopy->selectNsStr_len = properties->selectNsStr_len;
        list_init( &pcopy->selectNsList );
        pcopy->selectNsStr = heap_alloc(len);
        memcpy((xmlChar*)pcopy->selectNsStr, properties->selectNsStr, len);
        offset = pcopy->selectNsStr - properties->selectNsStr;

        LIST_FOR_EACH_ENTRY( ns, (&properties->selectNsList), select_ns_entry, entry )
        {
            new_ns = heap_alloc(sizeof(select_ns_entry));
            memcpy(new_ns, ns, sizeof(select_ns_entry));
            new_ns->href += offset;
            new_ns->prefix += offset;
            list_add_tail(&pcopy->selectNsList, &new_ns->entry);
        }

    }

    return pcopy;
}

static void free_properties(domdoc_properties* properties)
{
    if (properties)
    {
        if (properties->schemaCache)
            IXMLDOMSchemaCollection2_Release(properties->schemaCache);
        clear_selectNsList(&properties->selectNsList);
        heap_free((xmlChar*)properties->selectNsStr);
        heap_free(properties);
    }
}

static void release_namespaces(domdoc *This)
{
    if (This->namespaces)
    {
        IXMLDOMSchemaCollection2_Release(This->namespaces);
        This->namespaces = NULL;
    }
}

/* links a "<?xml" node as a first child */
void xmldoc_link_xmldecl(xmlDocPtr doc, xmlNodePtr node)
{
    assert(doc != NULL);
    if (doc->standalone != -1) xmlAddPrevSibling( doc->children, node );
}

/* unlinks a first "<?xml" child if it was created */
xmlNodePtr xmldoc_unlink_xmldecl(xmlDocPtr doc)
{
    static const xmlChar xmlA[] = "xml";
    xmlNodePtr node, first_child;

    assert(doc != NULL);

    /* xml declaration node could be created automatically after parsing or added
       to a tree later */
    first_child = doc->children;
    if (first_child && first_child->type == XML_PI_NODE && xmlStrEqual(first_child->name, xmlA))
    {
        node = first_child;
        xmlUnlinkNode( node );
    }
    else
        node = NULL;

    return node;
}

BOOL is_preserving_whitespace(xmlNodePtr node)
{
    domdoc_properties* properties = NULL;
    /* during parsing the xmlDoc._private stuff is not there */
    if (priv_from_xmlDocPtr(node->doc))
        properties = properties_from_xmlDocPtr(node->doc);
    return ((properties && properties->preserving == VARIANT_TRUE) ||
            xmlNodeGetSpacePreserve(node) == 1);
}

static inline BOOL strn_isspace(xmlChar const* str, int len)
{
    for (; str && len > 0 && *str; ++str, --len)
        if (!isspace(*str))
            break;

    return len == 0;
}

static void sax_characters(void *ctx, const xmlChar *ch, int len)
{
    xmlParserCtxtPtr ctxt;
    const domdoc *This;

    ctxt = (xmlParserCtxtPtr) ctx;
    This = (const domdoc*) ctxt->_private;

    if (ctxt->node)
    {
        xmlChar cur = *(ctxt->input->cur);

        /* Characters are reported with multiple calls, for example each charref is reported with a separate
           call and then parser appends it to a single text node or creates a new node if not created.
           It's not possible to tell if it's ignorable data or not just looking at data itself cause it could be
           space chars that separate charrefs or similar case. We only need to skip leading and trailing spaces,
           or whole node if it has nothing but space chars, so to detect leading space node->last is checked that
           contains text node pointer if already created, trailing spaces are detected directly looking at parser input
           for next '<' opening bracket - similar logic is used by libxml2 itself. Basically 'cur' == '<' means the last
           chunk of char data, in case it's not the last chunk we check for previously added node type and if it's not
           a text node it's safe to ignore.

           Note that during domdoc_loadXML() the xmlDocPtr->_private data is not available. */

        if (!This->properties->preserving &&
            !is_preserving_whitespace(ctxt->node) &&
            strn_isspace(ch, len) &&
            (!ctxt->node->last ||
            ((ctxt->node->last && (cur == '<' || ctxt->node->last->type != XML_TEXT_NODE))
           )))
            return;
    }

    xmlSAX2Characters(ctxt, ch, len);
}

static void LIBXML2_LOG_CALLBACK sax_error(void* ctx, char const* msg, ...)
{
    va_list ap;
    va_start(ap, msg);
    LIBXML2_CALLBACK_ERR(doparse, msg, ap);
    va_end(ap);
}

static void LIBXML2_LOG_CALLBACK sax_warning(void* ctx, char const* msg, ...)
{
    va_list ap;
    va_start(ap, msg);
    LIBXML2_CALLBACK_WARN(doparse, msg, ap);
    va_end(ap);
}

static void sax_serror(void* ctx, xmlErrorPtr err)
{
    LIBXML2_CALLBACK_SERROR(doparse, err);
}

static xmlDocPtr doparse(domdoc* This, char const* ptr, int len, xmlCharEncoding encoding)
{
    xmlDocPtr doc = NULL;
    xmlParserCtxtPtr pctx;
    static xmlSAXHandler sax_handler = {
        xmlSAX2InternalSubset,          /* internalSubset */
        xmlSAX2IsStandalone,            /* isStandalone */
        xmlSAX2HasInternalSubset,       /* hasInternalSubset */
        xmlSAX2HasExternalSubset,       /* hasExternalSubset */
        xmlSAX2ResolveEntity,           /* resolveEntity */
        xmlSAX2GetEntity,               /* getEntity */
        xmlSAX2EntityDecl,              /* entityDecl */
        xmlSAX2NotationDecl,            /* notationDecl */
        xmlSAX2AttributeDecl,           /* attributeDecl */
        xmlSAX2ElementDecl,             /* elementDecl */
        xmlSAX2UnparsedEntityDecl,      /* unparsedEntityDecl */
        xmlSAX2SetDocumentLocator,      /* setDocumentLocator */
        xmlSAX2StartDocument,           /* startDocument */
        xmlSAX2EndDocument,             /* endDocument */
        xmlSAX2StartElement,            /* startElement */
        xmlSAX2EndElement,              /* endElement */
        xmlSAX2Reference,               /* reference */
        sax_characters,                 /* characters */
        sax_characters,                 /* ignorableWhitespace */
        xmlSAX2ProcessingInstruction,   /* processingInstruction */
        xmlSAX2Comment,                 /* comment */
        sax_warning,                    /* warning */
        sax_error,                      /* error */
        sax_error,                      /* fatalError */
        xmlSAX2GetParameterEntity,      /* getParameterEntity */
        xmlSAX2CDataBlock,              /* cdataBlock */
        xmlSAX2ExternalSubset,          /* externalSubset */
        0,                              /* initialized */
        NULL,                           /* _private */
        xmlSAX2StartElementNs,          /* startElementNs */
        xmlSAX2EndElementNs,            /* endElementNs */
        sax_serror                      /* serror */
    };

    pctx = xmlCreateMemoryParserCtxt(ptr, len);
    if (!pctx)
    {
        ERR("Failed to create parser context\n");
        return NULL;
    }

    if (pctx->sax) xmlFree(pctx->sax);
    pctx->sax = &sax_handler;
    pctx->_private = This;
    pctx->recovery = 0;

    if (encoding != XML_CHAR_ENCODING_NONE)
        xmlSwitchEncoding(pctx, encoding);

    xmlParseDocument(pctx);

    if (pctx->wellFormed)
    {
        doc = pctx->myDoc;
    }
    else
    {
       xmlFreeDoc(pctx->myDoc);
       pctx->myDoc = NULL;
    }
    pctx->sax = NULL;
    xmlFreeParserCtxt(pctx);

    /* TODO: put this in one of the SAX callbacks */
    /* create first child as a <?xml...?> */
    if (doc && doc->standalone != -1)
    {
        xmlNodePtr node;
        char buff[30];
        xmlChar *xmlbuff = (xmlChar*)buff;

        node = xmlNewDocPI( doc, (xmlChar*)"xml", NULL );

        /* version attribute can't be omitted */
        sprintf(buff, "version=\"%s\"", doc->version ? (char*)doc->version : "1.0");
        xmlNodeAddContent( node, xmlbuff );

        if (doc->encoding)
        {
            sprintf(buff, " encoding=\"%s\"", doc->encoding);
            xmlNodeAddContent( node, xmlbuff );
        }

        if (doc->standalone != -2)
        {
            sprintf(buff, " standalone=\"%s\"", doc->standalone == 0 ? "no" : "yes");
            xmlNodeAddContent( node, xmlbuff );
        }

        xmldoc_link_xmldecl( doc, node );
    }

    return doc;
}

void xmldoc_init(xmlDocPtr doc, MSXML_VERSION version)
{
    doc->_private = create_priv();
    priv_from_xmlDocPtr(doc)->properties = create_properties(version);
}

LONG xmldoc_add_refs(xmlDocPtr doc, LONG refs)
{
    LONG ref = InterlockedExchangeAdd(&priv_from_xmlDocPtr(doc)->refs, refs) + refs;
    TRACE("(%p)->(%d)\n", doc, ref);
    return ref;
}

LONG xmldoc_add_ref(xmlDocPtr doc)
{
    return xmldoc_add_refs(doc, 1);
}

LONG xmldoc_release_refs(xmlDocPtr doc, LONG refs)
{
    xmldoc_priv *priv = priv_from_xmlDocPtr(doc);
    LONG ref = InterlockedExchangeAdd(&priv->refs, -refs) - refs;
    TRACE("(%p)->(%d)\n", doc, ref);

    if (ref < 0)
        WARN("negative refcount, expect troubles\n");

    if (ref == 0)
    {
        orphan_entry *orphan, *orphan2;
        TRACE("freeing docptr %p\n", doc);

        LIST_FOR_EACH_ENTRY_SAFE( orphan, orphan2, &priv->orphans, orphan_entry, entry )
        {
            xmlFreeNode( orphan->node );
            heap_free( orphan );
        }
        free_properties(priv->properties);
        heap_free(doc->_private);

        xmlFreeDoc(doc);
    }

    return ref;
}

LONG xmldoc_release(xmlDocPtr doc)
{
    return xmldoc_release_refs(doc, 1);
}

HRESULT xmldoc_add_orphan(xmlDocPtr doc, xmlNodePtr node)
{
    xmldoc_priv *priv = priv_from_xmlDocPtr(doc);
    orphan_entry *entry;

    entry = heap_alloc( sizeof (*entry) );
    if(!entry)
        return E_OUTOFMEMORY;

    entry->node = node;
    list_add_head( &priv->orphans, &entry->entry );
    return S_OK;
}

HRESULT xmldoc_remove_orphan(xmlDocPtr doc, xmlNodePtr node)
{
    xmldoc_priv *priv = priv_from_xmlDocPtr(doc);
    orphan_entry *entry, *entry2;

    LIST_FOR_EACH_ENTRY_SAFE( entry, entry2, &priv->orphans, orphan_entry, entry )
    {
        if( entry->node == node )
        {
            list_remove( &entry->entry );
            heap_free( entry );
            return S_OK;
        }
    }

    return S_FALSE;
}

static inline xmlDocPtr get_doc( domdoc *This )
{
    return This->node.node->doc;
}

static HRESULT attach_xmldoc(domdoc *This, xmlDocPtr xml )
{
    release_namespaces(This);

    if(This->node.node)
    {
        priv_from_xmlDocPtr(get_doc(This))->properties = NULL;
        if (xmldoc_release(get_doc(This)) != 0)
            priv_from_xmlDocPtr(get_doc(This))->properties =
                copy_properties(This->properties);
    }

    This->node.node = (xmlNodePtr) xml;

    if(This->node.node)
    {
        xmldoc_add_ref(get_doc(This));
        priv_from_xmlDocPtr(get_doc(This))->properties = This->properties;
    }

    return S_OK;
}

static inline domdoc *impl_from_IXMLDOMDocument3( IXMLDOMDocument3 *iface )
{
    return CONTAINING_RECORD(iface, domdoc, IXMLDOMDocument3_iface);
}

static inline domdoc *impl_from_IPersistStreamInit(IPersistStreamInit *iface)
{
    return CONTAINING_RECORD(iface, domdoc, IPersistStreamInit_iface);
}

static inline domdoc *impl_from_IObjectWithSite(IObjectWithSite *iface)
{
    return CONTAINING_RECORD(iface, domdoc, IObjectWithSite_iface);
}

static inline domdoc *impl_from_IObjectSafety(IObjectSafety *iface)
{
    return CONTAINING_RECORD(iface, domdoc, IObjectSafety_iface);
}

static inline domdoc *impl_from_IConnectionPointContainer(IConnectionPointContainer *iface)
{
    return CONTAINING_RECORD(iface, domdoc, IConnectionPointContainer_iface);
}

/************************************************************************
 * domdoc implementation of IPersistStream.
 */
static HRESULT WINAPI PersistStreamInit_QueryInterface(
    IPersistStreamInit *iface, REFIID riid, void **ppvObj)
{
    domdoc* This = impl_from_IPersistStreamInit(iface);
    return IXMLDOMDocument3_QueryInterface(&This->IXMLDOMDocument3_iface, riid, ppvObj);
}

static ULONG WINAPI PersistStreamInit_AddRef(
    IPersistStreamInit *iface)
{
    domdoc* This = impl_from_IPersistStreamInit(iface);
    return IXMLDOMDocument3_AddRef(&This->IXMLDOMDocument3_iface);
}

static ULONG WINAPI PersistStreamInit_Release(
    IPersistStreamInit *iface)
{
    domdoc* This = impl_from_IPersistStreamInit(iface);
    return IXMLDOMDocument3_Release(&This->IXMLDOMDocument3_iface);
}

static HRESULT WINAPI PersistStreamInit_GetClassID(
    IPersistStreamInit *iface, CLSID *classid)
{
    domdoc* This = impl_from_IPersistStreamInit(iface);
    TRACE("(%p)->(%p)\n", This, classid);

    if(!classid)
        return E_POINTER;

    *classid = *DOMDocument_version(This->properties->version);

    return S_OK;
}

static HRESULT WINAPI PersistStreamInit_IsDirty(
    IPersistStreamInit *iface)
{
    domdoc *This = impl_from_IPersistStreamInit(iface);
    FIXME("(%p): stub!\n", This);
    return S_FALSE;
}

static HRESULT domdoc_load_from_stream(domdoc *doc, ISequentialStream *stream)
{
    DWORD read, written, len;
    xmlDocPtr xmldoc = NULL;
    IStream *hstream;
    HGLOBAL hglobal;
    BYTE buf[4096];
    HRESULT hr;
    char *ptr;

    hstream = NULL;
    hr = CreateStreamOnHGlobal(NULL, TRUE, &hstream);
    if (FAILED(hr))
        return hr;

    do
    {
        ISequentialStream_Read(stream, buf, sizeof(buf), &read);
        hr = IStream_Write(hstream, buf, read, &written);
    } while(SUCCEEDED(hr) && written != 0 && read != 0);

    if (FAILED(hr))
    {
        ERR("failed to copy stream 0x%08x\n", hr);
        IStream_Release(hstream);
        return hr;
    }

    hr = GetHGlobalFromStream(hstream, &hglobal);
    if (FAILED(hr))
        return hr;

    len = GlobalSize(hglobal);
    ptr = GlobalLock(hglobal);
    if (len)
        xmldoc = doparse(doc, ptr, len, XML_CHAR_ENCODING_NONE);
    GlobalUnlock(hglobal);

    if (!xmldoc)
    {
        ERR("Failed to parse xml\n");
        return E_FAIL;
    }

    xmldoc->_private = create_priv();

    return attach_xmldoc(doc, xmldoc);
}

static HRESULT WINAPI PersistStreamInit_Load(IPersistStreamInit *iface, IStream *stream)
{
    domdoc *This = impl_from_IPersistStreamInit(iface);

    TRACE("(%p)->(%p)\n", This, stream);

    if (!stream)
        return E_INVALIDARG;

    return domdoc_load_from_stream(This, (ISequentialStream*)stream);
}

static HRESULT WINAPI PersistStreamInit_Save(
    IPersistStreamInit *iface, IStream *stream, BOOL clr_dirty)
{
    domdoc *This = impl_from_IPersistStreamInit(iface);
    BSTR xmlString;
    HRESULT hr;

    TRACE("(%p)->(%p %d)\n", This, stream, clr_dirty);

    hr = IXMLDOMDocument3_get_xml(&This->IXMLDOMDocument3_iface, &xmlString);
    if(hr == S_OK)
    {
        DWORD len = SysStringLen(xmlString) * sizeof(WCHAR);

        hr = IStream_Write( stream, xmlString, len, NULL );
        SysFreeString(xmlString);
    }

    TRACE("ret 0x%08x\n", hr);

    return hr;
}

static HRESULT WINAPI PersistStreamInit_GetSizeMax(
    IPersistStreamInit *iface, ULARGE_INTEGER *pcbSize)
{
    domdoc *This = impl_from_IPersistStreamInit(iface);
    TRACE("(%p)->(%p)\n", This, pcbSize);
    return E_NOTIMPL;
}

static HRESULT WINAPI PersistStreamInit_InitNew(
    IPersistStreamInit *iface)
{
    domdoc *This = impl_from_IPersistStreamInit(iface);
    TRACE("(%p)\n", This);
    return S_OK;
}

static const IPersistStreamInitVtbl xmldoc_IPersistStreamInit_VTable =
{
    PersistStreamInit_QueryInterface,
    PersistStreamInit_AddRef,
    PersistStreamInit_Release,
    PersistStreamInit_GetClassID,
    PersistStreamInit_IsDirty,
    PersistStreamInit_Load,
    PersistStreamInit_Save,
    PersistStreamInit_GetSizeMax,
    PersistStreamInit_InitNew
};

/* IXMLDOMDocument3 interface */

static const tid_t domdoc_se_tids[] = {
    IXMLDOMNode_tid,
    IXMLDOMDocument_tid,
    IXMLDOMDocument2_tid,
    IXMLDOMDocument3_tid,
    NULL_tid
};

static HRESULT WINAPI domdoc_QueryInterface( IXMLDOMDocument3 *iface, REFIID riid, void** ppvObject )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%s %p)\n", This, debugstr_guid( riid ), ppvObject );

    *ppvObject = NULL;

    if ( IsEqualGUID( riid, &IID_IUnknown ) ||
         IsEqualGUID( riid, &IID_IDispatch ) ||
         IsEqualGUID( riid, &IID_IXMLDOMNode ) ||
         IsEqualGUID( riid, &IID_IXMLDOMDocument ) ||
         IsEqualGUID( riid, &IID_IXMLDOMDocument2 )||
         IsEqualGUID( riid, &IID_IXMLDOMDocument3 ))
    {
        *ppvObject = iface;
    }
    else if (IsEqualGUID(&IID_IPersistStream, riid) ||
             IsEqualGUID(&IID_IPersistStreamInit, riid))
    {
        *ppvObject = &This->IPersistStreamInit_iface;
    }
    else if (IsEqualGUID(&IID_IObjectWithSite, riid))
    {
        *ppvObject = &This->IObjectWithSite_iface;
    }
    else if (IsEqualGUID(&IID_IObjectSafety, riid))
    {
        *ppvObject = &This->IObjectSafety_iface;
    }
    else if( IsEqualGUID( riid, &IID_ISupportErrorInfo ))
    {
        return node_create_supporterrorinfo(domdoc_se_tids, ppvObject);
    }
    else if(node_query_interface(&This->node, riid, ppvObject))
    {
        return *ppvObject ? S_OK : E_NOINTERFACE;
    }
    else if (IsEqualGUID( riid, &IID_IConnectionPointContainer ))
    {
        *ppvObject = &This->IConnectionPointContainer_iface;
    }
    else
    {
        TRACE("interface %s not implemented\n", debugstr_guid(riid));
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown*)*ppvObject);

    return S_OK;
}

static ULONG WINAPI domdoc_AddRef( IXMLDOMDocument3 *iface )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    ULONG ref = InterlockedIncrement( &This->ref );
    TRACE("(%p)->(%d)\n", This, ref );
    return ref;
}

static ULONG WINAPI domdoc_Release( IXMLDOMDocument3 *iface )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    LONG ref = InterlockedDecrement( &This->ref );

    TRACE("(%p)->(%d)\n", This, ref );

    if ( ref == 0 )
    {
        int eid;

        if(This->bsc)
            detach_bsc(This->bsc);

        if (This->site)
            IUnknown_Release( This->site );
        destroy_xmlnode(&This->node);

        for (eid = 0; eid < EVENTID_LAST; eid++)
            if (This->events[eid]) IDispatch_Release(This->events[eid]);

        release_namespaces(This);
        heap_free(This);
    }

    return ref;
}

static HRESULT WINAPI domdoc_GetTypeInfoCount( IXMLDOMDocument3 *iface, UINT* pctinfo )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    return IDispatchEx_GetTypeInfoCount(&This->node.dispex.IDispatchEx_iface, pctinfo);
}

static HRESULT WINAPI domdoc_GetTypeInfo(
    IXMLDOMDocument3 *iface,
    UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    return IDispatchEx_GetTypeInfo(&This->node.dispex.IDispatchEx_iface, iTInfo, lcid, ppTInfo);
}

static HRESULT WINAPI domdoc_GetIDsOfNames(
    IXMLDOMDocument3 *iface,
    REFIID riid,
    LPOLESTR* rgszNames,
    UINT cNames,
    LCID lcid,
    DISPID* rgDispId)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    return IDispatchEx_GetIDsOfNames(&This->node.dispex.IDispatchEx_iface,
        riid, rgszNames, cNames, lcid, rgDispId);
}

static HRESULT WINAPI domdoc_Invoke(
    IXMLDOMDocument3 *iface,
    DISPID dispIdMember,
    REFIID riid,
    LCID lcid,
    WORD wFlags,
    DISPPARAMS* pDispParams,
    VARIANT* pVarResult,
    EXCEPINFO* pExcepInfo,
    UINT* puArgErr)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    return IDispatchEx_Invoke(&This->node.dispex.IDispatchEx_iface,
        dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
}

static HRESULT WINAPI domdoc_get_nodeName(
    IXMLDOMDocument3 *iface,
    BSTR* name )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    static const WCHAR documentW[] = {'#','d','o','c','u','m','e','n','t',0};

    TRACE("(%p)->(%p)\n", This, name);

    return return_bstr(documentW, name);
}


static HRESULT WINAPI domdoc_get_nodeValue(
    IXMLDOMDocument3 *iface,
    VARIANT* value )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, value);

    if(!value)
        return E_INVALIDARG;

    V_VT(value) = VT_NULL;
    V_BSTR(value) = NULL; /* tests show that we should do this */
    return S_FALSE;
}


static HRESULT WINAPI domdoc_put_nodeValue(
    IXMLDOMDocument3 *iface,
    VARIANT value)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%s)\n", This, debugstr_variant(&value));
    return E_FAIL;
}


static HRESULT WINAPI domdoc_get_nodeType(
    IXMLDOMDocument3 *iface,
    DOMNodeType* type )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, type);

    *type = NODE_DOCUMENT;
    return S_OK;
}


static HRESULT WINAPI domdoc_get_parentNode(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode** parent )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, parent);

    return node_get_parent(&This->node, parent);
}


static HRESULT WINAPI domdoc_get_childNodes(
    IXMLDOMDocument3 *iface,
    IXMLDOMNodeList** childList )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, childList);

    return node_get_child_nodes(&This->node, childList);
}


static HRESULT WINAPI domdoc_get_firstChild(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode** firstChild )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, firstChild);

    return node_get_first_child(&This->node, firstChild);
}


static HRESULT WINAPI domdoc_get_lastChild(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode** lastChild )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, lastChild);

    return node_get_last_child(&This->node, lastChild);
}


static HRESULT WINAPI domdoc_get_previousSibling(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode** previousSibling )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, previousSibling);

    return return_null_node(previousSibling);
}


static HRESULT WINAPI domdoc_get_nextSibling(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode** nextSibling )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, nextSibling);

    return return_null_node(nextSibling);
}


static HRESULT WINAPI domdoc_get_attributes(
    IXMLDOMDocument3 *iface,
    IXMLDOMNamedNodeMap** attributeMap )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p)\n", This, attributeMap);

    return return_null_ptr((void**)attributeMap);
}


static HRESULT WINAPI domdoc_insertBefore(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode* newChild,
    VARIANT refChild,
    IXMLDOMNode** outNewChild )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    DOMNodeType type;
    HRESULT hr;

    TRACE("(%p)->(%p %s %p)\n", This, newChild, debugstr_variant(&refChild), outNewChild);

    hr = IXMLDOMNode_get_nodeType(newChild, &type);
    if (hr != S_OK) return hr;

    TRACE("new node type %d\n", type);
    switch (type)
    {
        case NODE_ATTRIBUTE:
        case NODE_DOCUMENT:
        case NODE_CDATA_SECTION:
            if (outNewChild) *outNewChild = NULL;
            return E_FAIL;
        default:
            return node_insert_before(&This->node, newChild, &refChild, outNewChild);
    }
}

static HRESULT WINAPI domdoc_replaceChild(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode* newChild,
    IXMLDOMNode* oldChild,
    IXMLDOMNode** outOldChild)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p %p %p)\n", This, newChild, oldChild, outOldChild);

    return node_replace_child(&This->node, newChild, oldChild, outOldChild);
}


static HRESULT WINAPI domdoc_removeChild(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode  *child,
    IXMLDOMNode **oldChild)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p %p)\n", This, child, oldChild);
    return node_remove_child(&This->node, child, oldChild);
}


static HRESULT WINAPI domdoc_appendChild(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode  *child,
    IXMLDOMNode **outChild)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p %p)\n", This, child, outChild);
    return node_append_child(&This->node, child, outChild);
}


static HRESULT WINAPI domdoc_hasChildNodes(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL *ret)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, ret);
    return node_has_childnodes(&This->node, ret);
}


static HRESULT WINAPI domdoc_get_ownerDocument(
    IXMLDOMDocument3 *iface,
    IXMLDOMDocument **doc)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, doc);
    return node_get_owner_doc(&This->node, doc);
}


static HRESULT WINAPI domdoc_cloneNode(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL deep,
    IXMLDOMNode** outNode)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%d %p)\n", This, deep, outNode);
    return node_clone( &This->node, deep, outNode );
}


static HRESULT WINAPI domdoc_get_nodeTypeString(
    IXMLDOMDocument3 *iface,
    BSTR *p)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    static const WCHAR documentW[] = {'d','o','c','u','m','e','n','t',0};

    TRACE("(%p)->(%p)\n", This, p);

    return return_bstr(documentW, p);
}


static HRESULT WINAPI domdoc_get_text(
    IXMLDOMDocument3 *iface,
    BSTR *p)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, p);
    return node_get_text(&This->node, p);
}


static HRESULT WINAPI domdoc_put_text(
    IXMLDOMDocument3 *iface,
    BSTR text )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%s)\n", This, debugstr_w(text));
    return E_FAIL;
}


static HRESULT WINAPI domdoc_get_specified(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL* isSpecified )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    FIXME("(%p)->(%p) stub!\n", This, isSpecified);
    *isSpecified = VARIANT_TRUE;
    return S_OK;
}


static HRESULT WINAPI domdoc_get_definition(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode** definitionNode )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    FIXME("(%p)->(%p)\n", This, definitionNode);
    return E_NOTIMPL;
}


static HRESULT WINAPI domdoc_get_nodeTypedValue(
    IXMLDOMDocument3 *iface,
    VARIANT* v )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, v);
    return return_null_var(v);
}

static HRESULT WINAPI domdoc_put_nodeTypedValue(
    IXMLDOMDocument3 *iface,
    VARIANT typedValue )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    FIXME("(%p)->(%s)\n", This, debugstr_variant(&typedValue));
    return E_NOTIMPL;
}


static HRESULT WINAPI domdoc_get_dataType(
    IXMLDOMDocument3 *iface,
    VARIANT* typename )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, typename);
    return return_null_var( typename );
}


static HRESULT WINAPI domdoc_put_dataType(
    IXMLDOMDocument3 *iface,
    BSTR dataTypeName )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    FIXME("(%p)->(%s)\n", This, debugstr_w(dataTypeName));

    if(!dataTypeName)
        return E_INVALIDARG;

    return E_FAIL;
}

static int XMLCALL domdoc_get_xml_writecallback(void *ctx, const char *data, int len)
{
    return xmlBufferAdd((xmlBufferPtr)ctx, (xmlChar*)data, len) == 0 ? len : 0;
}

static HRESULT WINAPI domdoc_get_xml(
    IXMLDOMDocument3 *iface,
    BSTR* p)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    xmlSaveCtxtPtr ctxt;
    xmlBufferPtr buf;
    int options;
    long ret;

    TRACE("(%p)->(%p)\n", This, p);

    if(!p)
        return E_INVALIDARG;

    *p = NULL;

    buf = xmlBufferCreate();
    if(!buf)
        return E_OUTOFMEMORY;

    options = XML_SAVE_FORMAT | XML_SAVE_NO_DECL;
    ctxt = xmlSaveToIO(domdoc_get_xml_writecallback, NULL, buf, "UTF-8", options);

    if(!ctxt)
    {
        xmlBufferFree(buf);
        return E_OUTOFMEMORY;
    }

    ret = xmlSaveDoc(ctxt, get_doc(This));
    /* flushes on close */
    xmlSaveClose(ctxt);

    TRACE("%ld, len=%d\n", ret, xmlBufferLength(buf));
    if(ret != -1 && xmlBufferLength(buf) > 0)
    {
        BSTR content;

        content = bstr_from_xmlChar(xmlBufferContent(buf));
        content = EnsureCorrectEOL(content);

        *p = content;
    }
    else
    {
        *p = SysAllocStringLen(NULL, 0);
    }

    xmlBufferFree(buf);

    return *p ? S_OK : E_OUTOFMEMORY;
}


static HRESULT WINAPI domdoc_transformNode(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode *node,
    BSTR *p)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p %p)\n", This, node, p);
    return node_transform_node(&This->node, node, p);
}


static HRESULT WINAPI domdoc_selectNodes(
    IXMLDOMDocument3 *iface,
    BSTR p,
    IXMLDOMNodeList **outList)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%s %p)\n", This, debugstr_w(p), outList);
    return node_select_nodes(&This->node, p, outList);
}


static HRESULT WINAPI domdoc_selectSingleNode(
    IXMLDOMDocument3 *iface,
    BSTR p,
    IXMLDOMNode **outNode)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%s %p)\n", This, debugstr_w(p), outNode);
    return node_select_singlenode(&This->node, p, outNode);
}


static HRESULT WINAPI domdoc_get_parsed(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL* isParsed )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    FIXME("(%p)->(%p) stub!\n", This, isParsed);
    *isParsed = VARIANT_TRUE;
    return S_OK;
}

static HRESULT WINAPI domdoc_get_namespaceURI(
    IXMLDOMDocument3 *iface,
    BSTR* namespaceURI )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, namespaceURI);
    return return_null_bstr( namespaceURI );
}

static HRESULT WINAPI domdoc_get_prefix(
    IXMLDOMDocument3 *iface,
    BSTR* prefix )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, prefix);
    return return_null_bstr( prefix );
}


static HRESULT WINAPI domdoc_get_baseName(
    IXMLDOMDocument3 *iface,
    BSTR* name )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, name);
    return return_null_bstr( name );
}


static HRESULT WINAPI domdoc_transformNodeToObject(
    IXMLDOMDocument3 *iface,
    IXMLDOMNode* stylesheet,
    VARIANT outputObject)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    FIXME("(%p)->(%p %s)\n", This, stylesheet, debugstr_variant(&outputObject));
    return E_NOTIMPL;
}


static HRESULT WINAPI domdoc_get_doctype(
    IXMLDOMDocument3 *iface,
    IXMLDOMDocumentType** doctype )
{
    domdoc *This = impl_from_IXMLDOMDocument3(iface);
    IXMLDOMNode *node;
    xmlDtdPtr dtd;
    HRESULT hr;

    TRACE("(%p)->(%p)\n", This, doctype);

    if (!doctype) return E_INVALIDARG;

    *doctype = NULL;

    dtd = xmlGetIntSubset(get_doc(This));
    if (!dtd) return S_FALSE;

    node = create_node((xmlNodePtr)dtd);
    if (!node) return S_FALSE;

    hr = IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMDocumentType, (void**)doctype);
    IXMLDOMNode_Release(node);

    return hr;
}


static HRESULT WINAPI domdoc_get_implementation(
    IXMLDOMDocument3 *iface,
    IXMLDOMImplementation** impl )
{
    domdoc *This = impl_from_IXMLDOMDocument3(iface);

    TRACE("(%p)->(%p)\n", This, impl);

    if(!impl)
        return E_INVALIDARG;

    *impl = (IXMLDOMImplementation*)create_doc_Implementation();

    return S_OK;
}

static HRESULT WINAPI domdoc_get_documentElement(
    IXMLDOMDocument3 *iface,
    IXMLDOMElement** DOMElement )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *element_node;
    xmlNodePtr root;
    HRESULT hr;

    TRACE("(%p)->(%p)\n", This, DOMElement);

    if(!DOMElement)
        return E_INVALIDARG;

    *DOMElement = NULL;

    root = xmlDocGetRootElement( get_doc(This) );
    if ( !root )
        return S_FALSE;

    element_node = create_node( root );
    if(!element_node) return S_FALSE;

    hr = IXMLDOMNode_QueryInterface(element_node, &IID_IXMLDOMElement, (void**)DOMElement);
    IXMLDOMNode_Release(element_node);

    return hr;
}


static HRESULT WINAPI domdoc_put_documentElement(
    IXMLDOMDocument3 *iface,
    IXMLDOMElement* DOMElement )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *elementNode;
    xmlNodePtr oldRoot;
    xmlDocPtr old_doc;
    xmlnode *xmlNode;
    int refcount = 0;
    HRESULT hr;

    TRACE("(%p)->(%p)\n", This, DOMElement);

    hr = IXMLDOMElement_QueryInterface( DOMElement, &IID_IXMLDOMNode, (void**)&elementNode );
    if(FAILED(hr))
        return hr;

    xmlNode = get_node_obj( elementNode );
    if(!xmlNode) return E_FAIL;

    if(!xmlNode->node->parent)
        if(xmldoc_remove_orphan(xmlNode->node->doc, xmlNode->node) != S_OK)
            WARN("%p is not an orphan of %p\n", xmlNode->node->doc, xmlNode->node);

    old_doc = xmlNode->node->doc;
    if (old_doc != get_doc(This))
        refcount = xmlnode_get_inst_cnt(xmlNode);

    /* old root is still orphaned by its document, update refcount from new root */
    if (refcount) xmldoc_add_refs(get_doc(This), refcount);
    oldRoot = xmlDocSetRootElement( get_doc(This), xmlNode->node);
    if (refcount) xmldoc_release_refs(old_doc, refcount);
    IXMLDOMNode_Release( elementNode );

    if(oldRoot)
        xmldoc_add_orphan(oldRoot->doc, oldRoot);

    return S_OK;
}


static HRESULT WINAPI domdoc_createElement(
    IXMLDOMDocument3 *iface,
    BSTR tagname,
    IXMLDOMElement** element )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *node;
    VARIANT type;
    HRESULT hr;

    TRACE("(%p)->(%s %p)\n", This, debugstr_w(tagname), element);

    if (!element || !tagname) return E_INVALIDARG;

    V_VT(&type) = VT_I1;
    V_I1(&type) = NODE_ELEMENT;

    hr = IXMLDOMDocument3_createNode(iface, type, tagname, NULL, &node);
    if (hr == S_OK)
    {
        IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void**)element);
        IXMLDOMNode_Release(node);
    }

    return hr;
}


static HRESULT WINAPI domdoc_createDocumentFragment(
    IXMLDOMDocument3 *iface,
    IXMLDOMDocumentFragment** frag )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *node;
    VARIANT type;
    HRESULT hr;

    TRACE("(%p)->(%p)\n", This, frag);

    if (!frag) return E_INVALIDARG;

    *frag = NULL;

    V_VT(&type) = VT_I1;
    V_I1(&type) = NODE_DOCUMENT_FRAGMENT;

    hr = IXMLDOMDocument3_createNode(iface, type, NULL, NULL, &node);
    if (hr == S_OK)
    {
        IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMDocumentFragment, (void**)frag);
        IXMLDOMNode_Release(node);
    }

    return hr;
}


static HRESULT WINAPI domdoc_createTextNode(
    IXMLDOMDocument3 *iface,
    BSTR data,
    IXMLDOMText** text )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *node;
    VARIANT type;
    HRESULT hr;

    TRACE("(%p)->(%s %p)\n", This, debugstr_w(data), text);

    if (!text) return E_INVALIDARG;

    *text = NULL;

    V_VT(&type) = VT_I1;
    V_I1(&type) = NODE_TEXT;

    hr = IXMLDOMDocument3_createNode(iface, type, NULL, NULL, &node);
    if (hr == S_OK)
    {
        IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMText, (void**)text);
        IXMLDOMNode_Release(node);
        hr = IXMLDOMText_put_data(*text, data);
    }

    return hr;
}


static HRESULT WINAPI domdoc_createComment(
    IXMLDOMDocument3 *iface,
    BSTR data,
    IXMLDOMComment** comment )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    VARIANT type;
    HRESULT hr;
    IXMLDOMNode *node;

    TRACE("(%p)->(%s %p)\n", This, debugstr_w(data), comment);

    if (!comment) return E_INVALIDARG;

    *comment = NULL;

    V_VT(&type) = VT_I1;
    V_I1(&type) = NODE_COMMENT;

    hr = IXMLDOMDocument3_createNode(iface, type, NULL, NULL, &node);
    if (hr == S_OK)
    {
        IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMComment, (void**)comment);
        IXMLDOMNode_Release(node);
        hr = IXMLDOMComment_put_data(*comment, data);
    }

    return hr;
}


static HRESULT WINAPI domdoc_createCDATASection(
    IXMLDOMDocument3 *iface,
    BSTR data,
    IXMLDOMCDATASection** cdata )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *node;
    VARIANT type;
    HRESULT hr;

    TRACE("(%p)->(%s %p)\n", This, debugstr_w(data), cdata);

    if (!cdata) return E_INVALIDARG;

    *cdata = NULL;

    V_VT(&type) = VT_I1;
    V_I1(&type) = NODE_CDATA_SECTION;

    hr = IXMLDOMDocument3_createNode(iface, type, NULL, NULL, &node);
    if (hr == S_OK)
    {
        IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMCDATASection, (void**)cdata);
        IXMLDOMNode_Release(node);
        hr = IXMLDOMCDATASection_put_data(*cdata, data);
    }

    return hr;
}


static HRESULT WINAPI domdoc_createProcessingInstruction(
    IXMLDOMDocument3 *iface,
    BSTR target,
    BSTR data,
    IXMLDOMProcessingInstruction** pi )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *node;
    VARIANT type;
    HRESULT hr;

    TRACE("(%p)->(%s %s %p)\n", This, debugstr_w(target), debugstr_w(data), pi);

    if (!pi) return E_INVALIDARG;

    *pi = NULL;

    V_VT(&type) = VT_I1;
    V_I1(&type) = NODE_PROCESSING_INSTRUCTION;

    hr = IXMLDOMDocument3_createNode(iface, type, target, NULL, &node);
    if (hr == S_OK)
    {
        xmlnode *node_obj;

        /* this is to bypass check in ::put_data() that blocks "<?xml" PIs */
        node_obj = get_node_obj(node);
        hr = node_set_content(node_obj, data);

        IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMProcessingInstruction, (void**)pi);
        IXMLDOMNode_Release(node);
    }

    return hr;
}


static HRESULT WINAPI domdoc_createAttribute(
    IXMLDOMDocument3 *iface,
    BSTR name,
    IXMLDOMAttribute** attribute )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *node;
    VARIANT type;
    HRESULT hr;

    TRACE("(%p)->(%s %p)\n", This, debugstr_w(name), attribute);

    if (!attribute || !name) return E_INVALIDARG;

    V_VT(&type) = VT_I1;
    V_I1(&type) = NODE_ATTRIBUTE;

    hr = IXMLDOMDocument3_createNode(iface, type, name, NULL, &node);
    if (hr == S_OK)
    {
        IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMAttribute, (void**)attribute);
        IXMLDOMNode_Release(node);
    }

    return hr;
}


static HRESULT WINAPI domdoc_createEntityReference(
    IXMLDOMDocument3 *iface,
    BSTR name,
    IXMLDOMEntityReference** entityref )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMNode *node;
    VARIANT type;
    HRESULT hr;

    TRACE("(%p)->(%s %p)\n", This, debugstr_w(name), entityref);

    if (!entityref) return E_INVALIDARG;

    *entityref = NULL;

    V_VT(&type) = VT_I1;
    V_I1(&type) = NODE_ENTITY_REFERENCE;

    hr = IXMLDOMDocument3_createNode(iface, type, name, NULL, &node);
    if (hr == S_OK)
    {
        IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMEntityReference, (void**)entityref);
        IXMLDOMNode_Release(node);
    }

    return hr;
}

xmlChar* tagName_to_XPath(const BSTR tagName)
{
    xmlChar *query, *tmp;
    static const xmlChar mod_pre[] = "*[local-name()='";
    static const xmlChar mod_post[] = "']";
    static const xmlChar prefix[] = "descendant::";
    const WCHAR *tokBegin, *tokEnd;
    int len;

    query = xmlStrdup(prefix);

    tokBegin = tagName;
    while (tokBegin && *tokBegin)
    {
        switch (*tokBegin)
        {
        case '/':
            query = xmlStrcat(query, BAD_CAST "/");
            ++tokBegin;
            break;
        case '*':
            query = xmlStrcat(query, BAD_CAST "*");
            ++tokBegin;
            break;
        default:
            query = xmlStrcat(query, mod_pre);
            tokEnd = tokBegin;
            while (*tokEnd && *tokEnd != '/')
                ++tokEnd;
            len = WideCharToMultiByte(CP_UTF8, 0, tokBegin, tokEnd-tokBegin, NULL, 0, NULL, NULL);
            tmp = xmlMalloc(len);
            WideCharToMultiByte(CP_UTF8, 0, tokBegin, tokEnd-tokBegin, (char*)tmp, len, NULL, NULL);
            query = xmlStrncat(query, tmp, len);
            xmlFree(tmp);
            tokBegin = tokEnd;
            query = xmlStrcat(query, mod_post);
        }
    }

    return query;
}

static HRESULT WINAPI domdoc_getElementsByTagName(
    IXMLDOMDocument3 *iface,
    BSTR tagName,
    IXMLDOMNodeList** resultList )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    xmlChar *query;
    HRESULT hr;
    BOOL XPath;

    TRACE("(%p)->(%s, %p)\n", This, debugstr_w(tagName), resultList);

    if (!tagName || !resultList) return E_INVALIDARG;

    XPath = This->properties->XPath;
    This->properties->XPath = TRUE;
    query = tagName_to_XPath(tagName);
    hr = create_selection((xmlNodePtr)get_doc(This), query, resultList);
    xmlFree(query);
    This->properties->XPath = XPath;

    return hr;
}

static HRESULT get_node_type(VARIANT Type, DOMNodeType * type)
{
    VARIANT tmp;
    HRESULT hr;

    VariantInit(&tmp);
    hr = VariantChangeType(&tmp, &Type, 0, VT_I4);
    if(FAILED(hr))
        return E_INVALIDARG;

    *type = V_I4(&tmp);

    return S_OK;
}

static HRESULT WINAPI domdoc_createNode(
    IXMLDOMDocument3 *iface,
    VARIANT Type,
    BSTR name,
    BSTR namespaceURI,
    IXMLDOMNode** node )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    DOMNodeType node_type;
    xmlNodePtr xmlnode;
    xmlChar *xml_name, *href;
    HRESULT hr;

    TRACE("(%p)->(%s %s %s %p)\n", This, debugstr_variant(&Type), debugstr_w(name), debugstr_w(namespaceURI), node);

    if(!node) return E_INVALIDARG;

    hr = get_node_type(Type, &node_type);
    if(FAILED(hr)) return hr;

    TRACE("node_type %d\n", node_type);

    /* exit earlier for types that need name */
    switch(node_type)
    {
    case NODE_ELEMENT:
    case NODE_ATTRIBUTE:
    case NODE_ENTITY_REFERENCE:
    case NODE_PROCESSING_INSTRUCTION:
        if (!name || *name == 0) return E_FAIL;
        break;
    default:
        break;
    }

    xml_name = xmlchar_from_wchar(name);
    /* prevent empty href to be allocated */
    href = namespaceURI ? xmlchar_from_wchar(namespaceURI) : NULL;

    switch(node_type)
    {
    case NODE_ELEMENT:
    {
        xmlChar *local, *prefix;

        local = xmlSplitQName2(xml_name, &prefix);

        xmlnode = xmlNewDocNode(get_doc(This), NULL, local ? local : xml_name, NULL);

        /* allow to create default namespace xmlns= */
        if (local || (href && *href))
        {
            xmlNsPtr ns = xmlNewNs(xmlnode, href, prefix);
            xmlSetNs(xmlnode, ns);
        }

        xmlFree(local);
        xmlFree(prefix);

        break;
    }
    case NODE_ATTRIBUTE:
    {
        xmlChar *local, *prefix;

        local = xmlSplitQName2(xml_name, &prefix);

        xmlnode = (xmlNodePtr)xmlNewDocProp(get_doc(This), local ? local : xml_name, NULL);

        if (local || (href && *href))
        {
            /* we need a floating namespace here, it can't be created linked to attribute from
               a start */
            xmlNsPtr ns = xmlNewNs(NULL, href, prefix);
            xmlSetNs(xmlnode, ns);
        }

        xmlFree(local);
        xmlFree(prefix);

        break;
    }
    case NODE_TEXT:
        xmlnode = (xmlNodePtr)xmlNewDocText(get_doc(This), NULL);
        break;
    case NODE_CDATA_SECTION:
        xmlnode = xmlNewCDataBlock(get_doc(This), NULL, 0);
        break;
    case NODE_ENTITY_REFERENCE:
        xmlnode = xmlNewReference(get_doc(This), xml_name);
        break;
    case NODE_PROCESSING_INSTRUCTION:
#ifdef HAVE_XMLNEWDOCPI
        xmlnode = xmlNewDocPI(get_doc(This), xml_name, NULL);
#else
        FIXME("xmlNewDocPI() not supported, use libxml2 2.6.15 or greater\n");
        xmlnode = NULL;
#endif
        break;
    case NODE_COMMENT:
        xmlnode = xmlNewDocComment(get_doc(This), NULL);
        break;
    case NODE_DOCUMENT_FRAGMENT:
        xmlnode = xmlNewDocFragment(get_doc(This));
        break;
    /* unsupported types */
    case NODE_DOCUMENT:
    case NODE_DOCUMENT_TYPE:
    case NODE_ENTITY:
    case NODE_NOTATION:
        heap_free(xml_name);
        return E_INVALIDARG;
    default:
        FIXME("unhandled node type %d\n", node_type);
        xmlnode = NULL;
        break;
    }

    *node = create_node(xmlnode);
    heap_free(xml_name);
    heap_free(href);

    if(*node)
    {
        TRACE("created node (%d, %p, %p)\n", node_type, *node, xmlnode);
        xmldoc_add_orphan(xmlnode->doc, xmlnode);
        return S_OK;
    }

    return E_FAIL;
}

static HRESULT WINAPI domdoc_nodeFromID(
    IXMLDOMDocument3 *iface,
    BSTR idString,
    IXMLDOMNode** node )
{
    domdoc *This = impl_from_IXMLDOMDocument3(iface);
    FIXME("(%p)->(%s %p)\n", This, debugstr_w(idString), node);
    return E_NOTIMPL;
}

static HRESULT domdoc_onDataAvailable(void *obj, char *ptr, DWORD len)
{
    domdoc *This = obj;
    xmlDocPtr xmldoc;

    xmldoc = doparse(This, ptr, len, XML_CHAR_ENCODING_NONE);
    if(xmldoc) {
        xmldoc->_private = create_priv();
        return attach_xmldoc(This, xmldoc);
    }

    return S_OK;
}

static HRESULT domdoc_load_moniker(domdoc *This, IMoniker *mon)
{
    bsc_t *bsc;
    HRESULT hr;

    hr = bind_url(mon, domdoc_onDataAvailable, This, &bsc);
    if(FAILED(hr))
        return hr;

    if(This->bsc) {
        hr = detach_bsc(This->bsc);
        if(FAILED(hr))
            return hr;
    }

    This->bsc = bsc;
    return S_OK;
}

static HRESULT WINAPI domdoc_load(
    IXMLDOMDocument3 *iface,
    VARIANT source,
    VARIANT_BOOL* isSuccessful )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    LPWSTR filename = NULL;
    HRESULT hr = S_FALSE;
    xmlDocPtr xmldoc;

    TRACE("(%p)->(%s)\n", This, debugstr_variant(&source));

    if (!isSuccessful)
        return E_POINTER;
    *isSuccessful = VARIANT_FALSE;

    assert( &This->node );

    switch( V_VT(&source) )
    {
    case VT_BSTR:
        filename = V_BSTR(&source);
        break;
    case VT_BSTR|VT_BYREF:
        if (!V_BSTRREF(&source)) return E_INVALIDARG;
        filename = *V_BSTRREF(&source);
        break;
    case VT_ARRAY|VT_UI1:
        {
            SAFEARRAY *psa = V_ARRAY(&source);
            char *str;
            LONG len;
            UINT dim = SafeArrayGetDim(psa);

            switch (dim)
            {
            case 0:
                ERR("SAFEARRAY == NULL\n");
                hr = This->error = E_INVALIDARG;
                break;
            case 1:
                /* Only takes UTF-8 strings.
                 * NOT NULL-terminated. */
                SafeArrayAccessData(psa, (void**)&str);
                SafeArrayGetUBound(psa, 1, &len);

                if ((xmldoc = doparse(This, str, ++len, XML_CHAR_ENCODING_UTF8)))
                {
                    hr = This->error = S_OK;
                    *isSuccessful = VARIANT_TRUE;
                    TRACE("parsed document %p\n", xmldoc);
                }
                else
                {
                    This->error = E_FAIL;
                    TRACE("failed to parse document\n");
                }

                SafeArrayUnaccessData(psa);

                if(xmldoc)
                {
                    xmldoc->_private = create_priv();
                    return attach_xmldoc(This, xmldoc);
                }
                break;
            default:
                FIXME("unhandled SAFEARRAY dim: %d\n", dim);
                hr = This->error = E_NOTIMPL;
            }
        }
        break;
    case VT_UNKNOWN:
    {
        ISequentialStream *stream = NULL;
        IXMLDOMDocument3 *newdoc = NULL;

        if (!V_UNKNOWN(&source)) return E_INVALIDARG;

        hr = IUnknown_QueryInterface(V_UNKNOWN(&source), &IID_IXMLDOMDocument3, (void**)&newdoc);
        if(hr == S_OK)
        {
            if(newdoc)
            {
                domdoc *newDoc = impl_from_IXMLDOMDocument3( newdoc );

                xmldoc = xmlCopyDoc(get_doc(newDoc), 1);
                xmldoc->_private = create_priv();
                hr = attach_xmldoc(This, xmldoc);

                if(SUCCEEDED(hr))
                    *isSuccessful = VARIANT_TRUE;

                return hr;
            }
        }

        hr = IUnknown_QueryInterface(V_UNKNOWN(&source), &IID_IStream, (void**)&stream);
        if (FAILED(hr))
            hr = IUnknown_QueryInterface(V_UNKNOWN(&source), &IID_ISequentialStream, (void**)&stream);

        if (hr == S_OK)
        {
            hr = domdoc_load_from_stream(This, stream);
            if (hr == S_OK)
                *isSuccessful = VARIANT_TRUE;
            ISequentialStream_Release(stream);
            return hr;
        }

        FIXME("unsupported IUnknown type (0x%08x) (%p)\n", hr, V_UNKNOWN(&source)->lpVtbl);
        break;
    }
    default:
        FIXME("VT type not supported (%d)\n", V_VT(&source));
    }

    if ( filename )
    {
        IMoniker *mon;

        hr = create_moniker_from_url( filename, &mon);
        if ( SUCCEEDED(hr) )
        {
            hr = domdoc_load_moniker( This, mon );
            IMoniker_Release(mon);
        }

        if ( FAILED(hr) )
            This->error = E_FAIL;
        else
        {
            hr = This->error = S_OK;
            *isSuccessful = VARIANT_TRUE;
        }
    }

    if(!filename || FAILED(hr)) {
        xmldoc = xmlNewDoc(NULL);
        xmldoc->_private = create_priv();
        hr = attach_xmldoc(This, xmldoc);
        if(SUCCEEDED(hr))
            hr = S_FALSE;
    }

    TRACE("ret (%d)\n", hr);

    return hr;
}


static HRESULT WINAPI domdoc_get_readyState(
    IXMLDOMDocument3 *iface,
    LONG *value )
{
    domdoc *This = impl_from_IXMLDOMDocument3(iface);
    FIXME("stub! (%p)->(%p)\n", This, value);

    if (!value)
        return E_INVALIDARG;

    *value = READYSTATE_COMPLETE;
    return S_OK;
}


static HRESULT WINAPI domdoc_get_parseError(
    IXMLDOMDocument3 *iface,
    IXMLDOMParseError** errorObj )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    static const WCHAR err[] = {'e','r','r','o','r',0};
    BSTR error_string = NULL;

    FIXME("(%p)->(%p): creating a dummy parseError\n", iface, errorObj);

    if(This->error)
        error_string = SysAllocString(err);

    *errorObj = create_parseError(This->error, NULL, error_string, NULL, 0, 0, 0);
    if(!*errorObj) return E_OUTOFMEMORY;
    return S_OK;
}


static HRESULT WINAPI domdoc_get_url(
    IXMLDOMDocument3 *iface,
    BSTR* urlString )
{
    domdoc *This = impl_from_IXMLDOMDocument3(iface);
    FIXME("(%p)->(%p)\n", This, urlString);
    return E_NOTIMPL;
}


static HRESULT WINAPI domdoc_get_async(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL* isAsync )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%p: %d)\n", This, isAsync, This->async);
    *isAsync = This->async;
    return S_OK;
}


static HRESULT WINAPI domdoc_put_async(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL isAsync )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%d)\n", This, isAsync);
    This->async = isAsync;
    return S_OK;
}


static HRESULT WINAPI domdoc_abort(
    IXMLDOMDocument3 *iface )
{
    domdoc *This = impl_from_IXMLDOMDocument3(iface);
    FIXME("%p\n", This);
    return E_NOTIMPL;
}

/* don't rely on data to be in BSTR format, treat it as WCHAR string */
static HRESULT WINAPI domdoc_loadXML(
    IXMLDOMDocument3 *iface,
    BSTR data,
    VARIANT_BOOL* isSuccessful )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    xmlDocPtr xmldoc = NULL;
    HRESULT hr = S_FALSE, hr2;

    TRACE("(%p)->(%s %p)\n", This, debugstr_w(data), isSuccessful );

    assert ( &This->node );

    if ( isSuccessful )
    {
        *isSuccessful = VARIANT_FALSE;

        if (data)
        {
            WCHAR *ptr = data;

            /* skip leading spaces if needed */
            if (This->properties->version == MSXML_DEFAULT || This->properties->version == MSXML26)
                while (*ptr && isspaceW(*ptr)) ptr++;

            xmldoc = doparse(This, (char*)ptr, strlenW(ptr)*sizeof(WCHAR), XML_CHAR_ENCODING_UTF16LE);
            if ( !xmldoc )
            {
                This->error = E_FAIL;
                TRACE("failed to parse document\n");
            }
            else
            {
                hr = This->error = S_OK;
                *isSuccessful = VARIANT_TRUE;
                TRACE("parsed document %p\n", xmldoc);
            }
        }
    }

    if(!xmldoc)
        xmldoc = xmlNewDoc(NULL);
    xmldoc->_private = create_priv();
    hr2 = attach_xmldoc(This, xmldoc);
    if( FAILED(hr2) )
        hr = hr2;

    return hr;
}

static int XMLCALL domdoc_save_writecallback(void *ctx, const char *buffer, int len)
{
    DWORD written = -1;

    if(!WriteFile(ctx, buffer, len, &written, NULL))
    {
        WARN("write error\n");
        return -1;
    }
    else
        return written;
}

static int XMLCALL domdoc_save_closecallback(void *ctx)
{
    return CloseHandle(ctx) ? 0 : -1;
}

static int XMLCALL domdoc_stream_save_writecallback(void *ctx, const char *buffer, int len)
{
    ULONG written = 0;
    HRESULT hr;

    hr = IStream_Write((IStream*)ctx, buffer, len, &written);
    TRACE("0x%08x %p %d %u\n", hr, buffer, len, written);
    if (hr != S_OK)
    {
        WARN("stream write error: 0x%08x\n", hr);
        return -1;
    }
    else
        return len;
}

static int XMLCALL domdoc_stream_save_closecallback(void *ctx)
{
    IStream_Release((IStream*)ctx);
    return 0;
}

static HRESULT WINAPI domdoc_save(
    IXMLDOMDocument3 *iface,
    VARIANT destination )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    xmlSaveCtxtPtr ctx = NULL;
    xmlNodePtr xmldecl;
    HRESULT ret = S_OK;

    TRACE("(%p)->(%s)\n", This, debugstr_variant(&destination));

    switch (V_VT(&destination))
    {
    case VT_UNKNOWN:
        {
            IUnknown *pUnk = V_UNKNOWN(&destination);
            IXMLDOMDocument3 *document;
            IStream *stream;

            ret = IUnknown_QueryInterface(pUnk, &IID_IXMLDOMDocument3, (void**)&document);
            if(ret == S_OK)
            {
                VARIANT_BOOL success;
                BSTR xml;

                ret = IXMLDOMDocument3_get_xml(iface, &xml);
                if(ret == S_OK)
                {
                    ret = IXMLDOMDocument3_loadXML(document, xml, &success);
                    SysFreeString(xml);
                }

                IXMLDOMDocument3_Release(document);
                return ret;
            }

            ret = IUnknown_QueryInterface(pUnk, &IID_IStream, (void**)&stream);
            if(ret == S_OK)
            {
                int options = get_doc(This)->standalone == -1 ? XML_SAVE_NO_DECL : 0;
                ctx = xmlSaveToIO(domdoc_stream_save_writecallback,
                    domdoc_stream_save_closecallback, stream, NULL, options);

                if(!ctx)
                {
                    IStream_Release(stream);
                    return E_FAIL;
                }
            }
        }
        break;

    case VT_BSTR:
    case VT_BSTR | VT_BYREF:
        {
            int options = get_doc(This)->standalone == -1 ? XML_SAVE_NO_DECL : 0;

            /* save with file path */
            HANDLE handle = CreateFileW( (V_VT(&destination) & VT_BYREF)? *V_BSTRREF(&destination) : V_BSTR(&destination),
                                         GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
            if( handle == INVALID_HANDLE_VALUE )
            {
                WARN("failed to create file\n");
                return E_FAIL;
            }

            /* disable top XML declaration */
            ctx = xmlSaveToIO(domdoc_save_writecallback, domdoc_save_closecallback,
                              handle, NULL, options);
            if (!ctx)
            {
                CloseHandle(handle);
                return E_FAIL;
            }
        }
        break;

    default:
        FIXME("Unhandled VARIANT: %s\n", debugstr_variant(&destination));
        return S_FALSE;
    }

    xmldecl = xmldoc_unlink_xmldecl(get_doc(This));
    if (xmlSaveDoc(ctx, get_doc(This)) == -1) ret = S_FALSE;
    xmldoc_link_xmldecl(get_doc(This), xmldecl);

    /* will release resources through close callback */
    xmlSaveClose(ctx);

    return ret;
}

static HRESULT WINAPI domdoc_get_validateOnParse(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL* isValidating )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p: %d)\n", This, isValidating, This->validating);
    *isValidating = This->validating;
    return S_OK;
}


static HRESULT WINAPI domdoc_put_validateOnParse(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL isValidating )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%d)\n", This, isValidating);
    This->validating = isValidating;
    return S_OK;
}


static HRESULT WINAPI domdoc_get_resolveExternals(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL* isResolving )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p: %d)\n", This, isResolving, This->resolving);
    *isResolving = This->resolving;
    return S_OK;
}


static HRESULT WINAPI domdoc_put_resolveExternals(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL isResolving )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%d)\n", This, isResolving);
    This->resolving = isResolving;
    return S_OK;
}


static HRESULT WINAPI domdoc_get_preserveWhiteSpace(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL* isPreserving )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p: %d)\n", This, isPreserving, This->properties->preserving);
    *isPreserving = This->properties->preserving;
    return S_OK;
}


static HRESULT WINAPI domdoc_put_preserveWhiteSpace(
    IXMLDOMDocument3 *iface,
    VARIANT_BOOL isPreserving )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%d)\n", This, isPreserving);
    This->properties->preserving = isPreserving;
    return S_OK;
}


static HRESULT WINAPI domdoc_put_onreadystatechange(
    IXMLDOMDocument3 *iface,
    VARIANT event )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%s)\n", This, debugstr_variant(&event));
    return set_doc_event(This, EVENTID_READYSTATECHANGE, &event);
}


static HRESULT WINAPI domdoc_put_onDataAvailable(IXMLDOMDocument3 *iface, VARIANT sink)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    FIXME("(%p)->(%s): stub\n", This, debugstr_variant(&sink));
    return E_NOTIMPL;
}

static HRESULT WINAPI domdoc_put_onTransformNode(IXMLDOMDocument3 *iface, VARIANT sink )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    FIXME("(%p)->(%s): stub\n", This, debugstr_variant(&sink));
    return E_NOTIMPL;
}

static HRESULT WINAPI domdoc_get_namespaces(
    IXMLDOMDocument3* iface,
    IXMLDOMSchemaCollection** collection )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    HRESULT hr;

    FIXME("(%p)->(%p): semi-stub\n", This, collection);

    if (!collection) return E_POINTER;

    if (!This->namespaces)
    {
        hr = SchemaCache_create(This->properties->version, NULL, (void**)&This->namespaces);
        if (hr != S_OK) return hr;

        hr = cache_from_doc_ns(This->namespaces, &This->node);
        if (hr != S_OK)
            release_namespaces(This);
    }

    if (This->namespaces)
        return IXMLDOMSchemaCollection2_QueryInterface(This->namespaces,
                   &IID_IXMLDOMSchemaCollection, (void**)collection);

    return hr;
}

static HRESULT WINAPI domdoc_get_schemas(
    IXMLDOMDocument3* iface,
    VARIANT* schema )
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    IXMLDOMSchemaCollection2* cur_schema = This->properties->schemaCache;
    HRESULT hr = S_FALSE;

    TRACE("(%p)->(%p)\n", This, schema);

    V_VT(schema) = VT_NULL;
    /* just to reset pointer part, cause that's what application is expected to use */
    V_DISPATCH(schema) = NULL;

    if(cur_schema)
    {
        hr = IXMLDOMSchemaCollection2_QueryInterface(cur_schema, &IID_IDispatch, (void**)&V_DISPATCH(schema));
        if(SUCCEEDED(hr))
            V_VT(schema) = VT_DISPATCH;
    }
    return hr;
}

static HRESULT WINAPI domdoc_putref_schemas(
    IXMLDOMDocument3* iface,
    VARIANT schema)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    HRESULT hr = E_FAIL;
    IXMLDOMSchemaCollection2* new_schema = NULL;

    FIXME("(%p)->(%s): semi-stub\n", This, debugstr_variant(&schema));
    switch(V_VT(&schema))
    {
    case VT_UNKNOWN:
        if (V_UNKNOWN(&schema))
        {
            hr = IUnknown_QueryInterface(V_UNKNOWN(&schema), &IID_IXMLDOMSchemaCollection, (void**)&new_schema);
            break;
        }
        /* fallthrough */
    case VT_DISPATCH:
        if (V_DISPATCH(&schema))
        {
            hr = IDispatch_QueryInterface(V_DISPATCH(&schema), &IID_IXMLDOMSchemaCollection, (void**)&new_schema);
            break;
        }
        /* fallthrough */
    case VT_NULL:
    case VT_EMPTY:
        hr = S_OK;
        break;

    default:
        WARN("Can't get schema from vt %x\n", V_VT(&schema));
    }

    if(SUCCEEDED(hr))
    {
        IXMLDOMSchemaCollection2* old_schema = InterlockedExchangePointer((void**)&This->properties->schemaCache, new_schema);
        if(old_schema) IXMLDOMSchemaCollection2_Release(old_schema);
    }

    return hr;
}

static inline BOOL is_wellformed(xmlDocPtr doc)
{
#ifdef HAVE_XMLDOC_PROPERTIES
    return doc->properties & XML_DOC_WELLFORMED;
#else
    /* Not a full check, but catches the worst violations */
    xmlNodePtr child;
    int root = 0;

    for (child = doc->children; child != NULL; child = child->next)
    {
        switch (child->type)
        {
        case XML_ELEMENT_NODE:
            if (++root > 1)
                return FALSE;
            break;
        case XML_TEXT_NODE:
        case XML_CDATA_SECTION_NODE:
            return FALSE;
            break;
        default:
            break;
        }
    }

    return root == 1;
#endif
}

static void LIBXML2_LOG_CALLBACK validate_error(void* ctx, char const* msg, ...)
{
    va_list ap;
    va_start(ap, msg);
    LIBXML2_CALLBACK_ERR(domdoc_validateNode, msg, ap);
    va_end(ap);
}

static void LIBXML2_LOG_CALLBACK validate_warning(void* ctx, char const* msg, ...)
{
    va_list ap;
    va_start(ap, msg);
    LIBXML2_CALLBACK_WARN(domdoc_validateNode, msg, ap);
    va_end(ap);
}

static HRESULT WINAPI domdoc_validateNode(
    IXMLDOMDocument3* iface,
    IXMLDOMNode* node,
    IXMLDOMParseError** err)
{
    domdoc* This = impl_from_IXMLDOMDocument3(iface);
    LONG state, err_code = 0;
    HRESULT hr = S_OK;
    int validated = 0;

    TRACE("(%p)->(%p, %p)\n", This, node, err);
    IXMLDOMDocument3_get_readyState(iface, &state);
    if (state != READYSTATE_COMPLETE)
    {
        if (err)
           *err = create_parseError(err_code, NULL, NULL, NULL, 0, 0, 0);
        return E_PENDING;
    }

    if (!node)
    {
        if (err)
            *err = create_parseError(err_code, NULL, NULL, NULL, 0, 0, 0);
        return E_POINTER;
    }

    if (!get_node_obj(node)->node || get_node_obj(node)->node->doc != get_doc(This))
    {
        if (err)
            *err = create_parseError(err_code, NULL, NULL, NULL, 0, 0, 0);
        return E_FAIL;
    }

    if (!is_wellformed(get_doc(This)))
    {
        ERR("doc not well-formed\n");
        if (err)
            *err = create_parseError(E_XML_NOTWF, NULL, NULL, NULL, 0, 0, 0);
        return S_FALSE;
    }

    /* DTD validation */
    if (get_doc(This)->intSubset || get_doc(This)->extSubset)
    {
        xmlValidCtxtPtr vctx = xmlNewValidCtxt();
        vctx->error = validate_error;
        vctx->warning = validate_warning;
        ++validated;

        if (!((node == (IXMLDOMNode*)iface)?
              xmlValidateDocument(vctx, get_doc(This)) :
              xmlValidateElement(vctx, get_doc(This), get_node_obj(node)->node)))
        {
            /* TODO: get a real error code here */
            TRACE("DTD validation failed\n");
            err_code = E_XML_INVALID;
            hr = S_FALSE;
        }
        xmlFreeValidCtxt(vctx);
    }

    /* Schema validation */
    if (hr == S_OK && This->properties->schemaCache != NULL)
    {

        hr = SchemaCache_validate_tree(This->properties->schemaCache, get_node_obj(node)->node);
        if (SUCCEEDED(hr))
        {
            ++validated;
            /* TODO: get a real error code here */
            if (hr == S_OK)
            {
                TRACE("schema validation succeeded\n");
            }
            else
            {
                ERR("schema validation failed\n");
                err_code = E_XML_INVALID;
            }
        }
        else
        {
            /* not really OK, just didn't find a schema for the ns */
            hr = S_OK;
        }
    }

    if (!validated)
    {
        ERR("no DTD or schema found\n");
        err_code = E_XML_NODTD;
        hr = S_FALSE;
    }

    if (err)
        *err = create_parseError(err_code, NULL, NULL, NULL, 0, 0, 0);

    return hr;
}

static HRESULT WINAPI domdoc_validate(
    IXMLDOMDocument3* iface,
    IXMLDOMParseError** err)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    TRACE("(%p)->(%p)\n", This, err);
    return IXMLDOMDocument3_validateNode(iface, (IXMLDOMNode*)iface, err);
}

static HRESULT WINAPI domdoc_setProperty(
    IXMLDOMDocument3* iface,
    BSTR p,
    VARIANT value)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%s %s)\n", This, debugstr_w(p), debugstr_variant(&value));

    if (lstrcmpiW(p, PropertySelectionLanguageW) == 0)
    {
        VARIANT varStr;
        HRESULT hr;
        BSTR bstr;

        V_VT(&varStr) = VT_EMPTY;
        if (V_VT(&value) != VT_BSTR)
        {
            if (FAILED(hr = VariantChangeType(&varStr, &value, 0, VT_BSTR)))
                return hr;
            bstr = V_BSTR(&varStr);
        }
        else
            bstr = V_BSTR(&value);

        hr = S_OK;
        if (lstrcmpiW(bstr, PropValueXPathW) == 0)
            This->properties->XPath = TRUE;
        else if (lstrcmpiW(bstr, PropValueXSLPatternW) == 0)
            This->properties->XPath = FALSE;
        else
            hr = E_FAIL;

        VariantClear(&varStr);
        return hr;
    }
    else if (lstrcmpiW(p, PropertySelectionNamespacesW) == 0)
    {
        xmlChar *nsStr = (xmlChar*)This->properties->selectNsStr;
        struct list *pNsList;
        VARIANT varStr;
        HRESULT hr;
        BSTR bstr;

        V_VT(&varStr) = VT_EMPTY;
        if (V_VT(&value) != VT_BSTR)
        {
            if (FAILED(hr = VariantChangeType(&varStr, &value, 0, VT_BSTR)))
                return hr;
            bstr = V_BSTR(&varStr);
        }
        else
            bstr = V_BSTR(&value);

        hr = S_OK;

        pNsList = &(This->properties->selectNsList);
        clear_selectNsList(pNsList);
        heap_free(nsStr);
        nsStr = xmlchar_from_wchar(bstr);

        TRACE("property value: \"%s\"\n", debugstr_w(bstr));

        This->properties->selectNsStr = nsStr;
        This->properties->selectNsStr_len = xmlStrlen(nsStr);
        if (bstr && *bstr)
        {
            xmlChar *pTokBegin, *pTokEnd, *pTokInner;
            select_ns_entry* ns_entry = NULL;
            xmlXPathContextPtr ctx;

            ctx = xmlXPathNewContext(This->node.node->doc);
            pTokBegin = nsStr;

            /* skip leading spaces */
            while (*pTokBegin == ' '  || *pTokBegin == '\n' ||
                   *pTokBegin == '\t' || *pTokBegin == '\r')
                ++pTokBegin;

            for (; *pTokBegin; pTokBegin = pTokEnd)
            {
                if (ns_entry)
                    memset(ns_entry, 0, sizeof(select_ns_entry));
                else
                    ns_entry = heap_alloc_zero(sizeof(select_ns_entry));

                while (*pTokBegin == ' ')
                    ++pTokBegin;
                pTokEnd = pTokBegin;
                while (*pTokEnd != ' ' && *pTokEnd != 0)
                    ++pTokEnd;

                /* so it failed to advance which means we've got some trailing spaces */
                if (pTokEnd == pTokBegin) break;

                if (xmlStrncmp(pTokBegin, (xmlChar const*)"xmlns", 5) != 0)
                {
                    hr = E_FAIL;
                    WARN("Syntax error in xmlns string: %s\n\tat token: %s\n",
                          debugstr_w(bstr), debugstr_an((const char*)pTokBegin, pTokEnd-pTokBegin));
                    continue;
                }

                pTokBegin += 5;
                if (*pTokBegin == '=')
                {
                    /*valid for XSLPattern?*/
                    FIXME("Setting default xmlns not supported - skipping.\n");
                    continue;
                }
                else if (*pTokBegin == ':')
                {
                    ns_entry->prefix = ++pTokBegin;
                    for (pTokInner = pTokBegin; pTokInner != pTokEnd && *pTokInner != '='; ++pTokInner)
                        ;

                    if (pTokInner == pTokEnd)
                    {
                        hr = E_FAIL;
                        WARN("Syntax error in xmlns string: %s\n\tat token: %s\n",
                              debugstr_w(bstr), debugstr_an((const char*)pTokBegin, pTokEnd-pTokBegin));
                        continue;
                    }

                    ns_entry->prefix_end = *pTokInner;
                    *pTokInner = 0;
                    ++pTokInner;

                    if (pTokEnd-pTokInner > 1 &&
                        ((*pTokInner == '\'' && *(pTokEnd-1) == '\'') ||
                         (*pTokInner == '"' && *(pTokEnd-1) == '"')))
                    {
                        ns_entry->href = ++pTokInner;
                        ns_entry->href_end = *(pTokEnd-1);
                        *(pTokEnd-1) = 0;
                        list_add_tail(pNsList, &ns_entry->entry);
                        /*let libxml figure out if they're valid from here ;)*/
                        if (xmlXPathRegisterNs(ctx, ns_entry->prefix, ns_entry->href) != 0)
                        {
                            hr = E_FAIL;
                        }
                        ns_entry = NULL;
                        continue;
                    }
                    else
                    {
                        WARN("Syntax error in xmlns string: %s\n\tat token: %s\n",
                              debugstr_w(bstr), debugstr_an((const char*)pTokInner, pTokEnd-pTokInner));
                        list_add_tail(pNsList, &ns_entry->entry);

                        ns_entry = NULL;
                        hr = E_FAIL;
                        continue;
                    }
                }
                else
                {
                    hr = E_FAIL;
                    continue;
                }
            }
            heap_free(ns_entry);
            xmlXPathFreeContext(ctx);
        }

        VariantClear(&varStr);
        return hr;
    }
    else if (lstrcmpiW(p, PropertyProhibitDTDW) == 0 ||
             lstrcmpiW(p, PropertyNewParserW) == 0 ||
             lstrcmpiW(p, PropertyResolveExternalsW) == 0)
    {
        /* Ignore */
        FIXME("Ignoring property %s, value %s\n", debugstr_w(p), debugstr_variant(&value));
        return S_OK;
    }

    FIXME("Unknown property %s\n", debugstr_w(p));
    return E_FAIL;
}

static HRESULT WINAPI domdoc_getProperty(
    IXMLDOMDocument3* iface,
    BSTR p,
    VARIANT* var)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );

    TRACE("(%p)->(%s)\n", This, debugstr_w(p));

    if (!var)
        return E_INVALIDARG;

    if (lstrcmpiW(p, PropertySelectionLanguageW) == 0)
    {
        V_VT(var) = VT_BSTR;
        V_BSTR(var) = This->properties->XPath ?
                      SysAllocString(PropValueXPathW) :
                      SysAllocString(PropValueXSLPatternW);
        return V_BSTR(var) ? S_OK : E_OUTOFMEMORY;
    }
    else if (lstrcmpiW(p, PropertySelectionNamespacesW) == 0)
    {
        int lenA, lenW;
        BSTR rebuiltStr, cur;
        const xmlChar *nsStr;
        struct list *pNsList;
        select_ns_entry* pNsEntry;

        V_VT(var) = VT_BSTR;
        nsStr = This->properties->selectNsStr;
        pNsList = &This->properties->selectNsList;
        lenA = This->properties->selectNsStr_len;
        lenW = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)nsStr, lenA+1, NULL, 0);
        rebuiltStr = heap_alloc(lenW*sizeof(WCHAR));
        MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)nsStr, lenA+1, rebuiltStr, lenW);
        cur = rebuiltStr;
        /* this is fine because all of the chars that end tokens are ASCII*/
        LIST_FOR_EACH_ENTRY(pNsEntry, pNsList, select_ns_entry, entry)
        {
            while (*cur != 0) ++cur;
            if (pNsEntry->prefix_end)
            {
                *cur = pNsEntry->prefix_end;
                while (*cur != 0) ++cur;
            }

            if (pNsEntry->href_end)
            {
                *cur = pNsEntry->href_end;
            }
        }
        V_BSTR(var) = SysAllocString(rebuiltStr);
        heap_free(rebuiltStr);
        return S_OK;
    }

    FIXME("Unknown property %s\n", debugstr_w(p));
    return E_FAIL;
}

static HRESULT WINAPI domdoc_importNode(
    IXMLDOMDocument3* iface,
    IXMLDOMNode* node,
    VARIANT_BOOL deep,
    IXMLDOMNode** clone)
{
    domdoc *This = impl_from_IXMLDOMDocument3( iface );
    FIXME("(%p)->(%p %d %p): stub\n", This, node, deep, clone);
    return E_NOTIMPL;
}

static const struct IXMLDOMDocument3Vtbl XMLDOMDocument3Vtbl =
{
    domdoc_QueryInterface,
    domdoc_AddRef,
    domdoc_Release,
    domdoc_GetTypeInfoCount,
    domdoc_GetTypeInfo,
    domdoc_GetIDsOfNames,
    domdoc_Invoke,
    domdoc_get_nodeName,
    domdoc_get_nodeValue,
    domdoc_put_nodeValue,
    domdoc_get_nodeType,
    domdoc_get_parentNode,
    domdoc_get_childNodes,
    domdoc_get_firstChild,
    domdoc_get_lastChild,
    domdoc_get_previousSibling,
    domdoc_get_nextSibling,
    domdoc_get_attributes,
    domdoc_insertBefore,
    domdoc_replaceChild,
    domdoc_removeChild,
    domdoc_appendChild,
    domdoc_hasChildNodes,
    domdoc_get_ownerDocument,
    domdoc_cloneNode,
    domdoc_get_nodeTypeString,
    domdoc_get_text,
    domdoc_put_text,
    domdoc_get_specified,
    domdoc_get_definition,
    domdoc_get_nodeTypedValue,
    domdoc_put_nodeTypedValue,
    domdoc_get_dataType,
    domdoc_put_dataType,
    domdoc_get_xml,
    domdoc_transformNode,
    domdoc_selectNodes,
    domdoc_selectSingleNode,
    domdoc_get_parsed,
    domdoc_get_namespaceURI,
    domdoc_get_prefix,
    domdoc_get_baseName,
    domdoc_transformNodeToObject,
    domdoc_get_doctype,
    domdoc_get_implementation,
    domdoc_get_documentElement,
    domdoc_put_documentElement,
    domdoc_createElement,
    domdoc_createDocumentFragment,
    domdoc_createTextNode,
    domdoc_createComment,
    domdoc_createCDATASection,
    domdoc_createProcessingInstruction,
    domdoc_createAttribute,
    domdoc_createEntityReference,
    domdoc_getElementsByTagName,
    domdoc_createNode,
    domdoc_nodeFromID,
    domdoc_load,
    domdoc_get_readyState,
    domdoc_get_parseError,
    domdoc_get_url,
    domdoc_get_async,
    domdoc_put_async,
    domdoc_abort,
    domdoc_loadXML,
    domdoc_save,
    domdoc_get_validateOnParse,
    domdoc_put_validateOnParse,
    domdoc_get_resolveExternals,
    domdoc_put_resolveExternals,
    domdoc_get_preserveWhiteSpace,
    domdoc_put_preserveWhiteSpace,
    domdoc_put_onreadystatechange,
    domdoc_put_onDataAvailable,
    domdoc_put_onTransformNode,
    domdoc_get_namespaces,
    domdoc_get_schemas,
    domdoc_putref_schemas,
    domdoc_validate,
    domdoc_setProperty,
    domdoc_getProperty,
    domdoc_validateNode,
    domdoc_importNode
};

/* IConnectionPointContainer */
static HRESULT WINAPI ConnectionPointContainer_QueryInterface(IConnectionPointContainer *iface,
                                                              REFIID riid, void **ppv)
{
    domdoc *This = impl_from_IConnectionPointContainer(iface);
    return IXMLDOMDocument3_QueryInterface(&This->IXMLDOMDocument3_iface, riid, ppv);
}

static ULONG WINAPI ConnectionPointContainer_AddRef(IConnectionPointContainer *iface)
{
    domdoc *This = impl_from_IConnectionPointContainer(iface);
    return IXMLDOMDocument3_AddRef(&This->IXMLDOMDocument3_iface);
}

static ULONG WINAPI ConnectionPointContainer_Release(IConnectionPointContainer *iface)
{
    domdoc *This = impl_from_IConnectionPointContainer(iface);
    return IXMLDOMDocument3_Release(&This->IXMLDOMDocument3_iface);
}

static HRESULT WINAPI ConnectionPointContainer_EnumConnectionPoints(IConnectionPointContainer *iface,
        IEnumConnectionPoints **ppEnum)
{
    domdoc *This = impl_from_IConnectionPointContainer(iface);
    FIXME("(%p)->(%p): stub\n", This, ppEnum);
    return E_NOTIMPL;
}

static HRESULT WINAPI ConnectionPointContainer_FindConnectionPoint(IConnectionPointContainer *iface,
        REFIID riid, IConnectionPoint **cp)
{
    domdoc *This = impl_from_IConnectionPointContainer(iface);
    ConnectionPoint *iter;

    TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), cp);

    *cp = NULL;

    for(iter = This->cp_list; iter; iter = iter->next)
    {
        if (IsEqualGUID(iter->iid, riid))
            *cp = &iter->IConnectionPoint_iface;
    }

    if (*cp)
    {
        IConnectionPoint_AddRef(*cp);
        return S_OK;
    }

    FIXME("unsupported riid %s\n", debugstr_guid(riid));
    return CONNECT_E_NOCONNECTION;

}

static const struct IConnectionPointContainerVtbl ConnectionPointContainerVtbl =
{
    ConnectionPointContainer_QueryInterface,
    ConnectionPointContainer_AddRef,
    ConnectionPointContainer_Release,
    ConnectionPointContainer_EnumConnectionPoints,
    ConnectionPointContainer_FindConnectionPoint
};

/* IConnectionPoint */
static HRESULT WINAPI ConnectionPoint_QueryInterface(IConnectionPoint *iface,
                                                     REFIID riid, void **ppv)
{
    ConnectionPoint *This = impl_from_IConnectionPoint(iface);

    TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv );

    *ppv = NULL;

    if (IsEqualGUID(&IID_IUnknown, riid) ||
        IsEqualGUID(&IID_IConnectionPoint, riid))
    {
        *ppv = iface;
    }

    if (*ppv)
    {
        IConnectionPoint_AddRef(iface);
        return S_OK;
    }

    WARN("Unsupported interface %s\n", debugstr_guid(riid));
    return E_NOINTERFACE;
}

static ULONG WINAPI ConnectionPoint_AddRef(IConnectionPoint *iface)
{
    ConnectionPoint *This = impl_from_IConnectionPoint(iface);
    return IConnectionPointContainer_AddRef(This->container);
}

static ULONG WINAPI ConnectionPoint_Release(IConnectionPoint *iface)
{
    ConnectionPoint *This = impl_from_IConnectionPoint(iface);
    return IConnectionPointContainer_Release(This->container);
}

static HRESULT WINAPI ConnectionPoint_GetConnectionInterface(IConnectionPoint *iface, IID *iid)
{
    ConnectionPoint *This = impl_from_IConnectionPoint(iface);

    TRACE("(%p)->(%p)\n", This, iid);

    if (!iid) return E_POINTER;

    *iid = *This->iid;
    return S_OK;
}

static HRESULT WINAPI ConnectionPoint_GetConnectionPointContainer(IConnectionPoint *iface,
        IConnectionPointContainer **container)
{
    ConnectionPoint *This = impl_from_IConnectionPoint(iface);

    TRACE("(%p)->(%p)\n", This, container);

    if (!container) return E_POINTER;

    *container = This->container;
    IConnectionPointContainer_AddRef(*container);
    return S_OK;
}

static HRESULT WINAPI ConnectionPoint_Advise(IConnectionPoint *iface, IUnknown *unk_sink,
                                             DWORD *cookie)
{
    ConnectionPoint *This = impl_from_IConnectionPoint(iface);
    IUnknown *sink;
    HRESULT hr;
    DWORD i;

    TRACE("(%p)->(%p %p)\n", This, unk_sink, cookie);

    hr = IUnknown_QueryInterface(unk_sink, This->iid, (void**)&sink);
    if(FAILED(hr) && !IsEqualGUID(&IID_IPropertyNotifySink, This->iid))
        hr = IUnknown_QueryInterface(unk_sink, &IID_IDispatch, (void**)&sink);
    if(FAILED(hr))
        return CONNECT_E_CANNOTCONNECT;

    if(This->sinks)
    {
        for (i = 0; i < This->sinks_size; i++)
            if (!This->sinks[i].unk)
                break;

        if (i == This->sinks_size)
            This->sinks = heap_realloc(This->sinks,(++This->sinks_size)*sizeof(*This->sinks));
    }
    else
    {
        This->sinks = heap_alloc(sizeof(*This->sinks));
        This->sinks_size = 1;
        i = 0;
    }

    This->sinks[i].unk = sink;
    if (cookie)
        *cookie = i+1;

    return S_OK;
}

static HRESULT WINAPI ConnectionPoint_Unadvise(IConnectionPoint *iface, DWORD cookie)
{
    ConnectionPoint *This = impl_from_IConnectionPoint(iface);

    TRACE("(%p)->(%d)\n", This, cookie);

    if (cookie == 0 || cookie > This->sinks_size || !This->sinks[cookie-1].unk)
        return CONNECT_E_NOCONNECTION;

    IUnknown_Release(This->sinks[cookie-1].unk);
    This->sinks[cookie-1].unk = NULL;

    return S_OK;
}

static HRESULT WINAPI ConnectionPoint_EnumConnections(IConnectionPoint *iface,
                                                      IEnumConnections **ppEnum)
{
    ConnectionPoint *This = impl_from_IConnectionPoint(iface);
    FIXME("(%p)->(%p): stub\n", This, ppEnum);
    return E_NOTIMPL;
}

static const IConnectionPointVtbl ConnectionPointVtbl =
{
    ConnectionPoint_QueryInterface,
    ConnectionPoint_AddRef,
    ConnectionPoint_Release,
    ConnectionPoint_GetConnectionInterface,
    ConnectionPoint_GetConnectionPointContainer,
    ConnectionPoint_Advise,
    ConnectionPoint_Unadvise,
    ConnectionPoint_EnumConnections
};

static void ConnectionPoint_Init(ConnectionPoint *cp, struct domdoc *doc, REFIID riid)
{
    cp->IConnectionPoint_iface.lpVtbl = &ConnectionPointVtbl;
    cp->doc = doc;
    cp->iid = riid;
    cp->sinks = NULL;
    cp->sinks_size = 0;

    cp->next = doc->cp_list;
    doc->cp_list = cp;

    cp->container = &doc->IConnectionPointContainer_iface;
}

/* domdoc implementation of IObjectWithSite */
static HRESULT WINAPI
domdoc_ObjectWithSite_QueryInterface( IObjectWithSite* iface, REFIID riid, void** ppvObject )
{
    domdoc *This = impl_from_IObjectWithSite(iface);
    return IXMLDOMDocument3_QueryInterface(&This->IXMLDOMDocument3_iface, riid, ppvObject);
}

static ULONG WINAPI domdoc_ObjectWithSite_AddRef( IObjectWithSite* iface )
{
    domdoc *This = impl_from_IObjectWithSite(iface);
    return IXMLDOMDocument3_AddRef(&This->IXMLDOMDocument3_iface);
}

static ULONG WINAPI domdoc_ObjectWithSite_Release( IObjectWithSite* iface )
{
    domdoc *This = impl_from_IObjectWithSite(iface);
    return IXMLDOMDocument3_Release(&This->IXMLDOMDocument3_iface);
}

static HRESULT WINAPI domdoc_ObjectWithSite_GetSite( IObjectWithSite *iface, REFIID iid, void **ppvSite )
{
    domdoc *This = impl_from_IObjectWithSite(iface);

    TRACE("(%p)->(%s %p)\n", This, debugstr_guid( iid ), ppvSite );

    if ( !This->site )
        return E_FAIL;

    return IUnknown_QueryInterface( This->site, iid, ppvSite );
}

static HRESULT WINAPI domdoc_ObjectWithSite_SetSite( IObjectWithSite *iface, IUnknown *punk )
{
    domdoc *This = impl_from_IObjectWithSite(iface);

    TRACE("(%p)->(%p)\n", iface, punk);

    if(!punk)
    {
        if(This->site)
        {
            IUnknown_Release( This->site );
            This->site = NULL;
        }

        return S_OK;
    }

    IUnknown_AddRef( punk );

    if(This->site)
        IUnknown_Release( This->site );

    This->site = punk;

    return S_OK;
}

static const IObjectWithSiteVtbl domdocObjectSite =
{
    domdoc_ObjectWithSite_QueryInterface,
    domdoc_ObjectWithSite_AddRef,
    domdoc_ObjectWithSite_Release,
    domdoc_ObjectWithSite_SetSite,
    domdoc_ObjectWithSite_GetSite
};

static HRESULT WINAPI domdoc_Safety_QueryInterface(IObjectSafety *iface, REFIID riid, void **ppv)
{
    domdoc *This = impl_from_IObjectSafety(iface);
    return IXMLDOMDocument3_QueryInterface(&This->IXMLDOMDocument3_iface, riid, ppv);
}

static ULONG WINAPI domdoc_Safety_AddRef(IObjectSafety *iface)
{
    domdoc *This = impl_from_IObjectSafety(iface);
    return IXMLDOMDocument3_AddRef(&This->IXMLDOMDocument3_iface);
}

static ULONG WINAPI domdoc_Safety_Release(IObjectSafety *iface)
{
    domdoc *This = impl_from_IObjectSafety(iface);
    return IXMLDOMDocument3_Release(&This->IXMLDOMDocument3_iface);
}

#define SAFETY_SUPPORTED_OPTIONS (INTERFACESAFE_FOR_UNTRUSTED_CALLER|INTERFACESAFE_FOR_UNTRUSTED_DATA|INTERFACE_USES_SECURITY_MANAGER)

static HRESULT WINAPI domdoc_Safety_GetInterfaceSafetyOptions(IObjectSafety *iface, REFIID riid,
        DWORD *supported, DWORD *enabled)
{
    domdoc *This = impl_from_IObjectSafety(iface);

    TRACE("(%p)->(%s %p %p)\n", This, debugstr_guid(riid), supported, enabled);

    if(!supported || !enabled) return E_POINTER;

    *supported = SAFETY_SUPPORTED_OPTIONS;
    *enabled = This->safeopt;

    return S_OK;
}

static HRESULT WINAPI domdoc_Safety_SetInterfaceSafetyOptions(IObjectSafety *iface, REFIID riid,
        DWORD mask, DWORD enabled)
{
    domdoc *This = impl_from_IObjectSafety(iface);
    TRACE("(%p)->(%s %x %x)\n", This, debugstr_guid(riid), mask, enabled);

    if ((mask & ~SAFETY_SUPPORTED_OPTIONS) != 0)
        return E_FAIL;

    This->safeopt = (This->safeopt & ~mask) | (mask & enabled);

    return S_OK;
}

#undef SAFETY_SUPPORTED_OPTIONS

static const IObjectSafetyVtbl domdocObjectSafetyVtbl = {
    domdoc_Safety_QueryInterface,
    domdoc_Safety_AddRef,
    domdoc_Safety_Release,
    domdoc_Safety_GetInterfaceSafetyOptions,
    domdoc_Safety_SetInterfaceSafetyOptions
};

static const tid_t domdoc_iface_tids[] = {
    IXMLDOMDocument3_tid,
    0
};

static dispex_static_data_t domdoc_dispex = {
    NULL,
    IXMLDOMDocument3_tid,
    NULL,
    domdoc_iface_tids
};

HRESULT get_domdoc_from_xmldoc(xmlDocPtr xmldoc, IXMLDOMDocument3 **document)
{
    domdoc *doc;

    doc = heap_alloc( sizeof (*doc) );
    if( !doc )
        return E_OUTOFMEMORY;

    doc->IXMLDOMDocument3_iface.lpVtbl = &XMLDOMDocument3Vtbl;
    doc->IPersistStreamInit_iface.lpVtbl = &xmldoc_IPersistStreamInit_VTable;
    doc->IObjectWithSite_iface.lpVtbl = &domdocObjectSite;
    doc->IObjectSafety_iface.lpVtbl = &domdocObjectSafetyVtbl;
    doc->IConnectionPointContainer_iface.lpVtbl = &ConnectionPointContainerVtbl;
    doc->ref = 1;
    doc->async = VARIANT_TRUE;
    doc->validating = 0;
    doc->resolving = 0;
    doc->properties = properties_from_xmlDocPtr(xmldoc);
    doc->error = S_OK;
    doc->site = NULL;
    doc->safeopt = 0;
    doc->bsc = NULL;
    doc->cp_list = NULL;
    doc->namespaces = NULL;
    memset(doc->events, 0, sizeof(doc->events));

    /* events connection points */
    ConnectionPoint_Init(&doc->cp_dispatch, doc, &IID_IDispatch);
    ConnectionPoint_Init(&doc->cp_propnotif, doc, &IID_IPropertyNotifySink);
    ConnectionPoint_Init(&doc->cp_domdocevents, doc, &DIID_XMLDOMDocumentEvents);

    init_xmlnode(&doc->node, (xmlNodePtr)xmldoc, (IXMLDOMNode*)&doc->IXMLDOMDocument3_iface,
            &domdoc_dispex);

    *document = &doc->IXMLDOMDocument3_iface;

    TRACE("returning iface %p\n", *document);
    return S_OK;
}

HRESULT DOMDocument_create(MSXML_VERSION version, IUnknown *pUnkOuter, void **ppObj)
{
    xmlDocPtr xmldoc;
    HRESULT hr;

    TRACE("(%d, %p, %p)\n", version, pUnkOuter, ppObj);

    xmldoc = xmlNewDoc(NULL);
    if(!xmldoc)
        return E_OUTOFMEMORY;

    xmldoc_init(xmldoc, version);

    hr = get_domdoc_from_xmldoc(xmldoc, (IXMLDOMDocument3**)ppObj);
    if(FAILED(hr))
    {
        free_properties(properties_from_xmlDocPtr(xmldoc));
        heap_free(xmldoc->_private);
        xmlFreeDoc(xmldoc);
        return hr;
    }

    return hr;
}

IUnknown* create_domdoc( xmlNodePtr document )
{
    void* pObj = NULL;
    HRESULT hr;

    TRACE("(%p)\n", document);

    hr = get_domdoc_from_xmldoc((xmlDocPtr)document, (IXMLDOMDocument3**)&pObj);
    if (FAILED(hr))
        return NULL;

    return pObj;
}

#else

HRESULT DOMDocument_create(MSXML_VERSION version, IUnknown *pUnkOuter, void **ppObj)
{
    MESSAGE("This program tried to use a DOMDocument object, but\n"
            "libxml2 support was not present at compile time.\n");
    return E_NOTIMPL;
}

#endif
