[MSXML3] Sync with Wine Staging 4.18. CORE-16441
[reactos.git] / dll / win32 / msxml3 / mxwriter.c
index 511f45f..a5bfdf1 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *    MXWriter implementation
  *
- * Copyright 2011-2014 Nikolay Sivov for CodeWeavers
+ * Copyright 2011-2014, 2016 Nikolay Sivov for CodeWeavers
  * Copyright 2011 Thomas Mullaly
  *
  * This library is free software; you can redistribute it and/or
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
-#include "precomp.h"
+#define COBJMACROS
+#include "config.h"
+
+#include <stdarg.h>
+#ifdef HAVE_LIBXML2
+# include <libxml/parser.h>
+#endif
+
+#include "windef.h"
+#include "winbase.h"
+#include "ole2.h"
+
+#include "msxml6.h"
+
+#include "wine/debug.h"
+#include "wine/list.h"
+
+#include "msxml_private.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(msxml);
 
 static const WCHAR emptyW[] = {0};
 static const WCHAR spaceW[] = {' '};
@@ -27,6 +46,8 @@ static const WCHAR quotW[]  = {'\"'};
 static const WCHAR closetagW[] = {'>','\r','\n'};
 static const WCHAR crlfW[] = {'\r','\n'};
 static const WCHAR entityW[] = {'<','!','E','N','T','I','T','Y',' '};
+static const WCHAR publicW[] = {'P','U','B','L','I','C',' '};
+static const WCHAR systemW[] = {'S','Y','S','T','E','M',' '};
 
 /* should be ordered as encoding names are sorted */
 typedef enum
@@ -78,13 +99,6 @@ static const struct xml_encoding_data xml_encoding_map[] = {
     { utf8W,        XmlEncoding_UTF8,      CP_UTF8 }
 };
 
