[PATCH] msxml3: Handle SAX tags and characters for mxwriter domdoc.

Jefferson Carpenter jeffersoncarpenter2 at gmail.com
Fri Apr 9 20:27:28 CDT 2021


Moved some code from mxwriter ISAXContentHandler implementation into 
free functions that can be swapped out for DOMDocument functions.

For other mxwriter callback functions, immediately return E_NOTIMPL if 
the destination is a DOMDocument.

Added test_mxwriter_domdoc_start_end_document to help check the 
implementation of DOMDocument locking.

Added "BOOL locked" to XML documents and a private interface that allows 
this variable to be set and checked.  (This variable is not used thread 
safely.  `LONG refs` is thread safe, but `struct list orphans` is not.)


thanks,
Jefferson
-------------- next part --------------
From 1540e4a9ea83d31a7d9474af7aaac1dbc42a9165 Mon Sep 17 00:00:00 2001
From: Jefferson Carpenter <jeffersoncarpenter2 at gmail.com>
Date: Sat, 10 Apr 2021 03:26:06 +0000
Subject: [PATCH] msxml3: Handle SAX tags and characters for mxwriter domdoc.

Signed-off-by: Jefferson Carpenter <jeffersoncarpenter2 at gmail.com>
---
 dlls/msxml3/domdoc.c          |  63 ++++
 dlls/msxml3/msxml_private.h   |   2 +
 dlls/msxml3/mxwriter.c        | 624 ++++++++++++++++++++++++++--------
 dlls/msxml3/node.c            |   6 +
 dlls/msxml3/tests/saxreader.c | 145 ++++++--
 include/xmldom.idl            |  15 +
 6 files changed, 683 insertions(+), 172 deletions(-)

diff --git a/dlls/msxml3/domdoc.c b/dlls/msxml3/domdoc.c
index 80c32e9ba99..3c68b3d177b 100644
--- a/dlls/msxml3/domdoc.c
+++ b/dlls/msxml3/domdoc.c
@@ -125,6 +125,7 @@ struct domdoc
     IPersistStreamInit        IPersistStreamInit_iface;
     IObjectWithSite           IObjectWithSite_iface;
     IObjectSafety             IObjectSafety_iface;
+    IWineXMLDOMDocumentLock   IWineXMLDOMDocumentLock_iface;
     IConnectionPointContainer IConnectionPointContainer_iface;
     LONG ref;
     VARIANT_BOOL async;
@@ -209,6 +210,7 @@ typedef struct _xmldoc_priv {
     LONG refs;
     struct list orphans;
     domdoc_properties* properties;
+    BOOL locked;
 } xmldoc_priv;
 
 typedef struct _orphan_entry {
@@ -281,6 +283,7 @@ static xmldoc_priv * create_priv(void)
         priv->refs = 0;
         list_init( &priv->orphans );
         priv->properties = NULL;
+        priv->locked = 0;
     }
 
     return priv;
@@ -668,6 +671,16 @@ HRESULT xmldoc_remove_orphan(xmlDocPtr doc, xmlNodePtr node)
     return S_FALSE;
 }
 
+BOOL xmldoc_get_locked(xmlDocPtr doc)
+{
+    return priv_from_xmlDocPtr(doc)->locked;
+}
+
+void xmldoc_put_locked(xmlDocPtr doc, BOOL put_locked)
+{
+    priv_from_xmlDocPtr(doc)->locked = put_locked;
+}
+
 static inline xmlDocPtr get_doc( domdoc *This )
 {
     return This->node.node->doc;
@@ -716,6 +729,11 @@ static inline domdoc *impl_from_IObjectSafety(IObjectSafety *iface)
     return CONTAINING_RECORD(iface, domdoc, IObjectSafety_iface);
 }
 
