xref: /reactos/dll/win32/xmllite/writer.c (revision b8dd046e)
1 /*
2  * IXmlWriter implementation
3  *
4  * Copyright 2011 Alistair Leslie-Hughes
5  * Copyright 2014-2018 Nikolay Sivov for CodeWeavers
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 #define COBJMACROS
22 
23 #include <assert.h>
24 #include <stdarg.h>
25 
26 #include "windef.h"
27 #include "winbase.h"
28 #include "objbase.h"
29 #include "xmllite.h"
30 #include "xmllite_private.h"
31 #ifdef __REACTOS__
32 #include <wchar.h>
33 #include <winnls.h>
34 #endif
35 #include "initguid.h"
36 
37 #include "wine/debug.h"
38 #include "wine/list.h"
39 
40 WINE_DEFAULT_DEBUG_CHANNEL(xmllite);
41 
42 /* not defined in public headers */
43 DEFINE_GUID(IID_IXmlWriterOutput, 0xc1131708, 0x0f59, 0x477f, 0x93, 0x59, 0x7d, 0x33, 0x24, 0x51, 0xbc, 0x1a);
44 
45 static const WCHAR closeelementW[] = {'<','/'};
46 static const WCHAR closetagW[] = {' ','/','>'};
47 static const WCHAR closepiW[] = {'?','>'};
48 static const WCHAR xmlnsW[] = {' ','x','m','l','n','s'};
49 static const WCHAR xmlnsuriW[] = {'h','t','t','p',':','/','/','w','w','w','.','w','3','.','o','r','g','/','2','0','0','0','/','x','m','l','n','s','/',0};
50 
51 struct output_buffer
52 {
53     char *data;
54     unsigned int allocated;
55     unsigned int written;
56     UINT codepage;
57 };
58 
59 typedef enum
60 {
61     XmlWriterState_Initial,         /* output is not set yet */
62     XmlWriterState_Ready,           /* SetOutput() was called, ready to start */
63     XmlWriterState_InvalidEncoding, /* SetOutput() was called, but output had invalid encoding */
64     XmlWriterState_PIDocStarted,    /* document was started with manually added 'xml' PI */
65     XmlWriterState_DocStarted,      /* document was started with WriteStartDocument() */
66     XmlWriterState_ElemStarted,     /* writing element */
67     XmlWriterState_Content,         /* content is accepted at this point */
68     XmlWriterState_DocClosed        /* WriteEndDocument was called */
69 } XmlWriterState;
70 
71 typedef struct
72 {
73     IXmlWriterOutput IXmlWriterOutput_iface;
74     LONG ref;
75     IUnknown *output;
76     ISequentialStream *stream;
77     IMalloc *imalloc;
78     xml_encoding encoding;
79     WCHAR *encoding_name; /* exactly as specified on output creation */
80     struct output_buffer buffer;
81     DWORD written : 1;
82 } xmlwriteroutput;
83 
84 static const struct IUnknownVtbl xmlwriteroutputvtbl;
85 
86 struct element
87 {
88     struct list entry;
89     WCHAR *qname;
90     unsigned int len; /* qname length in chars */
91     struct list ns;
92 };
93 
94 struct ns
95 {
96     struct list entry;
97     WCHAR *prefix;
98     int prefix_len;
99     WCHAR *uri;
100     BOOL emitted;
101     struct element *element;
102 };
103 
104 typedef struct _xmlwriter
105 {
106     IXmlWriter IXmlWriter_iface;
107     LONG ref;
108     IMalloc *imalloc;
109     xmlwriteroutput *output;
110     unsigned int indent_level;
111     BOOL indent;
112     BOOL bom;
113     BOOL omitxmldecl;
114     XmlConformanceLevel conformance;
115     XmlWriterState state;
116     struct list elements;
117     DWORD bomwritten : 1;
118     DWORD starttagopen : 1;
119     DWORD textnode : 1;
120 } xmlwriter;
121 
122 static inline xmlwriter *impl_from_IXmlWriter(IXmlWriter *iface)
123 {
124     return CONTAINING_RECORD(iface, xmlwriter, IXmlWriter_iface);
125 }
126 
127 static inline xmlwriteroutput *impl_from_IXmlWriterOutput(IXmlWriterOutput *iface)
128 {
129     return CONTAINING_RECORD(iface, xmlwriteroutput, IXmlWriterOutput_iface);
130 }
131 
132 static const char *debugstr_writer_prop(XmlWriterProperty prop)
133 {
134     static const char * const prop_names[] =
135     {
136         "MultiLanguage",
137         "Indent",
138         "ByteOrderMark",
139         "OmitXmlDeclaration",
140         "ConformanceLevel"
141     };
142 
143     if (prop > _XmlWriterProperty_Last)
144         return wine_dbg_sprintf("unknown property=%d", prop);
145 
146     return prop_names[prop];
147 }
148 
149 static HRESULT create_writer_output(IUnknown *stream, IMalloc *imalloc, xml_encoding encoding,
150     const WCHAR *encoding_name, xmlwriteroutput **out);
151 
152 /* writer output memory allocation functions */
153 static inline void *writeroutput_alloc(xmlwriteroutput *output, size_t len)
154 {
155     return m_alloc(output->imalloc, len);
156 }
157 
158 static inline void writeroutput_free(xmlwriteroutput *output, void *mem)
159 {
160     m_free(output->imalloc, mem);
161 }
162 
163 static inline void *writeroutput_realloc(xmlwriteroutput *output, void *mem, size_t len)
164 {
165     return m_realloc(output->imalloc, mem, len);
166 }
167 
168 /* writer memory allocation functions */
169 static inline void *writer_alloc(const xmlwriter *writer, size_t len)
170 {
171     return m_alloc(writer->imalloc, len);
172 }
173 
174 static inline void writer_free(const xmlwriter *writer, void *mem)
175 {
176     m_free(writer->imalloc, mem);
177 }
178 
179 static struct element *alloc_element(xmlwriter *writer, const WCHAR *prefix, const WCHAR *local)
180 {
181     struct element *ret;
182     int len;
183 
184     ret = writer_alloc(writer, sizeof(*ret));
185     if (!ret) return ret;
186 
187     len = prefix ? lstrlenW(prefix) + 1 /* ':' */ : 0;
188     len += lstrlenW(local);
189 
190     ret->qname = writer_alloc(writer, (len + 1)*sizeof(WCHAR));
191     ret->len = len;
192     if (prefix) {
193         static const WCHAR colonW[] = {':',0};
194         lstrcpyW(ret->qname, prefix);
195         lstrcatW(ret->qname, colonW);
196     }
197     else
198         ret->qname[0] = 0;
199     lstrcatW(ret->qname, local);
200     list_init(&ret->ns);
201 
202     return ret;
203 }
204 
205 static void writer_free_element(xmlwriter *writer, struct element *element)
206 {
207     struct ns *ns, *ns2;
208 
209     LIST_FOR_EACH_ENTRY_SAFE(ns, ns2, &element->ns, struct ns, entry)
210     {
211         list_remove(&ns->entry);
212         writer_free(writer, ns->prefix);
213         writer_free(writer, ns->uri);
214         writer_free(writer, ns);
215     }
216 
217     writer_free(writer, element->qname);
218     writer_free(writer, element);
219 }
220 
221 static void writer_free_element_stack(xmlwriter *writer)
222 {
223     struct element *element, *element2;
224 
225     LIST_FOR_EACH_ENTRY_SAFE(element, element2, &writer->elements, struct element, entry)
226     {
227         list_remove(&element->entry);
228         writer_free_element(writer, element);
229     }
230 }
231 
232 static void writer_push_element(xmlwriter *writer, struct element *element)
233 {
234     list_add_head(&writer->elements, &element->entry);
235 }
236 
237 static struct element *pop_element(xmlwriter *writer)
238 {
239     struct element *element = LIST_ENTRY(list_head(&writer->elements), struct element, entry);
240 
241     if (element)
242         list_remove(&element->entry);
243 
244     return element;
245 }
246 
247 static WCHAR *writer_strndupW(const xmlwriter *writer, const WCHAR *str, int len)
248 {
249     size_t size;
250     WCHAR *ret;
251 
252     if (!str)
253         return NULL;
254 
255     if (len == -1)
256         len = lstrlenW(str);
257 
258     size = (len + 1) * sizeof(WCHAR);
259     ret = writer_alloc(writer, size);
260     memcpy(ret, str, size);
261     return ret;
262 }
263 
264 static WCHAR *writer_strdupW(const xmlwriter *writer, const WCHAR *str)
265 {
266     return writer_strndupW(writer, str, -1);
267 }
268 
269 static struct ns *writer_push_ns(xmlwriter *writer, const WCHAR *prefix, int prefix_len, const WCHAR *uri)
270 {
271     struct element *element;
272     struct ns *ns;
273 
274     element = LIST_ENTRY(list_head(&writer->elements), struct element, entry);
275     if (!element)
276         return NULL;
277 
278     if ((ns = writer_alloc(writer, sizeof(*ns))))
279     {
280         ns->prefix = writer_strndupW(writer, prefix, prefix_len);
281         ns->prefix_len = prefix_len;
282         ns->uri = writer_strdupW(writer, uri);
283         ns->emitted = FALSE;
284         ns->element = element;
285         list_add_tail(&element->ns, &ns->entry);
286     }
287 
288     return ns;
289 }
290 
291 static BOOL is_empty_string(const WCHAR *str)
292 {
293     return !str || !*str;
294 }
295 
296 static struct ns *writer_find_ns_current(const xmlwriter *writer, const WCHAR *prefix, const WCHAR *uri)
297 {
298     struct element *element;
299     struct ns *ns;
300 
301     if (is_empty_string(prefix) || is_empty_string(uri))
302         return NULL;
303 
304     element = LIST_ENTRY(list_head(&writer->elements), struct element, entry);
305 
306     LIST_FOR_EACH_ENTRY(ns, &element->ns, struct ns, entry)
307     {
308         if (!wcscmp(uri, ns->uri) && !wcscmp(prefix, ns->prefix))
309             return ns;
310     }
311 
312     return NULL;
313 }
314 
315 static struct ns *writer_find_ns(const xmlwriter *writer, const WCHAR *prefix, const WCHAR *uri)
316 {
317     struct element *element;
318     struct ns *ns;
319 
320     if (is_empty_string(prefix) && is_empty_string(uri))
321         return NULL;
322 
323     LIST_FOR_EACH_ENTRY(element, &writer->elements, struct element, entry)
324     {
325         LIST_FOR_EACH_ENTRY(ns, &element->ns, struct ns, entry)
326         {
327             if (!uri)
328             {
329                 if (!ns->prefix) continue;
330                 if (!wcscmp(ns->prefix, prefix))
331                     return ns;
332             }
333             else if (!wcscmp(uri, ns->uri))
334             {
335                 if (prefix && !*prefix)
336                     return NULL;
337                 if (!prefix || !wcscmp(prefix, ns->prefix))
338                     return ns;
339             }
340         }
341     }
342 
343     return NULL;
344 }
345 
346 static HRESULT is_valid_ncname(const WCHAR *str, int *out)
347 {
348     int len = 0;
349 
350     *out = 0;
351 
352     if (!str || !*str)
353         return S_OK;
354 
355     while (*str)
356     {
357         if (!is_ncnamechar(*str))
358             return WC_E_NAMECHARACTER;
359         len++;
360         str++;
361     }
362 
363     *out = len;
364     return S_OK;
365 }
366 
367 static HRESULT is_valid_name(const WCHAR *str, unsigned int *out)
368 {
369     unsigned int len = 1;
370 
371     *out = 0;
372 
373     if (!str || !*str)
374         return S_OK;
375 
376     if (!is_namestartchar(*str++))
377         return WC_E_NAMECHARACTER;
378 
379     while (*str++)
380     {
381         if (!is_namechar(*str))
382             return WC_E_NAMECHARACTER;
383         len++;
384     }
385 
386     *out = len;
387     return S_OK;
388 }
389 
390 static HRESULT is_valid_pubid(const WCHAR *str, unsigned int *out)
391 {
392     unsigned int len = 0;
393 
394     *out = 0;
395 
396     if (!str || !*str)
397         return S_OK;
398 
399     while (*str)
400     {
401         if (!is_pubchar(*str++))
402             return WC_E_PUBLICID;
403         len++;
404     }
405 
406     *out = len;
407 
408     return S_OK;
409 }
410 
411 static HRESULT init_output_buffer(xmlwriteroutput *output)
412 {
413     struct output_buffer *buffer = &output->buffer;
414     const int initial_len = 0x2000;
415     UINT cp = ~0u;
416     HRESULT hr;
417 
418     if (FAILED(hr = get_code_page(output->encoding, &cp)))
419         WARN("Failed to get code page for specified encoding.\n");
420 
421     buffer->data = writeroutput_alloc(output, initial_len);
422     if (!buffer->data) return E_OUTOFMEMORY;
423 
424     memset(buffer->data, 0, 4);
425     buffer->allocated = initial_len;
426     buffer->written = 0;
427     buffer->codepage = cp;
428 
429     return S_OK;
430 }
431 
432 static void free_output_buffer(xmlwriteroutput *output)
433 {
434     struct output_buffer *buffer = &output->buffer;
435     writeroutput_free(output, buffer->data);
436     buffer->data = NULL;
437     buffer->allocated = 0;
438     buffer->written = 0;
439 }
440 
441 static HRESULT grow_output_buffer(xmlwriteroutput *output, int length)
442 {
443     struct output_buffer *buffer = &output->buffer;
444     /* grow if needed, plus 4 bytes to be sure null terminator will fit in */
445     if (buffer->allocated < buffer->written + length + 4) {
446         int grown_size = max(2*buffer->allocated, buffer->allocated + length);
447         char *ptr = writeroutput_realloc(output, buffer->data, grown_size);
448         if (!ptr) return E_OUTOFMEMORY;
449         buffer->data = ptr;
450         buffer->allocated = grown_size;
451     }
452 
453     return S_OK;
454 }
455 
456 static HRESULT write_output_buffer(xmlwriteroutput *output, const WCHAR *data, int len)
457 {
458     struct output_buffer *buffer = &output->buffer;
459     int length;
460     HRESULT hr;
461     char *ptr;
462 
463     if (buffer->codepage == 1200) {
464         /* For UTF-16 encoding just copy. */
465         length = len == -1 ? lstrlenW(data) : len;
466         if (length) {
467             length *= sizeof(WCHAR);
468 
469             hr = grow_output_buffer(output, length);
470             if (FAILED(hr)) return hr;
471             ptr = buffer->data + buffer->written;
472 
473             memcpy(ptr, data, length);
474             buffer->written += length;
475             ptr += length;
476             /* null termination */
477             *(WCHAR*)ptr = 0;
478         }
479     }
480     else {
481         length = WideCharToMultiByte(buffer->codepage, 0, data, len, NULL, 0, NULL, NULL);
482         hr = grow_output_buffer(output, length);
483         if (FAILED(hr)) return hr;
484         ptr = buffer->data + buffer->written;
485         length = WideCharToMultiByte(buffer->codepage, 0, data, len, ptr, length, NULL, NULL);
486         buffer->written += len == -1 ? length-1 : length;
487     }
488     output->written = length != 0;
489 
490     return S_OK;
491 }
492 
493 static HRESULT write_output_buffer_char(xmlwriteroutput *output, WCHAR ch)
494 {
495     return write_output_buffer(output, &ch, 1);
496 }
497 
498 static HRESULT write_output_buffer_quoted(xmlwriteroutput *output, const WCHAR *data, int len)
499 {
500     write_output_buffer_char(output, '"');
501     if (!is_empty_string(data))
502         write_output_buffer(output, data, len);
503     write_output_buffer_char(output, '"');
504     return S_OK;
505 }
506 
507 /* TODO: test if we need to validate char range */
508 static HRESULT write_output_qname(xmlwriteroutput *output, const WCHAR *prefix, int prefix_len,
509         const WCHAR *local_name, int local_len)
510 {
511     assert(prefix_len >= 0 && local_len >= 0);
512 
513     if (prefix_len)
514         write_output_buffer(output, prefix, prefix_len);
515 
516     if (prefix_len && local_len)
517         write_output_buffer_char(output, ':');
518 
519     write_output_buffer(output, local_name, local_len);
520 
521     return S_OK;
522 }
523 
524 static void writeroutput_release_stream(xmlwriteroutput *writeroutput)
525 {
526     if (writeroutput->stream) {
527         ISequentialStream_Release(writeroutput->stream);
528         writeroutput->stream = NULL;
529     }
530 }
531 
532 static inline HRESULT writeroutput_query_for_stream(xmlwriteroutput *writeroutput)
533 {
534     HRESULT hr;
535 
536     writeroutput_release_stream(writeroutput);
537     hr = IUnknown_QueryInterface(writeroutput->output, &IID_IStream, (void**)&writeroutput->stream);
538     if (hr != S_OK)
539         hr = IUnknown_QueryInterface(writeroutput->output, &IID_ISequentialStream, (void**)&writeroutput->stream);
540 
541     return hr;
542 }
543 
544 static HRESULT writeroutput_flush_stream(xmlwriteroutput *output)
545 {
546     struct output_buffer *buffer;
547     ULONG written, offset = 0;
548     HRESULT hr;
549 
550     if (!output || !output->stream)
551         return S_OK;
552 
553     buffer = &output->buffer;
554 
555     /* It will loop forever until everything is written or an error occurred. */
556     do {
557         written = 0;
558         hr = ISequentialStream_Write(output->stream, buffer->data + offset, buffer->written, &written);
559         if (FAILED(hr)) {
560             WARN("write to stream failed (0x%08x)\n", hr);
561             buffer->written = 0;
562             return hr;
563         }
564 
565         offset += written;
566         buffer->written -= written;
567     } while (buffer->written > 0);
568 
569     return S_OK;
570 }
571 
572 static HRESULT write_encoding_bom(xmlwriter *writer)
573 {
574     if (!writer->bom || writer->bomwritten) return S_OK;
575 
576     if (writer->output->encoding == XmlEncoding_UTF16) {
577         static const char utf16bom[] = {0xff, 0xfe};
578         struct output_buffer *buffer = &writer->output->buffer;
579         int len = sizeof(utf16bom);
580         HRESULT hr;
581 
582         hr = grow_output_buffer(writer->output, len);
583         if (FAILED(hr)) return hr;
584         memcpy(buffer->data + buffer->written, utf16bom, len);
585         buffer->written += len;
586     }
587 
588     writer->bomwritten = TRUE;
589     return S_OK;
590 }
591 
592 static const WCHAR *get_output_encoding_name(xmlwriteroutput *output)
593 {
594     if (output->encoding_name)
595         return output->encoding_name;
596 
597     return get_encoding_name(output->encoding);
598 }
599 
600 static HRESULT write_xmldecl(xmlwriter *writer, XmlStandalone standalone)
601 {
602     static const WCHAR versionW[] = {'<','?','x','m','l',' ','v','e','r','s','i','o','n','=','"','1','.','0','"'};
603     static const WCHAR encodingW[] = {' ','e','n','c','o','d','i','n','g','='};
604 
605     write_encoding_bom(writer);
606     writer->state = XmlWriterState_DocStarted;
607     if (writer->omitxmldecl) return S_OK;
608 
609     /* version */
610     write_output_buffer(writer->output, versionW, ARRAY_SIZE(versionW));
611 
612     /* encoding */
613     write_output_buffer(writer->output, encodingW, ARRAY_SIZE(encodingW));
614     write_output_buffer_quoted(writer->output, get_output_encoding_name(writer->output), -1);
615 
616     /* standalone */
617     if (standalone == XmlStandalone_Omit)
618         write_output_buffer(writer->output, closepiW, ARRAY_SIZE(closepiW));
619     else {
620         static const WCHAR standaloneW[] = {' ','s','t','a','n','d','a','l','o','n','e','=','\"'};
621         static const WCHAR yesW[] = {'y','e','s','\"','?','>'};
622         static const WCHAR noW[] = {'n','o','\"','?','>'};
623 
624         write_output_buffer(writer->output, standaloneW, ARRAY_SIZE(standaloneW));
625         if (standalone == XmlStandalone_Yes)
626             write_output_buffer(writer->output, yesW, ARRAY_SIZE(yesW));
627         else
628             write_output_buffer(writer->output, noW, ARRAY_SIZE(noW));
629     }
630 
631     return S_OK;
632 }
633 
634 static void writer_output_ns(xmlwriter *writer, struct element *element)
635 {
636     struct ns *ns;
637 
638     LIST_FOR_EACH_ENTRY(ns, &element->ns, struct ns, entry)
639     {
640         if (ns->emitted)
641             continue;
642 
643         write_output_qname(writer->output, xmlnsW, ARRAY_SIZE(xmlnsW), ns->prefix, ns->prefix_len);
644         write_output_buffer_char(writer->output, '=');
645         write_output_buffer_quoted(writer->output, ns->uri, -1);
646     }
647 }
648 
649 static HRESULT writer_close_starttag(xmlwriter *writer)
650 {
651     HRESULT hr;
652 
653     if (!writer->starttagopen) return S_OK;
654 
655     writer_output_ns(writer, LIST_ENTRY(list_head(&writer->elements), struct element, entry));
656     hr = write_output_buffer_char(writer->output, '>');
657     writer->starttagopen = 0;
658     return hr;
659 }
660 
661 static void writer_inc_indent(xmlwriter *writer)
662 {
663     writer->indent_level++;
664 }
665 
666 static void writer_dec_indent(xmlwriter *writer)
667 {
668     if (writer->indent_level)
669         writer->indent_level--;
670 }
671 
672 static void write_node_indent(xmlwriter *writer)
673 {
674     static const WCHAR dblspaceW[] = {' ',' '};
675     static const WCHAR crlfW[] = {'\r','\n'};
676     unsigned int indent_level = writer->indent_level;
677 
678     if (!writer->indent || writer->textnode)
679     {
680         writer->textnode = 0;
681         return;
682     }
683 
684     /* Do state check to prevent newline inserted after BOM. It is assumed that
685        state does not change between writing BOM and inserting indentation. */
686     if (writer->output->written && writer->state != XmlWriterState_Ready)
687         write_output_buffer(writer->output, crlfW, ARRAY_SIZE(crlfW));
688     while (indent_level--)
689         write_output_buffer(writer->output, dblspaceW, ARRAY_SIZE(dblspaceW));
690 
691     writer->textnode = 0;
692 }
693 
694 static HRESULT WINAPI xmlwriter_QueryInterface(IXmlWriter *iface, REFIID riid, void **ppvObject)
695 {
696     xmlwriter *This = impl_from_IXmlWriter(iface);
697 
698     TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppvObject);
699 
700     if (IsEqualGUID(riid, &IID_IXmlWriter) ||
701         IsEqualGUID(riid, &IID_IUnknown))
702     {
703         *ppvObject = iface;
704     }
705     else
706     {
707         FIXME("interface %s is not supported\n", debugstr_guid(riid));
708         *ppvObject = NULL;
709         return E_NOINTERFACE;
710     }
711 
712     IXmlWriter_AddRef(iface);
713 
714     return S_OK;
715 }
716 
717 static ULONG WINAPI xmlwriter_AddRef(IXmlWriter *iface)
718 {
719     xmlwriter *This = impl_from_IXmlWriter(iface);
720     ULONG ref = InterlockedIncrement(&This->ref);
721     TRACE("(%p)->(%u)\n", This, ref);
722     return ref;
723 }
724 
725 static ULONG WINAPI xmlwriter_Release(IXmlWriter *iface)
726 {
727     xmlwriter *This = impl_from_IXmlWriter(iface);
728     ULONG ref = InterlockedDecrement(&This->ref);
729 
730     TRACE("(%p)->(%u)\n", This, ref);
731 
732     if (ref == 0) {
733         IMalloc *imalloc = This->imalloc;
734 
735         writeroutput_flush_stream(This->output);
736         if (This->output) IUnknown_Release(&This->output->IXmlWriterOutput_iface);
737 
738         writer_free_element_stack(This);
739 
740         writer_free(This, This);
741         if (imalloc) IMalloc_Release(imalloc);
742     }
743 
744     return ref;
745 }
746 
747 /*** IXmlWriter methods ***/
748 static HRESULT WINAPI xmlwriter_SetOutput(IXmlWriter *iface, IUnknown *output)
749 {
750     xmlwriter *This = impl_from_IXmlWriter(iface);
751     IXmlWriterOutput *writeroutput;
752     HRESULT hr;
753 
754     TRACE("(%p)->(%p)\n", This, output);
755 
756     if (This->output) {
757         writeroutput_release_stream(This->output);
758         IUnknown_Release(&This->output->IXmlWriterOutput_iface);
759         This->output = NULL;
760         This->bomwritten = 0;
761         This->textnode = 0;
762         This->indent_level = 0;
763         writer_free_element_stack(This);
764     }
765 
766     /* just reset current output */
767     if (!output) {
768         This->state = XmlWriterState_Initial;
769         return S_OK;
770     }
771 
772     /* now try IXmlWriterOutput, ISequentialStream, IStream */
773     hr = IUnknown_QueryInterface(output, &IID_IXmlWriterOutput, (void**)&writeroutput);
774     if (hr == S_OK) {
775         if (writeroutput->lpVtbl == &xmlwriteroutputvtbl)
776             This->output = impl_from_IXmlWriterOutput(writeroutput);
777         else {
778             ERR("got external IXmlWriterOutput implementation: %p, vtbl=%p\n",
779                 writeroutput, writeroutput->lpVtbl);
780             IUnknown_Release(writeroutput);
781             return E_FAIL;
782         }
783     }
784 
785     if (hr != S_OK || !writeroutput) {
786         /* Create output for given stream. */
787         hr = create_writer_output(output, This->imalloc, XmlEncoding_UTF8, NULL, &This->output);
788         if (hr != S_OK)
789             return hr;
790     }
791 
792     if (This->output->encoding == XmlEncoding_Unknown)
793         This->state = XmlWriterState_InvalidEncoding;
794     else
795         This->state = XmlWriterState_Ready;
796     return writeroutput_query_for_stream(This->output);
797 }
798 
799 static HRESULT WINAPI xmlwriter_GetProperty(IXmlWriter *iface, UINT property, LONG_PTR *value)
800 {
801     xmlwriter *This = impl_from_IXmlWriter(iface);
802 
803     TRACE("(%p)->(%s %p)\n", This, debugstr_writer_prop(property), value);
804 
805     if (!value) return E_INVALIDARG;
806 
807     switch (property)
808     {
809         case XmlWriterProperty_Indent:
810             *value = This->indent;
811             break;
812         case XmlWriterProperty_ByteOrderMark:
813             *value = This->bom;
814             break;
815         case XmlWriterProperty_OmitXmlDeclaration:
816             *value = This->omitxmldecl;
817             break;
818         case XmlWriterProperty_ConformanceLevel:
819             *value = This->conformance;
820             break;
821         default:
822             FIXME("Unimplemented property (%u)\n", property);
823             return E_NOTIMPL;
824     }
825 
826     return S_OK;
827 }
828 
829 static HRESULT WINAPI xmlwriter_SetProperty(IXmlWriter *iface, UINT property, LONG_PTR value)
830 {
831     xmlwriter *This = impl_from_IXmlWriter(iface);
832 
833     TRACE("(%p)->(%s %lu)\n", This, debugstr_writer_prop(property), value);
834 
835     switch (property)
836     {
837         case XmlWriterProperty_Indent:
838             This->indent = !!value;
839             break;
840         case XmlWriterProperty_ByteOrderMark:
841             This->bom = !!value;
842             break;
843         case XmlWriterProperty_OmitXmlDeclaration:
844             This->omitxmldecl = !!value;
845             break;
846         default:
847             FIXME("Unimplemented property (%u)\n", property);
848             return E_NOTIMPL;
849     }
850 
851     return S_OK;
852 }
853 
854 static HRESULT WINAPI xmlwriter_WriteAttributes(IXmlWriter *iface, IXmlReader *pReader,
855                                   BOOL fWriteDefaultAttributes)
856 {
857     xmlwriter *This = impl_from_IXmlWriter(iface);
858 
859     FIXME("%p %p %d\n", This, pReader, fWriteDefaultAttributes);
860 
861     return E_NOTIMPL;
862 }
863 
864 static void write_output_attribute(xmlwriter *writer, const WCHAR *prefix, int prefix_len,
865         const WCHAR *local, int local_len, const WCHAR *value)
866 {
867     write_output_buffer_char(writer->output, ' ');
868     write_output_qname(writer->output, prefix, prefix_len, local, local_len);
869     write_output_buffer_char(writer->output, '=');
870     write_output_buffer_quoted(writer->output, value, -1);
871 }
872 
873 static BOOL is_valid_xml_space_value(const WCHAR *value)
874 {
875     static const WCHAR preserveW[] = {'p','r','e','s','e','r','v','e',0};
876     static const WCHAR defaultW[] = {'d','e','f','a','u','l','t',0};
877 
878     if (!value)
879         return FALSE;
880 
881     return !wcscmp(value, preserveW) || !wcscmp(value, defaultW);
882 }
883 
884 static HRESULT WINAPI xmlwriter_WriteAttributeString(IXmlWriter *iface, LPCWSTR prefix,
885     LPCWSTR local, LPCWSTR uri, LPCWSTR value)
886 {
887     static const WCHAR spaceattrW[] = {'s','p','a','c','e',0};
888     static const WCHAR xmlnsW[] = {'x','m','l','n','s',0};
889     static const WCHAR xmlW[] = {'x','m','l',0};
890     xmlwriter *This = impl_from_IXmlWriter(iface);
891     BOOL is_xmlns_prefix, is_xmlns_local;
892     int prefix_len, local_len;
893     struct ns *ns;
894     HRESULT hr;
895 
896     TRACE("%p %s %s %s %s\n", This, debugstr_w(prefix), debugstr_w(local), debugstr_w(uri), debugstr_w(value));
897 
898     switch (This->state)
899     {
900     case XmlWriterState_Initial:
901         return E_UNEXPECTED;
902     case XmlWriterState_Ready:
903     case XmlWriterState_DocClosed:
904         This->state = XmlWriterState_DocClosed;
905         return WR_E_INVALIDACTION;
906     case XmlWriterState_InvalidEncoding:
907         return MX_E_ENCODING;
908     default:
909         ;
910     }
911 
912     /* Prefix "xmlns" */
913     is_xmlns_prefix = prefix && !wcscmp(prefix, xmlnsW);
914     if (is_xmlns_prefix && is_empty_string(uri) && is_empty_string(local))
915         return WR_E_NSPREFIXDECLARED;
916 
917     if (!local)
918         return E_INVALIDARG;
919 
920     /* Validate prefix and local name */
921     if (FAILED(hr = is_valid_ncname(prefix, &prefix_len)))
922         return hr;
923 
924     if (FAILED(hr = is_valid_ncname(local, &local_len)))
925         return hr;
926 
927     is_xmlns_local = !wcscmp(local, xmlnsW);
928 
929     /* Trivial case, no prefix. */
930     if (prefix_len == 0 && is_empty_string(uri))
931     {
932         write_output_attribute(This, prefix, prefix_len, local, local_len, value);
933         return S_OK;
934     }
935 
936     /* Predefined "xml" prefix. */
937     if (prefix_len && !wcscmp(prefix, xmlW))
938     {
939         /* Valid "space" value is enforced. */
940         if (!wcscmp(local, spaceattrW) && !is_valid_xml_space_value(value))
941             return WR_E_INVALIDXMLSPACE;
942 
943         /* Redefinition is not allowed. */
944         if (!is_empty_string(uri))
945             return WR_E_XMLPREFIXDECLARATION;
946 
947         write_output_attribute(This, prefix, prefix_len, local, local_len, value);
948 
949         return S_OK;
950     }
951 
952     if (is_xmlns_prefix || (prefix_len == 0 && uri && !wcscmp(uri, xmlnsuriW)))
953     {
954         if (prefix_len && !is_empty_string(uri))
955             return WR_E_XMLNSPREFIXDECLARATION;
956 
957         /* Look for exact match defined in current element, and write it out. */
958         if (!(ns = writer_find_ns_current(This, prefix, value)))
959             ns = writer_push_ns(This, local, local_len, value);
960         ns->emitted = TRUE;
961 
962         write_output_attribute(This, xmlnsW, ARRAY_SIZE(xmlnsW) - 1, local, local_len, value);
963 
964         return S_OK;
965     }
966 
967     /* Ignore prefix is URI wasn't specified. */
968     if (is_xmlns_local && is_empty_string(uri))
969     {
970         write_output_attribute(This, NULL, 0, xmlnsW, ARRAY_SIZE(xmlnsW) - 1, value);
971         return S_OK;
972     }
973 
974     if (!(ns = writer_find_ns(This, prefix, uri)))
975     {
976         if (is_empty_string(prefix) && !is_empty_string(uri))
977         {
978             FIXME("Prefix autogeneration is not implemented.\n");
979             return E_NOTIMPL;
980         }
981         if (!is_empty_string(uri))
982             ns = writer_push_ns(This, prefix, prefix_len, uri);
983     }
984 
985     if (ns)
986         write_output_attribute(This, ns->prefix, ns->prefix_len, local, local_len, value);
987     else
988         write_output_attribute(This, prefix, prefix_len, local, local_len, value);
989 
990     return S_OK;
991 }
992 
993 static void write_cdata_section(xmlwriteroutput *output, const WCHAR *data, int len)
994 {
995     static const WCHAR cdataopenW[] = {'<','!','[','C','D','A','T','A','['};
996     static const WCHAR cdatacloseW[] = {']',']','>'};
997     write_output_buffer(output, cdataopenW, ARRAY_SIZE(cdataopenW));
998     if (data)
999         write_output_buffer(output, data, len);
1000     write_output_buffer(output, cdatacloseW, ARRAY_SIZE(cdatacloseW));
1001 }
1002 
1003 static HRESULT WINAPI xmlwriter_WriteCData(IXmlWriter *iface, LPCWSTR data)
1004 {
1005     xmlwriter *This = impl_from_IXmlWriter(iface);
1006     int len;
1007 
1008     TRACE("%p %s\n", This, debugstr_w(data));
1009 
1010     switch (This->state)
1011     {
1012     case XmlWriterState_Initial:
1013         return E_UNEXPECTED;
1014     case XmlWriterState_ElemStarted:
1015         writer_close_starttag(This);
1016         break;
1017     case XmlWriterState_Ready:
1018     case XmlWriterState_DocClosed:
1019         This->state = XmlWriterState_DocClosed;
1020         return WR_E_INVALIDACTION;
1021     case XmlWriterState_InvalidEncoding:
1022         return MX_E_ENCODING;
1023     default:
1024         ;
1025     }
1026 
1027     len = data ? lstrlenW(data) : 0;
1028 
1029     write_node_indent(This);
1030     if (!len)
1031         write_cdata_section(This->output, NULL, 0);
1032     else {
1033         static const WCHAR cdatacloseW[] = {']',']','>',0};
1034         while (len) {
1035             const WCHAR *str = wcsstr(data, cdatacloseW);
1036             if (str) {
1037                 str += 2;
1038                 write_cdata_section(This->output, data, str - data);
1039                 len -= str - data;
1040                 data = str;
1041             }
1042             else {
1043                 write_cdata_section(This->output, data, len);
1044                 break;
1045             }
1046         }
1047     }
1048 
1049     return S_OK;
1050 }
1051 
1052 static HRESULT WINAPI xmlwriter_WriteCharEntity(IXmlWriter *iface, WCHAR ch)
1053 {
1054     static const WCHAR fmtW[] = {'&','#','x','%','x',';',0};
1055     xmlwriter *This = impl_from_IXmlWriter(iface);
1056     WCHAR bufW[16];
1057 
1058     TRACE("%p %#x\n", This, ch);
1059 
1060     switch (This->state)
1061     {
1062     case XmlWriterState_Initial:
1063         return E_UNEXPECTED;
1064     case XmlWriterState_InvalidEncoding:
1065         return MX_E_ENCODING;
1066     case XmlWriterState_ElemStarted:
1067         writer_close_starttag(This);
1068         break;
1069     case XmlWriterState_DocClosed:
1070         return WR_E_INVALIDACTION;
1071     default:
1072         ;
1073     }
1074 
1075     swprintf(bufW, fmtW, ch);
1076     write_output_buffer(This->output, bufW, -1);
1077 
1078     return S_OK;
1079 }
1080 
1081 static HRESULT WINAPI xmlwriter_WriteChars(IXmlWriter *iface, const WCHAR *pwch, UINT cwch)
1082 {
1083     xmlwriter *This = impl_from_IXmlWriter(iface);
1084 
1085     FIXME("%p %s %d\n", This, wine_dbgstr_w(pwch), cwch);
1086 
1087     switch (This->state)
1088     {
1089     case XmlWriterState_Initial:
1090         return E_UNEXPECTED;
1091     case XmlWriterState_InvalidEncoding:
1092         return MX_E_ENCODING;
1093     case XmlWriterState_DocClosed:
1094         return WR_E_INVALIDACTION;
1095     default:
1096         ;
1097     }
1098 
1099     return E_NOTIMPL;
1100 }
1101 
1102 
1103 static HRESULT WINAPI xmlwriter_WriteComment(IXmlWriter *iface, LPCWSTR comment)
1104 {
1105     static const WCHAR copenW[] = {'<','!','-','-'};
1106     static const WCHAR ccloseW[] = {'-','-','>'};
1107     xmlwriter *This = impl_from_IXmlWriter(iface);
1108 
1109     TRACE("%p %s\n", This, debugstr_w(comment));
1110 
1111     switch (This->state)
1112     {
1113     case XmlWriterState_Initial:
1114         return E_UNEXPECTED;
1115     case XmlWriterState_InvalidEncoding:
1116         return MX_E_ENCODING;
1117     case XmlWriterState_ElemStarted:
1118         writer_close_starttag(This);
1119         break;
1120     case XmlWriterState_DocClosed:
1121         return WR_E_INVALIDACTION;
1122     default:
1123         ;
1124     }
1125 
1126     write_node_indent(This);
1127     write_output_buffer(This->output, copenW, ARRAY_SIZE(copenW));
1128     if (comment) {
1129         int len = lstrlenW(comment), i;
1130 
1131         /* Make sure there's no two hyphen sequences in a string, space is used as a separator to produce compliant
1132            comment string */
1133         if (len > 1) {
1134             for (i = 0; i < len; i++) {
1135                 write_output_buffer(This->output, comment + i, 1);
1136                 if (comment[i] == '-' && (i + 1 < len) && comment[i+1] == '-')
1137                     write_output_buffer_char(This->output, ' ');
1138             }
1139         }
1140         else
1141             write_output_buffer(This->output, comment, len);
1142 
1143         if (len && comment[len-1] == '-')
1144             write_output_buffer_char(This->output, ' ');
1145     }
1146     write_output_buffer(This->output, ccloseW, ARRAY_SIZE(ccloseW));
1147 
1148     return S_OK;
1149 }
1150 
1151 static HRESULT WINAPI xmlwriter_WriteDocType(IXmlWriter *iface, LPCWSTR name, LPCWSTR pubid,
1152         LPCWSTR sysid, LPCWSTR subset)
1153 {
1154     static const WCHAR doctypeW[] = {'<','!','D','O','C','T','Y','P','E',' '};
1155     static const WCHAR publicW[] = {' ','P','U','B','L','I','C',' '};
1156     static const WCHAR systemW[] = {' ','S','Y','S','T','E','M',' '};
1157     xmlwriter *This = impl_from_IXmlWriter(iface);
1158     unsigned int name_len, pubid_len;
1159     HRESULT hr;
1160 
1161     TRACE("(%p)->(%s %s %s %s)\n", This, wine_dbgstr_w(name), wine_dbgstr_w(pubid), wine_dbgstr_w(sysid),
1162             wine_dbgstr_w(subset));
1163 
1164     switch (This->state)
1165     {
1166     case XmlWriterState_Initial:
1167         return E_UNEXPECTED;
1168     case XmlWriterState_InvalidEncoding:
1169         return MX_E_ENCODING;
1170     case XmlWriterState_Content:
1171     case XmlWriterState_DocClosed:
1172         return WR_E_INVALIDACTION;
1173     default:
1174         ;
1175     }
1176 
1177     if (is_empty_string(name))
1178         return E_INVALIDARG;
1179 
1180     if (FAILED(hr = is_valid_name(name, &name_len)))
1181         return hr;
1182 
1183     if (FAILED(hr = is_valid_pubid(pubid, &pubid_len)))
1184         return hr;
1185 
1186     write_output_buffer(This->output, doctypeW, ARRAY_SIZE(doctypeW));
1187     write_output_buffer(This->output, name, name_len);
1188 
1189     if (pubid)
1190     {
1191         write_output_buffer(This->output, publicW, ARRAY_SIZE(publicW));
1192         write_output_buffer_quoted(This->output, pubid, pubid_len);
1193         write_output_buffer_char(This->output, ' ');
1194         write_output_buffer_quoted(This->output, sysid, -1);
1195     }
1196     else if (sysid)
1197     {
1198         write_output_buffer(This->output, systemW, ARRAY_SIZE(systemW));
1199         write_output_buffer_quoted(This->output, sysid, -1);
1200     }
1201 
1202     if (subset)
1203     {
1204         write_output_buffer_char(This->output, ' ');
1205         write_output_buffer_char(This->output, '[');
1206         write_output_buffer(This->output, subset, -1);
1207         write_output_buffer_char(This->output, ']');
1208     }
1209     write_output_buffer_char(This->output, '>');
1210 
1211     This->state = XmlWriterState_Content;
1212 
1213     return S_OK;
1214 }
1215 
1216 static HRESULT WINAPI xmlwriter_WriteElementString(IXmlWriter *iface, LPCWSTR prefix,
1217                                      LPCWSTR local_name, LPCWSTR uri, LPCWSTR value)
1218 {
1219     xmlwriter *This = impl_from_IXmlWriter(iface);
1220     int prefix_len, local_len;
1221     struct ns *ns;
1222     HRESULT hr;
1223 
1224     TRACE("(%p)->(%s %s %s %s)\n", This, wine_dbgstr_w(prefix), wine_dbgstr_w(local_name),
1225                         wine_dbgstr_w(uri), wine_dbgstr_w(value));
1226 
1227     switch (This->state)
1228     {
1229     case XmlWriterState_Initial:
1230         return E_UNEXPECTED;
1231     case XmlWriterState_InvalidEncoding:
1232         return MX_E_ENCODING;
1233     case XmlWriterState_ElemStarted:
1234         writer_close_starttag(This);
1235         break;
1236     case XmlWriterState_DocClosed:
1237         return WR_E_INVALIDACTION;
1238     default:
1239         ;
1240     }
1241 
1242     if (!local_name)
1243         return E_INVALIDARG;
1244 
1245     /* Validate prefix and local name */
1246     if (FAILED(hr = is_valid_ncname(prefix, &prefix_len)))
1247         return hr;
1248 
1249     if (FAILED(hr = is_valid_ncname(local_name, &local_len)))
1250         return hr;
1251 
1252     ns = writer_find_ns(This, prefix, uri);
1253     if (!ns && !is_empty_string(prefix) && is_empty_string(uri))
1254         return WR_E_NSPREFIXWITHEMPTYNSURI;
1255 
1256     if (uri && !wcscmp(uri, xmlnsuriW))
1257     {
1258         if (!prefix)
1259             return WR_E_XMLNSPREFIXDECLARATION;
1260 
1261         if (!is_empty_string(prefix))
1262             return WR_E_XMLNSURIDECLARATION;
1263     }
1264 
1265     write_encoding_bom(This);
1266     write_node_indent(This);
1267 
1268     write_output_buffer_char(This->output, '<');
1269     if (ns)
1270         write_output_qname(This->output, ns->prefix, ns->prefix_len, local_name, local_len);
1271     else
1272         write_output_qname(This->output, prefix, prefix_len, local_name, local_len);
1273 
1274     if (!ns && (prefix_len || !is_empty_string(uri)))
1275     {
1276         write_output_qname(This->output, xmlnsW, ARRAY_SIZE(xmlnsW), prefix, prefix_len);
1277         write_output_buffer_char(This->output, '=');
1278         write_output_buffer_quoted(This->output, uri, -1);
1279     }
1280 
1281     if (value)
1282     {
1283         write_output_buffer_char(This->output, '>');
1284         write_output_buffer(This->output, value, -1);
1285         write_output_buffer(This->output, closeelementW, ARRAY_SIZE(closeelementW));
1286         write_output_qname(This->output, prefix, prefix_len, local_name, local_len);
1287         write_output_buffer_char(This->output, '>');
1288     }
1289     else
1290         write_output_buffer(This->output, closetagW, ARRAY_SIZE(closetagW));
1291 
1292     This->state = XmlWriterState_Content;
1293 
1294     return S_OK;
1295 }
1296 
1297 static HRESULT WINAPI xmlwriter_WriteEndDocument(IXmlWriter *iface)
1298 {
1299     xmlwriter *This = impl_from_IXmlWriter(iface);
1300 
1301     TRACE("%p\n", This);
1302 
1303     switch (This->state)
1304     {
1305     case XmlWriterState_Initial:
1306         return E_UNEXPECTED;
1307     case XmlWriterState_Ready:
1308     case XmlWriterState_DocClosed:
1309         This->state = XmlWriterState_DocClosed;
1310         return WR_E_INVALIDACTION;
1311     case XmlWriterState_InvalidEncoding:
1312         return MX_E_ENCODING;
1313     default:
1314         ;
1315     }
1316 
1317     /* empty element stack */
1318     while (IXmlWriter_WriteEndElement(iface) == S_OK)
1319         ;
1320 
1321     This->state = XmlWriterState_DocClosed;
1322     return S_OK;
1323 }
1324 
1325 static HRESULT WINAPI xmlwriter_WriteEndElement(IXmlWriter *iface)
1326 {
1327     xmlwriter *This = impl_from_IXmlWriter(iface);
1328     struct element *element;
1329 
1330     TRACE("%p\n", This);
1331 
1332     switch (This->state)
1333     {
1334     case XmlWriterState_Initial:
1335         return E_UNEXPECTED;
1336     case XmlWriterState_Ready:
1337     case XmlWriterState_DocClosed:
1338         This->state = XmlWriterState_DocClosed;
1339         return WR_E_INVALIDACTION;
1340     case XmlWriterState_InvalidEncoding:
1341         return MX_E_ENCODING;
1342     default:
1343         ;
1344     }
1345 
1346     element = pop_element(This);
1347     if (!element)
1348         return WR_E_INVALIDACTION;
1349 
1350     writer_dec_indent(This);
1351 
1352     if (This->starttagopen)
1353     {
1354         writer_output_ns(This, element);
1355         write_output_buffer(This->output, closetagW, ARRAY_SIZE(closetagW));
1356         This->starttagopen = 0;
1357     }
1358     else
1359     {
1360         /* Write full end tag. */
1361         write_node_indent(This);
1362         write_output_buffer(This->output, closeelementW, ARRAY_SIZE(closeelementW));
1363         write_output_buffer(This->output, element->qname, element->len);
1364         write_output_buffer_char(This->output, '>');
1365     }
1366     writer_free_element(This, element);
1367 
1368     return S_OK;
1369 }
1370 
1371 static HRESULT WINAPI xmlwriter_WriteEntityRef(IXmlWriter *iface, LPCWSTR pwszName)
1372 {
1373     xmlwriter *This = impl_from_IXmlWriter(iface);
1374 
1375     FIXME("%p %s\n", This, wine_dbgstr_w(pwszName));
1376 
1377     switch (This->state)
1378     {
1379     case XmlWriterState_Initial:
1380         return E_UNEXPECTED;
1381     case XmlWriterState_InvalidEncoding:
1382         return MX_E_ENCODING;
1383     case XmlWriterState_DocClosed:
1384         return WR_E_INVALIDACTION;
1385     default:
1386         ;
1387     }
1388 
1389     return E_NOTIMPL;
1390 }
1391 
1392 static HRESULT WINAPI xmlwriter_WriteFullEndElement(IXmlWriter *iface)
1393 {
1394     xmlwriter *This = impl_from_IXmlWriter(iface);
1395     struct element *element;
1396 
1397     TRACE("%p\n", This);
1398 
1399     switch (This->state)
1400     {
1401     case XmlWriterState_Initial:
1402         return E_UNEXPECTED;
1403     case XmlWriterState_Ready:
1404     case XmlWriterState_DocClosed:
1405         This->state = XmlWriterState_DocClosed;
1406         return WR_E_INVALIDACTION;
1407     case XmlWriterState_InvalidEncoding:
1408         return MX_E_ENCODING;
1409     case XmlWriterState_ElemStarted:
1410         writer_close_starttag(This);
1411         break;
1412     default:
1413         ;
1414     }
1415 
1416     element = pop_element(This);
1417     if (!element)
1418         return WR_E_INVALIDACTION;
1419 
1420     writer_dec_indent(This);
1421 
1422     /* don't force full end tag to the next line */
1423     if (This->state == XmlWriterState_ElemStarted)
1424     {
1425         This->state = XmlWriterState_Content;
1426         This->textnode = 0;
1427     }
1428     else
1429         write_node_indent(This);
1430 
1431     /* write full end tag */
1432     write_output_buffer(This->output, closeelementW, ARRAY_SIZE(closeelementW));
1433     write_output_buffer(This->output, element->qname, element->len);
1434     write_output_buffer_char(This->output, '>');
1435 
1436     writer_free_element(This, element);
1437 
1438     return S_OK;
1439 }
1440 
1441 static HRESULT WINAPI xmlwriter_WriteName(IXmlWriter *iface, LPCWSTR pwszName)
1442 {
1443     xmlwriter *This = impl_from_IXmlWriter(iface);
1444 
1445     FIXME("%p %s\n", This, wine_dbgstr_w(pwszName));
1446 
1447     switch (This->state)
1448     {
1449     case XmlWriterState_Initial:
1450         return E_UNEXPECTED;
1451     case XmlWriterState_Ready:
1452     case XmlWriterState_DocClosed:
1453         This->state = XmlWriterState_DocClosed;
1454         return WR_E_INVALIDACTION;
1455     case XmlWriterState_InvalidEncoding:
1456         return MX_E_ENCODING;
1457     default:
1458         ;
1459     }
1460 
1461     return E_NOTIMPL;
1462 }
1463 
1464 static HRESULT WINAPI xmlwriter_WriteNmToken(IXmlWriter *iface, LPCWSTR pwszNmToken)
1465 {
1466     xmlwriter *This = impl_from_IXmlWriter(iface);
1467 
1468     FIXME("%p %s\n", This, wine_dbgstr_w(pwszNmToken));
1469 
1470     switch (This->state)
1471     {
1472     case XmlWriterState_Initial:
1473         return E_UNEXPECTED;
1474     case XmlWriterState_Ready:
1475     case XmlWriterState_DocClosed:
1476         This->state = XmlWriterState_DocClosed;
1477         return WR_E_INVALIDACTION;
1478     case XmlWriterState_InvalidEncoding:
1479         return MX_E_ENCODING;
1480     default:
1481         ;
1482     }
1483 
1484     return E_NOTIMPL;
1485 }
1486 
1487 static HRESULT WINAPI xmlwriter_WriteNode(IXmlWriter *iface, IXmlReader *pReader,
1488                             BOOL fWriteDefaultAttributes)
1489 {
1490     xmlwriter *This = impl_from_IXmlWriter(iface);
1491 
1492     FIXME("%p %p %d\n", This, pReader, fWriteDefaultAttributes);
1493 
1494     return E_NOTIMPL;
1495 }
1496 
1497 static HRESULT WINAPI xmlwriter_WriteNodeShallow(IXmlWriter *iface, IXmlReader *pReader,
1498                                    BOOL fWriteDefaultAttributes)
1499 {
1500     xmlwriter *This = impl_from_IXmlWriter(iface);
1501 
1502     FIXME("%p %p %d\n", This, pReader, fWriteDefaultAttributes);
1503 
1504     return E_NOTIMPL;
1505 }
1506 
1507 static HRESULT WINAPI xmlwriter_WriteProcessingInstruction(IXmlWriter *iface, LPCWSTR name,
1508                                              LPCWSTR text)
1509 {
1510     xmlwriter *This = impl_from_IXmlWriter(iface);
1511     static const WCHAR xmlW[] = {'x','m','l',0};
1512     static const WCHAR openpiW[] = {'<','?'};
1513 
1514     TRACE("(%p)->(%s %s)\n", This, wine_dbgstr_w(name), wine_dbgstr_w(text));
1515 
1516     switch (This->state)
1517     {
1518     case XmlWriterState_Initial:
1519         return E_UNEXPECTED;
1520     case XmlWriterState_InvalidEncoding:
1521         return MX_E_ENCODING;
1522     case XmlWriterState_DocStarted:
1523         if (!wcscmp(name, xmlW))
1524             return WR_E_INVALIDACTION;
1525         break;
1526     case XmlWriterState_ElemStarted:
1527     case XmlWriterState_DocClosed:
1528         return WR_E_INVALIDACTION;
1529     default:
1530         ;
1531     }
1532 
1533     write_encoding_bom(This);
1534     write_node_indent(This);
1535     write_output_buffer(This->output, openpiW, ARRAY_SIZE(openpiW));
1536     write_output_buffer(This->output, name, -1);
1537     write_output_buffer_char(This->output, ' ');
1538     write_output_buffer(This->output, text, -1);
1539     write_output_buffer(This->output, closepiW, ARRAY_SIZE(closepiW));
1540 
1541     if (!wcscmp(name, xmlW))
1542         This->state = XmlWriterState_PIDocStarted;
1543 
1544     return S_OK;
1545 }
1546 
1547 static HRESULT WINAPI xmlwriter_WriteQualifiedName(IXmlWriter *iface, LPCWSTR pwszLocalName,
1548                                      LPCWSTR pwszNamespaceUri)
1549 {
1550     xmlwriter *This = impl_from_IXmlWriter(iface);
1551 
1552     FIXME("%p %s %s\n", This, wine_dbgstr_w(pwszLocalName), wine_dbgstr_w(pwszNamespaceUri));
1553 
1554     switch (This->state)
1555     {
1556     case XmlWriterState_Initial:
1557         return E_UNEXPECTED;
1558     case XmlWriterState_InvalidEncoding:
1559         return MX_E_ENCODING;
1560     case XmlWriterState_DocClosed:
1561         return WR_E_INVALIDACTION;
1562     default:
1563         ;
1564     }
1565 
1566     return E_NOTIMPL;
1567 }
1568 
1569 static HRESULT WINAPI xmlwriter_WriteRaw(IXmlWriter *iface, LPCWSTR data)
1570 {
1571     xmlwriter *This = impl_from_IXmlWriter(iface);
1572 
1573     TRACE("%p %s\n", This, debugstr_w(data));
1574 
1575     if (!data)
1576         return S_OK;
1577 
1578     switch (This->state)
1579     {
1580     case XmlWriterState_Initial:
1581         return E_UNEXPECTED;
1582     case XmlWriterState_Ready:
1583         write_xmldecl(This, XmlStandalone_Omit);
1584         /* fallthrough */
1585     case XmlWriterState_DocStarted:
1586     case XmlWriterState_PIDocStarted:
1587         break;
1588     case XmlWriterState_InvalidEncoding:
1589         return MX_E_ENCODING;
1590     default:
1591         This->state = XmlWriterState_DocClosed;
1592         return WR_E_INVALIDACTION;
1593     }
1594 
1595     write_output_buffer(This->output, data, -1);
1596     return S_OK;
1597 }
1598 
1599 static HRESULT WINAPI xmlwriter_WriteRawChars(IXmlWriter *iface,  const WCHAR *pwch, UINT cwch)
1600 {
1601     xmlwriter *This = impl_from_IXmlWriter(iface);
1602 
1603     FIXME("%p %s %d\n", This, wine_dbgstr_w(pwch), cwch);
1604 
1605     switch (This->state)
1606     {
1607     case XmlWriterState_Initial:
1608         return E_UNEXPECTED;
1609     case XmlWriterState_InvalidEncoding:
1610         return MX_E_ENCODING;
1611     case XmlWriterState_DocClosed:
1612         return WR_E_INVALIDACTION;
1613     default:
1614         ;
1615     }
1616 
1617     return E_NOTIMPL;
1618 }
1619 
1620 static HRESULT WINAPI xmlwriter_WriteStartDocument(IXmlWriter *iface, XmlStandalone standalone)
1621 {
1622     xmlwriter *This = impl_from_IXmlWriter(iface);
1623 
1624     TRACE("(%p)->(%d)\n", This, standalone);
1625 
1626     switch (This->state)
1627     {
1628     case XmlWriterState_Initial:
1629         return E_UNEXPECTED;
1630     case XmlWriterState_PIDocStarted:
1631         This->state = XmlWriterState_DocStarted;
1632         return S_OK;
1633     case XmlWriterState_Ready:
1634         break;
1635     case XmlWriterState_InvalidEncoding:
1636         return MX_E_ENCODING;
1637     default:
1638         This->state = XmlWriterState_DocClosed;
1639         return WR_E_INVALIDACTION;
1640     }
1641 
1642     return write_xmldecl(This, standalone);
1643 }
1644 
1645 static HRESULT WINAPI xmlwriter_WriteStartElement(IXmlWriter *iface, LPCWSTR prefix, LPCWSTR local_name, LPCWSTR uri)
1646 {
1647     xmlwriter *This = impl_from_IXmlWriter(iface);
1648     int prefix_len, local_len;
1649     struct element *element;
1650     struct ns *ns;
1651     HRESULT hr;
1652 
1653     TRACE("(%p)->(%s %s %s)\n", This, wine_dbgstr_w(prefix), wine_dbgstr_w(local_name), wine_dbgstr_w(uri));
1654 
1655     if (!local_name)
1656         return E_INVALIDARG;
1657 
1658     switch (This->state)
1659     {
1660     case XmlWriterState_Initial:
1661         return E_UNEXPECTED;
1662     case XmlWriterState_InvalidEncoding:
1663         return MX_E_ENCODING;
1664     case XmlWriterState_DocClosed:
1665         return WR_E_INVALIDACTION;
1666     case XmlWriterState_ElemStarted:
1667         writer_close_starttag(This);
1668         break;
1669     default:
1670         ;
1671     }
1672 
1673     /* Validate prefix and local name */
1674     if (FAILED(hr = is_valid_ncname(prefix, &prefix_len)))
1675         return hr;
1676 
1677     if (FAILED(hr = is_valid_ncname(local_name, &local_len)))
1678         return hr;
1679 
1680     if (uri && !wcscmp(uri, xmlnsuriW))
1681     {
1682         if (!prefix)
1683             return WR_E_XMLNSPREFIXDECLARATION;
1684 
1685         if (!is_empty_string(prefix))
1686             return WR_E_XMLNSURIDECLARATION;
1687     }
1688 
1689     ns = writer_find_ns(This, prefix, uri);
1690 
1691     element = alloc_element(This, prefix, local_name);
1692     if (!element)
1693         return E_OUTOFMEMORY;
1694 
1695     write_encoding_bom(This);
1696     write_node_indent(This);
1697 
1698     This->state = XmlWriterState_ElemStarted;
1699     This->starttagopen = 1;
1700 
1701     writer_push_element(This, element);
1702 
1703     if (!ns && uri)
1704         writer_push_ns(This, prefix, prefix_len, uri);
1705 
1706     write_output_buffer_char(This->output, '<');
1707     if (ns)
1708         write_output_qname(This->output, ns->prefix, ns->prefix_len, local_name, local_len);
1709     else
1710         write_output_qname(This->output, prefix, prefix_len, local_name, local_len);
1711     writer_inc_indent(This);
1712 
1713     return S_OK;
1714 }
1715 
1716 static void write_escaped_string(xmlwriter *writer, const WCHAR *string)
1717 {
1718     static const WCHAR ampW[] = {'&','a','m','p',';'};
1719     static const WCHAR ltW[] = {'&','l','t',';'};
1720     static const WCHAR gtW[] = {'&','g','t',';'};
1721 
1722     while (*string)
1723     {
1724         switch (*string)
1725         {
1726         case '<':
1727             write_output_buffer(writer->output, ltW, ARRAY_SIZE(ltW));
1728             break;
1729         case '&':
1730             write_output_buffer(writer->output, ampW, ARRAY_SIZE(ampW));
1731             break;
1732         case '>':
1733             write_output_buffer(writer->output, gtW, ARRAY_SIZE(gtW));
1734             break;
1735         default:
1736             write_output_buffer(writer->output, string, 1);
1737         }
1738 
1739         string++;
1740     }
1741 }
1742 
1743 static HRESULT WINAPI xmlwriter_WriteString(IXmlWriter *iface, const WCHAR *string)
1744 {
1745     xmlwriter *This = impl_from_IXmlWriter(iface);
1746 
1747     TRACE("%p %s\n", This, debugstr_w(string));
1748 
1749     if (!string)
1750         return S_OK;
1751 
1752     switch (This->state)
1753     {
1754     case XmlWriterState_Initial:
1755         return E_UNEXPECTED;
1756     case XmlWriterState_ElemStarted:
1757         writer_close_starttag(This);
1758         break;
1759     case XmlWriterState_Ready:
1760     case XmlWriterState_DocClosed:
1761         This->state = XmlWriterState_DocClosed;
1762         return WR_E_INVALIDACTION;
1763     case XmlWriterState_InvalidEncoding:
1764         return MX_E_ENCODING;
1765     default:
1766         ;
1767     }
1768 
1769     This->textnode = 1;
1770     write_escaped_string(This, string);
1771     return S_OK;
1772 }
1773 
1774 static HRESULT WINAPI xmlwriter_WriteSurrogateCharEntity(IXmlWriter *iface, WCHAR wchLow, WCHAR wchHigh)
1775 {
1776     xmlwriter *This = impl_from_IXmlWriter(iface);
1777 
1778     FIXME("%p %d %d\n", This, wchLow, wchHigh);
1779 
1780     return E_NOTIMPL;
1781 }
1782 
1783 static HRESULT WINAPI xmlwriter_WriteWhitespace(IXmlWriter *iface, LPCWSTR pwszWhitespace)
1784 {
1785     xmlwriter *This = impl_from_IXmlWriter(iface);
1786 
1787     FIXME("%p %s\n", This, wine_dbgstr_w(pwszWhitespace));
1788 
1789     return E_NOTIMPL;
1790 }
1791 
1792 static HRESULT WINAPI xmlwriter_Flush(IXmlWriter *iface)
1793 {
1794     xmlwriter *This = impl_from_IXmlWriter(iface);
1795 
1796     TRACE("%p\n", This);
1797 
1798     return writeroutput_flush_stream(This->output);
1799 }
1800 
1801 static const struct IXmlWriterVtbl xmlwriter_vtbl =
1802 {
1803     xmlwriter_QueryInterface,
1804     xmlwriter_AddRef,
1805     xmlwriter_Release,
1806     xmlwriter_SetOutput,
1807     xmlwriter_GetProperty,
1808     xmlwriter_SetProperty,
1809     xmlwriter_WriteAttributes,
1810     xmlwriter_WriteAttributeString,
1811     xmlwriter_WriteCData,
1812     xmlwriter_WriteCharEntity,
1813     xmlwriter_WriteChars,
1814     xmlwriter_WriteComment,
1815     xmlwriter_WriteDocType,
1816     xmlwriter_WriteElementString,
1817     xmlwriter_WriteEndDocument,
1818     xmlwriter_WriteEndElement,
1819     xmlwriter_WriteEntityRef,
1820     xmlwriter_WriteFullEndElement,
1821     xmlwriter_WriteName,
1822     xmlwriter_WriteNmToken,
1823     xmlwriter_WriteNode,
1824     xmlwriter_WriteNodeShallow,
1825     xmlwriter_WriteProcessingInstruction,
1826     xmlwriter_WriteQualifiedName,
1827     xmlwriter_WriteRaw,
1828     xmlwriter_WriteRawChars,
1829     xmlwriter_WriteStartDocument,
1830     xmlwriter_WriteStartElement,
1831     xmlwriter_WriteString,
1832     xmlwriter_WriteSurrogateCharEntity,
1833     xmlwriter_WriteWhitespace,
1834     xmlwriter_Flush
1835 };
1836 
1837 /** IXmlWriterOutput **/
1838 static HRESULT WINAPI xmlwriteroutput_QueryInterface(IXmlWriterOutput *iface, REFIID riid, void** ppvObject)
1839 {
1840     xmlwriteroutput *This = impl_from_IXmlWriterOutput(iface);
1841 
1842     TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppvObject);
1843 
1844     if (IsEqualGUID(riid, &IID_IXmlWriterOutput) ||
1845         IsEqualGUID(riid, &IID_IUnknown))
1846     {
1847         *ppvObject = iface;
1848     }
1849     else
1850     {
1851         WARN("interface %s not implemented\n", debugstr_guid(riid));
1852         *ppvObject = NULL;
1853         return E_NOINTERFACE;
1854     }
1855 
1856     IUnknown_AddRef(iface);
1857 
1858     return S_OK;
1859 }
1860 
1861 static ULONG WINAPI xmlwriteroutput_AddRef(IXmlWriterOutput *iface)
1862 {
1863     xmlwriteroutput *This = impl_from_IXmlWriterOutput(iface);
1864     ULONG ref = InterlockedIncrement(&This->ref);
1865     TRACE("(%p)->(%d)\n", This, ref);
1866     return ref;
1867 }
1868 
1869 static ULONG WINAPI xmlwriteroutput_Release(IXmlWriterOutput *iface)
1870 {
1871     xmlwriteroutput *This = impl_from_IXmlWriterOutput(iface);
1872     LONG ref = InterlockedDecrement(&This->ref);
1873 
1874     TRACE("(%p)->(%d)\n", This, ref);
1875 
1876     if (ref == 0)
1877     {
1878         IMalloc *imalloc = This->imalloc;
1879         if (This->output) IUnknown_Release(This->output);
1880         if (This->stream) ISequentialStream_Release(This->stream);
1881         free_output_buffer(This);
1882         writeroutput_free(This, This->encoding_name);
1883         writeroutput_free(This, This);
1884         if (imalloc) IMalloc_Release(imalloc);
1885     }
1886 
1887     return ref;
1888 }
1889 
1890 static const struct IUnknownVtbl xmlwriteroutputvtbl =
1891 {
1892     xmlwriteroutput_QueryInterface,
1893     xmlwriteroutput_AddRef,
1894     xmlwriteroutput_Release
1895 };
1896 
1897 HRESULT WINAPI CreateXmlWriter(REFIID riid, void **obj, IMalloc *imalloc)
1898 {
1899     xmlwriter *writer;
1900     HRESULT hr;
1901 
1902     TRACE("(%s, %p, %p)\n", wine_dbgstr_guid(riid), obj, imalloc);
1903 
1904     if (imalloc)
1905         writer = IMalloc_Alloc(imalloc, sizeof(*writer));
1906     else
1907         writer = heap_alloc(sizeof(*writer));
1908     if (!writer)
1909         return E_OUTOFMEMORY;
1910 
1911     memset(writer, 0, sizeof(*writer));
1912 
1913     writer->IXmlWriter_iface.lpVtbl = &xmlwriter_vtbl;
1914     writer->ref = 1;
1915     writer->imalloc = imalloc;
1916     if (imalloc) IMalloc_AddRef(imalloc);
1917     writer->bom = TRUE;
1918     writer->conformance = XmlConformanceLevel_Document;
1919     writer->state = XmlWriterState_Initial;
1920     list_init(&writer->elements);
1921 
1922     hr = IXmlWriter_QueryInterface(&writer->IXmlWriter_iface, riid, obj);
1923     IXmlWriter_Release(&writer->IXmlWriter_iface);
1924 
1925     TRACE("returning iface %p, hr %#x\n", *obj, hr);
1926 
1927     return hr;
1928 }
1929 
1930 static HRESULT create_writer_output(IUnknown *stream, IMalloc *imalloc, xml_encoding encoding,
1931     const WCHAR *encoding_name, xmlwriteroutput **out)
1932 {
1933     xmlwriteroutput *writeroutput;
1934     HRESULT hr;
1935 
1936     *out = NULL;
1937 
1938     if (imalloc)
1939         writeroutput = IMalloc_Alloc(imalloc, sizeof(*writeroutput));
1940     else
1941         writeroutput = heap_alloc(sizeof(*writeroutput));
1942     if (!writeroutput)
1943         return E_OUTOFMEMORY;
1944 
1945     writeroutput->IXmlWriterOutput_iface.lpVtbl = &xmlwriteroutputvtbl;
1946     writeroutput->ref = 1;
1947     writeroutput->imalloc = imalloc;
1948     if (imalloc)
1949         IMalloc_AddRef(imalloc);
1950     writeroutput->encoding = encoding;
1951     writeroutput->stream = NULL;
1952     hr = init_output_buffer(writeroutput);
1953     if (FAILED(hr)) {
1954         IUnknown_Release(&writeroutput->IXmlWriterOutput_iface);
1955         return hr;
1956     }
1957 
1958     if (encoding_name) {
1959         unsigned int size = (lstrlenW(encoding_name) + 1) * sizeof(WCHAR);
1960         writeroutput->encoding_name = writeroutput_alloc(writeroutput, size);
1961         memcpy(writeroutput->encoding_name, encoding_name, size);
1962     }
1963     else
1964         writeroutput->encoding_name = NULL;
1965     writeroutput->written = 0;
1966 
1967     IUnknown_QueryInterface(stream, &IID_IUnknown, (void**)&writeroutput->output);
1968 
1969     *out = writeroutput;
1970 
1971     TRACE("Created writer output %p\n", *out);
1972 
1973     return S_OK;
1974 }
1975 
1976 HRESULT WINAPI CreateXmlWriterOutputWithEncodingName(IUnknown *stream, IMalloc *imalloc, const WCHAR *encoding,
1977         IXmlWriterOutput **out)
1978 {
1979     xmlwriteroutput *output;
1980     xml_encoding xml_enc;
1981     HRESULT hr;
1982 
1983     TRACE("%p %p %s %p\n", stream, imalloc, debugstr_w(encoding), out);
1984 
1985     if (!stream || !out)
1986         return E_INVALIDARG;
1987 
1988     *out = NULL;
1989 
1990     xml_enc = encoding ? parse_encoding_name(encoding, -1) : XmlEncoding_UTF8;
1991     if (SUCCEEDED(hr = create_writer_output(stream, imalloc, xml_enc, encoding, &output)))
1992         *out = &output->IXmlWriterOutput_iface;
1993 
1994     return hr;
1995 }
1996 
1997 HRESULT WINAPI CreateXmlWriterOutputWithEncodingCodePage(IUnknown *stream, IMalloc *imalloc, UINT codepage,
1998         IXmlWriterOutput **out)
1999 {
2000     xmlwriteroutput *output;
2001     xml_encoding xml_enc;
2002     HRESULT hr;
2003 
2004     TRACE("%p %p %u %p\n", stream, imalloc, codepage, out);
2005 
2006     if (!stream || !out)
2007         return E_INVALIDARG;
2008 
2009     *out = NULL;
2010 
2011     xml_enc = get_encoding_from_codepage(codepage);
2012     if (SUCCEEDED(hr = create_writer_output(stream, imalloc, xml_enc, NULL, &output)))
2013         *out = &output->IXmlWriterOutput_iface;
2014 
2015     return hr;
2016 }
2017