-typedef enum
-{
-    OutputBuffer_Native  = 0x001,
-    OutputBuffer_Encoded = 0x010,
-    OutputBuffer_Both    = 0x100
-} output_mode;
-
 typedef enum
 {
     MXWriter_BOM = 0,
@@ -103,6 +117,7 @@ typedef enum
 
 typedef struct
 {
+    struct list entry;
     char *data;
     unsigned int allocated;
     unsigned int written;
@@ -110,9 +125,10 @@ typedef struct
 
 typedef struct
 {
-    encoded_buffer utf16;
     encoded_buffer encoded;
     UINT code_page;
+    UINT utf16_total;   /* total number of bytes written since last buffer reinitialization */
+    struct list blocks; /* only used when output was not set, for BSTR case */
 } output_buffer;
 
 typedef struct
@@ -151,9 +167,8 @@ typedef struct
     BSTR element;
 
     IStream *dest;
-    ULONG dest_written;
 
-    output_buffer *buffer;
+    output_buffer buffer;
 } mxwriter;
 
 typedef struct
@@ -210,7 +225,7 @@ static xml_encoding parse_encoding_name(const WCHAR *encoding)
     int min, max, n, c;
 
     min = 0;
-    max = sizeof(xml_encoding_map)/sizeof(struct xml_encoding_data) - 1;
+    max = ARRAY_SIZE(xml_encoding_map) - 1;
 
     while (min <= max)
     {
@@ -231,7 +246,7 @@ static xml_encoding parse_encoding_name(const WCHAR *encoding)
 
 static HRESULT init_encoded_buffer(encoded_buffer *buffer)
 {
-    const int initial_len = 0x2000;
+    const int initial_len = 0x1000;
     buffer->data = heap_alloc(initial_len);
     if (!buffer->data) return E_OUTOFMEMORY;
 
@@ -263,121 +278,202 @@ static HRESULT get_code_page(xml_encoding encoding, UINT *cp)
     return S_OK;
 }
 
-static HRESULT alloc_output_buffer(xml_encoding encoding, output_buffer **buffer)
+static HRESULT init_output_buffer(xml_encoding encoding, output_buffer *buffer)
 {
-    output_buffer *ret;
     HRESULT hr;
 
-    ret = heap_alloc(sizeof(*ret));
-    if (!ret) return E_OUTOFMEMORY;
-
-    hr = get_code_page(encoding, &ret->code_page);
-    if (hr != S_OK) {
-        heap_free(ret);
+    hr = get_code_page(encoding, &buffer->code_page);
+    if (hr != S_OK)
         return hr;
-    }
 
-    hr = init_encoded_buffer(&ret->utf16);
-    if (hr != S_OK) {
-        heap_free(ret);
+    hr = init_encoded_buffer(&buffer->encoded);
+    if (hr != S_OK)
         return hr;
-    }
 
-    /* currently we always create a default output buffer that is UTF-16 only,
-       but it's possible to allocate with specific encoding too */
-    if (encoding != XmlEncoding_UTF16) {
-        hr = init_encoded_buffer(&ret->encoded);
-        if (hr != S_OK) {
-            free_encoded_buffer(&ret->utf16);
-            heap_free(ret);
-            return hr;
-        }
-    }
-    else
-        memset(&ret->encoded, 0, sizeof(ret->encoded));
-
-    *buffer = ret;
+    list_init(&buffer->blocks);
+    buffer->utf16_total = 0;
 
     return S_OK;
 }
 
 static void free_output_buffer(output_buffer *buffer)
 {
+    encoded_buffer *cur, *cur2;
+
     free_encoded_buffer(&buffer->encoded);
-    free_encoded_buffer(&buffer->utf16);
-    heap_free(buffer);
-}
 
-static void grow_buffer(encoded_buffer *buffer, int length)
-{
-    /* grow if needed, plus 4 bytes to be sure null terminator will fit in */
-    if (buffer->allocated < buffer->written + length + 4)
+    LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &buffer->blocks, encoded_buffer, entry)
     {
-        int grown_size = max(2*buffer->allocated, buffer->allocated + length);
-        buffer->data = heap_realloc(buffer->data, grown_size);
-        buffer->allocated = grown_size;
+        list_remove(&cur->entry);
+        free_encoded_buffer(cur);
+        heap_free(cur);
     }
 }
 
-static HRESULT write_output_buffer_mode(output_buffer *buffer, output_mode mode, const WCHAR *data, int len)
+static HRESULT write_output_buffer(mxwriter *writer, const WCHAR *data, int len)
 {
-    int length;
-    char *ptr;
+    output_buffer *buffer = &writer->buffer;
+    encoded_buffer *buff;
+    unsigned int written;
+    int src_len;
+
+    if (!len || !*data)
+        return S_OK;
+
+    src_len = len == -1 ? strlenW(data) : len;
+    if (writer->dest)
+    {
+        buff = &buffer->encoded;
 
-    if (mode & (OutputBuffer_Encoded | OutputBuffer_Both)) {
-        if (buffer->code_page != ~0)
+        if (buffer->code_page == ~0)
         {
-            length = WideCharToMultiByte(buffer->code_page, 0, data, len, NULL, 0, NULL, NULL);
-            grow_buffer(&buffer->encoded, length);
-            ptr = buffer->encoded.data + buffer->encoded.written;
-            length = WideCharToMultiByte(buffer->code_page, 0, data, len, ptr, length, NULL, NULL);
-            buffer->encoded.written += len == -1 ? length-1 : length;
+            unsigned int avail = buff->allocated - buff->written;
+
+            src_len *= sizeof(WCHAR);
+            written = min(avail, src_len);
+
+            /* fill internal buffer first */
+            if (avail)
+            {
+                memcpy(buff->data + buff->written, data, written);
+                data += written / sizeof(WCHAR);
+                buff->written += written;
+                avail -= written;
+                src_len -= written;
+            }
+
+            if (!avail)
+            {
+                IStream_Write(writer->dest, buff->data, buff->written, &written);
+                buff->written = 0;
+                if (src_len >= buff->allocated)
+                    IStream_Write(writer->dest, data, src_len, &written);
+                else if (src_len)
+                {
+                    memcpy(buff->data, data, src_len);
+                    buff->written += src_len;
+                }
+            }
+        }
+        else
+        {
+            unsigned int avail = buff->allocated - buff->written;
+            int length;
+
+            length = WideCharToMultiByte(buffer->code_page, 0, data, src_len, NULL, 0, NULL, NULL);
+            if (avail >= length)
+            {
+                length = WideCharToMultiByte(buffer->code_page, 0, data, src_len, buff->data + buff->written, length, NULL, NULL);
+                buff->written += length;
+            }
+            else
+            {
+                /* drain what we go so far */
+                if (buff->written)
+                {
+                    IStream_Write(writer->dest, buff->data, buff->written, &written);
+                    buff->written = 0;
+                    avail = buff->allocated;
+                }
+
+                if (avail >= length)
+                {
+                    length = WideCharToMultiByte(buffer->code_page, 0, data, src_len, buff->data + buff->written, length, NULL, NULL);
+                    buff->written += length;
+                }
+                else
+                {
+                    char *mb;
+
+                    /* if current chunk is larger than total buffer size, convert it at once using temporary allocated buffer */
+                    mb = heap_alloc(length);
+                    if (!mb)
+                        return E_OUTOFMEMORY;
+
+                    length = WideCharToMultiByte(buffer->code_page, 0, data, src_len, mb, length, NULL, NULL);
+                    IStream_Write(writer->dest, mb, length, &written);
+                    heap_free(mb);
+                }
+            }
         }
     }
+    /* When writer has no output set we have to accumulate everything to return it later in a form of BSTR.
+       To achieve that:
 
-    if (mode & (OutputBuffer_Native | OutputBuffer_Both)) {
-        /* WCHAR data just copied */
-        length = len == -1 ? strlenW(data) : len;
-        if (length)
+       - fill a buffer already allocated as part of output buffer;
+       - when current buffer is full, allocate another one and switch to it; buffers themselves never grow,
+         but are linked together, with head pointing to first allocated buffer after initial one got filled;
+       - later during get_output() contents are concatenated by copying one after another to destination BSTR buffer,
+         that's returned to the client. */
+    else
+    {
+        /* select last used block */
+        if (list_empty(&buffer->blocks))
+            buff = &buffer->encoded;
+        else
+            buff = LIST_ENTRY(list_tail(&buffer->blocks), encoded_buffer, entry);
+
+        src_len *= sizeof(WCHAR);
+        while (src_len)
         {
-            length *= sizeof(WCHAR);
+            unsigned int avail = buff->allocated - buff->written;
+            unsigned int written = min(avail, src_len);
+
+            if (avail)
+            {
+                memcpy(buff->data + buff->written, data, written);
+                buff->written += written;
+                buffer->utf16_total += written;
+                src_len -= written;
+            }
+
+            /* alloc new block if needed and retry */
+            if (src_len)
+            {
+                encoded_buffer *next = heap_alloc(sizeof(*next));
+                HRESULT hr;
 
-            grow_buffer(&buffer->utf16, length);
-            ptr = buffer->utf16.data + buffer->utf16.written;
+                if (FAILED(hr = init_encoded_buffer(next))) {
+                    heap_free(next);
+                    return hr;
+                }
 
-            memcpy(ptr, data, length);
-            buffer->utf16.written += length;
-            ptr += length;
-            /* null termination */
-            memset(ptr, 0, sizeof(WCHAR));
+                list_add_tail(&buffer->blocks, &next->entry);
+                buff = next;
+            }
         }
     }
 
     return S_OK;
 }
 
-static HRESULT write_output_buffer(output_buffer *buffer, const WCHAR *data, int len)
-{
-    return write_output_buffer_mode(buffer, OutputBuffer_Both, data, len);
-}
-
-static HRESULT write_output_buffer_quoted(output_buffer *buffer, const WCHAR *data, int len)
+static HRESULT write_output_buffer_quoted(mxwriter *writer, const WCHAR *data, int len)
 {
-    write_output_buffer(buffer, quotW, 1);
-    write_output_buffer(buffer, data, len);
-    write_output_buffer(buffer, quotW, 1);
+    write_output_buffer(writer, quotW, 1);
+    write_output_buffer(writer, data, len);
+    write_output_buffer(writer, quotW, 1);
 
     return S_OK;
 }
 
 /* frees buffer data, reallocates with a default lengths */
-static void close_output_buffer(mxwriter *This)
+static void close_output_buffer(mxwriter *writer)
 {
-    heap_free(This->buffer->utf16.data);
-    heap_free(This->buffer->encoded.data);
-    init_encoded_buffer(&This->buffer->utf16);
-    init_encoded_buffer(&This->buffer->encoded);
-    get_code_page(This->xml_enc, &This->buffer->code_page);
+    encoded_buffer *cur, *cur2;
+
+    heap_free(writer->buffer.encoded.data);
+
+    LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &writer->buffer.blocks, encoded_buffer, entry)
+    {
+        list_remove(&cur->entry);
+        free_encoded_buffer(cur);
+        heap_free(cur);
+    }
+
+    init_encoded_buffer(&writer->buffer.encoded);
+    get_code_page(writer->xml_enc, &writer->buffer.code_page);
+    writer->buffer.utf16_total = 0;
+    list_init(&writer->buffer.blocks);
 }
 
 /* Escapes special characters like:
@@ -402,10 +498,10 @@ static WCHAR *get_escaped_string(const WCHAR *str, escape_mode mode, int *len)
     WCHAR *ptr, *ret;
 
     /* default buffer size to something if length is unknown */
-    conv_len = *len == -1 ? default_alloc : max(2**len, default_alloc);
+    conv_len = max(2**len, default_alloc);
     ptr = ret = heap_alloc(conv_len*sizeof(WCHAR));
 
-    while (*str && p)
+    while (p)
     {
         if (ptr - ret > conv_len - grow_thresh)
         {
@@ -419,21 +515,21 @@ static WCHAR *get_escaped_string(const WCHAR *str, escape_mode mode, int *len)
         {
         case '<':
             memcpy(ptr, ltW, sizeof(ltW));
-            ptr += sizeof(ltW)/sizeof(WCHAR);
+            ptr += ARRAY_SIZE(ltW);
             break;
         case '&':
             memcpy(ptr, ampW, sizeof(ampW));
-            ptr += sizeof(ampW)/sizeof(WCHAR);
+            ptr += ARRAY_SIZE(ampW);
             break;
         case '>':
             memcpy(ptr, gtW, sizeof(gtW));
-            ptr += sizeof(gtW)/sizeof(WCHAR);
+            ptr += ARRAY_SIZE(gtW);
             break;
         case '"':
             if (mode == EscapeValue)
             {
                 memcpy(ptr, equotW, sizeof(equotW));
-                ptr += sizeof(equotW)/sizeof(WCHAR);
+                ptr += ARRAY_SIZE(equotW);
                 break;
             }
             /* fallthrough for text mode */
@@ -443,16 +539,16 @@ static WCHAR *get_escaped_string(const WCHAR *str, escape_mode mode, int *len)
         }
 
         str++;
-        if (*len != -1) p--;
+        p--;
     }
 
-    if (*len != -1) *len = ptr-ret;
+    *len = ptr-ret;
     *++ptr = 0;
 
     return ret;
 }
 
-static void write_prolog_buffer(mxwriter *This)
+static void write_prolog_buffer(mxwriter *writer)
 {
     static const WCHAR versionW[] = {'<','?','x','m','l',' ','v','e','r','s','i','o','n','='};
     static const WCHAR encodingW[] = {' ','e','n','c','o','d','i','n','g','=','\"'};
@@ -461,99 +557,83 @@ static void write_prolog_buffer(mxwriter *This)
     static const WCHAR noW[] = {'n','o','\"','?','>'};
 
     /* version */
-    write_output_buffer(This->buffer, versionW, sizeof(versionW)/sizeof(WCHAR));
-    write_output_buffer_quoted(This->buffer, This->version, -1);
+    write_output_buffer(writer, versionW, ARRAY_SIZE(versionW));
+    write_output_buffer_quoted(writer, writer->version, -1);
 
     /* encoding */
-    write_output_buffer(This->buffer, encodingW, sizeof(encodingW)/sizeof(WCHAR));
+    write_output_buffer(writer, encodingW, ARRAY_SIZE(encodingW));
 
-    /* always write UTF-16 to WCHAR buffer */
-    write_output_buffer_mode(This->buffer, OutputBuffer_Native, utf16W, sizeof(utf16W)/sizeof(WCHAR) - 1);
-    write_output_buffer_mode(This->buffer, OutputBuffer_Encoded, This->encoding, -1);
-    write_output_buffer(This->buffer, quotW, 1);
+    if (writer->dest)
+        write_output_buffer(writer, writer->encoding, -1);
+    else
+        write_output_buffer(writer, utf16W, ARRAY_SIZE(utf16W) - 1);
+    write_output_buffer(writer, quotW, 1);
 
     /* standalone */
-    write_output_buffer(This->buffer, standaloneW, sizeof(standaloneW)/sizeof(WCHAR));
-    if (This->props[MXWriter_Standalone] == VARIANT_TRUE)
-        write_output_buffer(This->buffer, yesW, sizeof(yesW)/sizeof(WCHAR));
+    write_output_buffer(writer, standaloneW, ARRAY_SIZE(standaloneW));
+    if (writer->props[MXWriter_Standalone] == VARIANT_TRUE)
+        write_output_buffer(writer, yesW, ARRAY_SIZE(yesW));
     else
-        write_output_buffer(This->buffer, noW, sizeof(noW)/sizeof(WCHAR));
+        write_output_buffer(writer, noW, ARRAY_SIZE(noW));
 
-    write_output_buffer(This->buffer, crlfW, sizeof(crlfW)/sizeof(WCHAR));
-    This->newline = TRUE;
+    write_output_buffer(writer, crlfW, ARRAY_SIZE(crlfW));
+    writer->newline = TRUE;
 }
 
 /* Attempts to the write data from the mxwriter's buffer to
  * the destination stream (if there is one).
  */
-static HRESULT write_data_to_stream(mxwriter *This)
+static HRESULT write_data_to_stream(mxwriter *writer)
 {
-    encoded_buffer *buffer;
+    encoded_buffer *buffer = &writer->buffer.encoded;
     ULONG written = 0;
-    HRESULT hr;
 
-    if (!This->dest)
+    if (!writer->dest)
         return S_OK;
 
-    if (This->xml_enc != XmlEncoding_UTF16)
-        buffer = &This->buffer->encoded;
+    if (buffer->written == 0)
+    {
+        if (writer->xml_enc == XmlEncoding_UTF8)
+            IStream_Write(writer->dest, buffer->data, 0, &written);
+    }
     else
-        buffer = &This->buffer->utf16;
-
-    if (This->dest_written > buffer->written) {
-        ERR("Failed sanity check! Not sure what to do... (%d > %d)\n", This->dest_written, buffer->written);
-        return E_FAIL;
-    } else if (This->dest_written == buffer->written && This->xml_enc != XmlEncoding_UTF8)
-        /* Windows seems to make an empty write call when the encoding is UTF-8 and
-         * all the data has been written to the stream. It doesn't seem make this call
-         * for any other encodings.
-         */
-        return S_OK;
-
-    /* Write the current content from the output buffer into 'dest'.
-     * TODO: Check what Windows does if the IStream doesn't write all of
-     *       the data we give it at once.
-     */
-    hr = IStream_Write(This->dest, buffer->data+This->dest_written,
-                         buffer->written-This->dest_written, &written);
-    if (FAILED(hr)) {
-        WARN("Failed to write data to IStream (0x%08x)\n", hr);
-        return hr;
+    {
+        IStream_Write(writer->dest, buffer->data, buffer->written, &written);
+        buffer->written = 0;
     }
 
-    This->dest_written += written;
-    return hr;
+    return S_OK;
 }
 
 /* Newly added element start tag left unclosed cause for empty elements
    we have to close it differently. */
-static void close_element_starttag(const mxwriter *This)
+static void close_element_starttag(mxwriter *writer)
 {
     static const WCHAR gtW[] = {'>'};
-    if (!This->element) return;
-    write_output_buffer(This->buffer, gtW, 1);
+    if (!writer->element) return;
+    write_output_buffer(writer, gtW, 1);
 }
 
-static void write_node_indent(mxwriter *This)
+static void write_node_indent(mxwriter *writer)
 {
     static const WCHAR tabW[] = {'\t'};
-    int indent = This->indent;
+    int indent = writer->indent;
 
-    if (!This->props[MXWriter_Indent] || This->text)
+    if (!writer->props[MXWriter_Indent] || writer->text)
     {
-        This->text = FALSE;
+        writer->text = FALSE;
         return;
     }
 
     /* This is to workaround PI output logic that always puts newline chars,
        document prolog PI does that too. */
-    if (!This->newline)
-        write_output_buffer(This->buffer, crlfW, sizeof(crlfW)/sizeof(WCHAR));
+    if (!writer->newline)
+        write_output_buffer(writer, crlfW, ARRAY_SIZE(crlfW));
     while (indent--)
-        write_output_buffer(This->buffer, tabW, 1);
+        write_output_buffer(writer, tabW, 1);
 
-    This->newline = FALSE;
-    This->text = FALSE;
+    writer->newline = FALSE;
+    writer->text = FALSE;
 }
 
 static inline void writer_inc_indent(mxwriter *This)
@@ -592,7 +672,6 @@ static inline HRESULT flush_output_buffer(mxwriter *This)
 static inline void reset_output_buffer(mxwriter *This)
 {
     close_output_buffer(This);
-    This->dest_written = 0;
 }
 
 static HRESULT writer_set_property(mxwriter *writer, mxwriter_prop property, VARIANT_BOOL value)
@@ -754,7 +833,7 @@ static ULONG WINAPI mxwriter_Release(IMXWriter *iface)
     {
         /* Windows flushes the buffer when the interface is destroyed. */
         flush_output_buffer(This);
-        free_output_buffer(This->buffer);
+        free_output_buffer(&This->buffer);
 
         if (This->dest) IStream_Release(This->dest);
         SysFreeString(This->version);
@@ -858,22 +937,43 @@ static HRESULT WINAPI mxwriter_get_output(IMXWriter *iface, VARIANT *dest)
 
     if (!dest) return E_POINTER;
 
-    if (!This->dest)
+    if (This->dest)
+    {
+        /* we only support IStream output so far */
+        V_VT(dest) = VT_UNKNOWN;
+        V_UNKNOWN(dest) = (IUnknown*)This->dest;
+        IStream_AddRef(This->dest);
+    }
+    else
     {
-        HRESULT hr = flush_output_buffer(This);
+        encoded_buffer *buff;
+        char *dest_ptr;
+        HRESULT hr;
+
+        hr = flush_output_buffer(This);
         if (FAILED(hr))
             return hr;
 
         V_VT(dest)   = VT_BSTR;
-        V_BSTR(dest) = SysAllocString((WCHAR*)This->buffer->utf16.data);
+        V_BSTR(dest) = SysAllocStringLen(NULL, This->buffer.utf16_total / sizeof(WCHAR));
+        if (!V_BSTR(dest))
+            return E_OUTOFMEMORY;
 
-        return S_OK;
-    }
+        dest_ptr = (char*)V_BSTR(dest);
+        buff = &This->buffer.encoded;
 
-    /* we only support IStream output so far */
-    V_VT(dest) = VT_UNKNOWN;
-    V_UNKNOWN(dest) = (IUnknown*)This->dest;
-    IStream_AddRef(This->dest);
+        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 S_OK;
 }
@@ -1159,18 +1259,18 @@ static void mxwriter_write_attribute(mxwriter *writer, const WCHAR *qname, int q
     static const WCHAR eqW[] = {'='};
 
     /* space separator in front of every attribute */
-    write_output_buffer(writer->buffer, spaceW, 1);
-    write_output_buffer(writer->buffer, qname, qname_len);
-    write_output_buffer(writer->buffer, eqW, 1);
+    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->buffer, escaped, value_len);
+        write_output_buffer_quoted(writer, escaped, value_len);
         heap_free(escaped);
     }
     else
-        write_output_buffer_quoted(writer->buffer, value, value_len);
+        write_output_buffer_quoted(writer, value, value_len);
 }
 
 static void mxwriter_write_starttag(mxwriter *writer, const WCHAR *qname, int len)