+static inline domdoc *impl_from_IWineXMLDOMDocumentLock(IWineXMLDOMDocumentLock *iface)
+{
+    return CONTAINING_RECORD(iface, domdoc, IWineXMLDOMDocumentLock_iface);
+}
+
 static inline domdoc *impl_from_IConnectionPointContainer(IConnectionPointContainer *iface)
 {
     return CONTAINING_RECORD(iface, domdoc, IConnectionPointContainer_iface);
@@ -924,6 +942,10 @@ static HRESULT WINAPI domdoc_QueryInterface( IXMLDOMDocument3 *iface, REFIID rii
     {
         *ppvObject = &This->IObjectSafety_iface;
     }
+    else if (IsEqualGUID(&IID_IWineXMLDOMDocumentLock, riid))
+    {
+        *ppvObject = &This->IWineXMLDOMDocumentLock_iface;
+    }
     else if( IsEqualGUID( riid, &IID_ISupportErrorInfo ))
     {
         return node_create_supporterrorinfo(domdoc_se_tids, ppvObject);
@@ -1694,6 +1716,8 @@ static HRESULT WINAPI domdoc_createElement(
 
     TRACE("(%p)->(%s %p)\n", This, debugstr_w(tagname), element);
 
+    if (xmldoc_get_locked(get_doc(This))) return E_FAIL;
+
     if (!element || !tagname) return E_INVALIDARG;
 
     V_VT(&type) = VT_I1;
@@ -3653,6 +3677,44 @@ static const IObjectSafetyVtbl domdocObjectSafetyVtbl = {
     domdoc_Safety_SetInterfaceSafetyOptions
 };
 
+static HRESULT WINAPI domdoc_Lock_QueryInterface(IWineXMLDOMDocumentLock *iface, REFIID riid, void **ppv)
+{
+    domdoc *This = impl_from_IWineXMLDOMDocumentLock(iface);
+    return IXMLDOMDocument3_QueryInterface(&This->IXMLDOMDocument3_iface, riid, ppv);
+}
+
+static ULONG WINAPI domdoc_Lock_AddRef(IWineXMLDOMDocumentLock *iface)
+{
+    domdoc *This = impl_from_IWineXMLDOMDocumentLock(iface);
+    return IXMLDOMDocument3_AddRef(&This->IXMLDOMDocument3_iface);
+}
+
+static ULONG WINAPI domdoc_Lock_Release(IWineXMLDOMDocumentLock *iface)
+{
+    domdoc *This = impl_from_IWineXMLDOMDocumentLock(iface);
+    return IXMLDOMDocument3_Release(&This->IXMLDOMDocument3_iface);
+}
+
+static void WINAPI domdoc_Lock_put_locked(IWineXMLDOMDocumentLock *iface, BOOL put_locked)
+{
+    domdoc *This = impl_from_IWineXMLDOMDocumentLock(iface);
+    xmldoc_put_locked(get_doc(This), put_locked);
+}
+
+static void WINAPI domdoc_Lock_get_locked(IWineXMLDOMDocumentLock *iface, BOOL *get_locked)
+{
+    domdoc *This = impl_from_IWineXMLDOMDocumentLock(iface);
+    *get_locked = xmldoc_get_locked(get_doc(This));
+}
+
+static const IWineXMLDOMDocumentLockVtbl domdocWineLockVtbl = {
+    domdoc_Lock_QueryInterface,
+    domdoc_Lock_AddRef,
+    domdoc_Lock_Release,
+    domdoc_Lock_put_locked,
+    domdoc_Lock_get_locked,
+};
+
 static const tid_t domdoc_iface_tids[] = {
     IXMLDOMDocument3_tid,
     0
@@ -3677,6 +3739,7 @@ HRESULT get_domdoc_from_xmldoc(xmlDocPtr xmldoc, IXMLDOMDocument3 **document)
     doc->IPersistStreamInit_iface.lpVtbl = &xmldoc_IPersistStreamInit_VTable;
     doc->IObjectWithSite_iface.lpVtbl = &domdocObjectSite;
     doc->IObjectSafety_iface.lpVtbl = &domdocObjectSafetyVtbl;
+    doc->IWineXMLDOMDocumentLock_iface.lpVtbl = &domdocWineLockVtbl;
     doc->IConnectionPointContainer_iface.lpVtbl = &ConnectionPointContainerVtbl;
     doc->ref = 1;
     doc->async = VARIANT_TRUE;
diff --git a/dlls/msxml3/msxml_private.h b/dlls/msxml3/msxml_private.h
index a59e00bf2b3..109c98195f6 100644
--- a/dlls/msxml3/msxml_private.h
+++ b/dlls/msxml3/msxml_private.h
@@ -285,6 +285,8 @@ extern void xmlnode_release(xmlNodePtr node) DECLSPEC_HIDDEN;
 extern int xmlnode_get_inst_cnt( xmlnode *node ) DECLSPEC_HIDDEN;
 extern HRESULT xmldoc_add_orphan( xmlDocPtr doc, xmlNodePtr node ) DECLSPEC_HIDDEN;
 extern HRESULT xmldoc_remove_orphan( xmlDocPtr doc, xmlNodePtr node ) DECLSPEC_HIDDEN;
+extern BOOL xmldoc_get_locked( xmlDocPtr doc ) DECLSPEC_HIDDEN;
+extern void xmldoc_put_locked( xmlDocPtr doc, BOOL put_locked ) DECLSPEC_HIDDEN;
 extern void xmldoc_link_xmldecl(xmlDocPtr doc, xmlNodePtr node) DECLSPEC_HIDDEN;
 extern xmlNodePtr xmldoc_unlink_xmldecl(xmlDocPtr doc) DECLSPEC_HIDDEN;
 extern MSXML_VERSION xmldoc_version( xmlDocPtr doc ) DECLSPEC_HIDDEN;
diff --git a/dlls/msxml3/mxwriter.c b/dlls/msxml3/mxwriter.c
index ac3c0fa59c3..2a5f7202776 100644
--- a/dlls/msxml3/mxwriter.c
+++ b/dlls/msxml3/mxwriter.c
@@ -126,6 +126,29 @@ static const struct xml_encoding_data xml_encoding_map[] = {
     { windows_1258W,XmlEncoding_windows_1258, 1258 }
 };
 
+typedef struct _mxwriter mxwriter;
+
+typedef HRESULT (*writer_start_document)(mxwriter *writer);
+typedef HRESULT (*writer_end_document)(mxwriter *writer);
+typedef HRESULT (*writer_start_tag_string_len)(mxwriter *writer, const WCHAR *qname, int len);
+typedef HRESULT (*writer_start_tag_bstr)(mxwriter *writer, BSTR name);
+typedef HRESULT (*writer_end_tag)(mxwriter *writer, const WCHAR *qname, int len);
+typedef void (*writer_attribute)(mxwriter *writer, const WCHAR *qname, int qname_len,
+                                   const WCHAR *value, int value_len, BOOL escape);
+typedef HRESULT (*writer_characters)(mxwriter *writer, const WCHAR *chars, int nchars);
+typedef HRESULT (*writer_ignorable_whitespace)(mxwriter *writer, const WCHAR *chars, int nchars);
+
+typedef struct {
+    writer_start_document         start_document;
+    writer_end_document           end_document;
+    writer_start_tag_string_len   start_tag_string_len;
+    writer_start_tag_bstr         start_tag_bstr;
+    writer_end_tag                end_tag;
+    writer_attribute              attribute;
+    writer_characters             characters;
+    writer_ignorable_whitespace   ignorable_whitespace;
+} writer_sax_handlers;
+
 typedef enum
 {
     MXWriter_BOM = 0,
@@ -158,7 +181,7 @@ typedef struct
     struct list blocks; /* only used when output was not set, for BSTR case */
 } output_buffer;
 
-typedef struct
+typedef struct _mxwriter
 {
     DispatchEx dispex;
     IMXWriter IMXWriter_iface;
@@ -193,8 +216,16 @@ typedef struct
        we don't have to close */
     BSTR element;
 
+    writer_sax_handlers handlers;
+
+    /* Stream output */
     IStream *dest;
 
+    /* DOMDocument output */
+    IXMLDOMDocument *dest_doc;
+    IXMLDOMNode *current_node;
+    IWineXMLDOMDocumentLock *doc_lock;
+
     output_buffer buffer;
 } mxwriter;
 
@@ -503,6 +534,32 @@ static void close_output_buffer(mxwriter *writer)
     list_init(&writer->buffer.blocks);
 }
 
+static HRESULT coalesce_output_buffer(mxwriter *writer, BSTR *dest) {
+    encoded_buffer *buff;
+    char *dest_ptr;
+
+    *dest = SysAllocStringLen(NULL, writer->buffer.utf16_total / sizeof(WCHAR));
+    if (!*dest)
+        return E_OUTOFMEMORY;
+
+    dest_ptr = (char*)*dest;
+    buff = &writer->buffer.encoded;
+
+    if (buff->written)
+    {
+        memcpy(dest_ptr, buff->data, buff->written);
+        dest_ptr += buff->written;
+    }
+
+    LIST_FOR_EACH_ENTRY(buff, &writer->buffer.blocks, encoded_buffer, entry)
+    {
+        memcpy(dest_ptr, buff->data, buff->written);
+        dest_ptr += buff->written;
+    }
+
+    return S_OK;
+}
+
 /* Escapes special characters like:
    '<' -> "<"
    '&' -> "&"
@@ -707,6 +764,303 @@ static HRESULT writer_get_property(const mxwriter *writer, mxwriter_prop propert
     return S_OK;
 }
 
+static HRESULT writer_start_document_stream(mxwriter *writer) {
+
+    /* If properties have been changed since the last "endDocument" call
+     * we need to reset the output buffer. If we don't the output buffer
+     * could end up with multiple XML documents in it, plus this seems to
+     * be how Windows works.
+     */
+    if (writer->prop_changed) {
+        close_output_buffer(writer);
+        writer->prop_changed = FALSE;
+    }
+
+    if (writer->props[MXWriter_OmitXmlDecl] == VARIANT_TRUE) return S_OK;
+
+    write_prolog_buffer(writer);
+
+    if (writer->dest && writer->xml_enc == XmlEncoding_UTF16) {
+        static const char utf16BOM[] = {0xff,0xfe};
+
+        if (writer->props[MXWriter_BOM] == VARIANT_TRUE)
+            /* Windows passes a NULL pointer as the pcbWritten parameter and
+             * ignores any error codes returned from this Write call.
+             */
+            IStream_Write(writer->dest, utf16BOM, sizeof(utf16BOM), NULL);
+    }
+
+    return S_OK;
+}
+
+static HRESULT writer_end_document_stream(mxwriter *writer) {
+    return flush_output_buffer(writer);
+}
+
+static HRESULT writer_start_tag_string_len_stream(mxwriter *writer, const WCHAR *qname, int len)
+{
+    static const WCHAR ltW[] = {'<'};
+
+    close_element_starttag(writer);
+    set_element_name(writer, qname ? qname : emptyW, qname ? len : 0);
+
+    write_node_indent(writer);
+
+    write_output_buffer(writer, ltW, 1);
+    write_output_buffer(writer, qname ? qname : emptyW, qname ? len : 0);
+    writer_inc_indent(writer);
+
+    return S_OK;
+}
+
+static HRESULT writer_start_tag_bstr_stream(mxwriter *writer, BSTR name)
+{
+    return writer_start_tag_string_len_stream(writer, name, SysStringLen(name));
+}
+
+static HRESULT writer_end_tag_stream(mxwriter *writer, const WCHAR *qname, int len)
+{
+    writer_dec_indent(writer);
+
+    if (writer->element)
+    {
+        static const WCHAR closeW[] = {'/','>'};
+        write_output_buffer(writer, closeW, 2);
+    }
+    else
+    {
+        static const WCHAR closetagW[] = {'<','/'};
+        static const WCHAR gtW[] = {'>'};
+
+        write_node_indent(writer);
+        write_output_buffer(writer, closetagW, 2);
+        write_output_buffer(writer, qname, len);
+        write_output_buffer(writer, gtW, 1);
+    }
+
+    set_element_name(writer, NULL, 0);
+    return S_OK;
+}
+
+static void writer_attribute_stream(mxwriter *writer, const WCHAR *qname, int qname_len,
+    const WCHAR *value, int value_len, BOOL escape)
+{
+    static const WCHAR eqW[] = {'='};
+
+    /* space separator in front of every attribute */
+    write_output_buffer(writer, spaceW, 1);
+    write_output_buffer(writer, qname, qname_len);
+    write_output_buffer(writer, eqW, 1);
+
+    if (escape)
+    {
+        WCHAR *escaped = get_escaped_string(value, EscapeValue, &value_len);
+        write_output_buffer_quoted(writer, escaped, value_len);
+        heap_free(escaped);
+    }
+    else
+        write_output_buffer_quoted(writer, value, value_len);
+}
+
+static HRESULT writer_characters_stream(mxwriter *writer, const WCHAR *chars, int nchars)
+{
+    close_element_starttag(writer);
+    set_element_name(writer, NULL, 0);
+
+    if (!writer->cdata)
+        writer->text = TRUE;
+
+    if (nchars)
+    {
+        if (writer->cdata || writer->props[MXWriter_DisableEscaping] == VARIANT_TRUE)
+            write_output_buffer(writer, chars, nchars);
+        else
+        {
+            int len = nchars;
+            WCHAR *escaped;
+
+            escaped = get_escaped_string(chars, EscapeText, &len);
+            write_output_buffer(writer, escaped, len);
+            heap_free(escaped);
+        }
+    }
+
+    return S_OK;
+}
+
+static HRESULT writer_ignorable_whitespace_stream(mxwriter *writer, const WCHAR *chars, int nchars)
+{
+    return write_output_buffer(writer, chars, nchars);
+}
+
+static const writer_sax_handlers writer_sax_handlers_stream =
+{
+    writer_start_document_stream,
+    writer_end_document_stream,
+    writer_start_tag_string_len_stream,
+    writer_start_tag_bstr_stream,
+    writer_end_tag_stream,
+    writer_attribute_stream,
+    writer_characters_stream,
+    writer_ignorable_whitespace_stream,
+};
+
+
+static HRESULT domdoc_maybe_write_chars(mxwriter *writer) {
+    HRESULT hr = S_OK;
+    IXMLDOMText *text_node = NULL;
+    BSTR text = NULL;
+
+    hr = coalesce_output_buffer(writer, &text);
+    if (FAILED(hr)) goto done;
+
+    if (!SysStringLen(text)) goto done;
+
+    hr = IXMLDOMDocument_createTextNode(writer->dest_doc, text, &text_node);
+    if (FAILED(hr)) goto done;
+
+    hr = IXMLDOMNode_appendChild(writer->current_node, (IXMLDOMNode*)text_node, NULL);
+    if (FAILED(hr)) goto done;
+
+done:
+    if (text_node) IXMLDOMText_Release(text_node);
+    if (text) SysFreeString(text);
+
+    /* reset character buffer */
+    FIXME("here\n");
+    close_output_buffer(writer);
+
+    return hr;
+}
+
+static HRESULT writer_start_document_domdoc(mxwriter *writer) {
+    HRESULT hr = S_OK;
+    IXMLDOMElement *clear_document_element = NULL;
+    BOOL locked;
+
+    IWineXMLDOMDocumentLock_get_locked(writer->doc_lock, &locked);
+    if (locked) {
+        IWineXMLDOMDocumentLock_put_locked(writer->doc_lock, 0);
+        return E_FAIL;
+    }
+
+    hr = IXMLDOMDocument_get_documentElement(writer->dest_doc, &clear_document_element);
+    if (FAILED(hr)) {
+        ERR("Could not get document element from mxwriter domdoc output\n");
+        goto done;
+    }
+
+    if (clear_document_element) {
+        hr = IXMLDOMDocument_removeChild(writer->dest_doc, (IXMLDOMNode *)clear_document_element, NULL);
+
+        IXMLDOMElement_Release(clear_document_element);
+
+        if (FAILED(hr)) {
+            ERR("Could not remove document element from mxwriter domdoc output\n");
+            goto done;
+        }
+    }
+
+    IWineXMLDOMDocumentLock_put_locked(writer->doc_lock, 1);
+
+done:
+    return S_OK;
+}
+
+static HRESULT writer_end_document_domdoc(mxwriter *writer) {
+    IWineXMLDOMDocumentLock_put_locked(writer->doc_lock, 0);
+    return S_OK;
+}
+
+static HRESULT writer_start_tag_bstr_domdoc(mxwriter *writer, BSTR name)
+{
+    HRESULT hr;
+    IXMLDOMElement *element = NULL;
+
+    IWineXMLDOMDocumentLock_put_locked(writer->doc_lock, 0);
+
+    hr = domdoc_maybe_write_chars(writer);
+    if (FAILED(hr)) goto done;
+
+    hr = IXMLDOMDocument_createElement(writer->dest_doc, name, &element);
+    if (FAILED(hr)) goto done;
+
+    hr = IXMLDOMNode_appendChild(writer->current_node, (IXMLDOMNode*)element, NULL);
+    if (FAILED(hr)) goto done;
+
+    if (writer->current_node) IXMLDOMNode_Release(writer->current_node);
+    hr = IXMLDOMElement_QueryInterface(element, &IID_IXMLDOMNode, (void**)&writer->current_node);
+    if (FAILED(hr)) goto done;
+
+done:
+    IWineXMLDOMDocumentLock_put_locked(writer->doc_lock, 1);
+    if (element) IXMLDOMElement_Release(element);
+    return hr;
+}
+
+static HRESULT writer_start_tag_string_len_domdoc(mxwriter *writer, const WCHAR *qname, int len)
+{
+    HRESULT hr;
+    BSTR tag_name = SysAllocStringLen(qname, len);
+
+    if (!tag_name) {
+        return E_OUTOFMEMORY;
+    }
+
+    hr = writer_start_tag_bstr_domdoc(writer, tag_name);
+
+    SysFreeString(tag_name);
+    return hr;
+}
+
+static HRESULT writer_end_tag_domdoc(mxwriter *writer, const WCHAR *qname, int len) {
+    HRESULT hr;
+    IXMLDOMNode *parent_node;
+
+    IWineXMLDOMDocumentLock_put_locked(writer->doc_lock, 0);
+
+    hr = domdoc_maybe_write_chars(writer);
+    if (FAILED(hr)) return hr;
+
+    IWineXMLDOMDocumentLock_put_locked(writer->doc_lock, 1);
+
+    hr = IXMLDOMNode_get_parentNode(writer->current_node, &parent_node);
+    if (FAILED(hr)) return hr;
+
+    IXMLDOMNode_Release(writer->current_node);
+    writer->current_node = parent_node;
+    return hr;
+}
+
+static void writer_attribute_domdoc(mxwriter *writer, const WCHAR *qname, int qname_len,
+    const WCHAR *value, int value_len, BOOL escape)
+{
+    FIXME("not implemented: domdoc destination attributes\n");
+}
+
+static HRESULT writer_characters_domdoc(mxwriter *writer, const WCHAR *chars, int nchars)
+{
+    return write_output_buffer(writer, chars, nchars);
+}
+
+static HRESULT writer_ignorable_whitespace_domdoc(mxwriter *writer, const WCHAR *chars, int nchars)
+{
+    return S_OK;
+}
+
+static const writer_sax_handlers writer_sax_handlers_domdoc =
+{
+    writer_start_document_domdoc,
+    writer_end_document_domdoc,
+    writer_start_tag_string_len_domdoc,
+    writer_start_tag_bstr_domdoc,
+    writer_end_tag_domdoc,
+    writer_attribute_domdoc,
+    writer_characters_domdoc,
+    writer_ignorable_whitespace_domdoc,
+};
+
+
 static inline mxwriter *impl_from_IMXWriter(IMXWriter *iface)
 {
     return CONTAINING_RECORD(iface, mxwriter, IMXWriter_iface);
@@ -855,6 +1209,8 @@ static ULONG WINAPI mxwriter_Release(IMXWriter *iface)
         free_output_buffer(&This->buffer);
 
         if (This->dest) IStream_Release(This->dest);
+        if (This->dest_doc) IXMLDOMDocument_Release(This->dest_doc);
+        if (This->current_node) IXMLDOMNode_Release(This->current_node);
         SysFreeString(This->version);
         SysFreeString(This->encoding);
 
@@ -902,6 +1258,23 @@ static HRESULT WINAPI mxwriter_Invoke(
         dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
 }
 
+static void mxwriter_free_output(mxwriter *writer) {
+    if (writer->dest) IStream_Release(writer->dest);
+    if (writer->dest_doc) IXMLDOMDocument_Release(writer->dest_doc);
+    if (writer->current_node) IXMLDOMNode_Release(writer->current_node);
+    if (writer->doc_lock) {
+        IWineXMLDOMDocumentLock_put_locked(writer->doc_lock, 0);
+        IWineXMLDOMDocumentLock_Release(writer->doc_lock);
+    }
+    writer->dest = NULL;
+    writer->dest_doc = NULL;
+    writer->current_node = NULL;
+    writer->doc_lock = NULL;
+
+    /* Recreate the output buffer to make sure it's using the correct encoding. */
+    close_output_buffer(writer);
+}
+
 static HRESULT WINAPI mxwriter_put_output(IMXWriter *iface, VARIANT dest)
 {
     mxwriter *This = impl_from_IMXWriter( iface );
@@ -917,9 +1290,8 @@ static HRESULT WINAPI mxwriter_put_output(IMXWriter *iface, VARIANT dest)
     {
     case VT_EMPTY:
     {
-        if (This->dest) IStream_Release(This->dest);
-        This->dest = NULL;
-        close_output_buffer(This);
+        mxwriter_free_output(This);
+        This->handlers = writer_sax_handlers_stream;
         break;
     }
     case VT_UNKNOWN:
@@ -929,17 +1301,51 @@ static HRESULT WINAPI mxwriter_put_output(IMXWriter *iface, VARIANT dest)
         hr = IUnknown_QueryInterface(V_UNKNOWN(&dest), &IID_IStream, (void**)&stream);
         if (hr == S_OK)
         {
-            /* Recreate the output buffer to make sure it's using the correct encoding. */
-            close_output_buffer(This);
-
-            if (This->dest) IStream_Release(This->dest);
+            mxwriter_free_output(This);
             This->dest = stream;
+            This->handlers = writer_sax_handlers_stream;
             break;
         }
 
         FIXME("unhandled interface type for VT_UNKNOWN destination\n");
         return E_NOTIMPL;
     }
+    case VT_DISPATCH:
+    {
+        IXMLDOMDocument         *document = NULL;
+        IXMLDOMNode             *node = NULL;
+        IWineXMLDOMDocumentLock *lock = NULL;
+
+        hr = IDispatch_QueryInterface(V_DISPATCH(&dest), &IID_IXMLDOMDocument, (void**)&document);
+        if (hr == S_OK) {
+            hr = IXMLDOMDocument_QueryInterface(document, &IID_IXMLDOMNode, (void**)&node);
+            if (FAILED(hr)) {
+                ERR("Could not query IXMLDOMDocument mxwriter domdoc output for IXMLDOMNode\n");
+
+                IXMLDOMDocument_Release(document);
+                return hr;
+            }
+
+            hr = IXMLDOMDocument_QueryInterface(document, &IID_IWineXMLDOMDocumentLock, (void**)&lock);
+            if (FAILED(hr)) {
+                FIXME("Could not query IXMLDOMDocument mxwriter domdoc output for IWineXMLDOMDocumentLock\n");
+
+                IXMLDOMDocument_Release(document);
+                IXMLDOMNode_Release(node);
+                return hr;
+            }
+
+            mxwriter_free_output(This);
+            This->dest_doc = document;
+            This->current_node = node;
+            This->doc_lock = lock;
+            This->handlers = writer_sax_handlers_domdoc;
+            break;
+        }
+
+        FIXME("unhandled interface type for VT_DISPATCH destination\n");
+        return E_NOTIMPL;
+    }
     default:
         FIXME("unhandled destination type %s\n", debugstr_variant(&dest));
         return E_NOTIMPL;
@@ -963,10 +1369,12 @@ static HRESULT WINAPI mxwriter_get_output(IMXWriter *iface, VARIANT *dest)
         V_UNKNOWN(dest) = (IUnknown*)This->dest;
         IStream_AddRef(This->dest);
     }
+    else if (This->dest_doc) {
+        FIXME("not implemented for domdoc: (%p)->(%p)\n", This, dest);
+        return E_NOTIMPL;
+    }
     else
     {
-        encoded_buffer *buff;
-        char *dest_ptr;
         HRESULT hr;
 
         hr = flush_output_buffer(This);
@@ -974,24 +1382,7 @@ static HRESULT WINAPI mxwriter_get_output(IMXWriter *iface, VARIANT *dest)
             return hr;
 
         V_VT(dest)   = VT_BSTR;
-        V_BSTR(dest) = SysAllocStringLen(NULL, This->buffer.utf16_total / sizeof(WCHAR));
-        if (!V_BSTR(dest))
-            return E_OUTOFMEMORY;
-
-        dest_ptr = (char*)V_BSTR(dest);
-        buff = &This->buffer.encoded;
-
-        if (buff->written)
-        {
-            memcpy(dest_ptr, buff->data, buff->written);
-            dest_ptr += buff->written;
-        }
-
-        LIST_FOR_EACH_ENTRY(buff, &This->buffer.blocks, encoded_buffer, entry)
-        {
-            memcpy(dest_ptr, buff->data, buff->written);
-            dest_ptr += buff->written;
-        }
+        return coalesce_output_buffer(This, &V_BSTR(dest));
     }
 
     return S_OK;
@@ -1215,31 +1606,7 @@ static HRESULT WINAPI SAXContentHandler_startDocument(ISAXContentHandler *iface)
 
     TRACE("(%p)\n", This);
 
-    /* If properties have been changed since the last "endDocument" call
-     * we need to reset the output buffer. If we don't the output buffer
-     * could end up with multiple XML documents in it, plus this seems to
-     * be how Windows works.
-     */
-    if (This->prop_changed) {
-        close_output_buffer(This);
-        This->prop_changed = FALSE;
-    }
-
-    if (This->props[MXWriter_OmitXmlDecl] == VARIANT_TRUE) return S_OK;
-
-    write_prolog_buffer(This);
-
-    if (This->dest && This->xml_enc == XmlEncoding_UTF16) {
-        static const char utf16BOM[] = {0xff,0xfe};
-
-        if (This->props[MXWriter_BOM] == VARIANT_TRUE)
-            /* Windows passes a NULL pointer as the pcbWritten parameter and
-             * ignores any error codes returned from this Write call.
-             */
-            IStream_Write(This->dest, utf16BOM, sizeof(utf16BOM), NULL);
-    }
-
-    return S_OK;
+    return This->handlers.start_document(This);
 }
 
 static HRESULT WINAPI SAXContentHandler_endDocument(ISAXContentHandler *iface)
@@ -1247,7 +1614,7 @@ static HRESULT WINAPI SAXContentHandler_endDocument(ISAXContentHandler *iface)
     mxwriter *This = impl_from_ISAXContentHandler( iface );
     TRACE("(%p)\n", This);
     This->prop_changed = FALSE;
-    return flush_output_buffer(This);
+    return This->handlers.end_document(This);
 }
 
 static HRESULT WINAPI SAXContentHandler_startPrefixMapping(
@@ -1272,40 +1639,6 @@ static HRESULT WINAPI SAXContentHandler_endPrefixMapping(
     return S_OK;
 }
 
-static void mxwriter_write_attribute(mxwriter *writer, const WCHAR *qname, int qname_len,
-    const WCHAR *value, int value_len, BOOL escape)
-{
-    static const WCHAR eqW[] = {'='};
-
-    /* space separator in front of every attribute */
-    write_output_buffer(writer, spaceW, 1);
-    write_output_buffer(writer, qname, qname_len);
-    write_output_buffer(writer, eqW, 1);
-
-    if (escape)
-    {
-        WCHAR *escaped = get_escaped_string(value, EscapeValue, &value_len);
-        write_output_buffer_quoted(writer, escaped, value_len);
-        heap_free(escaped);
-    }
-    else
-        write_output_buffer_quoted(writer, value, value_len);
-}
-
-static void mxwriter_write_starttag(mxwriter *writer, const WCHAR *qname, int len)
-{
-    static const WCHAR ltW[] = {'<'};
-
-    close_element_starttag(writer);
-    set_element_name(writer, qname ? qname : emptyW, qname ? len : 0);
-
-    write_node_indent(writer);
-
-    write_output_buffer(writer, ltW, 1);
-    write_output_buffer(writer, qname ? qname : emptyW, qname ? len : 0);
-    writer_inc_indent(writer);
-}
-
 static HRESULT WINAPI SAXContentHandler_startElement(
     ISAXContentHandler *iface,
     const WCHAR *namespaceUri,
@@ -1316,6 +1649,7 @@ static HRESULT WINAPI SAXContentHandler_startElement(
     int nQName,
     ISAXAttributes *attr)
 {
+    HRESULT hr;
     mxwriter *This = impl_from_ISAXContentHandler( iface );
 
     TRACE("(%p)->(%s %s %s %p)\n", This, debugstr_wn(namespaceUri, nnamespaceUri),
@@ -1325,7 +1659,8 @@ static HRESULT WINAPI SAXContentHandler_startElement(
         (nQName == -1 && This->class_version == MSXML6))
         return E_INVALIDARG;
 
-    mxwriter_write_starttag(This, QName, nQName);
+    hr = This->handlers.start_tag_string_len(This, QName, nQName);
+    if (FAILED(hr)) return hr;
 
     if (attr)
     {
@@ -1349,7 +1684,7 @@ static HRESULT WINAPI SAXContentHandler_startElement(
             hr = ISAXAttributes_getValue(attr, i, &value, &value_len);
             if (FAILED(hr)) return hr;
 
-            mxwriter_write_attribute(This, qname, qname_len, value, value_len, escape);
+            This->handlers.attribute(This, qname, qname_len, value, value_len, escape);
         }
     }
 
@@ -1374,27 +1709,7 @@ static HRESULT WINAPI SAXContentHandler_endElement(
          (nQName == -1 && This->class_version == MSXML6))
         return E_INVALIDARG;
 
-    writer_dec_indent(This);
-
-    if (This->element)
-    {
-        static const WCHAR closeW[] = {'/','>'};
-        write_output_buffer(This, closeW, 2);
-    }
-    else
-    {
-        static const WCHAR closetagW[] = {'<','/'};
-        static const WCHAR gtW[] = {'>'};
-
-        write_node_indent(This);
-        write_output_buffer(This, closetagW, 2);
-        write_output_buffer(This, QName, nQName);
-        write_output_buffer(This, gtW, 1);
-    }
-
-    set_element_name(This, NULL, 0);
-
-    return S_OK;
+    return This->handlers.end_tag(This, QName, nQName);
 }
 
 static HRESULT WINAPI SAXContentHandler_characters(
@@ -1408,28 +1723,7 @@ static HRESULT WINAPI SAXContentHandler_characters(
 
     if (!chars) return E_INVALIDARG;
 
-    close_element_starttag(This);
-    set_element_name(This, NULL, 0);
-
-    if (!This->cdata)
-        This->text = TRUE;
-
-    if (nchars)
-    {
-        if (This->cdata || This->props[MXWriter_DisableEscaping] == VARIANT_TRUE)
-            write_output_buffer(This, chars, nchars);
-        else
-        {
-            int len = nchars;
-            WCHAR *escaped;
-
-            escaped = get_escaped_string(chars, EscapeText, &len);
-            write_output_buffer(This, escaped, len);
-            heap_free(escaped);
-        }
-    }
-
-    return S_OK;
+    return This->handlers.characters(This, chars, nchars);
 }
 
 static HRESULT WINAPI SAXContentHandler_ignorableWhitespace(
@@ -1443,9 +1737,7 @@ static HRESULT WINAPI SAXContentHandler_ignorableWhitespace(
 
     if (!chars) return E_INVALIDARG;
 
-    write_output_buffer(This, chars, nchars);
-
-    return S_OK;
+    return This->handlers.ignorable_whitespace(This, chars, nchars);
 }
 
 static HRESULT WINAPI SAXContentHandler_processingInstruction(
@@ -1543,6 +1835,11 @@ static HRESULT WINAPI SAXLexicalHandler_startDTD(ISAXLexicalHandler *iface,
 
     if (!name) return E_INVALIDARG;
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_output_buffer(This, doctypeW, ARRAY_SIZE(doctypeW));
 
     if (*name)
@@ -1586,6 +1883,11 @@ static HRESULT WINAPI SAXLexicalHandler_endDTD(ISAXLexicalHandler *iface)
 
     TRACE("(%p)\n", This);
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_output_buffer(This, closedtdW, ARRAY_SIZE(closedtdW));
 
     return S_OK;
@@ -1612,6 +1914,11 @@ static HRESULT WINAPI SAXLexicalHandler_startCDATA(ISAXLexicalHandler *iface)
 
     TRACE("(%p)\n", This);
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_node_indent(This);
     write_output_buffer(This, scdataW, ARRAY_SIZE(scdataW));
     This->cdata = TRUE;
@@ -1626,6 +1933,11 @@ static HRESULT WINAPI SAXLexicalHandler_endCDATA(ISAXLexicalHandler *iface)
 
     TRACE("(%p)\n", This);
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_output_buffer(This, ecdataW, ARRAY_SIZE(ecdataW));
     This->cdata = FALSE;
 
@@ -1642,6 +1954,11 @@ static HRESULT WINAPI SAXLexicalHandler_comment(ISAXLexicalHandler *iface, const
 
     if (!chars) return E_INVALIDARG;
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     close_element_starttag(This);
     write_node_indent(This);
 
@@ -1698,6 +2015,11 @@ static HRESULT WINAPI SAXDeclHandler_elementDecl(ISAXDeclHandler *iface,
 
     if (!name || !model) return E_INVALIDARG;
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_output_buffer(This, elementW, ARRAY_SIZE(elementW));
     if (n_name) {
         write_output_buffer(This, name, n_name);
@@ -1723,6 +2045,11 @@ static HRESULT WINAPI SAXDeclHandler_attributeDecl(ISAXDeclHandler *iface,
         debugstr_wn(attr, n_attr), n_attr, debugstr_wn(type, n_type), n_type, debugstr_wn(Default, n_default), n_default,
         debugstr_wn(value, n_value), n_value);
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_output_buffer(This, attlistW, ARRAY_SIZE(attlistW));
     if (n_element) {
         write_output_buffer(This, element, n_element);
@@ -1762,6 +2089,11 @@ static HRESULT WINAPI SAXDeclHandler_internalEntityDecl(ISAXDeclHandler *iface,
 
     if (!name || !value) return E_INVALIDARG;
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_output_buffer(This, entityW, ARRAY_SIZE(entityW));
     if (n_name) {
         write_output_buffer(This, name, n_name);
@@ -1787,6 +2119,11 @@ static HRESULT WINAPI SAXDeclHandler_externalEntityDecl(ISAXDeclHandler *iface,
 
     if (!name || !systemId) return E_INVALIDARG;
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_output_buffer(This, entityW, ARRAY_SIZE(entityW));
     if (n_name) {
         write_output_buffer(This, name, n_name);
@@ -2155,6 +2492,7 @@ static HRESULT WINAPI VBSAXContentHandler_endPrefixMapping(IVBSAXContentHandler
 static HRESULT WINAPI VBSAXContentHandler_startElement(IVBSAXContentHandler *iface,
     BSTR *namespaceURI, BSTR *localName, BSTR *QName, IVBSAXAttributes *attrs)
 {
+    HRESULT hr;
     mxwriter *This = impl_from_IVBSAXContentHandler( iface );
 
     TRACE("(%p)->(%p %p %p %p)\n", This, namespaceURI, localName, QName, attrs);
@@ -2164,7 +2502,8 @@ static HRESULT WINAPI VBSAXContentHandler_startElement(IVBSAXContentHandler *ifa
 
     TRACE("(%s %s %s)\n", debugstr_w(*namespaceURI), debugstr_w(*localName), debugstr_w(*QName));
 
-    mxwriter_write_starttag(This, *QName, SysStringLen(*QName));
+    hr = This->handlers.start_tag_bstr(This, *QName);
+    if (FAILED(hr)) return hr;
 
     if (attrs)
     {
@@ -2191,7 +2530,7 @@ static HRESULT WINAPI VBSAXContentHandler_startElement(IVBSAXContentHandler *ifa
                 return hr;
             }
 
-            mxwriter_write_attribute(This, qname, SysStringLen(qname), value, SysStringLen(value), escape);
+            This->handlers.attribute(This, qname, SysStringLen(qname), value, SysStringLen(value), escape);
             SysFreeString(qname);
             SysFreeString(value);
         }
@@ -2318,6 +2657,11 @@ static HRESULT WINAPI SAXDTDHandler_notationDecl(ISAXDTDHandler *iface,
     if (!name || !n_name)
         return E_INVALIDARG;
 
+    if (This->dest_doc) {
+        FIXME("not implemented for domdoc destination\n");
+        return E_NOTIMPL;
+    }
+
     write_output_buffer(This, notationW, ARRAY_SIZE(notationW));
     write_output_buffer(This, name, n_name);
 
@@ -2638,12 +2982,16 @@ HRESULT MXWriter_create(MSXML_VERSION version, void **ppObj)
     This->xml_enc  = XmlEncoding_UTF16;
 
     This->element = NULL;
+    This->handlers = writer_sax_handlers_stream;
     This->cdata = FALSE;
     This->indent = 0;
     This->text = FALSE;
     This->newline = FALSE;
 
     This->dest = NULL;
+    This->dest_doc = NULL;
+    This->current_node = NULL;
+    This->doc_lock = NULL;
 
     hr = init_output_buffer(This->xml_enc, &This->buffer);
     if (hr != S_OK) {
diff --git a/dlls/msxml3/node.c b/dlls/msxml3/node.c
index 67f322eb0e5..9cbb2305830 100644
--- a/dlls/msxml3/node.c
+++ b/dlls/msxml3/node.c
@@ -457,6 +457,8 @@ HRESULT node_insert_before(xmlnode *This, IXMLDOMNode *new_child, const VARIANT
     if(!new_child)
         return E_INVALIDARG;
 
+    if (xmldoc_get_locked(This->node->doc)) return E_FAIL;
+
     node_obj = get_node_obj(new_child);
     if(!node_obj) return E_FAIL;
 
@@ -569,6 +571,8 @@ HRESULT node_replace_child(xmlnode *This, IXMLDOMNode *newChild, IXMLDOMNode *ol
     if(!newChild || !oldChild)
         return E_INVALIDARG;
 
+    if (xmldoc_get_locked(This->node->doc)) return E_FAIL;
+
     if(ret)
         *ret = NULL;
 
@@ -627,6 +631,8 @@ HRESULT node_remove_child(xmlnode *This, IXMLDOMNode* child, IXMLDOMNode** oldCh
 
     if(!child) return E_INVALIDARG;
 
+    if (xmldoc_get_locked(This->node->doc)) return E_FAIL;
+
     if(oldChild)
         *oldChild = NULL;
 
diff --git a/dlls/msxml3/tests/saxreader.c b/dlls/msxml3/tests/saxreader.c
index 35d0e71545b..9b97d4c9204 100644
--- a/dlls/msxml3/tests/saxreader.c
+++ b/dlls/msxml3/tests/saxreader.c
@@ -4367,6 +4367,87 @@ static void test_mxwriter_stream(void)
     free_bstrs();
 }
 
+static void test_mxwriter_domdoc_start_end_document(void)
+{
+    IXMLDOMDocument *domdoc;
+    IMXWriter *writer;
+    IMXWriter *writer2;
+    ISAXContentHandler *content;
+    ISAXContentHandler *content2;
+    IXMLDOMElement *element;
+    HRESULT hr;
+    VARIANT dest;
+
+    /* Create both writers and attach DOMDocument output */
+    hr = CoCreateInstance(&CLSID_MXXMLWriter60, NULL, CLSCTX_INPROC_SERVER, &IID_IMXWriter, (void**)&writer);
+    ok(hr == S_OK, "Failed to create a writer, hr %#x.\n", hr);
+
+    hr = IMXWriter_QueryInterface(writer, &IID_ISAXContentHandler, (void**)&content);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
+    hr = CoCreateInstance(&CLSID_MXXMLWriter60, NULL, CLSCTX_INPROC_SERVER, &IID_IMXWriter, (void**)&writer2);
+    ok(hr == S_OK, "Failed to create a writer, hr %#x.\n", hr);
+
+    hr = IMXWriter_QueryInterface(writer, &IID_ISAXContentHandler, (void**)&content2);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
+    hr = CoCreateInstance(&CLSID_DOMDocument60, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument, (void **)&domdoc);
+    ok(hr == S_OK, "Failed to create a document, hr %#x.\n", hr);
+
+    V_VT(&dest) = VT_DISPATCH;
+    V_DISPATCH(&dest) = (IDispatch *)domdoc;
+
+    hr = IMXWriter_put_output(writer, dest);
+    ok(hr == S_OK, "Failed to set writer output, hr %#x.\n", hr);
+
+    hr = IMXWriter_put_output(writer2, dest);
+    ok(hr == S_OK, "Failed to set writer output, hr %#x.\n", hr);
+
+
+    /* After writer starts document, createElement may not be called. */
+    hr = ISAXContentHandler_startDocument(content);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
+    hr = IXMLDOMDocument_createElement(domdoc, _bstr_("TestElement"), &element);
+    ok(hr == E_FAIL, "Unexpected hr %#x.\n", hr);
+
+
+    /* After writer2 starts document, createElement may be called again. */
+    hr = ISAXContentHandler_startDocument(content2);
+    ok(hr == E_FAIL, "Unexpected hr %#x.\n", hr);
+
+    hr = IXMLDOMDocument_createElement(domdoc, _bstr_("TestElement"), &element);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+    ok(element != NULL, "Expected element.\n");
+    IXMLDOMElement_Release(element);
+
+
+    /* Two writers may work together to build DOM. */
+    hr = ISAXContentHandler_startDocument(content);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
+    hr = ISAXContentHandler_startElement(content2, L"", 0, L"", 0, L"BankAccount", 11, NULL);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
+    hr = IXMLDOMDocument_get_documentElement(domdoc, &element);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+    ok(element != NULL, "Expected document root.\n");
+    IXMLDOMElement_Release(element);
+
+    hr = ISAXContentHandler_endElement(content, L"", 0, L"", 0, L"BankAccount", 11);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
+    hr = ISAXContentHandler_endDocument(content2);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
+
+    IXMLDOMDocument_Release(domdoc);
+    ISAXContentHandler_Release(content2);
+    IMXWriter_Release(writer2);
+    ISAXContentHandler_Release(content);
+    IMXWriter_Release(writer);
+}
+
 static void test_mxwriter_domdoc(void)
 {
     ISAXContentHandler *content;
@@ -4377,6 +4458,7 @@ static void test_mxwriter_domdoc(void)
     IXMLDOMElement *root = NULL;
     IXMLDOMNodeList *node_list = NULL;
     IXMLDOMNode *node = NULL;
+    IXMLDOMNode *child_node = NULL;
     LONG list_length = 0;
     BSTR str;
 
@@ -4394,14 +4476,7 @@ static void test_mxwriter_domdoc(void)
     V_DISPATCH(&dest) = (IDispatch *)domdoc;
 
     hr = IMXWriter_put_output(writer, dest);
-todo_wine
     ok(hr == S_OK, "Failed to set writer output, hr %#x.\n", hr);
-    if (FAILED(hr))
-    {
-        IXMLDOMDocument_Release(domdoc);
-        IMXWriter_Release(writer);
-        return;
-    }
 
     /* Add root element to document. */
     hr = IXMLDOMDocument_createElement(domdoc, _bstr_("TestElement"), &root);
@@ -4420,11 +4495,9 @@ todo_wine
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
 
     hr = IXMLDOMDocument_get_documentElement(domdoc, &root);
-todo_wine
     ok(hr == S_FALSE, "Unexpected hr %#x.\n", hr);
 
     hr = IXMLDOMDocument_createElement(domdoc, _bstr_("TestElement"), &root);
-todo_wine
     ok(hr == E_FAIL, "Unexpected hr %#x.\n", hr);
 
     /* startElement allows document root node to be accessed. */
@@ -4437,10 +4510,18 @@ todo_wine
 
     hr = IXMLDOMElement_get_nodeName(root, &str);
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
-todo_wine
     ok(!lstrcmpW(L"BankAccount", str), "Unexpected name %s.\n", wine_dbgstr_w(str));
     SysFreeString(str);
 
+    hr = IXMLDOMDocument_removeChild(domdoc, (IXMLDOMNode *)root, NULL);
+    ok(hr == E_FAIL, "Unexpected hr %#x.\n", hr);
+
+    hr = IXMLDOMDocument_replaceChild(domdoc, (IXMLDOMNode *)root, (IXMLDOMNode *)root, NULL);
+    ok(hr == E_FAIL, "Unexpected hr %#x.\n", hr);
+
+    hr = IXMLDOMDocument_appendChild(domdoc, (IXMLDOMNode *)root, NULL);
+    ok(hr == E_FAIL, "Unexpected hr %#x.\n", hr);
+
     /* startElement immediately updates previous node. */
     hr = ISAXContentHandler_startElement(content, L"", 0, L"", 0, L"Number", 6, NULL);
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
@@ -4450,46 +4531,49 @@ todo_wine
 
     hr = IXMLDOMNodeList_get_length(node_list, &list_length);
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
-todo_wine
     ok(list_length == 1, "list length %i, expected 1\n", list_length);
 
     hr = IXMLDOMNodeList_get_item(node_list, 0, &node);
-todo_wine
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
 
     hr = IXMLDOMNode_get_nodeName(node, &str);
-todo_wine {
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
     ok(!lstrcmpW(L"Number", str), "got %s\n", wine_dbgstr_w(str));
-}
     SysFreeString(str);
 
     /* characters not immediately visible. */
     hr = ISAXContentHandler_characters(content, L"12345", 5);
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
 
+    hr = ISAXContentHandler_characters(content, L"67890", 5);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
     hr = IXMLDOMNode_get_text(node, &str);
-todo_wine {
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
     ok(!lstrcmpW(L"", str), "got %s\n", wine_dbgstr_w(str));
-}
     SysFreeString(str);
 
-    /* characters visible after endElement. */
-    hr = ISAXContentHandler_endElement(content, L"", 0, L"", 0, L"Number", 6);
+    /* characters visible after new startElement. */
+    hr = ISAXContentHandler_startElement(content, L"", 0, L"", 0, L"Number Child", 12, NULL);
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
 
-    hr = IXMLDOMNode_get_text(node, &str);
-todo_wine {
+    hr = IXMLDOMNode_get_firstChild(node, &child_node);
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
-    ok(!lstrcmpW(L"12345", str), "got %s\n", wine_dbgstr_w(str));
-}
-    SysFreeString(str);
 
+    hr = IXMLDOMNode_get_text(child_node, &str);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+    ok(!lstrcmpW(L"1234567890", str), "got %s\n", wine_dbgstr_w(str));
+    SysFreeString(str);
+    IXMLDOMNode_Release(child_node);
     IXMLDOMNode_Release(node);
 
-    /* second startElement updates the existing node list. */
+    hr = ISAXContentHandler_endElement(content, L"", 0, L"", 0, L"Number Child", 12);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+
+    hr = ISAXContentHandler_endElement(content, L"", 0, L"", 0, L"Number", 6);
+    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
 
+    /* second startElement updates the existing node list. */
     hr = ISAXContentHandler_startElement(content, L"", 0, L"", 0, L"Name", 4, NULL);
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
 
@@ -4503,27 +4587,20 @@ todo_wine {
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
 
     hr = IXMLDOMNodeList_get_length(node_list, &list_length);
-todo_wine {
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
     ok(2 == list_length, "list length %i, expected 2\n", list_length);
-}
     hr = IXMLDOMNodeList_get_item(node_list, 1, &node);
-todo_wine
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
 
     hr = IXMLDOMNode_get_nodeName(node, &str);
-todo_wine {
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
     ok(!lstrcmpW(L"Name", str), "got %s\n", wine_dbgstr_w(str));
-}
     SysFreeString(str);
 
     hr = IXMLDOMNode_get_text(node, &str);
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
-todo_wine {
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
     ok(!lstrcmpW(L"Captain Ahab", str), "got %s\n", wine_dbgstr_w(str));
-}
     SysFreeString(str);
 
     IXMLDOMNode_Release(node);
@@ -4541,16 +4618,15 @@ todo_wine {
 
     /* finally check doc output */
     hr = IXMLDOMDocument_get_xml(domdoc, &str);
-todo_wine {
     ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
+todo_wine
     ok(!lstrcmpW(
             L"<BankAccount>"
-            "<Number>12345</Number>"
+            "<Number>1234567890<Number Child></Number Child></Number>"
             "<Name>Captain Ahab</Name>"
             "</BankAccount>\r\n",
             str),
         "got %s\n", wine_dbgstr_w(str));
-}
     SysFreeString(str);
 
     IXMLDOMDocument_Release(domdoc);
@@ -5961,6 +6037,7 @@ START_TEST(saxreader)
         test_mxwriter_properties();
         test_mxwriter_flush();
         test_mxwriter_stream();
+        test_mxwriter_domdoc_start_end_document();
         test_mxwriter_domdoc();
         test_mxwriter_encoding();
         test_mxwriter_dispex();
diff --git a/include/xmldom.idl b/include/xmldom.idl
index 8bca78ab7ff..2f2e60da6a5 100644
--- a/include/xmldom.idl
+++ b/include/xmldom.idl
@@ -33,6 +33,7 @@ interface IXMLDOMImplementation;
 interface IXMLDOMNode;
 interface IXMLDOMDocumentFragment;
 interface IXMLDOMDocument;
+interface IWineXMLDOMDocumentLock;
 interface IXMLDOMNodeList;
 interface IXMLDOMNamedNodeMap;
 interface IXMLDOMCharacterData;
@@ -327,6 +328,20 @@ interface IXMLDOMDocument : IXMLDOMNode
     HRESULT ontransformnode( [in] VARIANT ontransformnodeSink );
 }
 
+[
+local,
+object,
+uuid(ba65d4a0-e836-4bd7-9905-bbcdea5b997d)
+]
+interface IWineXMLDOMDocumentLock : IUnknown
+{
+    [propput]
+    void locked( [in] BOOL set_locked );
+
+    [propget]
+    void locked( [out,retval] BOOL *get_locked );
+}
+
 [
 local,
 object,
-- 
2.26.2



More information about the wine-devel mailing list