msxml3: Support startDTD()/endDTD() in MXWriter.
[wine] / dlls / msxml3 / mxwriter.c
1 /*
2  *    MXWriter implementation
3  *
4  * Copyright 2011 Nikolay Sivov for CodeWeavers
5  * Copyright 2011 Thomas Mullaly
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #define COBJMACROS
23 #include "config.h"
24
25 #include <stdarg.h>
26 #ifdef HAVE_LIBXML2
27 # include <libxml/parser.h>
28 #endif
29
30 #include "windef.h"
31 #include "winbase.h"
32 #include "ole2.h"
33
34 #include "msxml6.h"
35
36 #include "wine/debug.h"
37
38 #include "msxml_private.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(msxml);
41
42 static const WCHAR utf16W[] = {'U','T','F','-','1','6',0};
43 static const WCHAR emptyW[] = {0};
44 static const WCHAR spaceW[] = {' '};
45 static const WCHAR quotW[]  = {'\"'};
46
47 typedef enum
48 {
49     XmlEncoding_UTF8,
50     XmlEncoding_UTF16,
51     XmlEncoding_Unknown
52 } xml_encoding;
53
54 typedef enum
55 {
56     OutputBuffer_Native  = 0x001,
57     OutputBuffer_Encoded = 0x010,
58     OutputBuffer_Both    = 0x100
59 } output_mode;
60
61 typedef enum
62 {
63     MXWriter_BOM = 0,
64     MXWriter_DisableEscaping,
65     MXWriter_Indent,
66     MXWriter_OmitXmlDecl,
67     MXWriter_Standalone,
68     MXWriter_LastProp
69 } mxwriter_prop;
70
71 typedef struct
72 {
73     char *data;
74     unsigned int allocated;
75     unsigned int written;
76 } encoded_buffer;
77
78 typedef struct
79 {
80     encoded_buffer utf16;
81     encoded_buffer encoded;
82     UINT code_page;
83 } output_buffer;
84
85 typedef struct
86 {
87     DispatchEx dispex;
88     IMXWriter IMXWriter_iface;
89     ISAXContentHandler ISAXContentHandler_iface;
90     ISAXLexicalHandler ISAXLexicalHandler_iface;
91
92     LONG ref;
93     MSXML_VERSION class_version;
94
95     VARIANT_BOOL props[MXWriter_LastProp];
96     BOOL prop_changed;
97
98     BSTR version;
99
100     BSTR encoding; /* exact property value */
101     xml_encoding xml_enc;
102
103     /* contains a pending (or not closed yet) element name or NULL if
104        we don't have to close */
105     BSTR element;
106
107     IStream *dest;
108     ULONG dest_written;
109
110     output_buffer *buffer;
111 } mxwriter;
112
113 static xml_encoding parse_encoding_name(const WCHAR *encoding)
114 {
115     static const WCHAR utf8W[]  = {'U','T','F','-','8',0};
116     if (!strcmpiW(encoding, utf8W))  return XmlEncoding_UTF8;
117     if (!strcmpiW(encoding, utf16W)) return XmlEncoding_UTF16;
118     return XmlEncoding_Unknown;
119 }
120
121 static HRESULT init_encoded_buffer(encoded_buffer *buffer)
122 {
123     const int initial_len = 0x2000;
124     buffer->data = heap_alloc(initial_len);
125     if (!buffer->data) return E_OUTOFMEMORY;
126
127     memset(buffer->data, 0, 4);
128     buffer->allocated = initial_len;
129     buffer->written = 0;
130
131     return S_OK;
132 }
133
134 static void free_encoded_buffer(encoded_buffer *buffer)
135 {
136     heap_free(buffer->data);
137 }
138
139 static HRESULT get_code_page(xml_encoding encoding, UINT *cp)
140 {
141     switch (encoding)
142     {
143     case XmlEncoding_UTF8:
144         *cp = CP_UTF8;
145         break;
146     case XmlEncoding_UTF16:
147         *cp = ~0;
148         break;
149     default:
150         FIXME("unsupported encoding %d\n", encoding);
151         return E_NOTIMPL;
152     }
153
154     return S_OK;
155 }
156
157 static HRESULT alloc_output_buffer(xml_encoding encoding, output_buffer **buffer)
158 {
159     output_buffer *ret;
160     HRESULT hr;
161
162     ret = heap_alloc(sizeof(*ret));
163     if (!ret) return E_OUTOFMEMORY;
164
165     hr = get_code_page(encoding, &ret->code_page);
166     if (hr != S_OK) {
167         heap_free(ret);
168         return hr;
169     }
170
171     hr = init_encoded_buffer(&ret->utf16);
172     if (hr != S_OK) {
173         heap_free(ret);
174         return hr;
175     }
176
177     if (ret->code_page == CP_UTF8) {
178         hr = init_encoded_buffer(&ret->encoded);
179         if (hr != S_OK) {
180             free_encoded_buffer(&ret->utf16);
181             heap_free(ret);
182             return hr;
183         }
184     }
185     else
186         memset(&ret->encoded, 0, sizeof(ret->encoded));
187
188     *buffer = ret;
189
190     return S_OK;
191 }
192
193 static void free_output_buffer(output_buffer *buffer)
194 {
195     free_encoded_buffer(&buffer->encoded);
196     free_encoded_buffer(&buffer->utf16);
197     heap_free(buffer);
198 }
199
200 static void grow_buffer(encoded_buffer *buffer, int length)
201 {
202     /* grow if needed, plus 4 bytes to be sure null terminator will fit in */
203     if (buffer->allocated < buffer->written + length + 4)
204     {
205         int grown_size = max(2*buffer->allocated, buffer->allocated + length);
206         buffer->data = heap_realloc(buffer->data, grown_size);
207         buffer->allocated = grown_size;
208     }
209 }
210
211 static HRESULT write_output_buffer_mode(output_buffer *buffer, output_mode mode, const WCHAR *data, int len)
212 {
213     int length;
214     char *ptr;
215
216     if (mode & (OutputBuffer_Encoded | OutputBuffer_Both)) {
217         if (buffer->code_page == CP_UTF8)
218         {
219             length = WideCharToMultiByte(buffer->code_page, 0, data, len, NULL, 0, NULL, NULL);
220             grow_buffer(&buffer->encoded, length);
221             ptr = buffer->encoded.data + buffer->encoded.written;
222             length = WideCharToMultiByte(buffer->code_page, 0, data, len, ptr, length, NULL, NULL);
223             buffer->encoded.written += len == -1 ? length-1 : length;
224         }
225     }
226
227     if (mode & (OutputBuffer_Native | OutputBuffer_Both)) {
228         /* WCHAR data just copied */
229         length = len == -1 ? strlenW(data) : len;
230         if (length)
231         {
232             length *= sizeof(WCHAR);
233
234             grow_buffer(&buffer->utf16, length);
235             ptr = buffer->utf16.data + buffer->utf16.written;
236
237             memcpy(ptr, data, length);
238             buffer->utf16.written += length;
239             ptr += length;
240             /* null termination */
241             memset(ptr, 0, sizeof(WCHAR));
242         }
243     }
244
245     return S_OK;
246 }
247
248 static HRESULT write_output_buffer(output_buffer *buffer, const WCHAR *data, int len)
249 {
250     return write_output_buffer_mode(buffer, OutputBuffer_Both, data, len);
251 }
252
253 /* frees buffer data, reallocates with a default lengths */
254 static void close_output_buffer(mxwriter *This)
255 {
256     heap_free(This->buffer->utf16.data);
257     heap_free(This->buffer->encoded.data);
258     init_encoded_buffer(&This->buffer->utf16);
259     init_encoded_buffer(&This->buffer->encoded);
260     get_code_page(This->xml_enc, &This->buffer->code_page);
261 }
262
263 /* escapes special characters like:
264    '<' -> "&lt;"
265    '&' -> "&amp;"
266    '"' -> "&quot;"
267    '>' -> "&gt;"
268 */
269 static WCHAR *get_escaped_string(const WCHAR *str, int *len)
270 {
271     static const WCHAR ltW[]    = {'&','l','t',';'};
272     static const WCHAR ampW[]   = {'&','a','m','p',';'};
273     static const WCHAR equotW[] = {'&','q','u','o','t',';'};
274     static const WCHAR gtW[]    = {'&','g','t',';'};
275
276     const int default_alloc = 100;
277     const int grow_thresh = 10;
278     int p = *len, conv_len;
279     WCHAR *ptr, *ret;
280
281     /* default buffer size to something if length is unknown */
282     conv_len = *len == -1 ? default_alloc : max(2**len, default_alloc);
283     ptr = ret = heap_alloc(conv_len*sizeof(WCHAR));
284
285     while (*str && p)
286     {
287         if (ptr - ret > conv_len - grow_thresh)
288         {
289             int written = ptr - ret;
290             conv_len *= 2;
291             ptr = ret = heap_realloc(ret, conv_len*sizeof(WCHAR));
292             ptr += written;
293         }
294
295         switch (*str)
296         {
297         case '<':
298             memcpy(ptr, ltW, sizeof(ltW));
299             ptr += sizeof(ltW)/sizeof(WCHAR);
300             break;
301         case '&':
302             memcpy(ptr, ampW, sizeof(ampW));
303             ptr += sizeof(ampW)/sizeof(WCHAR);
304             break;
305         case '"':
306             memcpy(ptr, equotW, sizeof(equotW));
307             ptr += sizeof(equotW)/sizeof(WCHAR);
308             break;
309         case '>':
310             memcpy(ptr, gtW, sizeof(gtW));
311             ptr += sizeof(gtW)/sizeof(WCHAR);
312             break;
313         default:
314             *ptr++ = *str;
315             break;
316         }
317
318         str++;
319         if (*len != -1) p--;
320     }
321
322     if (*len != -1) *len = ptr-ret;
323     *++ptr = 0;
324
325     return ret;
326 }
327
328 static void write_prolog_buffer(const mxwriter *This)
329 {
330     static const WCHAR versionW[] = {'<','?','x','m','l',' ','v','e','r','s','i','o','n','=','\"'};
331     static const WCHAR encodingW[] = {' ','e','n','c','o','d','i','n','g','=','\"'};
332     static const WCHAR standaloneW[] = {' ','s','t','a','n','d','a','l','o','n','e','=','\"'};
333     static const WCHAR yesW[] = {'y','e','s','\"','?','>'};
334     static const WCHAR noW[] = {'n','o','\"','?','>'};
335     static const WCHAR crlfW[] = {'\r','\n'};
336
337     /* version */
338     write_output_buffer(This->buffer, versionW, sizeof(versionW)/sizeof(WCHAR));
339     write_output_buffer(This->buffer, This->version, -1);
340     write_output_buffer(This->buffer, quotW, 1);
341
342     /* encoding */
343     write_output_buffer(This->buffer, encodingW, sizeof(encodingW)/sizeof(WCHAR));
344
345     /* always write UTF-16 to WCHAR buffer */
346     write_output_buffer_mode(This->buffer, OutputBuffer_Native, utf16W, sizeof(utf16W)/sizeof(WCHAR) - 1);
347     write_output_buffer_mode(This->buffer, OutputBuffer_Encoded, This->encoding, -1);
348     write_output_buffer(This->buffer, quotW, 1);
349
350     /* standalone */
351     write_output_buffer(This->buffer, standaloneW, sizeof(standaloneW)/sizeof(WCHAR));
352     if (This->props[MXWriter_Standalone] == VARIANT_TRUE)
353         write_output_buffer(This->buffer, yesW, sizeof(yesW)/sizeof(WCHAR));
354     else
355         write_output_buffer(This->buffer, noW, sizeof(noW)/sizeof(WCHAR));
356
357     write_output_buffer(This->buffer, crlfW, sizeof(crlfW)/sizeof(WCHAR));
358 }
359
360 /* Attempts to the write data from the mxwriter's buffer to
361  * the destination stream (if there is one).
362  */
363 static HRESULT write_data_to_stream(mxwriter *This)
364 {
365     encoded_buffer *buffer;
366     ULONG written = 0;
367     HRESULT hr;
368
369     if (!This->dest)
370         return S_OK;
371
372     /* The xmlOutputBuffer doesn't copy its contents from its 'buffer' to the
373      * 'conv' buffer when UTF8 encoding is used.
374      */
375     if (This->xml_enc != XmlEncoding_UTF16)
376         buffer = &This->buffer->encoded;
377     else
378         buffer = &This->buffer->utf16;
379
380     if (This->dest_written > buffer->written) {
381         ERR("Failed sanity check! Not sure what to do... (%d > %d)\n", This->dest_written, buffer->written);
382         return E_FAIL;
383     } else if (This->dest_written == buffer->written && This->xml_enc != XmlEncoding_UTF8)
384         /* Windows seems to make an empty write call when the encoding is UTF-8 and
385          * all the data has been written to the stream. It doesn't seem make this call
386          * for any other encodings.
387          */
388         return S_OK;
389
390     /* Write the current content from the output buffer into 'dest'.
391      * TODO: Check what Windows does if the IStream doesn't write all of
392      *       the data we give it at once.
393      */
394     hr = IStream_Write(This->dest, buffer->data+This->dest_written,
395                          buffer->written-This->dest_written, &written);
396     if (FAILED(hr)) {
397         WARN("Failed to write data to IStream (0x%08x)\n", hr);
398         return hr;
399     }
400
401     This->dest_written += written;
402     return hr;
403 }
404
405 /* Newly added element start tag left unclosed cause for empty elements
406    we have to close it differently. */
407 static void close_element_starttag(const mxwriter *This)
408 {
409     static const WCHAR gtW[] = {'>'};
410     if (!This->element) return;
411     write_output_buffer(This->buffer, gtW, 1);
412 }
413
414 static void set_element_name(mxwriter *This, const WCHAR *name, int len)
415 {
416     SysFreeString(This->element);
417     This->element = name ? SysAllocStringLen(name, len) : NULL;
418 }
419
420 static inline HRESULT flush_output_buffer(mxwriter *This)
421 {
422     close_element_starttag(This);
423     set_element_name(This, NULL, 0);
424     return write_data_to_stream(This);
425 }
426
427 /* Resets the mxwriter's output buffer by closing it, then creating a new
428  * output buffer using the given encoding.
429  */
430 static inline void reset_output_buffer(mxwriter *This)
431 {
432     close_output_buffer(This);
433     This->dest_written = 0;
434 }
435
436 static HRESULT writer_set_property(mxwriter *writer, mxwriter_prop property, VARIANT_BOOL value)
437 {
438     writer->props[property] = value;
439     writer->prop_changed = TRUE;
440     return S_OK;
441 }
442
443 static HRESULT writer_get_property(const mxwriter *writer, mxwriter_prop property, VARIANT_BOOL *value)
444 {
445     if (!value) return E_POINTER;
446     *value = writer->props[property];
447     return S_OK;
448 }
449
450 static inline mxwriter *impl_from_IMXWriter(IMXWriter *iface)
451 {
452     return CONTAINING_RECORD(iface, mxwriter, IMXWriter_iface);
453 }
454
455 static inline mxwriter *impl_from_ISAXContentHandler(ISAXContentHandler *iface)
456 {
457     return CONTAINING_RECORD(iface, mxwriter, ISAXContentHandler_iface);
458 }
459
460 static inline mxwriter *impl_from_ISAXLexicalHandler(ISAXLexicalHandler *iface)
461 {
462     return CONTAINING_RECORD(iface, mxwriter, ISAXLexicalHandler_iface);
463 }
464
465 static HRESULT WINAPI mxwriter_QueryInterface(IMXWriter *iface, REFIID riid, void **obj)
466 {
467     mxwriter *This = impl_from_IMXWriter( iface );
468
469     TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), obj);
470
471     *obj = NULL;
472
473     if ( IsEqualGUID( riid, &IID_IMXWriter ) ||
474          IsEqualGUID( riid, &IID_IDispatch ) ||
475          IsEqualGUID( riid, &IID_IUnknown ) )
476     {
477         *obj = &This->IMXWriter_iface;
478     }
479     else if ( IsEqualGUID( riid, &IID_ISAXContentHandler ) )
480     {
481         *obj = &This->ISAXContentHandler_iface;
482     }
483     else if ( IsEqualGUID( riid, &IID_ISAXLexicalHandler ) )
484     {
485         *obj = &This->ISAXLexicalHandler_iface;
486     }
487     else if (dispex_query_interface(&This->dispex, riid, obj))
488     {
489         return *obj ? S_OK : E_NOINTERFACE;
490     }
491     else
492     {
493         ERR("interface %s not implemented\n", debugstr_guid(riid));
494         *obj = NULL;
495         return E_NOINTERFACE;
496     }
497
498     IMXWriter_AddRef(iface);
499     return S_OK;
500 }
501
502 static ULONG WINAPI mxwriter_AddRef(IMXWriter *iface)
503 {
504     mxwriter *This = impl_from_IMXWriter( iface );
505     LONG ref = InterlockedIncrement(&This->ref);
506
507     TRACE("(%p)->(%d)\n", This, ref);
508
509     return ref;
510 }
511
512 static ULONG WINAPI mxwriter_Release(IMXWriter *iface)
513 {
514     mxwriter *This = impl_from_IMXWriter( iface );
515     ULONG ref = InterlockedDecrement(&This->ref);
516
517     TRACE("(%p)->(%d)\n", This, ref);
518
519     if(!ref)
520     {
521         /* Windows flushes the buffer when the interface is destroyed. */
522         flush_output_buffer(This);
523         free_output_buffer(This->buffer);
524
525         if (This->dest) IStream_Release(This->dest);
526         SysFreeString(This->version);
527         SysFreeString(This->encoding);
528
529         SysFreeString(This->element);
530         release_dispex(&This->dispex);
531         heap_free(This);
532     }
533
534     return ref;
535 }
536
537 static HRESULT WINAPI mxwriter_GetTypeInfoCount(IMXWriter *iface, UINT* pctinfo)
538 {
539     mxwriter *This = impl_from_IMXWriter( iface );
540     return IDispatchEx_GetTypeInfoCount(&This->dispex.IDispatchEx_iface, pctinfo);
541 }
542
543 static HRESULT WINAPI mxwriter_GetTypeInfo(
544     IMXWriter *iface,
545     UINT iTInfo, LCID lcid,
546     ITypeInfo** ppTInfo )
547 {
548     mxwriter *This = impl_from_IMXWriter( iface );
549     return IDispatchEx_GetTypeInfo(&This->dispex.IDispatchEx_iface,
550         iTInfo, lcid, ppTInfo);
551 }
552
553 static HRESULT WINAPI mxwriter_GetIDsOfNames(
554     IMXWriter *iface,
555     REFIID riid, LPOLESTR* rgszNames,
556     UINT cNames, LCID lcid, DISPID* rgDispId )
557 {
558     mxwriter *This = impl_from_IMXWriter( iface );
559     return IDispatchEx_GetIDsOfNames(&This->dispex.IDispatchEx_iface,
560         riid, rgszNames, cNames, lcid, rgDispId);
561 }
562
563 static HRESULT WINAPI mxwriter_Invoke(
564     IMXWriter *iface,
565     DISPID dispIdMember, REFIID riid, LCID lcid,
566     WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult,
567     EXCEPINFO* pExcepInfo, UINT* puArgErr )
568 {
569     mxwriter *This = impl_from_IMXWriter( iface );
570     return IDispatchEx_Invoke(&This->dispex.IDispatchEx_iface,
571         dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
572 }
573
574 static HRESULT WINAPI mxwriter_put_output(IMXWriter *iface, VARIANT dest)
575 {
576     mxwriter *This = impl_from_IMXWriter( iface );
577     HRESULT hr;
578
579     TRACE("(%p)->(%s)\n", This, debugstr_variant(&dest));
580
581     hr = flush_output_buffer(This);
582     if (FAILED(hr))
583         return hr;
584
585     switch (V_VT(&dest))
586     {
587     case VT_EMPTY:
588     {
589         if (This->dest) IStream_Release(This->dest);
590         This->dest = NULL;
591         reset_output_buffer(This);
592         break;
593     }
594     case VT_UNKNOWN:
595     {
596         IStream *stream;
597
598         hr = IUnknown_QueryInterface(V_UNKNOWN(&dest), &IID_IStream, (void**)&stream);
599         if (hr == S_OK)
600         {
601             /* Recreate the output buffer to make sure it's using the correct encoding. */
602             reset_output_buffer(This);
603
604             if (This->dest) IStream_Release(This->dest);
605             This->dest = stream;
606             break;
607         }
608
609         FIXME("unhandled interface type for VT_UNKNOWN destination\n");
610         return E_NOTIMPL;
611     }
612     default:
613         FIXME("unhandled destination type %s\n", debugstr_variant(&dest));
614         return E_NOTIMPL;
615     }
616
617     return S_OK;
618 }
619
620 static HRESULT WINAPI mxwriter_get_output(IMXWriter *iface, VARIANT *dest)
621 {
622     mxwriter *This = impl_from_IMXWriter( iface );
623
624     TRACE("(%p)->(%p)\n", This, dest);
625
626     if (!This->dest)
627     {
628         HRESULT hr = flush_output_buffer(This);
629         if (FAILED(hr))
630             return hr;
631
632         V_VT(dest)   = VT_BSTR;
633         V_BSTR(dest) = SysAllocString((WCHAR*)This->buffer->utf16.data);
634
635         return S_OK;
636     }
637     else
638         FIXME("not implemented when stream is set up\n");
639
640     return E_NOTIMPL;
641 }
642
643 static HRESULT WINAPI mxwriter_put_encoding(IMXWriter *iface, BSTR encoding)
644 {
645     mxwriter *This = impl_from_IMXWriter( iface );
646     xml_encoding enc;
647     HRESULT hr;
648
649     TRACE("(%p)->(%s)\n", This, debugstr_w(encoding));
650
651     enc = parse_encoding_name(encoding);
652     if (enc == XmlEncoding_Unknown)
653     {
654         FIXME("unsupported encoding %s\n", debugstr_w(encoding));
655         return E_INVALIDARG;
656     }
657
658     hr = flush_output_buffer(This);
659     if (FAILED(hr))
660         return hr;
661
662     SysReAllocString(&This->encoding, encoding);
663     This->xml_enc = enc;
664
665     TRACE("got encoding %d\n", This->xml_enc);
666     reset_output_buffer(This);
667     return S_OK;
668 }
669
670 static HRESULT WINAPI mxwriter_get_encoding(IMXWriter *iface, BSTR *encoding)
671 {
672     mxwriter *This = impl_from_IMXWriter( iface );
673
674     TRACE("(%p)->(%p)\n", This, encoding);
675
676     if (!encoding) return E_POINTER;
677
678     *encoding = SysAllocString(This->encoding);
679     if (!*encoding) return E_OUTOFMEMORY;
680
681     return S_OK;
682 }
683
684 static HRESULT WINAPI mxwriter_put_byteOrderMark(IMXWriter *iface, VARIANT_BOOL value)
685 {
686     mxwriter *This = impl_from_IMXWriter( iface );
687
688     TRACE("(%p)->(%d)\n", This, value);
689     return writer_set_property(This, MXWriter_BOM, value);
690 }
691
692 static HRESULT WINAPI mxwriter_get_byteOrderMark(IMXWriter *iface, VARIANT_BOOL *value)
693 {
694     mxwriter *This = impl_from_IMXWriter( iface );
695
696     TRACE("(%p)->(%p)\n", This, value);
697     return writer_get_property(This, MXWriter_BOM, value);
698 }
699
700 static HRESULT WINAPI mxwriter_put_indent(IMXWriter *iface, VARIANT_BOOL value)
701 {
702     mxwriter *This = impl_from_IMXWriter( iface );
703
704     TRACE("(%p)->(%d)\n", This, value);
705     return writer_set_property(This, MXWriter_Indent, value);
706 }
707
708 static HRESULT WINAPI mxwriter_get_indent(IMXWriter *iface, VARIANT_BOOL *value)
709 {
710     mxwriter *This = impl_from_IMXWriter( iface );
711
712     TRACE("(%p)->(%p)\n", This, value);
713     return writer_get_property(This, MXWriter_Indent, value);
714 }
715
716 static HRESULT WINAPI mxwriter_put_standalone(IMXWriter *iface, VARIANT_BOOL value)
717 {
718     mxwriter *This = impl_from_IMXWriter( iface );
719
720     TRACE("(%p)->(%d)\n", This, value);
721     return writer_set_property(This, MXWriter_Standalone, value);
722 }
723
724 static HRESULT WINAPI mxwriter_get_standalone(IMXWriter *iface, VARIANT_BOOL *value)
725 {
726     mxwriter *This = impl_from_IMXWriter( iface );
727
728     TRACE("(%p)->(%p)\n", This, value);
729     return writer_get_property(This, MXWriter_Standalone, value);
730 }
731
732 static HRESULT WINAPI mxwriter_put_omitXMLDeclaration(IMXWriter *iface, VARIANT_BOOL value)
733 {
734     mxwriter *This = impl_from_IMXWriter( iface );
735
736     TRACE("(%p)->(%d)\n", This, value);
737     return writer_set_property(This, MXWriter_OmitXmlDecl, value);
738 }
739
740 static HRESULT WINAPI mxwriter_get_omitXMLDeclaration(IMXWriter *iface, VARIANT_BOOL *value)
741 {
742     mxwriter *This = impl_from_IMXWriter( iface );
743
744     TRACE("(%p)->(%p)\n", This, value);
745     return writer_get_property(This, MXWriter_OmitXmlDecl, value);
746 }
747
748 static HRESULT WINAPI mxwriter_put_version(IMXWriter *iface, BSTR version)
749 {
750     mxwriter *This = impl_from_IMXWriter( iface );
751
752     TRACE("(%p)->(%s)\n", This, debugstr_w(version));
753
754     if (!version) return E_INVALIDARG;
755
756     SysFreeString(This->version);
757     This->version = SysAllocString(version);
758
759     return S_OK;
760 }
761
762 static HRESULT WINAPI mxwriter_get_version(IMXWriter *iface, BSTR *version)
763 {
764     mxwriter *This = impl_from_IMXWriter( iface );
765
766     TRACE("(%p)->(%p)\n", This, version);
767
768     if (!version) return E_POINTER;
769
770     return return_bstr(This->version, version);
771 }
772
773 static HRESULT WINAPI mxwriter_put_disableOutputEscaping(IMXWriter *iface, VARIANT_BOOL value)
774 {
775     mxwriter *This = impl_from_IMXWriter( iface );
776
777     TRACE("(%p)->(%d)\n", This, value);
778     return writer_set_property(This, MXWriter_DisableEscaping, value);
779 }
780
781 static HRESULT WINAPI mxwriter_get_disableOutputEscaping(IMXWriter *iface, VARIANT_BOOL *value)
782 {
783     mxwriter *This = impl_from_IMXWriter( iface );
784
785     TRACE("(%p)->(%p)\n", This, value);
786     return writer_get_property(This, MXWriter_DisableEscaping, value);
787 }
788
789 static HRESULT WINAPI mxwriter_flush(IMXWriter *iface)
790 {
791     mxwriter *This = impl_from_IMXWriter( iface );
792     TRACE("(%p)\n", This);
793     return flush_output_buffer(This);
794 }
795
796 static const struct IMXWriterVtbl MXWriterVtbl =
797 {
798     mxwriter_QueryInterface,
799     mxwriter_AddRef,
800     mxwriter_Release,
801     mxwriter_GetTypeInfoCount,
802     mxwriter_GetTypeInfo,
803     mxwriter_GetIDsOfNames,
804     mxwriter_Invoke,
805     mxwriter_put_output,
806     mxwriter_get_output,
807     mxwriter_put_encoding,
808     mxwriter_get_encoding,
809     mxwriter_put_byteOrderMark,
810     mxwriter_get_byteOrderMark,
811     mxwriter_put_indent,
812     mxwriter_get_indent,
813     mxwriter_put_standalone,
814     mxwriter_get_standalone,
815     mxwriter_put_omitXMLDeclaration,
816     mxwriter_get_omitXMLDeclaration,
817     mxwriter_put_version,
818     mxwriter_get_version,
819     mxwriter_put_disableOutputEscaping,
820     mxwriter_get_disableOutputEscaping,
821     mxwriter_flush
822 };
823
824 /*** ISAXContentHandler ***/
825 static HRESULT WINAPI SAXContentHandler_QueryInterface(
826     ISAXContentHandler *iface,
827     REFIID riid,
828     void **obj)
829 {
830     mxwriter *This = impl_from_ISAXContentHandler( iface );
831     return IMXWriter_QueryInterface(&This->IMXWriter_iface, riid, obj);
832 }
833
834 static ULONG WINAPI SAXContentHandler_AddRef(ISAXContentHandler *iface)
835 {
836     mxwriter *This = impl_from_ISAXContentHandler( iface );
837     return IMXWriter_AddRef(&This->IMXWriter_iface);
838 }
839
840 static ULONG WINAPI SAXContentHandler_Release(ISAXContentHandler *iface)
841 {
842     mxwriter *This = impl_from_ISAXContentHandler( iface );
843     return IMXWriter_Release(&This->IMXWriter_iface);
844 }
845
846 static HRESULT WINAPI SAXContentHandler_putDocumentLocator(
847     ISAXContentHandler *iface,
848     ISAXLocator *locator)
849 {
850     mxwriter *This = impl_from_ISAXContentHandler( iface );
851     FIXME("(%p)->(%p)\n", This, locator);
852     return E_NOTIMPL;
853 }
854
855 static HRESULT WINAPI SAXContentHandler_startDocument(ISAXContentHandler *iface)
856 {
857     mxwriter *This = impl_from_ISAXContentHandler( iface );
858
859     TRACE("(%p)\n", This);
860
861     /* If properties have been changed since the last "endDocument" call
862      * we need to reset the output buffer. If we don't the output buffer
863      * could end up with multiple XML documents in it, plus this seems to
864      * be how Windows works.
865      */
866     if (This->prop_changed) {
867         reset_output_buffer(This);
868         This->prop_changed = FALSE;
869     }
870
871     if (This->props[MXWriter_OmitXmlDecl] == VARIANT_TRUE) return S_OK;
872
873     write_prolog_buffer(This);
874
875     if (This->dest && This->xml_enc == XmlEncoding_UTF16) {
876         static const char utf16BOM[] = {0xff,0xfe};
877
878         if (This->props[MXWriter_BOM] == VARIANT_TRUE)
879             /* Windows passes a NULL pointer as the pcbWritten parameter and
880              * ignores any error codes returned from this Write call.
881              */
882             IStream_Write(This->dest, utf16BOM, sizeof(utf16BOM), NULL);
883     }
884
885     return S_OK;
886 }
887
888 static HRESULT WINAPI SAXContentHandler_endDocument(ISAXContentHandler *iface)
889 {
890     mxwriter *This = impl_from_ISAXContentHandler( iface );
891     TRACE("(%p)\n", This);
892     This->prop_changed = FALSE;
893     return flush_output_buffer(This);
894 }
895
896 static HRESULT WINAPI SAXContentHandler_startPrefixMapping(
897     ISAXContentHandler *iface,
898     const WCHAR *prefix,
899     int nprefix,
900     const WCHAR *uri,
901     int nuri)
902 {
903     mxwriter *This = impl_from_ISAXContentHandler( iface );
904     FIXME("(%p)->(%s %s)\n", This, debugstr_wn(prefix, nprefix), debugstr_wn(uri, nuri));
905     return E_NOTIMPL;
906 }
907
908 static HRESULT WINAPI SAXContentHandler_endPrefixMapping(
909     ISAXContentHandler *iface,
910     const WCHAR *prefix,
911     int nprefix)
912 {
913     mxwriter *This = impl_from_ISAXContentHandler( iface );
914     FIXME("(%p)->(%s)\n", This, debugstr_wn(prefix, nprefix));
915     return E_NOTIMPL;
916 }
917
918 static HRESULT WINAPI SAXContentHandler_startElement(
919     ISAXContentHandler *iface,
920     const WCHAR *namespaceUri,
921     int nnamespaceUri,
922     const WCHAR *local_name,
923     int nlocal_name,
924     const WCHAR *QName,
925     int nQName,
926     ISAXAttributes *attr)
927 {
928     mxwriter *This = impl_from_ISAXContentHandler( iface );
929     static const WCHAR ltW[] = {'<'};
930
931     TRACE("(%p)->(%s %s %s %p)\n", This, debugstr_wn(namespaceUri, nnamespaceUri),
932         debugstr_wn(local_name, nlocal_name), debugstr_wn(QName, nQName), attr);
933
934     if ((!namespaceUri || !local_name || !QName) && This->class_version != MSXML6)
935         return E_INVALIDARG;
936
937     close_element_starttag(This);
938     set_element_name(This, QName ? QName  : emptyW,
939                            QName ? nQName : 0);
940
941     write_output_buffer(This->buffer, ltW, 1);
942     write_output_buffer(This->buffer, QName, nQName);
943
944     if (attr)
945     {
946         HRESULT hr;
947         INT length;
948         INT i;
949
950         hr = ISAXAttributes_getLength(attr, &length);
951         if (FAILED(hr)) return hr;
952
953         for (i = 0; i < length; i++)
954         {
955             static const WCHAR eqqW[] = {'=','\"'};
956             const WCHAR *str;
957             WCHAR *escaped;
958             INT len = 0;
959
960             hr = ISAXAttributes_getQName(attr, i, &str, &len);
961             if (FAILED(hr)) return hr;
962
963             /* space separator in front of every attribute */
964             write_output_buffer(This->buffer, spaceW, 1);
965             write_output_buffer(This->buffer, str, len);
966
967             write_output_buffer(This->buffer, eqqW, 2);
968
969             len = 0;
970             hr = ISAXAttributes_getValue(attr, i, &str, &len);
971             if (FAILED(hr)) return hr;
972
973             escaped = get_escaped_string(str, &len);
974             write_output_buffer(This->buffer, escaped, len);
975             heap_free(escaped);
976
977             write_output_buffer(This->buffer, quotW, 1);
978         }
979     }
980
981     return S_OK;
982 }
983
984 static HRESULT WINAPI SAXContentHandler_endElement(
985     ISAXContentHandler *iface,
986     const WCHAR *namespaceUri,
987     int nnamespaceUri,
988     const WCHAR * local_name,
989     int nlocal_name,
990     const WCHAR *QName,
991     int nQName)
992 {
993     mxwriter *This = impl_from_ISAXContentHandler( iface );
994
995     TRACE("(%p)->(%s:%d %s:%d %s:%d)\n", This, debugstr_wn(namespaceUri, nnamespaceUri), nnamespaceUri,
996         debugstr_wn(local_name, nlocal_name), nlocal_name, debugstr_wn(QName, nQName), nQName);
997
998     if ((!namespaceUri || !local_name || !QName) && This->class_version != MSXML6)
999         return E_INVALIDARG;
1000
1001     if (This->element && QName && !strncmpW(This->element, QName, nQName))
1002     {
1003         static const WCHAR closeW[] = {'/','>'};
1004
1005         write_output_buffer(This->buffer, closeW, 2);
1006     }
1007     else
1008     {
1009         static const WCHAR closetagW[] = {'<','/'};
1010         static const WCHAR gtW[] = {'>'};
1011
1012         write_output_buffer(This->buffer, closetagW, 2);
1013         write_output_buffer(This->buffer, QName, nQName);
1014         write_output_buffer(This->buffer, gtW, 1);
1015     }
1016
1017     set_element_name(This, NULL, 0);
1018
1019     return S_OK;
1020 }
1021
1022 static HRESULT WINAPI SAXContentHandler_characters(
1023     ISAXContentHandler *iface,
1024     const WCHAR *chars,
1025     int nchars)
1026 {
1027     mxwriter *This = impl_from_ISAXContentHandler( iface );
1028
1029     TRACE("(%p)->(%s:%d)\n", This, debugstr_wn(chars, nchars), nchars);
1030
1031     if (!chars) return E_INVALIDARG;
1032
1033     close_element_starttag(This);
1034     set_element_name(This, NULL, 0);
1035
1036     if (nchars)
1037         write_output_buffer(This->buffer, chars, nchars);
1038
1039     return S_OK;
1040 }
1041
1042 static HRESULT WINAPI SAXContentHandler_ignorableWhitespace(
1043     ISAXContentHandler *iface,
1044     const WCHAR *chars,
1045     int nchars)
1046 {
1047     mxwriter *This = impl_from_ISAXContentHandler( iface );
1048     FIXME("(%p)->(%s)\n", This, debugstr_wn(chars, nchars));
1049     return E_NOTIMPL;
1050 }
1051
1052 static HRESULT WINAPI SAXContentHandler_processingInstruction(
1053     ISAXContentHandler *iface,
1054     const WCHAR *target,
1055     int ntarget,
1056     const WCHAR *data,
1057     int ndata)
1058 {
1059     mxwriter *This = impl_from_ISAXContentHandler( iface );
1060     FIXME("(%p)->(%s %s)\n", This, debugstr_wn(target, ntarget), debugstr_wn(data, ndata));
1061     return E_NOTIMPL;
1062 }
1063
1064 static HRESULT WINAPI SAXContentHandler_skippedEntity(
1065     ISAXContentHandler *iface,
1066     const WCHAR *name,
1067     int nname)
1068 {
1069     mxwriter *This = impl_from_ISAXContentHandler( iface );
1070     FIXME("(%p)->(%s)\n", This, debugstr_wn(name, nname));
1071     return E_NOTIMPL;
1072 }
1073
1074 static const struct ISAXContentHandlerVtbl SAXContentHandlerVtbl =
1075 {
1076     SAXContentHandler_QueryInterface,
1077     SAXContentHandler_AddRef,
1078     SAXContentHandler_Release,
1079     SAXContentHandler_putDocumentLocator,
1080     SAXContentHandler_startDocument,
1081     SAXContentHandler_endDocument,
1082     SAXContentHandler_startPrefixMapping,
1083     SAXContentHandler_endPrefixMapping,
1084     SAXContentHandler_startElement,
1085     SAXContentHandler_endElement,
1086     SAXContentHandler_characters,
1087     SAXContentHandler_ignorableWhitespace,
1088     SAXContentHandler_processingInstruction,
1089     SAXContentHandler_skippedEntity
1090 };
1091
1092 /*** ISAXLexicalHandler ***/
1093 static HRESULT WINAPI SAXLexicalHandler_QueryInterface(ISAXLexicalHandler *iface,
1094     REFIID riid, void **obj)
1095 {
1096     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1097     return IMXWriter_QueryInterface(&This->IMXWriter_iface, riid, obj);
1098 }
1099
1100 static ULONG WINAPI SAXLexicalHandler_AddRef(ISAXLexicalHandler *iface)
1101 {
1102     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1103     return IMXWriter_AddRef(&This->IMXWriter_iface);
1104 }
1105
1106 static ULONG WINAPI SAXLexicalHandler_Release(ISAXLexicalHandler *iface)
1107 {
1108     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1109     return IMXWriter_Release(&This->IMXWriter_iface);
1110 }
1111
1112 static HRESULT WINAPI SAXLexicalHandler_startDTD(ISAXLexicalHandler *iface,
1113     const WCHAR *name, int name_len, const WCHAR *publicId, int publicId_len,
1114     const WCHAR *systemId, int systemId_len)
1115 {
1116     static const WCHAR doctypeW[] = {'<','!','D','O','C','T','Y','P','E',' '};
1117     static const WCHAR openintW[] = {'[','\r','\n'};
1118
1119     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1120
1121     TRACE("(%p)->(%s %s %s)\n", This, debugstr_wn(name, name_len), debugstr_wn(publicId, publicId_len),
1122         debugstr_wn(systemId, systemId_len));
1123
1124     if (!name) return E_INVALIDARG;
1125
1126     write_output_buffer(This->buffer, doctypeW, sizeof(doctypeW)/sizeof(WCHAR));
1127
1128     if (*name)
1129     {
1130         write_output_buffer(This->buffer, name, name_len);
1131         write_output_buffer(This->buffer, spaceW, 1);
1132     }
1133
1134     if (publicId)
1135     {
1136         static const WCHAR publicW[] = {'P','U','B','L','I','C',' '};
1137
1138         write_output_buffer(This->buffer, publicW, sizeof(publicW)/sizeof(WCHAR));
1139         write_output_buffer(This->buffer, quotW, 1);
1140         write_output_buffer(This->buffer, publicId, publicId_len);
1141         write_output_buffer(This->buffer, quotW, 1);
1142
1143         if (!systemId) return E_INVALIDARG;
1144
1145         if (*publicId)
1146             write_output_buffer(This->buffer, spaceW, 1);
1147
1148         write_output_buffer(This->buffer, quotW, 1);
1149         write_output_buffer(This->buffer, systemId, systemId_len);
1150         write_output_buffer(This->buffer, quotW, 1);
1151
1152         if (*systemId)
1153             write_output_buffer(This->buffer, spaceW, 1);
1154     }
1155     else if (systemId)
1156     {
1157         static const WCHAR systemW[] = {'S','Y','S','T','E','M',' '};
1158
1159         write_output_buffer(This->buffer, systemW, sizeof(systemW)/sizeof(WCHAR));
1160         write_output_buffer(This->buffer, quotW, 1);
1161         write_output_buffer(This->buffer, systemId, systemId_len);
1162         write_output_buffer(This->buffer, quotW, 1);
1163         if (*systemId)
1164             write_output_buffer(This->buffer, spaceW, 1);
1165     }
1166
1167     write_output_buffer(This->buffer, openintW, sizeof(openintW)/sizeof(WCHAR));
1168
1169     return S_OK;
1170 }
1171
1172 static HRESULT WINAPI SAXLexicalHandler_endDTD(ISAXLexicalHandler *iface)
1173 {
1174     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1175     static const WCHAR closedtdW[] = {']','>','\r','\n'};
1176
1177     TRACE("(%p)\n", This);
1178
1179     write_output_buffer(This->buffer, closedtdW, sizeof(closedtdW)/sizeof(WCHAR));
1180
1181     return S_OK;
1182 }
1183
1184 static HRESULT WINAPI SAXLexicalHandler_startEntity(ISAXLexicalHandler *iface, const WCHAR *name, int len)
1185 {
1186     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1187     FIXME("(%p)->(%s): stub\n", This, debugstr_wn(name, len));
1188     return E_NOTIMPL;
1189 }
1190
1191 static HRESULT WINAPI SAXLexicalHandler_endEntity(ISAXLexicalHandler *iface, const WCHAR *name, int len)
1192 {
1193     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1194     FIXME("(%p)->(%s): stub\n", This, debugstr_wn(name, len));
1195     return E_NOTIMPL;
1196 }
1197
1198 static HRESULT WINAPI SAXLexicalHandler_startCDATA(ISAXLexicalHandler *iface)
1199 {
1200     static const WCHAR scdataW[] = {'<','!','[','C','D','A','T','A','['};
1201     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1202
1203     TRACE("(%p)\n", This);
1204
1205     write_output_buffer(This->buffer, scdataW, sizeof(scdataW)/sizeof(WCHAR));
1206
1207     return S_OK;
1208 }
1209
1210 static HRESULT WINAPI SAXLexicalHandler_endCDATA(ISAXLexicalHandler *iface)
1211 {
1212     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1213     static const WCHAR ecdataW[] = {']',']','>'};
1214
1215     TRACE("(%p)\n", This);
1216
1217     write_output_buffer(This->buffer, ecdataW, sizeof(ecdataW)/sizeof(WCHAR));
1218
1219     return S_OK;
1220 }
1221
1222 static HRESULT WINAPI SAXLexicalHandler_comment(ISAXLexicalHandler *iface, const WCHAR *chars, int nchars)
1223 {
1224     mxwriter *This = impl_from_ISAXLexicalHandler( iface );
1225     static const WCHAR copenW[] = {'<','!','-','-'};
1226     static const WCHAR ccloseW[] = {'-','-','>','\r','\n'};
1227
1228     TRACE("(%p)->(%s:%d)\n", This, debugstr_wn(chars, nchars), nchars);
1229
1230     if (!chars) return E_INVALIDARG;
1231
1232     close_element_starttag(This);
1233
1234     write_output_buffer(This->buffer, copenW, sizeof(copenW)/sizeof(WCHAR));
1235     if (nchars)
1236         write_output_buffer(This->buffer, chars, nchars);
1237     write_output_buffer(This->buffer, ccloseW, sizeof(ccloseW)/sizeof(WCHAR));
1238
1239     return S_OK;
1240 }
1241
1242 static const struct ISAXLexicalHandlerVtbl SAXLexicalHandlerVtbl =
1243 {
1244     SAXLexicalHandler_QueryInterface,
1245     SAXLexicalHandler_AddRef,
1246     SAXLexicalHandler_Release,
1247     SAXLexicalHandler_startDTD,
1248     SAXLexicalHandler_endDTD,
1249     SAXLexicalHandler_startEntity,
1250     SAXLexicalHandler_endEntity,
1251     SAXLexicalHandler_startCDATA,
1252     SAXLexicalHandler_endCDATA,
1253     SAXLexicalHandler_comment
1254 };
1255
1256 static const tid_t mxwriter_iface_tids[] = {
1257     IMXWriter_tid,
1258     0
1259 };
1260
1261 static dispex_static_data_t mxwriter_dispex = {
1262     NULL,
1263     IMXWriter_tid,
1264     NULL,
1265     mxwriter_iface_tids
1266 };
1267
1268 HRESULT MXWriter_create(MSXML_VERSION version, IUnknown *outer, void **ppObj)
1269 {
1270     static const WCHAR version10W[] = {'1','.','0',0};
1271     mxwriter *This;
1272     HRESULT hr;
1273
1274     TRACE("(%p, %p)\n", outer, ppObj);
1275
1276     if (outer) FIXME("support aggregation, outer\n");
1277
1278     This = heap_alloc( sizeof (*This) );
1279     if(!This)
1280         return E_OUTOFMEMORY;
1281
1282     This->IMXWriter_iface.lpVtbl = &MXWriterVtbl;
1283     This->ISAXContentHandler_iface.lpVtbl = &SAXContentHandlerVtbl;
1284     This->ISAXLexicalHandler_iface.lpVtbl = &SAXLexicalHandlerVtbl;
1285     This->ref = 1;
1286     This->class_version = version;
1287
1288     This->props[MXWriter_BOM] = VARIANT_TRUE;
1289     This->props[MXWriter_DisableEscaping] = VARIANT_FALSE;
1290     This->props[MXWriter_Indent] = VARIANT_FALSE;
1291     This->props[MXWriter_OmitXmlDecl] = VARIANT_FALSE;
1292     This->props[MXWriter_Standalone] = VARIANT_FALSE;
1293     This->prop_changed = FALSE;
1294     This->encoding = SysAllocString(utf16W);
1295     This->version  = SysAllocString(version10W);
1296     This->xml_enc  = XmlEncoding_UTF16;
1297
1298     This->element = NULL;
1299
1300     This->dest = NULL;
1301     This->dest_written = 0;
1302
1303     hr = alloc_output_buffer(This->xml_enc, &This->buffer);
1304     if (hr != S_OK) {
1305         SysFreeString(This->encoding);
1306         SysFreeString(This->version);
1307         heap_free(This);
1308         return hr;
1309     }
1310
1311     init_dispex(&This->dispex, (IUnknown*)&This->IMXWriter_iface, &mxwriter_dispex);
1312
1313     *ppObj = &This->IMXWriter_iface;
1314
1315     TRACE("returning iface %p\n", *ppObj);
1316
1317     return S_OK;
1318 }