@@ -1182,8 +1282,8 @@ static void mxwriter_write_starttag(mxwriter *writer, const WCHAR *qname, int le
 
     write_node_indent(writer);
 
-    write_output_buffer(writer->buffer, ltW, 1);
-    write_output_buffer(writer->buffer, qname ? qname : emptyW, qname ? len : 0);
+    write_output_buffer(writer, ltW, 1);
+    write_output_buffer(writer, qname ? qname : emptyW, qname ? len : 0);
     writer_inc_indent(writer);
 }
 
@@ -1260,7 +1360,7 @@ static HRESULT WINAPI SAXContentHandler_endElement(
     if (This->element)
     {
         static const WCHAR closeW[] = {'/','>'};
-        write_output_buffer(This->buffer, closeW, 2);
+        write_output_buffer(This, closeW, 2);
     }
     else
     {
@@ -1268,9 +1368,9 @@ static HRESULT WINAPI SAXContentHandler_endElement(
         static const WCHAR gtW[] = {'>'};
 
         write_node_indent(This);
-        write_output_buffer(This->buffer, closetagW, 2);
-        write_output_buffer(This->buffer, QName, nQName);
-        write_output_buffer(This->buffer, gtW, 1);
+        write_output_buffer(This, closetagW, 2);
+        write_output_buffer(This, QName, nQName);
+        write_output_buffer(This, gtW, 1);
     }
 
     set_element_name(This, NULL, 0);
@@ -1298,14 +1398,14 @@ static HRESULT WINAPI SAXContentHandler_characters(
     if (nchars)
     {
         if (This->cdata || This->props[MXWriter_DisableEscaping] == VARIANT_TRUE)
-            write_output_buffer(This->buffer, chars, nchars);
+            write_output_buffer(This, chars, nchars);
         else
         {
             int len = nchars;
             WCHAR *escaped;
 
             escaped = get_escaped_string(chars, EscapeText, &len);
-            write_output_buffer(This->buffer, escaped, len);
+            write_output_buffer(This, escaped, len);
             heap_free(escaped);
         }
     }
@@ -1324,7 +1424,7 @@ static HRESULT WINAPI SAXContentHandler_ignorableWhitespace(
 
     if (!chars) return E_INVALIDARG;
 
-    write_output_buffer(This->buffer, chars, nchars);
+    write_output_buffer(This, chars, nchars);
 
     return S_OK;
 }
@@ -1345,18 +1445,18 @@ static HRESULT WINAPI SAXContentHandler_processingInstruction(
     if (!target) return E_INVALIDARG;
 
     write_node_indent(This);
-    write_output_buffer(This->buffer, openpiW, sizeof(openpiW)/sizeof(WCHAR));
+    write_output_buffer(This, openpiW, ARRAY_SIZE(openpiW));
 
     if (*target)
-        write_output_buffer(This->buffer, target, ntarget);
+        write_output_buffer(This, target, ntarget);
 
     if (data && *data && ndata)
     {
-        write_output_buffer(This->buffer, spaceW, 1);
-        write_output_buffer(This->buffer, data, ndata);
+        write_output_buffer(This, spaceW, 1);
+        write_output_buffer(This, data, ndata);
     }
 
-    write_output_buffer(This->buffer, closepiW, sizeof(closepiW)/sizeof(WCHAR));
+    write_output_buffer(This, closepiW, ARRAY_SIZE(closepiW));
     This->newline = TRUE;
 
     return S_OK;
@@ -1424,42 +1524,38 @@ static HRESULT WINAPI SAXLexicalHandler_startDTD(ISAXLexicalHandler *iface,
 
     if (!name) return E_INVALIDARG;
 
-    write_output_buffer(This->buffer, doctypeW, sizeof(doctypeW)/sizeof(WCHAR));
+    write_output_buffer(This, doctypeW, ARRAY_SIZE(doctypeW));
 
     if (*name)
     {
-        write_output_buffer(This->buffer, name, name_len);
-        write_output_buffer(This->buffer, spaceW, 1);
+        write_output_buffer(This, name, name_len);
+        write_output_buffer(This, spaceW, 1);
     }
 
     if (publicId)
     {
-        static const WCHAR publicW[] = {'P','U','B','L','I','C',' '};
-
-        write_output_buffer(This->buffer, publicW, sizeof(publicW)/sizeof(WCHAR));
-        write_output_buffer_quoted(This->buffer, publicId, publicId_len);
+        write_output_buffer(This, publicW, ARRAY_SIZE(publicW));
+        write_output_buffer_quoted(This, publicId, publicId_len);
 
         if (!systemId) return E_INVALIDARG;
 
         if (*publicId)
-            write_output_buffer(This->buffer, spaceW, 1);
+            write_output_buffer(This, spaceW, 1);
 
-        write_output_buffer_quoted(This->buffer, systemId, systemId_len);
+        write_output_buffer_quoted(This, systemId, systemId_len);
 
         if (*systemId)
-            write_output_buffer(This->buffer, spaceW, 1);
+            write_output_buffer(This, spaceW, 1);
     }
     else if (systemId)
     {
-        static const WCHAR systemW[] = {'S','Y','S','T','E','M',' '};
-
-        write_output_buffer(This->buffer, systemW, sizeof(systemW)/sizeof(WCHAR));
-        write_output_buffer_quoted(This->buffer, systemId, systemId_len);
+        write_output_buffer(This, systemW, ARRAY_SIZE(systemW));
+        write_output_buffer_quoted(This, systemId, systemId_len);
         if (*systemId)
-            write_output_buffer(This->buffer, spaceW, 1);
+            write_output_buffer(This, spaceW, 1);
     }
 
-    write_output_buffer(This->buffer, openintW, sizeof(openintW)/sizeof(WCHAR));
+    write_output_buffer(This, openintW, ARRAY_SIZE(openintW));
 
     return S_OK;
 }
@@ -1471,7 +1567,7 @@ static HRESULT WINAPI SAXLexicalHandler_endDTD(ISAXLexicalHandler *iface)
 
     TRACE("(%p)\n", This);
 
-    write_output_buffer(This->buffer, closedtdW, sizeof(closedtdW)/sizeof(WCHAR));
+    write_output_buffer(This, closedtdW, ARRAY_SIZE(closedtdW));
 
     return S_OK;
 }
@@ -1498,7 +1594,7 @@ static HRESULT WINAPI SAXLexicalHandler_startCDATA(ISAXLexicalHandler *iface)
     TRACE("(%p)\n", This);
 
     write_node_indent(This);
-    write_output_buffer(This->buffer, scdataW, sizeof(scdataW)/sizeof(WCHAR));
+    write_output_buffer(This, scdataW, ARRAY_SIZE(scdataW));
     This->cdata = TRUE;
 
     return S_OK;
@@ -1511,7 +1607,7 @@ static HRESULT WINAPI SAXLexicalHandler_endCDATA(ISAXLexicalHandler *iface)
 
     TRACE("(%p)\n", This);
 
-    write_output_buffer(This->buffer, ecdataW, sizeof(ecdataW)/sizeof(WCHAR));
+    write_output_buffer(This, ecdataW, ARRAY_SIZE(ecdataW));
     This->cdata = FALSE;
 
     return S_OK;
@@ -1530,10 +1626,10 @@ static HRESULT WINAPI SAXLexicalHandler_comment(ISAXLexicalHandler *iface, const
     close_element_starttag(This);
     write_node_indent(This);
 
-    write_output_buffer(This->buffer, copenW, sizeof(copenW)/sizeof(WCHAR));
+    write_output_buffer(This, copenW, ARRAY_SIZE(copenW));
     if (nchars)
-        write_output_buffer(This->buffer, chars, nchars);
-    write_output_buffer(This->buffer, ccloseW, sizeof(ccloseW)/sizeof(WCHAR));
+        write_output_buffer(This, chars, nchars);
+    write_output_buffer(This, ccloseW, ARRAY_SIZE(ccloseW));
 
     return S_OK;
 }
@@ -1583,14 +1679,14 @@ static HRESULT WINAPI SAXDeclHandler_elementDecl(ISAXDeclHandler *iface,
 
     if (!name || !model) return E_INVALIDARG;
 
-    write_output_buffer(This->buffer, elementW, sizeof(elementW)/sizeof(WCHAR));
+    write_output_buffer(This, elementW, ARRAY_SIZE(elementW));
     if (n_name) {
-        write_output_buffer(This->buffer, name, n_name);
-        write_output_buffer(This->buffer, spaceW, sizeof(spaceW)/sizeof(WCHAR));
+        write_output_buffer(This, name, n_name);
+        write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
     }
     if (n_model)
-        write_output_buffer(This->buffer, model, n_model);
-    write_output_buffer(This->buffer, closetagW, sizeof(closetagW)/sizeof(WCHAR));
+        write_output_buffer(This, model, n_model);
+    write_output_buffer(This, closetagW, ARRAY_SIZE(closetagW));
 
     return S_OK;
 }
@@ -1608,31 +1704,31 @@ 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);
 
-    write_output_buffer(This->buffer, attlistW, sizeof(attlistW)/sizeof(WCHAR));
+    write_output_buffer(This, attlistW, ARRAY_SIZE(attlistW));
     if (n_element) {
-        write_output_buffer(This->buffer, element, n_element);
-        write_output_buffer(This->buffer, spaceW, sizeof(spaceW)/sizeof(WCHAR));
+        write_output_buffer(This, element, n_element);
+        write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
     }
 
     if (n_attr) {
-        write_output_buffer(This->buffer, attr, n_attr);
-        write_output_buffer(This->buffer, spaceW, sizeof(spaceW)/sizeof(WCHAR));
+        write_output_buffer(This, attr, n_attr);
+        write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
     }
 
     if (n_type) {
-        write_output_buffer(This->buffer, type, n_type);
-        write_output_buffer(This->buffer, spaceW, sizeof(spaceW)/sizeof(WCHAR));
+        write_output_buffer(This, type, n_type);
+        write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
     }
 
     if (n_default) {
-        write_output_buffer(This->buffer, Default, n_default);
-        write_output_buffer(This->buffer, spaceW, sizeof(spaceW)/sizeof(WCHAR));
+        write_output_buffer(This, Default, n_default);
+        write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
     }
 
     if (n_value)
-        write_output_buffer_quoted(This->buffer, value, n_value);
+        write_output_buffer_quoted(This, value, n_value);
 
-    write_output_buffer(This->buffer, closetagW, sizeof(closetagW)/sizeof(WCHAR));
+    write_output_buffer(This, closetagW, ARRAY_SIZE(closetagW));
 
     return S_OK;
 }
@@ -1647,16 +1743,16 @@ static HRESULT WINAPI SAXDeclHandler_internalEntityDecl(ISAXDeclHandler *iface,
 
     if (!name || !value) return E_INVALIDARG;
 
-    write_output_buffer(This->buffer, entityW, sizeof(entityW)/sizeof(WCHAR));
+    write_output_buffer(This, entityW, ARRAY_SIZE(entityW));
     if (n_name) {
-        write_output_buffer(This->buffer, name, n_name);
-        write_output_buffer(This->buffer, spaceW, sizeof(spaceW)/sizeof(WCHAR));
+        write_output_buffer(This, name, n_name);
+        write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
     }
 
     if (n_value)
-        write_output_buffer_quoted(This->buffer, value, n_value);
+        write_output_buffer_quoted(This, value, n_value);
 
-    write_output_buffer(This->buffer, closetagW, sizeof(closetagW)/sizeof(WCHAR));
+    write_output_buffer(This, closetagW, ARRAY_SIZE(closetagW));
 
     return S_OK;
 }
@@ -1665,37 +1761,33 @@ static HRESULT WINAPI SAXDeclHandler_externalEntityDecl(ISAXDeclHandler *iface,
     const WCHAR *name, int n_name, const WCHAR *publicId, int n_publicId,
     const WCHAR *systemId, int n_systemId)
 {
-    static const WCHAR publicW[] = {'P','U','B','L','I','C',' '};
-    static const WCHAR systemW[] = {'S','Y','S','T','E','M',' '};
     mxwriter *This = impl_from_ISAXDeclHandler( iface );
 
     TRACE("(%p)->(%s:%d %s:%d %s:%d)\n", This, debugstr_wn(name, n_name), n_name,
         debugstr_wn(publicId, n_publicId), n_publicId, debugstr_wn(systemId, n_systemId), n_systemId);
 
-    if (!name) return E_INVALIDARG;
-    if (publicId && !systemId) return E_INVALIDARG;
-    if (!publicId && !systemId) return E_INVALIDARG;
+    if (!name || !systemId) return E_INVALIDARG;
 
-    write_output_buffer(This->buffer, entityW, sizeof(entityW)/sizeof(WCHAR));
+    write_output_buffer(This, entityW, ARRAY_SIZE(entityW));
     if (n_name) {
-        write_output_buffer(This->buffer, name, n_name);
-        write_output_buffer(This->buffer, spaceW, sizeof(spaceW)/sizeof(WCHAR));
+        write_output_buffer(This, name, n_name);
+        write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
     }
 
     if (publicId)
     {
-        write_output_buffer(This->buffer, publicW, sizeof(publicW)/sizeof(WCHAR));
-        write_output_buffer_quoted(This->buffer, publicId, n_publicId);
-        write_output_buffer(This->buffer, spaceW, sizeof(spaceW)/sizeof(WCHAR));
-        write_output_buffer_quoted(This->buffer, systemId, n_systemId);
+        write_output_buffer(This, publicW, ARRAY_SIZE(publicW));
+        write_output_buffer_quoted(This, publicId, n_publicId);
+        write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
+        write_output_buffer_quoted(This, systemId, n_systemId);
     }
     else
     {
-        write_output_buffer(This->buffer, systemW, sizeof(systemW)/sizeof(WCHAR));
-        write_output_buffer_quoted(This->buffer, systemId, n_systemId);
+        write_output_buffer(This, systemW, ARRAY_SIZE(systemW));
+        write_output_buffer_quoted(This, systemId, n_systemId);
     }
 
-    write_output_buffer(This->buffer, closetagW, sizeof(closetagW)/sizeof(WCHAR));
+    write_output_buffer(This, closetagW, ARRAY_SIZE(closetagW));
 
     return S_OK;
 }
@@ -2114,7 +2206,7 @@ static HRESULT WINAPI VBSAXContentHandler_characters(IVBSAXContentHandler *iface
     if (!chars)
         return E_POINTER;
 
-    return ISAXContentHandler_characters(&This->ISAXContentHandler_iface, *chars, -1);
+    return ISAXContentHandler_characters(&This->ISAXContentHandler_iface, *chars, SysStringLen(*chars));
 }
 
 static HRESULT WINAPI VBSAXContentHandler_ignorableWhitespace(IVBSAXContentHandler *iface, BSTR *chars)
@@ -2194,14 +2286,45 @@ static ULONG WINAPI SAXDTDHandler_Release(ISAXDTDHandler *iface)
 }
 
 static HRESULT WINAPI SAXDTDHandler_notationDecl(ISAXDTDHandler *iface,
-    const WCHAR *name, INT nname,
-    const WCHAR *publicid, INT npublicid,
-    const WCHAR *systemid, INT nsystemid)
+    const WCHAR *name, INT n_name,
+    const WCHAR *publicid, INT n_publicid,
+    const WCHAR *systemid, INT n_systemid)
 {
+    static const WCHAR notationW[] = {'<','!','N','O','T','A','T','I','O','N',' '};
     mxwriter *This = impl_from_ISAXDTDHandler( iface );
-    FIXME("(%p)->(%s:%d, %s:%d, %s:%d): stub\n", This, debugstr_wn(name, nname), nname,
-        debugstr_wn(publicid, npublicid), npublicid, debugstr_wn(systemid, nsystemid), nsystemid);
-    return E_NOTIMPL;
+
+    TRACE("(%p)->(%s:%d, %s:%d, %s:%d)\n", This, debugstr_wn(name, n_name), n_name,
+        debugstr_wn(publicid, n_publicid), n_publicid, debugstr_wn(systemid, n_systemid), n_systemid);
+
+    if (!name || !n_name)
+        return E_INVALIDARG;
+
+    write_output_buffer(This, notationW, ARRAY_SIZE(notationW));
+    write_output_buffer(This, name, n_name);
+
+    if (!publicid && !systemid)
+        return E_INVALIDARG;
+
+    write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
+    if (publicid)
+    {
+        write_output_buffer(This, publicW, ARRAY_SIZE(publicW));
+        write_output_buffer_quoted(This, publicid, n_publicid);
+        if (systemid)
+        {
+            write_output_buffer(This, spaceW, ARRAY_SIZE(spaceW));
+            write_output_buffer_quoted(This, systemid, n_systemid);
+        }
+    }
+    else
+    {
+        write_output_buffer(This, systemW, ARRAY_SIZE(systemW));
+        write_output_buffer_quoted(This, systemid, n_systemid);
+    }
+
+    write_output_buffer(This, closetagW, ARRAY_SIZE(closetagW));
+
+    return S_OK;
 }
 
 static HRESULT WINAPI SAXDTDHandler_unparsedEntityDecl(ISAXDTDHandler *iface,
@@ -2502,9 +2625,8 @@ HRESULT MXWriter_create(MSXML_VERSION version, void **ppObj)
     This->newline = FALSE;
 
     This->dest = NULL;
-    This->dest_written = 0;
 
-    hr = alloc_output_buffer(This->xml_enc, &This->buffer);
+    hr = init_output_buffer(This->xml_enc, &This->buffer);
     if (hr != S_OK) {
         SysFreeString(This->encoding);
         SysFreeString(This->version);