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