From 07fd479930b6ea8d8dceddd9dd1924a99601a1f6 Mon Sep 17 00:00:00 2001 From: Jefferson Carpenter Date: Wed, 20 Jan 2021 01:07:44 +0000 Subject: [PATCH] msxml3: Implement mxwriter ISAXContentHandler domdoc destination. Signed-off-by: Jefferson Carpenter --- dlls/msxml3/mxwriter.c | 406 +++++++++++++++++++++++++++------- dlls/msxml3/tests/saxreader.c | 217 ++++++++++++++++++ 2 files changed, 547 insertions(+), 76 deletions(-) diff --git a/dlls/msxml3/mxwriter.c b/dlls/msxml3/mxwriter.c index 029e9aac0d0..0b8eddc44cd 100644 --- a/dlls/msxml3/mxwriter.c +++ b/dlls/msxml3/mxwriter.c @@ -142,6 +142,13 @@ typedef enum EscapeText } escape_mode; +typedef enum +{ + OutputIStream, + OutputString, + OutputDOMDocument, +} output_type; + typedef struct { struct list entry; @@ -193,7 +200,11 @@ typedef struct we don't have to close */ BSTR element; + IXMLDOMNode *cur_node; /* current node for DOMDocument destination */ + + output_type output_ty; IStream *dest; + IXMLDOMDocument *dest_doc; output_buffer buffer; } mxwriter; @@ -715,6 +726,32 @@ static HRESULT writer_get_property(const mxwriter *writer, mxwriter_prop propert return S_OK; } +static void push_cur_node(mxwriter *writer, IXMLDOMNode *node) +{ + if (writer->cur_node) { + IXMLDOMNode_Release(writer->cur_node); + } + + IXMLDOMNode_AddRef(node); + writer->cur_node = node; +} + +static HRESULT pop_cur_node(mxwriter *writer) +{ + HRESULT hr; + IXMLDOMNode *parent_node = NULL; + + if (!writer->cur_node) return E_INVALIDARG; + + hr = IXMLDOMNode_get_parentNode(writer->cur_node, &parent_node); + if (FAILED(hr)) return hr; + + IXMLDOMNode_Release(writer->cur_node); + writer->cur_node = parent_node; + + return S_OK; +} + static inline mxwriter *impl_from_IMXWriter(IMXWriter *iface) { return CONTAINING_RECORD(iface, mxwriter, IMXWriter_iface); @@ -863,6 +900,9 @@ static ULONG WINAPI mxwriter_Release(IMXWriter *iface) free_output_buffer(&This->buffer); if (This->dest) IStream_Release(This->dest); + while (!FAILED(pop_cur_node(This))); + if (This->dest_doc) IXMLDOMDocument_Release(This->dest_doc); + SysFreeString(This->version); SysFreeString(This->encoding); @@ -926,8 +966,14 @@ static HRESULT WINAPI mxwriter_put_output(IMXWriter *iface, VARIANT dest) case VT_EMPTY: { if (This->dest) IStream_Release(This->dest); + while (!FAILED(pop_cur_node(This))); + if (This->dest_doc) IXMLDOMDocument_Release(This->dest_doc); + This->dest = NULL; + This->dest_doc = NULL; + reset_output_buffer(This); + This->output_ty = OutputString; break; } case VT_UNKNOWN: @@ -941,13 +987,41 @@ static HRESULT WINAPI mxwriter_put_output(IMXWriter *iface, VARIANT dest) reset_output_buffer(This); if (This->dest) IStream_Release(This->dest); + while (!FAILED(pop_cur_node(This))); + if (This->dest_doc) IXMLDOMDocument_Release(This->dest_doc); + This->dest = stream; + This->dest_doc = NULL; + This->output_ty = OutputIStream; break; } FIXME("unhandled interface type for VT_UNKNOWN destination\n"); return E_NOTIMPL; } + case VT_DISPATCH: + { + IXMLDOMDocument *document; + + hr = IDispatch_QueryInterface(V_DISPATCH(&dest), &IID_IXMLDOMDocument, (void**)&document); + if (hr == S_OK) { + /* Reset output buffer to initial state. */ + reset_output_buffer(This); + + if (This->dest) IStream_Release(This->dest); + while (!FAILED(pop_cur_node(This))); + if (This->dest_doc) IXMLDOMDocument_Release(This->dest_doc); + + This->dest = NULL; + This->dest_doc = document; + push_cur_node(This, (IXMLDOMNode*)document); + This->output_ty = OutputDOMDocument; + break; + } + + FIXME("unhandled interface type for VT_DISPATCH destination\n"); + return E_NOTIMPL; + } default: FIXME("unhandled destination type %s\n", debugstr_variant(&dest)); return E_NOTIMPL; @@ -1223,28 +1297,36 @@ static HRESULT WINAPI SAXContentHandler_startDocument(ISAXContentHandler *iface) TRACE("(%p)\n", This); - /* If properties have been changed since the last "endDocument" call - * we need to reset the output buffer. If we don't the output buffer - * could end up with multiple XML documents in it, plus this seems to - * be how Windows works. - */ - if (This->prop_changed) { - reset_output_buffer(This); - This->prop_changed = FALSE; - } + switch (This->output_ty) + { + case OutputIStream: + case OutputString: + /* If properties have been changed since the last "endDocument" call + * we need to reset the output buffer. If we don't the output buffer + * could end up with multiple XML documents in it, plus this seems to + * be how Windows works. + */ + if (This->prop_changed) { + reset_output_buffer(This); + This->prop_changed = FALSE; + } - if (This->props[MXWriter_OmitXmlDecl] == VARIANT_TRUE) return S_OK; + if (This->props[MXWriter_OmitXmlDecl] == VARIANT_TRUE) return S_OK; - write_prolog_buffer(This); + write_prolog_buffer(This); - if (This->dest && This->xml_enc == XmlEncoding_UTF16) { - static const char utf16BOM[] = {0xff,0xfe}; + if (This->dest && This->xml_enc == XmlEncoding_UTF16) { + static const char utf16BOM[] = {0xff,0xfe}; - if (This->props[MXWriter_BOM] == VARIANT_TRUE) - /* Windows passes a NULL pointer as the pcbWritten parameter and - * ignores any error codes returned from this Write call. - */ - IStream_Write(This->dest, utf16BOM, sizeof(utf16BOM), NULL); + if (This->props[MXWriter_BOM] == VARIANT_TRUE) + /* Windows passes a NULL pointer as the pcbWritten parameter and + * ignores any error codes returned from this Write call. + */ + IStream_Write(This->dest, utf16BOM, sizeof(utf16BOM), NULL); + } + break; + case OutputDOMDocument: + break; } return S_OK; @@ -1254,7 +1336,15 @@ static HRESULT WINAPI SAXContentHandler_endDocument(ISAXContentHandler *iface) { mxwriter *This = impl_from_ISAXContentHandler( iface ); TRACE("(%p)\n", This); - This->prop_changed = FALSE; + switch (This->output_ty) + { + case OutputIStream: + case OutputString: + This->prop_changed = FALSE; + break; + case OutputDOMDocument: + break; + } return flush_output_buffer(This); } @@ -1333,32 +1423,96 @@ static HRESULT WINAPI SAXContentHandler_startElement( (nQName == -1 && This->class_version == MSXML6)) return E_INVALIDARG; - mxwriter_write_starttag(This, QName, nQName); + switch (This->output_ty) { + case OutputIStream: + case OutputString: + mxwriter_write_starttag(This, QName, nQName); - if (attr) - { - int length, i, escape; - HRESULT hr; + if (attr) + { + int length, i, escape; + HRESULT hr; - hr = ISAXAttributes_getLength(attr, &length); - if (FAILED(hr)) return hr; + hr = ISAXAttributes_getLength(attr, &length); + if (FAILED(hr)) return hr; - escape = This->props[MXWriter_DisableEscaping] == VARIANT_FALSE || + escape = This->props[MXWriter_DisableEscaping] == VARIANT_FALSE || (This->class_version == MSXML4 || This->class_version == MSXML6); - for (i = 0; i < length; i++) - { - int qname_len = 0, value_len = 0; - const WCHAR *qname, *value; + for (i = 0; i < length; i++) + { + int qname_len = 0, value_len = 0; + const WCHAR *qname, *value; - hr = ISAXAttributes_getQName(attr, i, &qname, &qname_len); - if (FAILED(hr)) return hr; + hr = ISAXAttributes_getQName(attr, i, &qname, &qname_len); + if (FAILED(hr)) return hr; + + hr = ISAXAttributes_getValue(attr, i, &value, &value_len); + if (FAILED(hr)) return hr; - hr = ISAXAttributes_getValue(attr, i, &value, &value_len); + mxwriter_write_attribute(This, qname, qname_len, value, value_len, escape); + } + } + break; + case OutputDOMDocument: + { + HRESULT hr = S_OK; + IXMLDOMElement *element = NULL; + BSTR tag_name = NULL; + + tag_name = SysAllocStringLen(QName, nQName); + if (!tag_name) { + hr = E_OUTOFMEMORY; + goto done; + } + + hr = IXMLDOMDocument_createElement(This->dest_doc, tag_name, &element); + if (FAILED(hr)) goto done; + + if (attr) { + int length, i; + BSTR attribute_name = NULL; + VARIANT attribute_value; + + hr = ISAXAttributes_getLength(attr, &length); if (FAILED(hr)) return hr; - mxwriter_write_attribute(This, qname, qname_len, value, value_len, escape); + for (i = 0; i < length; i++) + { + int qname_len = 0, value_len = 0; + const WCHAR *qname, *value; + + hr = ISAXAttributes_getQName(attr, i, &qname, &qname_len); + if (FAILED(hr)) return hr; + + hr = ISAXAttributes_getValue(attr, i, &value, &value_len); + if (FAILED(hr)) return hr; + + attribute_name = SysAllocStringLen(qname, qname_len); + if (!attribute_name) goto done; + + V_VT(&attribute_value) = VT_BSTR; + V_BSTR(&attribute_value) = SysAllocStringLen(value, value_len); + if (!V_BSTR(&attribute_value)) goto done; + + IXMLDOMElement_setAttribute(element, attribute_name, attribute_value); + + SysFreeString(V_BSTR(&attribute_value)); + SysFreeString(attribute_name); + } } + + hr = IXMLDOMNode_appendChild(This->cur_node, (IXMLDOMNode*)element, NULL); + if (FAILED(hr)) goto done; + + push_cur_node(This, (IXMLDOMNode*)element); + + done: + if (element) IXMLDOMElement_Release(element); + if (tag_name) SysFreeString(tag_name); + + return hr; + } } return S_OK; @@ -1382,25 +1536,34 @@ static HRESULT WINAPI SAXContentHandler_endElement( (nQName == -1 && This->class_version == MSXML6)) return E_INVALIDARG; - writer_dec_indent(This); - - if (This->element) + switch (This->output_ty) { - static const WCHAR closeW[] = {'/','>'}; - write_output_buffer(This, closeW, 2); - } - else - { - static const WCHAR closetagW[] = {'<','/'}; - static const WCHAR gtW[] = {'>'}; + case OutputIStream: + case OutputString: + writer_dec_indent(This); - write_node_indent(This); - write_output_buffer(This, closetagW, 2); - write_output_buffer(This, QName, nQName); - write_output_buffer(This, gtW, 1); - } + if (This->element) + { + static const WCHAR closeW[] = {'/','>'}; + write_output_buffer(This, closeW, 2); + } + else + { + static const WCHAR closetagW[] = {'<','/'}; + static const WCHAR gtW[] = {'>'}; - set_element_name(This, NULL, 0); + write_node_indent(This); + write_output_buffer(This, closetagW, 2); + write_output_buffer(This, QName, nQName); + write_output_buffer(This, gtW, 1); + } + + set_element_name(This, NULL, 0); + break; + case OutputDOMDocument: + pop_cur_node(This); + break; + } return S_OK; } @@ -1416,25 +1579,54 @@ static HRESULT WINAPI SAXContentHandler_characters( if (!chars) return E_INVALIDARG; - close_element_starttag(This); - set_element_name(This, NULL, 0); + switch (This->output_ty) { + case OutputIStream: + case OutputString: + close_element_starttag(This); + set_element_name(This, NULL, 0); - if (!This->cdata) - This->text = TRUE; + if (!This->cdata) + This->text = TRUE; - if (nchars) - { - if (This->cdata || This->props[MXWriter_DisableEscaping] == VARIANT_TRUE) - write_output_buffer(This, chars, nchars); - else + if (nchars) { - int len = nchars; - WCHAR *escaped; + if (This->cdata || This->props[MXWriter_DisableEscaping] == VARIANT_TRUE) + write_output_buffer(This, chars, nchars); + else + { + int len = nchars; + WCHAR *escaped; - escaped = get_escaped_string(chars, EscapeText, &len); - write_output_buffer(This, escaped, len); - heap_free(escaped); + escaped = get_escaped_string(chars, EscapeText, &len); + write_output_buffer(This, escaped, len); + heap_free(escaped); + } } + break; + case OutputDOMDocument: + { + HRESULT hr = S_OK; + IXMLDOMText *text_node = NULL; + BSTR text = NULL; + + text = SysAllocStringLen(chars, nchars); + if (!text) { + hr = E_OUTOFMEMORY; + goto done; + } + + hr = IXMLDOMDocument_createTextNode(This->dest_doc, text, &text_node); + if (FAILED(hr)) goto done; + + hr = IXMLDOMNode_appendChild(This->cur_node, (IXMLDOMNode*)text_node, NULL); + if (FAILED(hr)) goto done; + + done: + if (text_node) IXMLDOMText_Release(text_node); + if (text) SysFreeString(text); + + return hr; + } } return S_OK; @@ -1451,7 +1643,15 @@ static HRESULT WINAPI SAXContentHandler_ignorableWhitespace( if (!chars) return E_INVALIDARG; - write_output_buffer(This, chars, nchars); + switch (This->output_ty) + { + case OutputIStream: + case OutputString: + write_output_buffer(This, chars, nchars); + break; + case OutputDOMDocument: + break; + } return S_OK; } @@ -1471,20 +1671,71 @@ static HRESULT WINAPI SAXContentHandler_processingInstruction( if (!target) return E_INVALIDARG; - write_node_indent(This); - write_output_buffer(This, openpiW, ARRAY_SIZE(openpiW)); + switch (This->output_ty) { + case OutputIStream: + case OutputString: + write_node_indent(This); + write_output_buffer(This, openpiW, ARRAY_SIZE(openpiW)); - if (*target) - write_output_buffer(This, target, ntarget); + if (*target) + write_output_buffer(This, target, ntarget); - if (data && *data && ndata) + if (data && *data && ndata) + { + write_output_buffer(This, spaceW, 1); + write_output_buffer(This, data, ndata); + } + + write_output_buffer(This, closepiW, ARRAY_SIZE(closepiW)); + This->newline = TRUE; + break; + case OutputDOMDocument: { - write_output_buffer(This, spaceW, 1); - write_output_buffer(This, data, ndata); - } + HRESULT hr = S_OK; + IXMLDOMNode *first_child = NULL; + IXMLDOMProcessingInstruction *processing_instruction = NULL; + VARIANT ref_node; + BSTR target_bstr = NULL; + BSTR data_bstr = NULL; + + target_bstr = SysAllocStringLen(target, ntarget); + if (!target_bstr) { + hr = E_OUTOFMEMORY; + goto done; + } + + data_bstr = SysAllocStringLen(data, ndata); + if (!data_bstr) { + hr = E_OUTOFMEMORY; + goto done; + } - write_output_buffer(This, closepiW, ARRAY_SIZE(closepiW)); - This->newline = TRUE; + hr = IXMLDOMDocument_createProcessingInstruction( + This->dest_doc, target_bstr, data_bstr, &processing_instruction + ); + if (FAILED(hr)) goto done; + + hr = IXMLDOMDocument_get_firstChild(This->dest_doc, &first_child); + if (FAILED(hr)) goto done; + + V_VT(&ref_node) = VT_UNKNOWN; + V_UNKNOWN(&ref_node) = (IUnknown*)first_child; + + hr = IXMLDOMDocument_insertBefore( + This->dest_doc, (IXMLDOMNode*)processing_instruction, ref_node, NULL + ); + if (FAILED(hr)) goto done; + + done: + if (first_child) IXMLDOMNode_Release(first_child); + if (processing_instruction) + IXMLDOMProcessingInstruction_Release(processing_instruction); + if (data_bstr) SysFreeString(data_bstr); + if (target_bstr) SysFreeString(target_bstr); + + return hr; + } + } return S_OK; } @@ -2651,7 +2902,10 @@ HRESULT MXWriter_create(MSXML_VERSION version, void **ppObj) This->text = FALSE; This->newline = FALSE; + This->output_ty = OutputString; This->dest = NULL; + This->dest_doc = NULL; + This->cur_node = NULL; hr = init_output_buffer(This->xml_enc, &This->buffer); if (hr != S_OK) { diff --git a/dlls/msxml3/tests/saxreader.c b/dlls/msxml3/tests/saxreader.c index ab814c5f764..1a89d88ccef 100644 --- a/dlls/msxml3/tests/saxreader.c +++ b/dlls/msxml3/tests/saxreader.c @@ -4367,6 +4367,222 @@ static void test_mxwriter_stream(void) free_bstrs(); } +static void test_mxwriter_domdoc(void) +{ + IMXWriter *writer; + ISAXContentHandler *content; + IXMLDOMDocument3 *domdoc; + IDispatch *dispatch; + ISAXAttributes *sax_attributes; + IMXAttributes *mx_attributes; + HRESULT hr; + VARIANT dest; + + IXMLDOMElement *root = NULL; + IXMLDOMNodeList *node_list = NULL; + IXMLDOMNode *node = NULL; + IXMLDOMNamedNodeMap *attribute_map = NULL; + IXMLDOMNode *attribute_node = NULL; + LONG list_length = 0; + BSTR str; + + /* Create writer and attach DOMDocument output */ + hr = CoCreateInstance(&CLSID_MXXMLWriter60, NULL, CLSCTX_INPROC_SERVER, + &IID_IMXWriter, (void**)&writer); + EXPECT_HR(hr, S_OK); + + hr = IMXWriter_QueryInterface(writer, &IID_ISAXContentHandler, (void**)&content); + EXPECT_HR(hr, S_OK); + + hr = CoCreateInstance(&CLSID_DOMDocument60, NULL, CLSCTX_INPROC_SERVER, + &IID_IXMLDOMDocument3, (void**)&domdoc); + EXPECT_HR(hr, S_OK); + + hr = IXMLDOMDocument3_QueryInterface(domdoc, &IID_IDispatch, (void**)&dispatch); + EXPECT_HR(hr, S_OK); + + V_VT(&dest) = VT_DISPATCH; + V_DISPATCH(&dest) = dispatch; + + hr = IMXWriter_put_output(writer, dest); + EXPECT_HR(hr, S_OK); + + + /* Provide writer with events */ + hr = ISAXContentHandler_startDocument(content); + EXPECT_HR(hr, S_OK); + + hr = ISAXContentHandler_processingInstruction( + content, + L"targ", 4, L"type=\"test sheet\" test=\"something\"", 34 + ); + EXPECT_HR(hr, S_OK); + + /* */ + hr = ISAXContentHandler_startElement(content, L"", 0, L"", 0, L"BankAccount", 11, NULL); + EXPECT_HR(hr, S_OK); + + /* 12345 */ + hr = CoCreateInstance(&CLSID_SAXAttributes60, NULL, CLSCTX_INPROC_SERVER, + &IID_IMXAttributes, (void**)&mx_attributes); + EXPECT_HR(hr, S_OK); + + hr = IMXAttributes_addAttribute( + mx_attributes, _bstr_("uri"), _bstr_("local"), + _bstr_("bank"), _bstr_("type"), _bstr_("World Bank") + ); + EXPECT_HR(hr, S_OK); + + hr = IMXAttributes_addAttribute( + mx_attributes, _bstr_("uri"), _bstr_("local"), + _bstr_("branch"), _bstr_("type"), _bstr_("Japan") + ); + EXPECT_HR(hr, S_OK); + + hr = IMXAttributes_QueryInterface(mx_attributes, &IID_ISAXAttributes, (void**)&sax_attributes); + EXPECT_HR(hr, S_OK); + + hr = ISAXContentHandler_startElement(content, L"", 0, L"", 0, L"Number", 6, sax_attributes); + EXPECT_HR(hr, S_OK); + + ISAXAttributes_Release(sax_attributes); + IMXAttributes_Release(mx_attributes); + + hr = ISAXContentHandler_characters(content, L"12345", 5); + EXPECT_HR(hr, S_OK); + + hr = ISAXContentHandler_endElement(content, L"", 0, L"", 0, L"Number", 6); + EXPECT_HR(hr, S_OK); + + /* Captain Ahab */ + hr = ISAXContentHandler_startElement(content, L"", 0, L"", 0, L"Name", 4, NULL); + EXPECT_HR(hr, S_OK); + + hr = ISAXContentHandler_characters(content, L"Captain Ahab", 12); + EXPECT_HR(hr, S_OK); + + hr = ISAXContentHandler_endElement(content, L"", 0, L"", 0, L"Name", 4); + EXPECT_HR(hr, S_OK); + + /* */ + hr = ISAXContentHandler_endElement(content, L"", 0, L"", 0, L"BankAccount", 11); + EXPECT_HR(hr, S_OK); + + hr = ISAXContentHandler_endDocument(content); + EXPECT_HR(hr, S_OK); + + + /* Assert that it created the correct document */ + hr = IXMLDOMDocument3_get_documentElement(domdoc, &root); + EXPECT_HR(hr, S_OK); + + /* */ + hr = IXMLDOMElement_get_nodeName(root, &str); + EXPECT_HR(hr, S_OK); + ok(!lstrcmpW(L"BankAccount", str), "got %s\n", wine_dbgstr_w(str)); + SysFreeString(str); + + hr = IXMLDOMElement_get_childNodes(root, &node_list); + EXPECT_HR(hr, S_OK); + + hr = IXMLDOMNodeList_get_length(node_list, &list_length); + EXPECT_HR(hr, S_OK); + ok(2 == list_length, "list length %i, expected 1\n", list_length); + + /* 12345 */ + hr = IXMLDOMNode_get_text(node, &str); + EXPECT_HR(hr, S_OK); + ok(!lstrcmpW(L"12345", str), "got %s\n", wine_dbgstr_w(str)); + SysFreeString(str); + + IXMLDOMNode_Release(node); + + /* Captain Ahab */ + hr = IXMLDOMNodeList_get_item(node_list, 1, &node); + EXPECT_HR(hr, S_OK); + + hr = IXMLDOMNode_get_nodeName(node, &str); + EXPECT_HR(hr, S_OK); + ok(!lstrcmpW(L"Name", str), "got %s\n", wine_dbgstr_w(str)); + SysFreeString(str); + + hr = IXMLDOMNode_get_text(node, &str); + EXPECT_HR(hr, S_OK); + ok(!lstrcmpW(L"Captain Ahab", str), "got %s\n", wine_dbgstr_w(str)); + SysFreeString(str); + IXMLDOMNode_Release(node); + IXMLDOMNodeList_Release(node_list); + IXMLDOMElement_Release(root); + + /* finally test doc output */ + hr = IXMLDOMDocument3_get_xml(domdoc, &str); + EXPECT_HR(hr, S_OK); + todo_wine ok( + !lstrcmpW( + L"\r\n" + "" + "12345" + "Captain Ahab" + "\r\n", + str), + "got %s\n", wine_dbgstr_w(str)); + SysFreeString(str); + + IDispatch_Release(dispatch); + IXMLDOMDocument3_Release(domdoc); + ISAXContentHandler_Release(content); + IMXWriter_Release(writer); + + free_bstrs(); +} + static const char *encoding_names[] = { "iso-8859-1", "iso-8859-2", @@ -5768,6 +5984,7 @@ START_TEST(saxreader) test_mxwriter_properties(); test_mxwriter_flush(); test_mxwriter_stream(); + test_mxwriter_domdoc(); test_mxwriter_encoding(); test_mxwriter_dispex(); test_mxwriter_indent(); -- 2.26.2