| /* | |
| * Node implementation | |
| * | |
| * Copyright 2005 Mike McCormack | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #define COBJMACROS | |
| #include <stdarg.h> | |
| #include <assert.h> | |
| #include <libxml/parser.h> | |
| #include <libxml/parserInternals.h> | |
| #include <libxml/xmlerror.h> | |
| #include <libxml/HTMLtree.h> | |
| #include <libxml/tree.h> | |
| #include <libxslt/pattern.h> | |
| #include <libxslt/transform.h> | |
| #include <libxslt/imports.h> | |
| #include <libxslt/variables.h> | |
| #include <libxslt/xsltutils.h> | |
| #include <libxslt/xsltInternals.h> | |
| #include <libxslt/documents.h> | |
| #include <libxslt/extensions.h> | |
| #include <libxslt/xsltInternals.h> | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winuser.h" | |
| #include "winnls.h" | |
| #include "ole2.h" | |
| #include "msxml6.h" | |
| #include <activscp.h> | |
| #include "objsafe.h" | |
| #include "msxml_private.h" | |
| #include "saxreader_extensions.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(msxml); | |
| static const IID IID_domnode = {0x4f2f4ba2,0xb822,0x11df,{0x8b,0x8a,0x68,0x50,0xdf,0xd7,0x20,0x85}}; | |
| struct string_buffer | |
| { | |
| WCHAR *data; | |
| size_t count; | |
| size_t capacity; | |
| HRESULT _status; | |
| HRESULT *status; | |
| }; | |
| static void string_buffer_init(struct string_buffer *buffer) | |
| { | |
| memset(buffer, 0, sizeof(*buffer)); | |
| buffer->status = &buffer->_status; | |
| } | |
| static void string_append(struct string_buffer *buffer, const WCHAR *str, UINT len) | |
| { | |
| if (*buffer->status != S_OK) | |
| return; | |
| if (!array_reserve((void **)&buffer->data, &buffer->capacity, buffer->count + len, sizeof(*str))) | |
| { | |
| *buffer->status = E_OUTOFMEMORY; | |
| free(buffer->data); | |
| return; | |
| } | |
| memcpy(buffer->data + buffer->count, str, len * sizeof(WCHAR)); | |
| buffer->count += len; | |
| } | |
| static void string_buffer_cleanup(struct string_buffer *buffer) | |
| { | |
| free(buffer->data); | |
| buffer->data = NULL; | |
| buffer->count = buffer->capacity = 0; | |
| } | |
| static HRESULT string_to_bstr_slice(struct string_buffer *buffer, size_t offset, size_t length, BSTR *str) | |
| { | |
| if (!str) | |
| { | |
| string_buffer_cleanup(buffer); | |
| return E_INVALIDARG; | |
| } | |
| *str = NULL; | |
| if (*buffer->status != S_OK) | |
| { | |
| string_buffer_cleanup(buffer); | |
| return *buffer->status; | |
| } | |
| if ((length && offset >= buffer->count) || length + offset > buffer->count) | |
| { | |
| *buffer->status = E_INVALIDARG; | |
| string_buffer_cleanup(buffer); | |
| return *buffer->status; | |
| } | |
| *str = SysAllocStringLen(buffer->data + offset, length); | |
| string_buffer_cleanup(buffer); | |
| return *str ? S_OK : E_OUTOFMEMORY; | |
| } | |
| static HRESULT string_to_bstr(struct string_buffer *buffer, BSTR *str) | |
| { | |
| return string_to_bstr_slice(buffer, 0, buffer->count, str); | |
| } | |
| bool node_query_interface(struct domnode *node, REFIID riid, void **obj) | |
| { | |
| if (IsEqualGUID(&IID_domnode, riid)) | |
| { | |
| *obj = node; | |
| return true; | |
| } | |
| return false; | |
| } | |
| /* common ISupportErrorInfo implementation */ | |
| typedef struct { | |
| ISupportErrorInfo ISupportErrorInfo_iface; | |
| LONG ref; | |
| const tid_t* iids; | |
| } SupportErrorInfo; | |
| static inline SupportErrorInfo *impl_from_ISupportErrorInfo(ISupportErrorInfo *iface) | |
| { | |
| return CONTAINING_RECORD(iface, SupportErrorInfo, ISupportErrorInfo_iface); | |
| } | |
| static HRESULT WINAPI SupportErrorInfo_QueryInterface(ISupportErrorInfo *iface, REFIID riid, void **obj) | |
| { | |
| TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); | |
| *obj = NULL; | |
| if (IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_ISupportErrorInfo)) { | |
| *obj = iface; | |
| ISupportErrorInfo_AddRef(iface); | |
| return S_OK; | |
| } | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI SupportErrorInfo_AddRef(ISupportErrorInfo *iface) | |
| { | |
| SupportErrorInfo *This = impl_from_ISupportErrorInfo(iface); | |
| ULONG ref = InterlockedIncrement(&This->ref); | |
| TRACE("%p, refcount %ld.\n", iface, ref ); | |
| return ref; | |
| } | |
| static ULONG WINAPI SupportErrorInfo_Release(ISupportErrorInfo *iface) | |
| { | |
| SupportErrorInfo *This = impl_from_ISupportErrorInfo(iface); | |
| LONG ref = InterlockedDecrement(&This->ref); | |
| TRACE("%p, refcount %ld.\n", iface, ref); | |
| if (!ref) | |
| free(This); | |
| return ref; | |
| } | |
| static HRESULT WINAPI SupportErrorInfo_InterfaceSupportsErrorInfo(ISupportErrorInfo *iface, REFIID riid) | |
| { | |
| SupportErrorInfo *This = impl_from_ISupportErrorInfo(iface); | |
| enum tid_t const *tid; | |
| TRACE("%p, %s.\n", iface, debugstr_guid(riid)); | |
| tid = This->iids; | |
| while (*tid != NULL_tid) | |
| { | |
| if (IsEqualGUID(riid, get_riid_from_tid(*tid))) | |
| return S_OK; | |
| tid++; | |
| } | |
| return S_FALSE; | |
| } | |
| static const struct ISupportErrorInfoVtbl SupportErrorInfoVtbl = { | |
| SupportErrorInfo_QueryInterface, | |
| SupportErrorInfo_AddRef, | |
| SupportErrorInfo_Release, | |
| SupportErrorInfo_InterfaceSupportsErrorInfo | |
| }; | |
| HRESULT node_create_supporterrorinfo(enum tid_t const *iids, void **obj) | |
| { | |
| SupportErrorInfo *This; | |
| This = malloc(sizeof(*This)); | |
| if (!This) return E_OUTOFMEMORY; | |
| This->ISupportErrorInfo_iface.lpVtbl = &SupportErrorInfoVtbl; | |
| This->ref = 1; | |
| This->iids = iids; | |
| *obj = &This->ISupportErrorInfo_iface; | |
| return S_OK; | |
| } | |
| struct domnode *get_node_obj(void *node) | |
| { | |
| struct domnode *obj = NULL; | |
| HRESULT hr; | |
| hr = IUnknown_QueryInterface((IUnknown *)node, &IID_domnode, (void **)&obj); | |
| if (!obj) WARN("Node is not our IXMLDOMNode implementation.\n"); | |
| return SUCCEEDED(hr) ? obj : NULL; | |
| } | |
| static struct domnode *node_get_doc(struct domnode *node) | |
| { | |
| return node->owner ? node->owner : node; | |
| } | |
| HRESULT node_get_name(struct domnode *node, BSTR *name) | |
| { | |
| return return_bstr(node->qname, name); | |
| } | |
| HRESULT node_append_data(struct domnode *node, BSTR data) | |
| { | |
| UINT len, current; | |
| BSTR str; | |
| if (!(len = SysStringLen(data))) | |
| return S_OK; | |
| current = SysStringLen(node->data); | |
| if (!(str = SysAllocStringLen(NULL, current + len))) | |
| return E_OUTOFMEMORY; | |
| memcpy(str, node->data, current * sizeof(WCHAR)); | |
| memcpy(&str[current], data, len * sizeof(WCHAR)); | |
| str[current + len] = 0; | |
| SysFreeString(node->data); | |
| node->data = str; | |
| return S_OK; | |
| } | |
| static HRESULT variant_get_str_value(const VARIANT *src, VARIANT *tmp, BSTR *value) | |
| { | |
| HRESULT hr; | |
| VariantInit(tmp); | |
| *value = NULL; | |
| if (V_VT(src) == VT_BSTR) | |
| { | |
| *value = V_BSTR(src); | |
| return S_OK; | |
| } | |
| hr = VariantChangeType(tmp, src, 0, VT_BSTR); | |
| if (hr != S_OK) | |
| { | |
| WARN("Failed to change to BSTR\n"); | |
| return hr; | |
| } | |
| *value = V_BSTR(tmp); | |
| return S_OK; | |
| } | |
| static HRESULT node_put_text_value(struct domnode *node, const VARIANT *value) | |
| { | |
| HRESULT hr; | |
| VARIANT v; | |
| BSTR str; | |
| if (FAILED(hr = variant_get_str_value(value, &v, &str))) | |
| return hr; | |
| hr = node_put_data(node, str); | |
| VariantClear(&v); | |
| return hr; | |
| } | |
| HRESULT node_put_value(struct domnode *node, VARIANT *value) | |
| { | |
| HRESULT hr; | |
| switch (node->type) | |
| { | |
| case NODE_TEXT: | |
| case NODE_COMMENT: | |
| case NODE_PROCESSING_INSTRUCTION: | |
| case NODE_CDATA_SECTION: | |
| case NODE_ATTRIBUTE: | |
| hr = node_put_text_value(node, value); | |
| break; | |
| default: | |
| FIXME("Unimplemented type %d.\n", node->type); | |
| hr = E_NOTIMPL; | |
| } | |
| return hr; | |
| } | |
| static HRESULT get_node(struct domnode *node, IXMLDOMNode **out) | |
| { | |
| if (!out) | |
| return E_INVALIDARG; | |
| return create_node(node, out); | |
| } | |
| HRESULT node_get_parent(struct domnode *node, IXMLDOMNode **ret) | |
| { | |
| return get_node(node->parent, ret); | |
| } | |
| HRESULT node_get_child_nodes(struct domnode *node, IXMLDOMNodeList **ret) | |
| { | |
| if(!ret) | |
| return E_INVALIDARG; | |
| return create_children_nodelist(node, ret); | |
| } | |
| static struct domnode *node_from_entry(struct list *entry) | |
| { | |
| return entry ? LIST_ENTRY(entry, struct domnode, entry) : NULL; | |
| } | |
| struct domnode *domnode_get_first_child(struct domnode *node) | |
| { | |
| return node_from_entry(list_head(&node->children)); | |
| } | |
| static struct domnode *domnode_get_previous_sibling(struct domnode *node) | |
| { | |
| if (node->parent) | |
| return node_from_entry(list_prev(&node->parent->children, &node->entry)); | |
| return NULL; | |
| } | |
| HRESULT node_get_first_child(struct domnode *node, IXMLDOMNode **ret) | |
| { | |
| return get_node(domnode_get_first_child(node), ret); | |
| } | |
| HRESULT node_get_last_child(struct domnode *node, IXMLDOMNode **ret) | |
| { | |
| return get_node(node_from_entry(list_tail(&node->children)), ret); | |
| } | |
| HRESULT node_get_previous_sibling(struct domnode *node, IXMLDOMNode **ret) | |
| { | |
| struct domnode *sibling = NULL; | |
| if (node->parent) | |
| sibling = node_from_entry(list_prev(&node->parent->children, &node->entry)); | |
| return get_node(sibling, ret); | |
| } | |
| struct domnode *domnode_get_next_sibling(struct domnode *node) | |
| { | |
| if (node->parent) | |
| return node_from_entry(list_next(&node->parent->children, &node->entry)); | |
| return NULL; | |
| } | |
| HRESULT node_get_next_sibling(struct domnode *node, IXMLDOMNode **ret) | |
| { | |
| return get_node(domnode_get_next_sibling(node), ret); | |
| } | |
| static bool domnode_is_valid_child_type(const struct domnode *node, struct domnode *child) | |
| { | |
| struct domnode *n; | |
| if (!(node->type == NODE_DOCUMENT | |
| || node->type == NODE_DOCUMENT_FRAGMENT | |
| || node->type == NODE_ELEMENT | |
| || node->type == NODE_ATTRIBUTE)) | |
| { | |
| return false; | |
| } | |
| if (child->type == NODE_DOCUMENT_FRAGMENT) | |
| { | |
| LIST_FOR_EACH_ENTRY(n, &child->children, struct domnode, entry) | |
| { | |
| if (!domnode_is_valid_child_type(node, n)) return false; | |
| } | |
| return true; | |
| } | |
| switch (node->type) | |
| { | |
| case NODE_DOCUMENT: | |
| if (child->type == NODE_ELEMENT) | |
| { | |
| /* Document is allowed a single element child. */ | |
| LIST_FOR_EACH_ENTRY(n, &node->children, struct domnode, entry) | |
| { | |
| if (n->type == NODE_ELEMENT) | |
| return false; | |
| } | |
| return true; | |
| } | |
| return (child->type == NODE_COMMENT | |
| || child->type == NODE_PROCESSING_INSTRUCTION); | |
| case NODE_DOCUMENT_FRAGMENT: | |
| case NODE_ELEMENT: | |
| return (child->type == NODE_TEXT | |
| || child->type == NODE_COMMENT | |
| || child->type == NODE_ELEMENT | |
| || child->type == NODE_CDATA_SECTION | |
| || child->type == NODE_ENTITY_REFERENCE | |
| || child->type == NODE_PROCESSING_INSTRUCTION); | |
| case NODE_ATTRIBUTE: | |
| return (child->type == NODE_TEXT | |
| || child->type == NODE_ENTITY_REFERENCE); | |
| default: | |
| return false; | |
| } | |
| } | |
| /* Link to a new owner, transferring outstanding references. */ | |
| static void domnode_set_owner(struct domnode *node, struct domnode *owner) | |
| { | |
| struct domnode *n, *old_owner; | |
| /* Document node does not have the owner set. */ | |
| owner = node_get_doc(owner); | |
| assert(!!owner); | |
| old_owner = node->owner; | |
| if (old_owner == owner) | |
| return; | |
| list_remove(&node->owner_entry); | |
| node->owner = owner; | |
| owner->refcount += node->refcount; | |
| list_add_tail(&owner->owned, &node->owner_entry); | |
| LIST_FOR_EACH_ENTRY(n, &node->children, struct domnode, entry) | |
| { | |
| domnode_set_owner(n, owner); | |
| } | |
| LIST_FOR_EACH_ENTRY(n, &node->attributes, struct domnode, entry) | |
| { | |
| domnode_set_owner(n, owner); | |
| } | |
| old_owner->refcount -= node->refcount; | |
| if (old_owner->refcount == 0) | |
| domnode_destroy_tree(old_owner); | |
| } | |
| static void domnode_unlink_child(struct domnode *child) | |
| { | |
| struct domnode *node, *top; | |
| if (!child->parent) | |
| return; | |
| /* TODO: should fail to remove doctype child */ | |
| /* Drop references for the parent and up */ | |
| node = child->parent; | |
| while (node) | |
| { | |
| top = node; | |
| node->refcount -= child->refcount; | |
| node = node->parent; | |
| } | |
| list_remove(&child->entry); | |
| child->parent = NULL; | |
| if (top->refcount == 0) | |
| domnode_destroy_tree(top); | |
| } | |
| static void domnode_add_refs(struct domnode *node, unsigned int count) | |
| { | |
| struct domnode *p = node; | |
| while (p) | |
| { | |
| p->refcount += count; | |
| p = p->parent; | |
| } | |
| } | |
| static void domnode_insert_domnode(struct domnode *node, struct domnode *child, struct domnode *ref_child) | |
| { | |
| struct domnode *n, *next; | |
| if (child->type == NODE_DOCUMENT_FRAGMENT) | |
| { | |
| LIST_FOR_EACH_ENTRY_SAFE(n, next, &child->children, struct domnode, entry) | |
| { | |
| domnode_insert_domnode(node, n, ref_child); | |
| } | |
| return; | |
| } | |
| domnode_unlink_child(child); | |
| if (ref_child) | |
| list_add_before(&ref_child->entry, &child->entry); | |
| else | |
| list_add_tail(&node->children, &child->entry); | |
| child->parent = node; | |
| domnode_add_refs(child->parent, child->refcount); | |
| domnode_set_owner(child, node); | |
| } | |
| HRESULT node_insert_before(struct domnode *node, IXMLDOMNode *new_child, const VARIANT *ref_child, | |
| IXMLDOMNode **ret) | |
| { | |
| struct domnode *child_node, *ref_node = NULL; | |
| if (!new_child) | |
| return E_INVALIDARG; | |
| if (ret) | |
| *ret = NULL; | |
| if (!(child_node = get_node_obj(new_child))) | |
| return E_FAIL; | |
| switch (V_VT(ref_child)) | |
| { | |
| case VT_EMPTY: | |
| case VT_NULL: | |
| break; | |
| case VT_UNKNOWN: | |
| case VT_DISPATCH: | |
| if (!V_UNKNOWN(ref_child)) | |
| break; | |
| if (!(ref_node = get_node_obj(V_UNKNOWN(ref_child)))) | |
| return E_FAIL; | |
| if (ref_node->parent != node) | |
| { | |
| WARN("Reference node is not a child of this node.\n"); | |
| return E_FAIL; | |
| } | |
| break; | |
| default: | |
| FIXME("Unexpected reference node type %s.\n", debugstr_variant(ref_child)); | |
| return E_FAIL; | |
| } | |
| if (!domnode_is_valid_child_type(node, child_node)) | |
| { | |
| WARN("Can't add node %p as a child for %p.\n", child_node, node); | |
| return E_FAIL; | |
| } | |
| domnode_insert_domnode(node, child_node, ref_node); | |
| if (ret) | |
| { | |
| *ret = new_child; | |
| IXMLDOMNode_AddRef(*ret); | |
| } | |
| return S_OK; | |
| } | |
| HRESULT node_replace_child(struct domnode *node, IXMLDOMNode *newChild, IXMLDOMNode *oldChild, | |
| IXMLDOMNode **ret) | |
| { | |
| struct domnode *old_child, *new_child, *curr; | |
| VARIANT ref; | |
| HRESULT hr; | |
| if (!newChild || !oldChild) | |
| return E_INVALIDARG; | |
| if (ret) | |
| *ret = NULL; | |
| if (!(new_child = get_node_obj(newChild))) | |
| return E_FAIL; | |
| if (!(old_child = get_node_obj(oldChild))) | |
| return E_FAIL; | |
| if (old_child->parent != node) | |
| { | |
| WARN("Node %p is not a child of %p.\n", old_child, node); | |
| return E_INVALIDARG; | |
| } | |
| curr = node; | |
| while (curr) | |
| { | |
| if (curr == new_child) | |
| { | |
| WARN("Attempt to create a cycle.\n"); | |
| return E_FAIL; | |
| } | |
| curr = curr->parent; | |
| } | |
| V_VT(&ref) = VT_DISPATCH; | |
| V_DISPATCH(&ref) = (IDispatch *)oldChild; | |
| if (FAILED(hr = node_insert_before(node, newChild, &ref, NULL))) | |
| return hr; | |
| return node_remove_child(node, oldChild, ret); | |
| } | |
| static void domnode_unlink_children(struct domnode *node) | |
| { | |
| struct domnode *child, *next; | |
| LIST_FOR_EACH_ENTRY_SAFE(child, next, &node->children, struct domnode, entry) | |
| { | |
| domnode_unlink_child(child); | |
| } | |
| } | |
| static void domnode_append_child(struct domnode *parent, struct domnode *node) | |
| { | |
| domnode_unlink_child(node); | |
| list_add_tail(&parent->children, &node->entry); | |
| node->parent = parent; | |
| domnode_add_refs(node->parent, node->refcount); | |
| domnode_set_owner(node, parent); | |
| } | |
| HRESULT node_put_data(struct domnode *node, const WCHAR *data) | |
| { | |
| struct string_buffer buffer; | |
| const WCHAR *p = data; | |
| struct domnode *child; | |
| if (node->flags & DOMNODE_READONLY_VALUE) | |
| return E_FAIL; | |
| /* Markup is checked and rejected. */ | |
| if (node->type == NODE_COMMENT && data && wcsstr(data, L"-->")) | |
| return E_INVALIDARG; | |
| if (node->type == NODE_PROCESSING_INSTRUCTION && data && wcsstr(data, L"?>")) | |
| return E_INVALIDARG; | |
| string_buffer_init(&buffer); | |
| while (p && *p) | |
| { | |
| if (*p == '\r') | |
| { | |
| if (p[1] == '\n') ++p; | |
| string_append(&buffer, L"\n", 1); | |
| } | |
| else | |
| { | |
| string_append(&buffer, p, 1); | |
| } | |
| ++p; | |
| } | |
| node->flags &= ~DOMNODE_PARSED_VALUE; | |
| switch (node->type) | |
| { | |
| case NODE_ATTRIBUTE: | |
| case NODE_ELEMENT: | |
| domnode_unlink_children(node); | |
| /* TODO: error handling */ | |
| domnode_create(NODE_TEXT, NULL, 0, NULL, 0, node->owner, &child); | |
| domnode_append_child(node, child); | |
| return string_to_bstr(&buffer, &child->data); | |
| default: | |
| SysFreeString(node->data); | |
| return string_to_bstr(&buffer, &node->data); | |
| } | |
| } | |
| struct node_dump_ns | |
| { | |
| struct list entry; | |
| BSTR prefix; | |
| BSTR uri; | |
| bool own_uri; | |
| int depth; | |
| }; | |
| struct node_dump_context | |
| { | |
| struct string_buffer buffer; | |
| struct | |
| { | |
| char *data; | |
| size_t capacity; | |
| } scratch; | |
| struct list namespaces; | |
| int depth; | |
| unsigned int indent; | |
| bool needs_formatting; | |
| bool only_utf16_encoding_decl; | |
| IStream *stream; | |
| UINT codepage; | |
| HRESULT status; | |
| }; | |
| static void node_dump_push_namespace(struct node_dump_context *context, BSTR prefix, BSTR uri, bool own_uri) | |
| { | |
| struct node_dump_ns *ns; | |
| if (context->status != S_OK) | |
| return; | |
| if (!(ns = calloc(1, sizeof(*ns)))) | |
| { | |
| context->status = E_OUTOFMEMORY; | |
| return; | |
| } | |
| ns->depth = context->depth; | |
| ns->prefix = prefix; | |
| ns->uri = uri; | |
| ns->own_uri = own_uri; | |
| list_add_tail(&context->namespaces, &ns->entry); | |
| } | |
| static void node_dump_pop_namespaces(struct node_dump_context *context) | |
| { | |
| struct node_dump_ns *ns, *next; | |
| LIST_FOR_EACH_ENTRY_SAFE_REV(ns, next, &context->namespaces, struct node_dump_ns, entry) | |
| { | |
| if (ns->depth != context->depth) | |
| break; | |
| list_remove(&ns->entry); | |
| if (ns->own_uri) | |
| SysFreeString(ns->uri); | |
| free(ns); | |
| } | |
| } | |
| static void node_dump(struct domnode *node, struct node_dump_context *context); | |
| static void node_dump_append(struct node_dump_context *context, const WCHAR *text, unsigned int length) | |
| { | |
| ULONG written, required; | |
| if (context->status != S_OK) | |
| return; | |
| if (context->stream) | |
| { | |
| if (context->codepage == ~0u) | |
| { | |
| context->status = IStream_Write(context->stream, text, length * sizeof(WCHAR), &written); | |
| } | |
| else | |
| { | |
| /* NOTE: might be useful to buffer */ | |
| required = WideCharToMultiByte(context->codepage, 0, text, length, NULL, 0, NULL, NULL); | |
| if (!array_reserve((void **)&context->scratch.data, &context->scratch.capacity, | |
| required, sizeof(*context->scratch.data))) | |
| { | |
| context->status = E_OUTOFMEMORY; | |
| return; | |
| } | |
| WideCharToMultiByte(context->codepage, 0, text, length, context->scratch.data, required, NULL, NULL); | |
| context->status = IStream_Write(context->stream, context->scratch.data, required, &written); | |
| } | |
| } | |
| else | |
| { | |
| string_append(&context->buffer, text, length); | |
| } | |
| } | |
| static HRESULT node_get_pi_data(const struct domnode *node, BSTR *p) | |
| { | |
| struct node_dump_context context = { 0 }; | |
| bool separate = false; | |
| struct domnode *attr; | |
| if (!wcscmp(node->name, L"xml")) | |
| { | |
| string_buffer_init(&context.buffer); | |
| LIST_FOR_EACH_ENTRY(attr, &node->attributes, struct domnode, entry) | |
| { | |
| if (separate) | |
| node_dump_append(&context, L" ", 1); | |
| node_dump(attr, &context); | |
| separate = true; | |
| } | |
| return string_to_bstr(&context.buffer, p); | |
| } | |
| else | |
| { | |
| return return_bstr(node->data ? node->data : L"", p); | |
| } | |
| } | |
| HRESULT node_get_data(struct domnode *node, BSTR *p) | |
| { | |
| switch (node->type) | |
| { | |
| case NODE_TEXT: | |
| case NODE_COMMENT: | |
| case NODE_CDATA_SECTION: | |
| return return_bstr(node->data ? node->data : L"", p); | |
| case NODE_PROCESSING_INSTRUCTION: | |
| return node_get_pi_data(node, p); | |
| default: | |
| FIXME("Unhandled type %d.\n", node->type); | |
| return E_NOTIMPL; | |
| } | |
| } | |
| HRESULT node_remove_child(struct domnode *node, IXMLDOMNode *child, IXMLDOMNode **oldChild) | |
| { | |
| struct domnode *child_node; | |
| if (!child) | |
| return E_INVALIDARG; | |
| if (oldChild) | |
| *oldChild = NULL; | |
| child_node = get_node_obj(child); | |
| if (!child_node) | |
| return E_FAIL; | |
| if (child_node->parent != node) | |
| { | |
| WARN("Node %p is not a child of %p.\n", child, node); | |
| return E_INVALIDARG; | |
| } | |
| if (child_node->type == NODE_DOCUMENT_TYPE) | |
| { | |
| WARN("Can't remove doctype child.\n"); | |
| return E_FAIL; | |
| } | |
| domnode_unlink_child(child_node); | |
| if (oldChild) | |
| { | |
| IXMLDOMNode_AddRef(child); | |
| *oldChild = child; | |
| } | |
| /* Child node is never freed here, we'll always have outstanding references at this point. */ | |
| return S_OK; | |
| } | |
| static void domnode_unlink_attribute(struct domnode *attr) | |
| { | |
| struct domnode *node, *top; | |
| if (!attr->parent) | |
| return; | |
| /* Drop references for the parent and up */ | |
| node = attr->parent; | |
| while (node) | |
| { | |
| top = node; | |
| node->refcount -= attr->refcount; | |
| node = node->parent; | |
| } | |
| list_remove(&attr->entry); | |
| attr->parent = NULL; | |
| if (top->refcount == 0) | |
| domnode_destroy_tree(top); | |
| } | |
| HRESULT node_remove_attribute_node(struct domnode *node, IXMLDOMAttribute *attr, IXMLDOMAttribute **old_attr) | |
| { | |
| struct domnode *attr_node; | |
| if (!attr) | |
| return E_INVALIDARG; | |
| if (old_attr) | |
| *old_attr = NULL; | |
| attr_node = get_node_obj(attr); | |
| if (!attr_node) | |
| return E_FAIL; | |
| if (attr_node->parent != node) | |
| { | |
| WARN("Node %p is not an attribute of %p.\n", attr, node); | |
| return E_INVALIDARG; | |
| } | |
| domnode_unlink_attribute(attr_node); | |
| if (old_attr) | |
| { | |
| IXMLDOMAttribute_AddRef(attr); | |
| *old_attr = attr; | |
| } | |
| /* Attribute node is never freed here, we'll always have outstanding references at this point. */ | |
| return S_OK; | |
| } | |
| HRESULT domnode_get_attribute(const struct domnode *node, const WCHAR *name, struct domnode **attr) | |
| { | |
| struct domnode *n; | |
| /* TODO: return E_FAIL for invalid XML names */ | |
| LIST_FOR_EACH_ENTRY(n, &node->attributes, struct domnode, entry) | |
| { | |
| if (!wcscmp(n->qname, name)) | |
| { | |
| *attr = n; | |
| return S_OK; | |
| } | |
| } | |
| *attr = NULL; | |
| return S_FALSE; | |
| } | |
| static bool is_same_uri(const struct domnode *node, const WCHAR *uri) | |
| { | |
| if (node->uri) | |
| return uri && !wcscmp(node->uri, uri); | |
| return !uri; | |
| } | |
| static HRESULT domnode_get_qualified_attribute(const struct domnode *node, const WCHAR *name, | |
| const WCHAR *uri, struct domnode **attr) | |
| { | |
| struct domnode *n; | |
| LIST_FOR_EACH_ENTRY(n, &node->attributes, struct domnode, entry) | |
| { | |
| if (!wcscmp(n->name, name) && is_same_uri(n, uri)) | |
| { | |
| *attr = n; | |
| return S_OK; | |
| } | |
| } | |
| *attr = NULL; | |
| return S_FALSE; | |
| } | |
| HRESULT node_remove_attribute(struct domnode *node, const WCHAR *name, IXMLDOMNode **ret) | |
| { | |
| struct domnode *attr; | |
| HRESULT hr; | |
| if (!name) | |
| return E_INVALIDARG; | |
| if (ret) | |
| *ret = NULL; | |
| if ((hr = domnode_get_attribute(node, name, &attr)) != S_OK) | |
| return hr; | |
| if (ret) | |
| hr = create_node(attr, ret); | |
| if (SUCCEEDED(hr)) | |
| domnode_unlink_attribute(attr); | |
| return hr; | |
| } | |
| HRESULT domnode_create(DOMNodeType type, const WCHAR *name, int name_len, const WCHAR *uri, int uri_len, | |
| struct domnode *owner, struct domnode **node) | |
| { | |
| struct domnode *object; | |
| WCHAR *p; | |
| *node = NULL; | |
| if (!(object = calloc(1, sizeof(*object)))) | |
| return E_OUTOFMEMORY; | |
| list_init(&object->entry); | |
| list_init(&object->owner_entry); | |
| list_init(&object->children); | |
| list_init(&object->attributes); | |
| list_init(&object->owned); | |
| object->owner = owner; | |
| /* Document node does not have an owner */ | |
| if (owner) | |
| list_add_tail(&owner->owned, &object->owner_entry); | |
| object->qname = SysAllocStringLen(name, name_len); | |
| if (type == NODE_ELEMENT || type == NODE_ATTRIBUTE) | |
| { | |
| if (!parser_is_valid_qualified_name(object->qname)) | |
| { | |
| domnode_destroy_tree(object); | |
| return E_FAIL; | |
| } | |
| if ((p = wcschr(object->qname, ':'))) | |
| { | |
| object->prefix = SysAllocStringLen(object->qname, p - object->qname); | |
| object->name = SysAllocString(p + 1); | |
| } | |
| else | |
| { | |
| object->name = object->qname; | |
| } | |
| } | |
| else | |
| { | |
| object->name = object->qname; | |
| } | |
| if (uri_len) | |
| { | |
| if (!(object->uri = SysAllocStringLen(uri, uri_len))) | |
| { | |
| domnode_destroy_tree(object); | |
| return E_OUTOFMEMORY; | |
| } | |
| } | |
| object->type = type; | |
| switch (object->type) | |
| { | |
| case NODE_PROCESSING_INSTRUCTION: | |
| if (!wcscmp(object->name, L"xml")) | |
| object->flags |= DOMNODE_READONLY_VALUE; | |
| break; | |
| default: | |
| ; | |
| } | |
| *node = object; | |
| return S_OK; | |
| } | |
| static void domnode_insert_attribute(struct domnode *parent, struct domnode *node, struct domnode *ref_node) | |
| { | |
| domnode_unlink_attribute(node); | |
| if (ref_node) | |
| list_add_before(&ref_node->entry, &node->entry); | |
| else | |
| list_add_tail(&parent->attributes, &node->entry); | |
| node->parent = parent; | |
| domnode_add_refs(node->parent, node->refcount); | |
| domnode_set_owner(node, parent); | |
| } | |
| static HRESULT parse_xml_decl_append_attribute(struct domnode *pi, const WCHAR *name, BSTR value) | |
| { | |
| struct domnode *attribute; | |
| HRESULT hr; | |
| if (FAILED(hr = domnode_create(NODE_ATTRIBUTE, name, wcslen(name), NULL, 0, pi->owner, &attribute))) | |
| return hr; | |
| if (SUCCEEDED(hr = node_put_data(attribute, value))) | |
| domnode_insert_attribute(pi, attribute, NULL); | |
| else | |
| domnode_destroy_tree(attribute); | |
| return hr; | |
| } | |
| static HRESULT parse_xml_decl(struct domnode *pi, const WCHAR *data) | |
| { | |
| BSTR version, encoding, standalone; | |
| HRESULT hr; | |
| if (FAILED(hr = parse_xml_decl_body(data, &version, &encoding, &standalone))) | |
| return hr; | |
| hr = parse_xml_decl_append_attribute(pi, L"version", version); | |
| if (SUCCEEDED(hr) && encoding) | |
| hr = parse_xml_decl_append_attribute(pi, L"encoding", encoding); | |
| if (SUCCEEDED(hr) && standalone) | |
| hr = parse_xml_decl_append_attribute(pi, L"standalone", standalone); | |
| SysFreeString(version); | |
| SysFreeString(encoding); | |
| SysFreeString(standalone); | |
| return hr; | |
| } | |
| /* Parses declaration into attributes, public API does not allow setting data for such nodes. */ | |
| HRESULT create_pi_node(struct domnode *doc, const WCHAR *target, const WCHAR *data, IXMLDOMProcessingInstruction **ret) | |
| { | |
| struct domnode *pi; | |
| HRESULT hr; | |
| if (!ret) | |
| return E_INVALIDARG; | |
| if (!target || !*target) | |
| return E_FAIL; | |
| *ret = NULL; | |
| if (FAILED(hr = domnode_create(NODE_PROCESSING_INSTRUCTION, target, wcslen(target), NULL, 0, doc, &pi))) | |
| return hr; | |
| if (!wcscmp(target, L"xml")) | |
| hr = parse_xml_decl(pi, data); | |
| else | |
| hr = node_put_data(pi, data); | |
| if (SUCCEEDED(hr)) | |
| return create_node(pi, (IXMLDOMNode **)ret); | |
| domnode_destroy_tree(pi); | |
| return hr; | |
| } | |
| HRESULT node_append_child(struct domnode *node, IXMLDOMNode *child, IXMLDOMNode **outChild) | |
| { | |
| VARIANT var; | |
| VariantInit(&var); | |
| return node_insert_before(node, child, &var, outChild); | |
| } | |
| HRESULT node_has_childnodes(const struct domnode *node, VARIANT_BOOL *ret) | |
| { | |
| if (!ret) | |
| return E_INVALIDARG; | |
| if (list_empty(&node->children)) | |
| { | |
| *ret = VARIANT_FALSE; | |
| return S_FALSE; | |
| } | |
| *ret = VARIANT_TRUE; | |
| return S_OK; | |
| } | |
| HRESULT node_get_owner_document(const struct domnode *node, IXMLDOMDocument **doc) | |
| { | |
| IXMLDOMNode *node_obj; | |
| HRESULT hr; | |
| if (!doc) | |
| return E_INVALIDARG; | |
| if (FAILED(hr = get_node(node->owner, &node_obj))) | |
| return hr; | |
| hr = IXMLDOMNode_QueryInterface(node_obj, &IID_IXMLDOMDocument, (void **)doc); | |
| IXMLDOMNode_Release(node_obj); | |
| return hr; | |
| } | |
| struct domnode *domnode_addref(struct domnode *node) | |
| { | |
| if (node->owner) | |
| ++node->owner->refcount; | |
| domnode_add_refs(node, 1); | |
| return node; | |
| } | |
| struct domdoc_properties *domdoc_create_properties(MSXML_VERSION version) | |
| { | |
| struct domdoc_properties *properties = malloc(sizeof(*properties)); | |
| list_init(&properties->selectNsList); | |
| properties->preserving = VARIANT_FALSE; | |
| properties->validating = VARIANT_TRUE; | |
| properties->schemaCache = NULL; | |
| properties->selectNsStr = calloc(1, sizeof(xmlChar)); | |
| properties->selectNsStr_len = 0; | |
| /* properties that are dependent on object versions */ | |
| properties->version = version; | |
| properties->XPath = (version == MSXML4 || version == MSXML6); | |
| properties->prohibit_dtd = version == MSXML6; | |
| properties->normalize_attribute_values = false; | |
| properties->max_element_depth = version > MSXML3 ? 256 : 5000; | |
| /* document uri */ | |
| properties->uri = NULL; | |
| return properties; | |
| } | |
| static void domdoc_properties_destroy(struct domdoc_properties *properties) | |
| { | |
| if (properties->schemaCache) | |
| IXMLDOMSchemaCollection2_Release(properties->schemaCache); | |
| domdoc_properties_clear_selection_namespaces(&properties->selectNsList); | |
| free((xmlChar*)properties->selectNsStr); | |
| if (properties->uri) | |
| IUri_Release(properties->uri); | |
| free(properties); | |
| } | |
| static struct domdoc_properties* domdoc_properties_clone(struct domdoc_properties const* properties) | |
| { | |
| struct domdoc_properties* pcopy = malloc(sizeof(*pcopy)); | |
| select_ns_entry const* ns = NULL; | |
| select_ns_entry* new_ns = NULL; | |
| int len = (properties->selectNsStr_len+1)*sizeof(xmlChar); | |
| ptrdiff_t offset; | |
| if (pcopy) | |
| { | |
| pcopy->version = properties->version; | |
| pcopy->preserving = properties->preserving; | |
| pcopy->validating = properties->validating; | |
| pcopy->prohibit_dtd = properties->prohibit_dtd; | |
| pcopy->normalize_attribute_values = properties->normalize_attribute_values; | |
| pcopy->schemaCache = properties->schemaCache; | |
| if (pcopy->schemaCache) | |
| IXMLDOMSchemaCollection2_AddRef(pcopy->schemaCache); | |
| pcopy->XPath = properties->XPath; | |
| pcopy->max_element_depth = properties->max_element_depth; | |
| pcopy->selectNsStr_len = properties->selectNsStr_len; | |
| list_init( &pcopy->selectNsList ); | |
| pcopy->selectNsStr = malloc(len); | |
| memcpy((xmlChar*)pcopy->selectNsStr, properties->selectNsStr, len); | |
| offset = pcopy->selectNsStr - properties->selectNsStr; | |
| LIST_FOR_EACH_ENTRY( ns, (&properties->selectNsList), select_ns_entry, entry ) | |
| { | |
| new_ns = malloc(sizeof(select_ns_entry)); | |
| memcpy(new_ns, ns, sizeof(select_ns_entry)); | |
| new_ns->href += offset; | |
| new_ns->prefix += offset; | |
| list_add_tail(&pcopy->selectNsList, &new_ns->entry); | |
| } | |
| pcopy->uri = properties->uri; | |
| if (pcopy->uri) | |
| IUri_AddRef(pcopy->uri); | |
| } | |
| return pcopy; | |
| } | |
| void domnode_destroy_tree(struct domnode *tree) | |
| { | |
| struct domnode *node, *next; | |
| if (tree->type == NODE_DOCUMENT) | |
| { | |
| /* For the document nodes we can skip hierarchical calls, | |
| since all the nodes are in the owner list. */ | |
| LIST_FOR_EACH_ENTRY_SAFE(node, next, &tree->owned, struct domnode, owner_entry) | |
| { | |
| list_remove(&node->owner_entry); | |
| node->owner = NULL; | |
| list_init(&node->children); | |
| list_init(&node->attributes); | |
| domnode_destroy_tree(node); | |
| } | |
| domdoc_properties_destroy(tree->properties); | |
| tree->properties = NULL; | |
| } | |
| else | |
| { | |
| list_remove(&tree->owner_entry); | |
| LIST_FOR_EACH_ENTRY_SAFE(node, next, &tree->children, struct domnode, entry) | |
| { | |
| list_remove(&node->entry); | |
| node->parent = NULL; | |
| domnode_destroy_tree(node); | |
| } | |
| LIST_FOR_EACH_ENTRY_SAFE(node, next, &tree->attributes, struct domnode, entry) | |
| { | |
| list_remove(&node->entry); | |
| domnode_destroy_tree(node); | |
| } | |
| if (tree->type == NODE_DOCUMENT_TYPE) | |
| { | |
| parser_dtd_release(tree->dtd); | |
| tree->dtd = NULL; | |
| } | |
| } | |
| if (tree->prefix) | |
| { | |
| SysFreeString(tree->prefix); | |
| SysFreeString(tree->qname); | |
| } | |
| SysFreeString(tree->name); | |
| SysFreeString(tree->data); | |
| SysFreeString(tree->uri); | |
| free(tree); | |
| } | |
| void domnode_release(struct domnode *node) | |
| { | |
| struct domnode *top; | |
| if (node->owner) | |
| --node->owner->refcount; | |
| while (node) | |
| { | |
| top = node; | |
| --node->refcount; | |
| node = node->parent; | |
| } | |
| if (top->refcount == 0) | |
| domnode_destroy_tree(top); | |
| } | |
| /* TODO: needs to handle allocation failures */ | |
| HRESULT node_clone_domnode(struct domnode *node, bool deep, struct domnode **cloned) | |
| { | |
| struct domnode *object, *n, *child; | |
| HRESULT hr; | |
| *cloned = NULL; | |
| if (FAILED(hr = domnode_create(node->type, node->name, SysStringLen(node->name), node->uri, SysStringLen(node->uri), | |
| node->owner, &object))) | |
| { | |
| return hr; | |
| } | |
| if (node->type == NODE_DOCUMENT) | |
| object->properties = domdoc_properties_clone(node->properties); | |
| else if (node->type == NODE_DOCUMENT_TYPE) | |
| object->dtd = parser_dtd_addref(node->dtd); | |
| if (deep) | |
| { | |
| LIST_FOR_EACH_ENTRY(n, &node->children, struct domnode, entry) | |
| { | |
| node_clone_domnode(n, true, &child); | |
| domnode_append_child(object, child); | |
| } | |
| } | |
| LIST_FOR_EACH_ENTRY(n, &node->attributes, struct domnode, entry) | |
| { | |
| node_clone_domnode(n, true, &child); | |
| domnode_insert_attribute(object, child, NULL); | |
| } | |
| if (node->data) | |
| object->data = SysAllocStringLen(node->data, SysStringLen(node->data)); | |
| *cloned = object; | |
| return hr; | |
| } | |
| HRESULT node_clone(struct domnode *node, VARIANT_BOOL deep, IXMLDOMNode **out) | |
| { | |
| struct domnode *cloned_node; | |
| HRESULT hr; | |
| if (!out) | |
| return E_INVALIDARG; | |
| *out = NULL; | |
| if (FAILED(hr = node_clone_domnode(node, !!deep, &cloned_node))) | |
| return hr; | |
| if (FAILED(hr = create_node(cloned_node, out))) | |
| domnode_destroy_tree(cloned_node); | |
| return hr; | |
| } | |
| static bool is_preserving_whitespace(const struct domnode *node) | |
| { | |
| const struct domnode *doc = node->owner ? node->owner : node; | |
| struct domnode *attr; | |
| BSTR value; | |
| bool ret; | |
| /* Document-wide property */ | |
| if (doc->properties->preserving == VARIANT_TRUE) | |
| return true; | |
| if (node->type != NODE_ELEMENT) | |
| return false; | |
| while (node) | |
| { | |
| if (domnode_get_qualified_attribute(node, L"space", | |
| L"http://www.w3.org/XML/1998/namespace", &attr) == S_OK) | |
| { | |
| if (node_get_text(attr, &value) == S_OK) | |
| { | |
| ret = !wcscmp(value, L"preserve"); | |
| SysFreeString(value); | |
| return ret; | |
| } | |
| } | |
| node = node->parent; | |
| } | |
| return false; | |
| } | |
| struct node_get_text_context | |
| { | |
| struct string_buffer buffer; | |
| bool first_textual_child; | |
| bool preserve; | |
| bool ignored_ws; | |
| int depth; | |
| }; | |
| /* Trim leading and trailing white spaces */ | |
| static WCHAR *domnode_get_text_trim(WCHAR *text, UINT *length) | |
| { | |
| WCHAR *start = text; | |
| UINT len = *length; | |
| while (start && xml_is_space(*start)) | |
| { | |
| ++start; | |
| --len; | |
| } | |
| while (len && xml_is_space(start[len - 1])) | |
| --len; | |
| *length = len; | |
| return start; | |
| } | |
| static const WCHAR *domnode_get_text_trim_leading(const WCHAR *text, UINT *length) | |
| { | |
| const WCHAR *start = text; | |
| UINT len = *length; | |
| while (start && xml_is_space(*start)) | |
| { | |
| ++start; | |
| --len; | |
| } | |
| *length = len; | |
| return start; | |
| } | |
| static void domnode_get_text_trim_trailing(struct string_buffer *buffer) | |
| { | |
| while (buffer->count && xml_is_space(buffer->data[buffer->count - 1])) | |
| --buffer->count; | |
| } | |
| static void domnode_append_ignored(const struct domnode *node, unsigned int flags, struct node_get_text_context *context) | |
| { | |
| /* Skip all leading ignored space */ | |
| if (!context->buffer.count) | |
| return; | |
| if (!context->ignored_ws && (node->flags & flags)) | |
| { | |
| context->ignored_ws = true; | |
| if (!context->first_textual_child || context->preserve) | |
| { | |
| string_append(&context->buffer, L" ", 1); | |
| context->first_textual_child = false; | |
| } | |
| } | |
| } | |
| static void domnode_append_ignored_pre(struct domnode *node, struct node_get_text_context *context) | |
| { | |
| struct domnode *sibling = domnode_get_previous_sibling(node); | |
| if (sibling) | |
| { | |
| if (sibling->type == NODE_ELEMENT) | |
| return domnode_append_ignored(sibling, DOMNODE_IGNORED_WS, context); | |
| } | |
| else if (node->parent && node->parent->type == NODE_ELEMENT) | |
| { | |
| domnode_append_ignored(node, DOMNODE_IGNORED_WS_AFTER_STARTTAG, context); | |
| } | |
| } | |
| static bool is_space_data(const WCHAR *data) | |
| { | |
| while (*data) | |
| { | |
| if (!xml_is_space(*data)) return false; | |
| ++data; | |
| } | |
| return true; | |
| } | |
| static void domnode_get_text(const struct domnode *node, struct node_get_text_context *context) | |
| { | |
| struct string_buffer *buffer = &context->buffer; | |
| struct domnode *child; | |
| const WCHAR *start; | |
| UINT length; | |
| switch (node->type) | |
| { | |
| case NODE_TEXT: | |
| if (context->first_textual_child && !context->preserve) | |
| { | |
| length = SysStringLen(node->data); | |
| start = domnode_get_text_trim_leading(node->data, &length); | |
| string_append(&context->buffer, start, length); | |
| } | |
| else if (!context->preserve && is_space_data(node->data)) | |
| { | |
| string_append(&context->buffer, L" ", 1); | |
| } | |
| else | |
| { | |
| string_append(&context->buffer, node->data, SysStringLen(node->data)); | |
| } | |
| context->first_textual_child = false; | |
| context->ignored_ws = false; | |
| break; | |
| case NODE_CDATA_SECTION: | |
| string_append(buffer, node->data, SysStringLen(node->data)); | |
| context->first_textual_child = false; | |
| context->ignored_ws = false; | |
| break; | |
| case NODE_ELEMENT: | |
| LIST_FOR_EACH_ENTRY(child, &node->children, struct domnode, entry) | |
| { | |
| domnode_append_ignored_pre(child, context); | |
| if (child->type == NODE_ELEMENT) ++context->depth; | |
| domnode_get_text(child, context); | |
| if (child->type == NODE_ELEMENT) --context->depth; | |
| } | |
| break; | |
| default: | |
| ; | |
| } | |
| } | |
| static void domnode_get_attribute_value(struct domnode *attr, struct string_buffer *buffer) | |
| { | |
| struct domnode *doc = node_get_doc(attr); | |
| const struct domdoc_properties *properties = doc->properties; | |
| struct domnode *child; | |
| const WCHAR *p; | |
| if (properties->normalize_attribute_values && (attr->flags & DOMNODE_PARSED_VALUE)) | |
| { | |
| LIST_FOR_EACH_ENTRY(child, &attr->children, struct domnode, entry) | |
| { | |
| p = child->data; | |
| while (*p) | |
| { | |
| if (*p == '\n' || *p == '\t') | |
| string_append(buffer, L" ", 1); | |
| else | |
| string_append(buffer, p, 1); | |
| ++p; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| LIST_FOR_EACH_ENTRY(child, &attr->children, struct domnode, entry) | |
| { | |
| string_append(buffer, child->data, SysStringLen(child->data)); | |
| } | |
| } | |
| } | |
| HRESULT node_get_text(struct domnode *node, BSTR *text) | |
| { | |
| struct node_get_text_context context = { 0 }; | |
| struct domnode *child; | |
| const WCHAR *start; | |
| UINT length; | |
| string_buffer_init(&context.buffer); | |
| context.preserve = is_preserving_whitespace(node); | |
| switch (node->type) | |
| { | |
| case NODE_COMMENT: | |
| case NODE_ENTITY_REFERENCE: | |
| case NODE_CDATA_SECTION: | |
| return return_bstr(node->data, text); | |
| case NODE_PROCESSING_INSTRUCTION: | |
| return node_get_pi_data(node, text); | |
| case NODE_TEXT: | |
| if (context.preserve) | |
| { | |
| string_append(&context.buffer, node->data, SysStringLen(node->data)); | |
| } | |
| else | |
| { | |
| length = SysStringLen(node->data); | |
| start = domnode_get_text_trim(node->data, &length); | |
| string_append(&context.buffer, start, length); | |
| } | |
| return string_to_bstr(&context.buffer, text); | |
| case NODE_ATTRIBUTE: | |
| domnode_get_attribute_value(node, &context.buffer); | |
| return string_to_bstr(&context.buffer, text); | |
| case NODE_DOCUMENT: | |
| case NODE_DOCUMENT_FRAGMENT: | |
| context.first_textual_child = true; | |
| LIST_FOR_EACH_ENTRY(child, &node->children, struct domnode, entry) | |
| { | |
| domnode_get_text(child, &context); | |
| } | |
| if (!context.preserve) | |
| domnode_get_text_trim_trailing(&context.buffer); | |
| return string_to_bstr(&context.buffer, text); | |
| case NODE_ELEMENT: | |
| /* Trim leading spaces of the first textual node. Append everything else as is, | |
| then trim trailing spaces. */ | |
| context.first_textual_child = true; | |
| domnode_get_text(node, &context); | |
| if (!context.preserve) | |
| domnode_get_text_trim_trailing(&context.buffer); | |
| return string_to_bstr(&context.buffer, text); | |
| default: | |
| FIXME("not implemented for node type %d\n", node->type); | |
| return E_NOTIMPL; | |
| } | |
| } | |
| HRESULT node_get_preserved_text(const struct domnode *node, BSTR *text) | |
| { | |
| struct node_get_text_context context = { 0 }; | |
| string_buffer_init(&context.buffer); | |
| context.preserve = true; | |
| switch (node->type) | |
| { | |
| case NODE_ELEMENT: | |
| domnode_get_text(node, &context); | |
| return string_to_bstr(&context.buffer, text); | |
| default: | |
| FIXME("not implemented for node type %d\n", node->type); | |
| return E_NOTIMPL; | |
| } | |
| } | |
| HRESULT node_get_value(struct domnode *node, VARIANT *value) | |
| { | |
| if (!value) | |
| return E_INVALIDARG; | |
| switch (node->type) | |
| { | |
| case NODE_TEXT: | |
| case NODE_CDATA_SECTION: | |
| case NODE_ATTRIBUTE: | |
| case NODE_PROCESSING_INSTRUCTION: | |
| case NODE_COMMENT: | |
| V_VT(value) = VT_BSTR; | |
| return node_get_text(node, &V_BSTR(value)); | |
| default: | |
| FIXME("Unsupported type %d.\n", node->type); | |
| return E_NOTIMPL; | |
| } | |
| } | |
| static void node_dump_xml_text(struct domnode *node, struct node_dump_context *context) | |
| { | |
| BSTR p = node->data; | |
| while (p && *p) | |
| { | |
| if (*p == '<') | |
| node_dump_append(context, L"<", 4); | |
| else if (*p == '>') | |
| node_dump_append(context, L">", 4); | |
| else if (*p == '&') | |
| node_dump_append(context, L"&", 5); | |
| else if (*p == '\n') | |
| node_dump_append(context, L"\r\n", 2); | |
| else | |
| node_dump_append(context, p, 1); | |
| ++p; | |
| } | |
| } | |
| static void node_dump_xml_attr(struct domnode *node, struct node_dump_context *context) | |
| { | |
| BSTR p = node->data; | |
| while (p && *p) | |
| { | |
| if (*p == '<') | |
| node_dump_append(context, L"<", 4); | |
| else if (*p == '>') | |
| node_dump_append(context, L">", 4); | |
| else if (*p == '&') | |
| node_dump_append(context, L"&", 5); | |
| else if (*p == '"') | |
| node_dump_append(context, L""", 6); | |
| else | |
| node_dump_append(context, p, 1); | |
| ++p; | |
| } | |
| } | |
| static void node_dump_xml(struct domnode *node, struct node_dump_context *context) | |
| { | |
| BSTR p = node->data; | |
| while (p && *p) | |
| { | |
| if (*p == '\n') | |
| node_dump_append(context, L"\r\n", 2); | |
| else | |
| node_dump_append(context, p, 1); | |
| ++p; | |
| } | |
| } | |
| static void node_dump_pi(struct domnode *node, struct node_dump_context *context) | |
| { | |
| VARIANT value; | |
| node_dump_append(context, L"<?", 2); | |
| node_dump_append(context, node->name, SysStringLen(node->name)); | |
| if (!wcscmp(node->name, L"xml")) | |
| { | |
| /* Make sure attributes are in correct order. */ | |
| node_dump_append(context, L" version=\"", 10); | |
| if (node_get_attribute_value(node, L"version", &value) == S_OK) | |
| { | |
| node_dump_append(context, V_BSTR(&value), SysStringLen(V_BSTR(&value))); | |
| VariantClear(&value); | |
| } | |
| else | |
| { | |
| node_dump_append(context, L"1.0", 3); | |
| } | |
| node_dump_append(context, L"\"", 1); | |
| if (node_get_attribute_value(node, L"encoding", &value) == S_OK) | |
| { | |
| if ((context->only_utf16_encoding_decl && !wcsicmp(V_BSTR(&value), L"UTF-16")) | |
| || !context->only_utf16_encoding_decl) | |
| { | |
| node_dump_append(context, L" encoding=\"", 11); | |
| node_dump_append(context, V_BSTR(&value), SysStringLen(V_BSTR(&value))); | |
| node_dump_append(context, L"\"", 1); | |
| } | |
| VariantClear(&value); | |
| } | |
| if (node_get_attribute_value(node, L"standalone", &value) == S_OK) | |
| { | |
| node_dump_append(context, L" standalone=\"", 13); | |
| node_dump_append(context, V_BSTR(&value), SysStringLen(V_BSTR(&value))); | |
| node_dump_append(context, L"\"", 1); | |
| VariantClear(&value); | |
| } | |
| } | |
| else | |
| { | |
| node_dump_append(context, L" ", 1); | |
| node_dump_xml(node, context); | |
| } | |
| node_dump_append(context, L"?>", 2); | |
| } | |
| static void node_dump_format(struct node_dump_context *context) | |
| { | |
| unsigned int indent = context->indent; | |
| if (!context->needs_formatting) | |
| return; | |
| node_dump_append(context, L"\r\n", 2); | |
| while (indent--) | |
| { | |
| node_dump_append(context, L"\t", 1); | |
| } | |
| } | |
| static void node_dump_qualified_name(struct node_dump_context *context, struct domnode *node) | |
| { | |
| node_dump_append(context, node->qname, SysStringLen(node->qname)); | |
| } | |
| static bool is_same_namespace_prefix(const struct domnode *node, const WCHAR *prefix) | |
| { | |
| if (node->prefix) | |
| return prefix && !wcscmp(node->prefix, prefix); | |
| return !prefix; | |
| } | |
| static bool is_namespace_definition(struct domnode *node) | |
| { | |
| if (!wcscmp(node->qname, L"xmlns")) | |
| return true; | |
| if (is_same_namespace_prefix(node, L"xmlns")) | |
| return true; | |
| return false; | |
| } | |
| static bool is_namespace_defined(struct node_dump_context *context, struct domnode *node) | |
| { | |
| struct node_dump_ns *ns; | |
| if (!node->uri) | |
| return true; | |
| /* The xmlns:xml namespace is predefined. */ | |
| if (is_same_namespace_prefix(node, L"xml")) | |
| return true; | |
| LIST_FOR_EACH_ENTRY_REV(ns, &context->namespaces, struct node_dump_ns, entry) | |
| { | |
| if (is_same_namespace_prefix(node, ns->prefix)) | |
| return !wcscmp(node->uri, ns->uri); | |
| } | |
| return false; | |
| } | |
| static void node_dump_namespace_for_node(struct node_dump_context *context, struct domnode *node) | |
| { | |
| node_dump_push_namespace(context, node->prefix, node->uri, false); | |
| node_dump_append(context, L" xmlns", 6); | |
| if (node->prefix) | |
| { | |
| node_dump_append(context, L":", 1); | |
| node_dump_append(context, node->prefix, SysStringLen(node->prefix)); | |
| } | |
| node_dump_append(context, L"=\"", 2); | |
| node_dump_append(context, node->uri, SysStringLen(node->uri)); | |
| node_dump_append(context, L"\"", 1); | |
| } | |
| static WCHAR *node_dump_get_namespace_prefix(struct domnode *attr) | |
| { | |
| return attr->prefix ? attr->name : NULL; | |
| } | |
| static void node_dump_element_attributes(struct node_dump_context *context, struct domnode *node) | |
| { | |
| struct domnode *attr; | |
| BSTR text; | |
| /* Collect explicitly defined namespaces */ | |
| LIST_FOR_EACH_ENTRY(attr, &node->attributes, struct domnode, entry) | |
| { | |
| if (is_namespace_definition(attr)) | |
| { | |
| node_get_text(attr, &text); | |
| node_dump_push_namespace(context, node_dump_get_namespace_prefix(attr), text, true); | |
| } | |
| } | |
| /* If namespace is implied, check if it's been defined already. This needs to be done for | |
| the element too. */ | |
| if (!is_namespace_defined(context, node)) | |
| node_dump_namespace_for_node(context, node); | |
| LIST_FOR_EACH_ENTRY(attr, &node->attributes, struct domnode, entry) | |
| { | |
| if (is_namespace_definition(attr)) | |
| continue; | |
| if (!is_namespace_defined(context, attr)) | |
| node_dump_namespace_for_node(context, attr); | |
| } | |
| LIST_FOR_EACH_ENTRY(attr, &node->attributes, struct domnode, entry) | |
| { | |
| node_dump_append(context, L" ", 1); | |
| node_dump(attr, context); | |
| } | |
| } | |
| static void node_dump(struct domnode *node, struct node_dump_context *context) | |
| { | |
| struct domnode *n; | |
| switch (node->type) | |
| { | |
| case NODE_COMMENT: | |
| node_dump_format(context); | |
| node_dump_append(context, L"<!--", 4); | |
| node_dump_xml(node, context); | |
| node_dump_append(context, L"-->", 3); | |
| context->needs_formatting = node->flags & DOMNODE_IGNORED_WS; | |
| break; | |
| case NODE_PROCESSING_INSTRUCTION: | |
| node_dump_format(context); | |
| node_dump_pi(node, context); | |
| context->needs_formatting = node->flags & DOMNODE_IGNORED_WS; | |
| break; | |
| case NODE_TEXT: | |
| node_dump_xml_text(node, context); | |
| context->needs_formatting = false; | |
| break; | |
| case NODE_CDATA_SECTION: | |
| node_dump_format(context); | |
| node_dump_append(context, L"<![CDATA[", 9); | |
| node_dump_xml(node, context); | |
| node_dump_append(context, L"]]>", 3); | |
| context->needs_formatting = node->flags & DOMNODE_IGNORED_WS; | |
| break; | |
| case NODE_ELEMENT: | |
| node_dump_format(context); | |
| node_dump_append(context, L"<", 1); | |
| node_dump_qualified_name(context, node); | |
| node_dump_element_attributes(context, node); | |
| if (list_empty(&node->children)) | |
| { | |
| node_dump_append(context, L"/>", 2); | |
| } | |
| else | |
| { | |
| node_dump_append(context, L">", 1); | |
| ++context->depth; | |
| ++context->indent; | |
| context->needs_formatting = node->flags & DOMNODE_IGNORED_WS_AFTER_STARTTAG; | |
| LIST_FOR_EACH_ENTRY(n, &node->children, struct domnode, entry) | |
| { | |
| node_dump(n, context); | |
| } | |
| --context->indent; | |
| --context->depth; | |
| node_dump_format(context); | |
| node_dump_append(context, L"</", 2); | |
| node_dump_qualified_name(context, node); | |
| node_dump_append(context, L">", 1); | |
| } | |
| node_dump_pop_namespaces(context); | |
| context->needs_formatting = node->flags & DOMNODE_IGNORED_WS; | |
| break; | |
| case NODE_DOCUMENT_FRAGMENT: | |
| LIST_FOR_EACH_ENTRY(n, &node->children, struct domnode, entry) | |
| { | |
| node_dump(n, context); | |
| } | |
| break; | |
| case NODE_ATTRIBUTE: | |
| node_dump_qualified_name(context, node); | |
| node_dump_append(context, L"=\"", 2); | |
| LIST_FOR_EACH_ENTRY(n, &node->children, struct domnode, entry) | |
| { | |
| node_dump_xml_attr(n, context); | |
| } | |
| node_dump_append(context, L"\"", 1); | |
| break; | |
| case NODE_ENTITY_REFERENCE: | |
| node_dump_append(context, L"&", 1); | |
| node_dump_append(context, node->name, SysStringLen(node->name)); | |
| node_dump_append(context, L";", 1); | |
| break; | |
| case NODE_DOCUMENT_TYPE: | |
| node_dump_append(context, node->data, SysStringLen(node->data)); | |
| break; | |
| default: | |
| FIXME("Unimplemented for type %d.\n", node->type); | |
| context->status = E_NOTIMPL; | |
| break; | |
| } | |
| } | |
| static void node_dump_context_init(struct node_dump_context *context, UINT codepage, IStream *stream) | |
| { | |
| memset(context, 0, sizeof(*context)); | |
| string_buffer_init(&context->buffer); | |
| list_init(&context->namespaces); | |
| context->buffer.status = &context->status; | |
| context->codepage = codepage; | |
| context->stream = stream; | |
| } | |
| static HRESULT node_dump_context_cleanup(struct node_dump_context *context) | |
| { | |
| HRESULT status = context->status; | |
| free(context->scratch.data); | |
| return status; | |
| } | |
| HRESULT node_get_xml(struct domnode *node, BSTR *ret) | |
| { | |
| struct node_dump_context context; | |
| struct domnode *n; | |
| if (!ret) | |
| return E_INVALIDARG; | |
| node_dump_context_init(&context, ~0u, NULL); | |
| context.only_utf16_encoding_decl = true; | |
| if (node->type == NODE_DOCUMENT) | |
| { | |
| LIST_FOR_EACH_ENTRY(n, &node->children, struct domnode, entry) | |
| { | |
| node_dump(n, &context); | |
| node_dump_append(&context, L"\r\n", 2); | |
| } | |
| } | |
| else | |
| { | |
| node_dump(node, &context); | |
| } | |
| return string_to_bstr(&context.buffer, ret); | |
| } | |
| /* duplicates xmlBufferWriteQuotedString() logic */ | |
| static void xml_write_quotedstring(xmlOutputBufferPtr buf, const xmlChar *string) | |
| { | |
| const xmlChar *cur, *base; | |
| if (xmlStrchr(string, '\"')) | |
| { | |
| if (xmlStrchr(string, '\'')) | |
| { | |
| xmlOutputBufferWrite(buf, 1, "\""); | |
| base = cur = string; | |
| while (*cur) | |
| { | |
| if (*cur == '"') | |
| { | |
| if (base != cur) | |
| xmlOutputBufferWrite(buf, cur-base, (const char*)base); | |
| xmlOutputBufferWrite(buf, 6, """); | |
| cur++; | |
| base = cur; | |
| } | |
| else | |
| cur++; | |
| } | |
| if (base != cur) | |
| xmlOutputBufferWrite(buf, cur-base, (const char*)base); | |
| xmlOutputBufferWrite(buf, 1, "\""); | |
| } | |
| else | |
| { | |
| xmlOutputBufferWrite(buf, 1, "\'"); | |
| xmlOutputBufferWriteString(buf, (const char*)string); | |
| xmlOutputBufferWrite(buf, 1, "\'"); | |
| } | |
| } | |
| else | |
| { | |
| xmlOutputBufferWrite(buf, 1, "\""); | |
| xmlOutputBufferWriteString(buf, (const char*)string); | |
| xmlOutputBufferWrite(buf, 1, "\""); | |
| } | |
| } | |
| static int XMLCALL transform_to_stream_write(void *context, const char *buffer, int len) | |
| { | |
| DWORD written; | |
| HRESULT hr = ISequentialStream_Write((ISequentialStream *)context, buffer, len, &written); | |
| return hr == S_OK ? written : -1; | |
| } | |
| /* Output for method "text" */ | |
| static void transform_write_text(xmlDocPtr result, xsltStylesheetPtr style, xmlOutputBufferPtr output) | |
| { | |
| xmlNodePtr cur = result->children; | |
| while (cur) | |
| { | |
| if (cur->type == XML_TEXT_NODE) | |
| xmlOutputBufferWriteString(output, (const char*)cur->content); | |
| /* skip to next node */ | |
| if (cur->children) | |
| { | |
| if ((cur->children->type != XML_ENTITY_DECL) && | |
| (cur->children->type != XML_ENTITY_REF_NODE) && | |
| (cur->children->type != XML_ENTITY_NODE)) | |
| { | |
| cur = cur->children; | |
| continue; | |
| } | |
| } | |
| if (cur->next) { | |
| cur = cur->next; | |
| continue; | |
| } | |
| do | |
| { | |
| cur = cur->parent; | |
| if (cur == NULL) | |
| break; | |
| if (cur == (xmlNodePtr) style->doc) { | |
| cur = NULL; | |
| break; | |
| } | |
| if (cur->next) { | |
| cur = cur->next; | |
| break; | |
| } | |
| } while (cur); | |
| } | |
| } | |
| #undef XSLT_GET_IMPORT_PTR | |
| #define XSLT_GET_IMPORT_PTR(res, style, name) { \ | |
| xsltStylesheetPtr st = style; \ | |
| res = NULL; \ | |
| while (st != NULL) { \ | |
| if (st->name != NULL) { res = st->name; break; } \ | |
| st = xsltNextImport(st); \ | |
| }} | |
| #undef XSLT_GET_IMPORT_INT | |
| #define XSLT_GET_IMPORT_INT(res, style, name) { \ | |
| xsltStylesheetPtr st = style; \ | |
| res = -1; \ | |
| while (st != NULL) { \ | |
| if (st->name != -1) { res = st->name; break; } \ | |
| st = xsltNextImport(st); \ | |
| }} | |
| static void transform_write_xmldecl(xmlDocPtr result, xsltStylesheetPtr style, BOOL omit_encoding, xmlOutputBufferPtr output) | |
| { | |
| int omit_xmldecl, standalone; | |
| XSLT_GET_IMPORT_INT(omit_xmldecl, style, omitXmlDeclaration); | |
| if (omit_xmldecl == 1) return; | |
| XSLT_GET_IMPORT_INT(standalone, style, standalone); | |
| xmlOutputBufferWriteString(output, "<?xml version="); | |
| if (result->version) | |
| { | |
| xmlOutputBufferWriteString(output, "\""); | |
| xmlOutputBufferWriteString(output, (const char *)result->version); | |
| xmlOutputBufferWriteString(output, "\""); | |
| } | |
| else | |
| xmlOutputBufferWriteString(output, "\"1.0\""); | |
| if (!omit_encoding) | |
| { | |
| const xmlChar *encoding; | |
| /* default encoding is UTF-16 */ | |
| XSLT_GET_IMPORT_PTR(encoding, style, encoding); | |
| xmlOutputBufferWriteString(output, " encoding="); | |
| xmlOutputBufferWriteString(output, "\""); | |
| xmlOutputBufferWriteString(output, encoding ? (const char *)encoding : "UTF-16"); | |
| xmlOutputBufferWriteString(output, "\""); | |
| } | |
| /* standalone attribute */ | |
| if (standalone != -1) | |
| xmlOutputBufferWriteString(output, standalone == 0 ? " standalone=\"no\"" : " standalone=\"yes\""); | |
| xmlOutputBufferWriteString(output, "?>"); | |
| } | |
| static void htmldtd_dumpcontent(xmlOutputBufferPtr buf, xmlDocPtr doc) | |
| { | |
| xmlDtdPtr cur = doc->intSubset; | |
| xmlOutputBufferWriteString(buf, "<!DOCTYPE "); | |
| xmlOutputBufferWriteString(buf, (const char *)cur->name); | |
| if (cur->ExternalID) | |
| { | |
| xmlOutputBufferWriteString(buf, " PUBLIC "); | |
| xml_write_quotedstring(buf, cur->ExternalID); | |
| if (cur->SystemID) | |
| { | |
| xmlOutputBufferWriteString(buf, " "); | |
| xml_write_quotedstring(buf, cur->SystemID); | |
| } | |
| } | |
| else if (cur->SystemID) | |
| { | |
| xmlOutputBufferWriteString(buf, " SYSTEM "); | |
| xml_write_quotedstring(buf, cur->SystemID); | |
| } | |
| xmlOutputBufferWriteString(buf, ">\n"); | |
| } | |
| /* Duplicates htmlDocContentDumpFormatOutput() the way we need it - doesn't add trailing newline. */ | |
| static void htmldoc_dumpcontent(xmlOutputBufferPtr buf, xmlDocPtr doc, const char *encoding, int format) | |
| { | |
| xmlElementType type; | |
| /* force HTML output */ | |
| type = doc->type; | |
| doc->type = XML_HTML_DOCUMENT_NODE; | |
| if (doc->intSubset) | |
| htmldtd_dumpcontent(buf, doc); | |
| if (doc->children) { | |
| xmlNodePtr cur = doc->children; | |
| while (cur) { | |
| htmlNodeDumpFormatOutput(buf, doc, cur, encoding, format); | |
| cur = cur->next; | |
| } | |
| } | |
| doc->type = type; | |
| } | |
| static inline BOOL transform_is_empty_resultdoc(xmlDocPtr result) | |
| { | |
| return !result->children || ((result->children->type == XML_DTD_NODE) && !result->children->next); | |
| } | |
| static inline BOOL transform_is_valid_method(xsltStylesheetPtr style) | |
| { | |
| return !style->methodURI || !(style->method && xmlStrEqual(style->method, (const xmlChar *)"xhtml")); | |
| } | |
| /* Helper to write transformation result to specified output buffer. */ | |
| static HRESULT node_transform_write(xsltStylesheetPtr style, xmlDocPtr result, BOOL omit_encoding, const char *encoding, xmlOutputBufferPtr output) | |
| { | |
| const xmlChar *method; | |
| int indent; | |
| if (!transform_is_valid_method(style)) | |
| { | |
| ERR("unknown output method\n"); | |
| return E_FAIL; | |
| } | |
| XSLT_GET_IMPORT_PTR(method, style, method) | |
| XSLT_GET_IMPORT_INT(indent, style, indent); | |
| if (!method && (result->type == XML_HTML_DOCUMENT_NODE)) | |
| method = (const xmlChar *) "html"; | |
| if (method && xmlStrEqual(method, (const xmlChar *)"html")) | |
| { | |
| htmlSetMetaEncoding(result, (const xmlChar *)encoding); | |
| if (indent == -1) | |
| indent = 1; | |
| htmldoc_dumpcontent(output, result, encoding, indent); | |
| } | |
| else if (method && xmlStrEqual(method, (const xmlChar *)"xhtml")) | |
| { | |
| htmlSetMetaEncoding(result, (const xmlChar *) encoding); | |
| htmlDocContentDumpOutput(output, result, encoding); | |
| } | |
| else if (method && xmlStrEqual(method, (const xmlChar *)"text")) | |
| transform_write_text(result, style, output); | |
| else | |
| { | |
| transform_write_xmldecl(result, style, omit_encoding, output); | |
| if (result->children) | |
| { | |
| xmlNodePtr child = result->children; | |
| while (child) | |
| { | |
| xmlNodeDumpOutput(output, result, child, 0, indent == 1, encoding); | |
| if (indent && ((child->type == XML_DTD_NODE) || ((child->type == XML_COMMENT_NODE) && child->next))) | |
| xmlOutputBufferWriteString(output, "\r\n"); | |
| child = child->next; | |
| } | |
| } | |
| } | |
| xmlOutputBufferFlush(output); | |
| return S_OK; | |
| } | |
| /* For BSTR output is always UTF-16, without 'encoding' attribute */ | |
| static HRESULT node_transform_write_to_bstr(xsltStylesheetPtr style, xmlDocPtr result, BSTR *str) | |
| { | |
| HRESULT hr = S_OK; | |
| if (transform_is_empty_resultdoc(result)) | |
| *str = SysAllocStringLen(NULL, 0); | |
| else | |
| { | |
| xmlOutputBufferPtr output = xmlAllocOutputBuffer(xmlFindCharEncodingHandler("UTF-16")); | |
| const xmlChar *content; | |
| size_t len; | |
| *str = NULL; | |
| if (!output) | |
| return E_OUTOFMEMORY; | |
| hr = node_transform_write(style, result, TRUE, "UTF-16", output); | |
| content = xmlBufContent(output->conv); | |
| len = xmlBufUse(output->conv); | |
| /* UTF-16 encoder places UTF-16 bom, we don't need it for BSTR */ | |
| content += sizeof(WCHAR); | |
| *str = SysAllocStringLen((WCHAR*)content, len/sizeof(WCHAR) - 1); | |
| xmlOutputBufferClose(output); | |
| } | |
| return *str ? hr : E_OUTOFMEMORY; | |
| } | |
| static HRESULT node_transform_write_to_stream(xsltStylesheetPtr style, xmlDocPtr result, ISequentialStream *stream) | |
| { | |
| static const xmlChar *utf16 = (const xmlChar*)"UTF-16"; | |
| xmlOutputBufferPtr output; | |
| const xmlChar *encoding; | |
| HRESULT hr; | |
| if (transform_is_empty_resultdoc(result)) | |
| { | |
| WARN("empty result document\n"); | |
| return S_OK; | |
| } | |
| if (style->methodURI && (!style->method || !xmlStrEqual(style->method, (const xmlChar *) "xhtml"))) | |
| { | |
| ERR("unknown output method\n"); | |
| return E_FAIL; | |
| } | |
| /* default encoding is UTF-16 */ | |
| XSLT_GET_IMPORT_PTR(encoding, style, encoding); | |
| if (!encoding) | |
| encoding = utf16; | |
| output = xmlOutputBufferCreateIO(transform_to_stream_write, NULL, stream, xmlFindCharEncodingHandler((const char*)encoding)); | |
| if (!output) | |
| return E_OUTOFMEMORY; | |
| hr = node_transform_write(style, result, FALSE, (const char*)encoding, output); | |
| xmlOutputBufferClose(output); | |
| return hr; | |
| } | |
| struct import_buffer | |
| { | |
| char *data; | |
| int cur; | |
| int len; | |
| }; | |
| static int XMLCALL import_loader_io_read(void *context, char *out, int len) | |
| { | |
| struct import_buffer *buffer = (struct import_buffer *)context; | |
| TRACE("%p, %p, %d\n", context, out, len); | |
| if (buffer->cur == buffer->len) | |
| return 0; | |
| len = min(len, buffer->len - buffer->cur); | |
| memcpy(out, &buffer->data[buffer->cur], len); | |
| buffer->cur += len; | |
| TRACE("read %d\n", len); | |
| return len; | |
| } | |
| static int XMLCALL import_loader_io_close(void * context) | |
| { | |
| struct import_buffer *buffer = (struct import_buffer *)context; | |
| TRACE("%p\n", context); | |
| free(buffer->data); | |
| free(buffer); | |
| return 0; | |
| } | |
| static HRESULT import_loader_onDataAvailable(void *ctxt, char *ptr, DWORD len) | |
| { | |
| xmlParserInputPtr *input = (xmlParserInputPtr *)ctxt; | |
| xmlParserInputBufferPtr inputbuffer; | |
| struct import_buffer *buffer; | |
| buffer = malloc(sizeof(*buffer)); | |
| buffer->data = malloc(len); | |
| memcpy(buffer->data, ptr, len); | |
| buffer->cur = 0; | |
| buffer->len = len; | |
| inputbuffer = xmlParserInputBufferCreateIO(import_loader_io_read, import_loader_io_close, buffer, | |
| XML_CHAR_ENCODING_NONE); | |
| *input = xmlNewIOInputStream(NULL, inputbuffer, XML_CHAR_ENCODING_NONE); | |
| if (!*input) | |
| xmlFreeParserInputBuffer(inputbuffer); | |
| return *input ? S_OK : E_FAIL; | |
| } | |
| static HRESULT xslt_doc_get_uri(const xmlChar *uri, void *_ctxt, xsltLoadType type, IUri **doc_uri) | |
| { | |
| xsltStylesheetPtr style = (xsltStylesheetPtr)_ctxt; | |
| IUri *href_uri; | |
| HRESULT hr; | |
| BSTR uriW; | |
| *doc_uri = NULL; | |
| uriW = bstr_from_xmlChar(uri); | |
| hr = CreateUri(uriW, Uri_CREATE_ALLOW_RELATIVE | Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME, 0, &href_uri); | |
| SysFreeString(uriW); | |
| if (FAILED(hr)) | |
| { | |
| WARN("Failed to create href uri, %#lx.\n", hr); | |
| return hr; | |
| } | |
| if (type == XSLT_LOAD_STYLESHEET && style->doc && style->doc->name) | |
| { | |
| IUri *base_uri; | |
| BSTR baseuriW; | |
| baseuriW = bstr_from_xmlChar((xmlChar *)style->doc->name); | |
| hr = CreateUri(baseuriW, Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME, 0, &base_uri); | |
| SysFreeString(baseuriW); | |
| if (FAILED(hr)) | |
| { | |
| WARN("Failed to create base uri, %#lx.\n", hr); | |
| return hr; | |
| } | |
| hr = CoInternetCombineIUri(base_uri, href_uri, 0, doc_uri, 0); | |
| IUri_Release(base_uri); | |
| if (FAILED(hr)) | |
| WARN("Failed to combine uris, hr %#lx.\n", hr); | |
| } | |
| else | |
| { | |
| *doc_uri = href_uri; | |
| IUri_AddRef(*doc_uri); | |
| } | |
| IUri_Release(href_uri); | |
| return hr; | |
| } | |
| xmlDocPtr xslt_doc_default_loader(const xmlChar *uri, xmlDictPtr dict, int options, | |
| void *_ctxt, xsltLoadType type) | |
| { | |
| IUri *import_uri = NULL; | |
| xmlParserInputPtr input; | |
| xmlParserCtxtPtr pctxt; | |
| xmlDocPtr doc = NULL; | |
| IMoniker *moniker; | |
| HRESULT hr; | |
| bsc_t *bsc; | |
| BSTR uriW; | |
| TRACE("%s, %p, %#x, %p, %d\n", debugstr_a((const char *)uri), dict, options, _ctxt, type); | |
| pctxt = xmlNewParserCtxt(); | |
| if (!pctxt) | |
| return NULL; | |
| if (dict && pctxt->dict) | |
| { | |
| xmlDictFree(pctxt->dict); | |
| pctxt->dict = NULL; | |
| } | |
| if (dict) | |
| { | |
| pctxt->dict = dict; | |
| xmlDictReference(pctxt->dict); | |
| } | |
| xmlCtxtUseOptions(pctxt, options); | |
| hr = xslt_doc_get_uri(uri, _ctxt, type, &import_uri); | |
| if (FAILED(hr)) | |
| goto failed; | |
| hr = CreateURLMonikerEx2(NULL, import_uri, &moniker, 0); | |
| if (FAILED(hr)) | |
| goto failed; | |
| hr = bind_url(moniker, import_loader_onDataAvailable, &input, &bsc); | |
| IMoniker_Release(moniker); | |
| if (FAILED(hr)) | |
| goto failed; | |
| if (FAILED(detach_bsc(bsc))) | |
| goto failed; | |
| if (!input) | |
| goto failed; | |
| inputPush(pctxt, input); | |
| xmlParseDocument(pctxt); | |
| if (pctxt->wellFormed) | |
| { | |
| doc = pctxt->myDoc; | |
| /* Set imported uri, to give nested imports a chance. */ | |
| if (IUri_GetPropertyBSTR(import_uri, Uri_PROPERTY_ABSOLUTE_URI, &uriW, 0) == S_OK) | |
| { | |
| doc->name = (char *)xmlchar_from_wcharn(uriW, SysStringLen(uriW), TRUE); | |
| SysFreeString(uriW); | |
| } | |
| } | |
| else | |
| { | |
| doc = NULL; | |
| xmlFreeDoc(pctxt->myDoc); | |
| pctxt->myDoc = NULL; | |
| } | |
| failed: | |
| xmlFreeParserCtxt(pctxt); | |
| if (import_uri) | |
| IUri_Release(import_uri); | |
| return doc; | |
| } | |
| #ifdef _WIN64 | |
| #define IActiveScriptParse_Release IActiveScriptParse64_Release | |
| #define IActiveScriptParse_InitNew IActiveScriptParse64_InitNew | |
| #define IActiveScriptParse_ParseScriptText IActiveScriptParse64_ParseScriptText | |
| #else | |
| #define IActiveScriptParse_Release IActiveScriptParse32_Release | |
| #define IActiveScriptParse_InitNew IActiveScriptParse32_InitNew | |
| #define IActiveScriptParse_ParseScriptText IActiveScriptParse32_ParseScriptText | |
| #endif | |
| struct xsl_scripts | |
| { | |
| struct | |
| { | |
| IActiveScript *script; | |
| const xmlChar *uri; | |
| } *entries; | |
| size_t count; | |
| size_t capacity; | |
| }; | |
| static IActiveScript *xpath_msxsl_script_get_script(xmlXPathParserContextPtr ctxt, const xmlChar *uri) | |
| { | |
| xsltTransformContextPtr xslt_ctxt = xsltXPathGetTransformContext(ctxt); | |
| struct xsl_scripts *scripts = xslt_ctxt->userData; | |
| for (size_t i = 0; i < scripts->count; ++i) | |
| { | |
| if (xmlStrEqual(scripts->entries[i].uri, uri)) | |
| return scripts->entries[i].script; | |
| } | |
| return NULL; | |
| } | |
| static HRESULT xpath_create_nodelist_from_set(xmlNodeSetPtr set, bool needs_copy, IXMLDOMNodeList **ret) | |
| { | |
| xmlNodeSet _copy = { 0 }; | |
| xmlXPathObjectPtr obj; | |
| HRESULT hr; | |
| if (needs_copy) | |
| { | |
| _copy.nodeTab = xmlMalloc(set->nodeNr * sizeof(*set->nodeTab)); | |
| _copy.nodeNr = set->nodeNr; | |
| _copy.nodeMax = set->nodeNr; | |
| for (int i = 0; i < set->nodeNr; ++i) | |
| _copy.nodeTab[i] = xmlCopyNode(set->nodeTab[i], 1); | |
| set = &_copy; | |
| } | |
| obj = xmlXPathNewNodeSetList(set); | |
| hr = create_selection_from_nodeset(obj, ret); | |
| xmlFree(_copy.nodeTab); | |
| return hr; | |
| } | |
| static void xpath_msxsl_script_function(xmlXPathParserContextPtr ctxt, int nargs) | |
| { | |
| xsltTransformContextPtr xslt_ctxt = xsltXPathGetTransformContext(ctxt); | |
| xmlXPathContextPtr xpath = ((struct _xsltTransformContext *)xslt_ctxt)->xpathCtxt; | |
| DISPPARAMS params = { 0 }; | |
| IActiveScript *script; | |
| VARIANT *args = NULL; | |
| IDispatch *disp; | |
| VARIANT result; | |
| DISPID dispid; | |
| HRESULT hr; | |
| BSTR name; | |
| TRACE("%s:%s\n", xpath->functionURI, xpath->function); | |
| script = xpath_msxsl_script_get_script(ctxt, xpath->functionURI); | |
| if (!script) | |
| { | |
| WARN("Couldn't find a script for %s.\n", xpath->functionURI); | |
| return; | |
| } | |
| if (FAILED(IActiveScript_GetScriptDispatch(script, NULL, &disp))) | |
| return; | |
| name = bstr_from_xmlChar(xpath->function); | |
| hr = IDispatch_GetIDsOfNames(disp, &IID_NULL, &name, 1, 0, &dispid); | |
| SysFreeString(name); | |
| if (FAILED(hr)) | |
| { | |
| WARN("Couldn't find %s function.\n", xpath->function); | |
| IDispatch_Release(disp); | |
| return; | |
| } | |
| if (nargs) | |
| { | |
| args = calloc(nargs, sizeof(*args)); | |
| for (int i = 0; i < nargs; ++i) | |
| { | |
| xmlXPathObjectType obj_type = XPATH_UNDEFINED; | |
| xmlXPathObjectPtr obj = NULL; | |
| xmlChar *s; | |
| /* Since we don't want to impose expectations on argument types, | |
| first inspect actual type on stack, and then pop with corresponding function. */ | |
| if (ctxt->valueNr > 0) | |
| { | |
| obj = ctxt->valueTab[ctxt->valueNr - 1]; | |
| obj_type = obj->type; | |
| } | |
| switch (obj_type) | |
| { | |
| case XPATH_XSLT_TREE: | |
| case XPATH_NODESET: | |
| { | |
| xmlNodeSetPtr nodeset = xmlXPathPopNodeSet(ctxt); | |
| IXMLDOMNodeList *nodelist; | |
| hr = xpath_create_nodelist_from_set(nodeset, obj_type == XPATH_XSLT_TREE, &nodelist); | |
| V_VT(&args[i]) = VT_DISPATCH; | |
| V_DISPATCH(&args[i]) = (IDispatch *)nodelist; | |
| break; | |
| } | |
| case XPATH_STRING: | |
| s = xmlXPathPopString(ctxt); | |
| V_VT(&args[i]) = VT_BSTR; | |
| V_BSTR(&args[i]) = bstr_from_xmlChar(s); | |
| xmlFree(s); | |
| break; | |
| case XPATH_NUMBER: | |
| V_VT(&args[i]) = VT_R8; | |
| V_R8(&args[i]) = xmlXPathPopNumber(ctxt); | |
| break; | |
| case XPATH_BOOLEAN: | |
| V_VT(&args[i]) = VT_BOOL; | |
| V_BOOL(&args[i]) = xmlXPathPopBoolean(ctxt) ? VARIANT_TRUE : VARIANT_FALSE; | |
| break; | |
| default: | |
| FIXME("Unexpected XPath (%s) value type %d.\n", xpath->function, obj->type); | |
| return; | |
| } | |
| } | |
| params.rgvarg = args; | |
| params.cArgs = nargs; | |
| } | |
| VariantInit(&result); | |
| hr = IDispatch_Invoke(disp, dispid, &IID_NULL, 0, DISPATCH_METHOD, ¶ms, &result, NULL, NULL); | |
| if (FAILED(hr)) | |
| WARN("User script Invoke() failed %#lx, function %s.\n", hr, xpath->function); | |
| if (args) | |
| { | |
| for (int i = 0; i < nargs; ++i) | |
| VariantClear(&args[i]); | |
| free(args); | |
| } | |
| switch (V_VT(&result)) | |
| { | |
| case VT_BSTR: | |
| { | |
| xmlChar *s = xmlchar_from_wcharn(V_BSTR(&result), -1, TRUE); | |
| xmlXPathReturnString(ctxt, s); | |
| break; | |
| } | |
| case VT_BOOL: | |
| xmlXPathReturnBoolean(ctxt, !!V_BOOL(&result)); | |
| break; | |
| case VT_INT: | |
| case VT_I1: | |
| case VT_UI1: | |
| case VT_I2: | |
| case VT_UI2: | |
| case VT_I4: | |
| case VT_UI4: | |
| case VT_R4: | |
| case VT_R8: | |
| VariantChangeType(&result, &result, 0, VT_R8); | |
| xmlXPathReturnNumber(ctxt, V_R8(&result)); | |
| break; | |
| case VT_EMPTY: | |
| xmlXPathReturnEmptyString(ctxt); | |
| break; | |
| default: | |
| FIXME("Unexpected return value %s.\n", debugstr_variant(&result)); | |
| xmlXPathReturnEmptyString(ctxt); | |
| } | |
| IDispatch_Release(disp); | |
| VariantClear(&result); | |
| } | |
| struct msxsl_script_site | |
| { | |
| IActiveScriptSite IActiveScriptSite_iface; | |
| IServiceProvider IServiceProvider_iface; | |
| LONG refcount; | |
| IServiceProvider *provider; | |
| }; | |
| static struct msxsl_script_site *impl_from_IActiveScriptSite(IActiveScriptSite *iface) | |
| { | |
| return CONTAINING_RECORD(iface, struct msxsl_script_site, IActiveScriptSite_iface); | |
| } | |
| static struct msxsl_script_site *impl_from_IServiceProvider(IServiceProvider *iface) | |
| { | |
| return CONTAINING_RECORD(iface, struct msxsl_script_site, IServiceProvider_iface); | |
| } | |
| static HRESULT WINAPI msxsl_script_site_QueryInterface(IActiveScriptSite *iface, REFIID riid, void **obj) | |
| { | |
| struct msxsl_script_site *site = impl_from_IActiveScriptSite(iface); | |
| if (IsEqualGUID(&IID_IUnknown, riid) | |
| || IsEqualGUID(&IID_IActiveScriptSite, riid)) | |
| { | |
| *obj = iface; | |
| IActiveScriptSite_AddRef(iface); | |
| return S_OK; | |
| } | |
| else if (IsEqualGUID(&IID_IServiceProvider, riid)) | |
| { | |
| *obj = &site->IServiceProvider_iface; | |
| IServiceProvider_AddRef(&site->IServiceProvider_iface); | |
| return S_OK; | |
| } | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI msxsl_script_site_AddRef(IActiveScriptSite *iface) | |
| { | |
| struct msxsl_script_site *site = impl_from_IActiveScriptSite(iface); | |
| return InterlockedIncrement(&site->refcount); | |
| } | |
| static ULONG WINAPI msxsl_script_site_Release(IActiveScriptSite *iface) | |
| { | |
| struct msxsl_script_site *site = impl_from_IActiveScriptSite(iface); | |
| LONG refcount = InterlockedDecrement(&site->refcount); | |
| if (!refcount) | |
| { | |
| if (site->provider) | |
| IServiceProvider_Release(site->provider); | |
| free(site); | |
| } | |
| return refcount; | |
| } | |
| static HRESULT WINAPI msxsl_script_site_GetLCID(IActiveScriptSite *iface, LCID *lcid) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI msxsl_script_site_GetItemInfo(IActiveScriptSite *iface, LPCOLESTR name, | |
| DWORD mask, IUnknown **item, ITypeInfo **typeinfo) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI msxsl_script_site_GetDocVersionString(IActiveScriptSite *iface, BSTR *version) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI msxsl_script_site_OnScriptTerminate(IActiveScriptSite *iface, | |
| const VARIANT *result, const EXCEPINFO *ei) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI msxsl_script_site_OnStateChange(IActiveScriptSite *iface, SCRIPTSTATE state) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI msxsl_script_site_OnScriptError(IActiveScriptSite *iface, IActiveScriptError *script_error) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI msxsl_script_site_OnEnterScript(IActiveScriptSite *iface) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI msxsl_script_site_OnLeaveScript(IActiveScriptSite *iface) | |
| { | |
| return S_OK; | |
| } | |
| static const IActiveScriptSiteVtbl msxsl_script_site_vtbl = | |
| { | |
| msxsl_script_site_QueryInterface, | |
| msxsl_script_site_AddRef, | |
| msxsl_script_site_Release, | |
| msxsl_script_site_GetLCID, | |
| msxsl_script_site_GetItemInfo, | |
| msxsl_script_site_GetDocVersionString, | |
| msxsl_script_site_OnScriptTerminate, | |
| msxsl_script_site_OnStateChange, | |
| msxsl_script_site_OnScriptError, | |
| msxsl_script_site_OnEnterScript, | |
| msxsl_script_site_OnLeaveScript | |
| }; | |
| static HRESULT WINAPI msxsl_script_site_servprov_QueryInterface(IServiceProvider *iface, REFIID riid, void **obj) | |
| { | |
| struct msxsl_script_site *site = impl_from_IServiceProvider(iface); | |
| return IActiveScriptSite_QueryInterface(&site->IActiveScriptSite_iface, riid, obj); | |
| } | |
| static ULONG WINAPI msxsl_script_site_servprov_AddRef(IServiceProvider *iface) | |
| { | |
| struct msxsl_script_site *site = impl_from_IServiceProvider(iface); | |
| return IActiveScriptSite_AddRef(&site->IActiveScriptSite_iface); | |
| } | |
| static ULONG WINAPI msxsl_script_site_servprov_Release(IServiceProvider *iface) | |
| { | |
| struct msxsl_script_site *site = impl_from_IServiceProvider(iface); | |
| return IActiveScriptSite_Release(&site->IActiveScriptSite_iface); | |
| } | |
| static HRESULT WINAPI msxsl_script_site_servprov_QueryService(IServiceProvider *iface, | |
| REFGUID service, REFIID riid, void **obj) | |
| { | |
| struct msxsl_script_site *site = impl_from_IServiceProvider(iface); | |
| if (site->provider) | |
| return IServiceProvider_QueryService(site->provider, service, riid, obj); | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static const IServiceProviderVtbl msxsl_script_site_servprov_vtbl = | |
| { | |
| msxsl_script_site_servprov_QueryInterface, | |
| msxsl_script_site_servprov_AddRef, | |
| msxsl_script_site_servprov_Release, | |
| msxsl_script_site_servprov_QueryService, | |
| }; | |
| static HRESULT msxsl_create_script_site(IXMLDOMDocument *doc, IActiveScriptSite **obj) | |
| { | |
| struct msxsl_script_site *object; | |
| IServiceProvider *provider = NULL; | |
| IObjectWithSite *ows; | |
| HRESULT hr; | |
| if (FAILED(hr = IXMLDOMDocument_QueryInterface(doc, &IID_IObjectWithSite, (void **)&ows))) | |
| return hr; | |
| IObjectWithSite_GetSite(ows, &IID_IServiceProvider, (void **)&provider); | |
| IObjectWithSite_Release(ows); | |
| object = calloc(1, sizeof(*object)); | |
| object->IActiveScriptSite_iface.lpVtbl = &msxsl_script_site_vtbl; | |
| object->IServiceProvider_iface.lpVtbl = &msxsl_script_site_servprov_vtbl; | |
| object->refcount = 1; | |
| object->provider = provider; | |
| *obj = &object->IActiveScriptSite_iface; | |
| return S_OK; | |
| } | |
| static void node_transform_bind_scripts(xsltTransformContextPtr ctxt, IXMLDOMDocument *owner_doc, | |
| xmlDocPtr sheet, struct xsl_scripts *scripts) | |
| { | |
| IActiveScriptSite *script_site; | |
| xmlNodePtr root, child, node; | |
| DWORD supported, enabled = 0; | |
| IObjectSafety *object_safety; | |
| xmlNsPtr ns; | |
| IXMLDOMDocument_QueryInterface(owner_doc, &IID_IObjectSafety, (void **)&object_safety); | |
| IObjectSafety_GetInterfaceSafetyOptions(object_safety, NULL, &supported, &enabled); | |
| IObjectSafety_Release(object_safety); | |
| if (FAILED(msxsl_create_script_site(owner_doc, &script_site))) | |
| return; | |
| root = xmlDocGetRootElement(sheet); | |
| for (node = root->children; node; node = node->next) | |
| { | |
| if (xmlStrEqual(node->name, BAD_CAST "script") | |
| && node->ns | |
| && xmlStrEqual(node->ns->prefix, BAD_CAST "msxsl") | |
| && xmlStrEqual(node->ns->href, BAD_CAST "urn:schemas-microsoft-com:xslt")) | |
| { | |
| child = node->children; | |
| if (child && child->type == XML_CDATA_SECTION_NODE) | |
| { | |
| IActiveScript *active_script; | |
| xmlChar *language, *prefix; | |
| IActiveScriptParse *parser; | |
| TYPEATTR *typeattr; | |
| BSTR text, progid; | |
| IDispatch *disp; | |
| ITypeInfo *ti; | |
| CLSID clsid; | |
| HRESULT hr; | |
| if (!(prefix = xmlGetProp(node, BAD_CAST "implements-prefix"))) | |
| { | |
| WARN("<msxsl:script> without 'implements-prefix'.\n"); | |
| continue; | |
| } | |
| if (!(ns = xmlSearchNs(sheet, node, prefix))) | |
| { | |
| WARN("Couldn't locate script element namespace for \"%s\".\n", debugstr_a((char *)prefix)); | |
| continue; | |
| } | |
| if (!(language = xmlGetProp(node, BAD_CAST "language"))) | |
| language = BAD_CAST "javascript"; | |
| progid = bstr_from_xmlChar(language); | |
| if (FAILED(hr = CLSIDFromProgID(progid, &clsid))) | |
| WARN("Unknown engine progid %s.\n", debugstr_w(progid)); | |
| SysFreeString(progid); | |
| if (FAILED(hr)) | |
| return; | |
| if (FAILED(hr = CoCreateInstance(&clsid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, | |
| &IID_IActiveScript, (void **)&active_script))) | |
| { | |
| WARN("Failed to create a script engine instance, hr %#lx.\n", hr); | |
| continue; | |
| } | |
| IActiveScript_QueryInterface(active_script, &IID_IObjectSafety, (void **)&object_safety); | |
| IObjectSafety_SetInterfaceSafetyOptions(object_safety, NULL, enabled, enabled); | |
| IObjectSafety_Release(object_safety); | |
| IActiveScript_QueryInterface(active_script, &IID_IActiveScriptParse, (void **)&parser); | |
| IActiveScript_SetScriptSite(active_script, script_site); | |
| IActiveScriptParse_InitNew(parser); | |
| IActiveScript_SetScriptState(active_script, SCRIPTSTATE_STARTED); | |
| text = bstr_from_xmlChar(child->content); | |
| hr = IActiveScriptParse_ParseScriptText(parser, text, NULL, NULL, NULL, 0, 0, 0, NULL, NULL); | |
| IActiveScriptParse_Release(parser); | |
| SysFreeString(text); | |
| if (FAILED(hr)) | |
| { | |
| WARN("Failed to parse script for the namespace %s, hr %#lx.\n", prefix, hr); | |
| IActiveScript_Release(active_script); | |
| continue; | |
| } | |
| IActiveScript_GetScriptDispatch(active_script, NULL, &disp); | |
| IDispatch_GetTypeInfo(disp, 0, LOCALE_USER_DEFAULT, &ti); | |
| ITypeInfo_GetTypeAttr(ti, &typeattr); | |
| for (int i = 0; i < typeattr->cFuncs; ++i) | |
| { | |
| FUNCDESC *funcdesc; | |
| xmlChar *func_name; | |
| BSTR name; | |
| ITypeInfo_GetFuncDesc(ti, i, &funcdesc); | |
| ITypeInfo_GetDocumentation(ti, funcdesc->memid, &name, NULL, NULL, NULL); | |
| func_name = xmlchar_from_wchar(name); | |
| xsltRegisterExtFunction(ctxt, func_name, ns->href, xpath_msxsl_script_function); | |
| SysFreeString(name); | |
| free(func_name); | |
| } | |
| IDispatch_Release(disp); | |
| ITypeInfo_Release(ti); | |
| array_reserve((void **)&scripts->entries, &scripts->capacity, scripts->count + 1, sizeof(*scripts->entries)); | |
| scripts->entries[scripts->count].script = active_script; | |
| scripts->entries[scripts->count].uri = ns->href; | |
| scripts->count++; | |
| } | |
| } | |
| } | |
| IActiveScriptSite_Release(script_site); | |
| } | |
| HRESULT node_transform_node_params(struct domnode *node, IXMLDOMNode *stylesheet, BSTR *p, | |
| ISequentialStream *stream, const struct xslprocessor_params *params) | |
| { | |
| xmlDocPtr sheet_doc, node_doc; | |
| struct domnode *sheet_domdoc; | |
| IXMLDOMDocument *owner_doc; | |
| xsltStylesheetPtr xsltSS; | |
| xmlNodePtr xmlnode; | |
| HRESULT hr = S_OK; | |
| if (!stylesheet || (!p && !stream)) | |
| return E_INVALIDARG; | |
| if (p) *p = NULL; | |
| if (FAILED(IXMLDOMNode_QueryInterface(stylesheet, &IID_IXMLDOMDocument, (void **)&owner_doc))) | |
| { | |
| if (FAILED(hr = IXMLDOMNode_get_ownerDocument(stylesheet, &owner_doc))) | |
| return hr; | |
| } | |
| sheet_domdoc = get_node_obj(owner_doc); | |
| if (!sheet_domdoc) | |
| { | |
| IXMLDOMDocument_Release(owner_doc); | |
| return E_FAIL; | |
| } | |
| sheet_doc = create_xmldoc_from_domdoc(sheet_domdoc, &xmlnode); | |
| node_doc = create_xmldoc_from_domdoc(node, &xmlnode); | |
| xsltSS = xsltParseStylesheetDoc(sheet_doc); | |
| if (xsltSS) | |
| { | |
| struct xsl_scripts scripts = { 0 }; | |
| const char **xslparams = NULL; | |
| xsltTransformContextPtr ctxt; | |
| xmlDocPtr result; | |
| unsigned int i; | |
| /* convert our parameter list to libxml2 format */ | |
| if (params && params->count) | |
| { | |
| struct xslprocessor_par *par; | |
| i = 0; | |
| xslparams = malloc((params->count * 2 + 1) * sizeof(char*)); | |
| LIST_FOR_EACH_ENTRY(par, ¶ms->list, struct xslprocessor_par, entry) | |
| { | |
| xslparams[i++] = (char*)xmlchar_from_wchar(par->name); | |
| xslparams[i++] = (char*)xmlchar_from_wchar(par->value); | |
| } | |
| xslparams[i] = NULL; | |
| } | |
| ctxt = xsltNewTransformContext(xsltSS, node_doc); | |
| node_transform_bind_scripts(ctxt, owner_doc, sheet_doc, &scripts); | |
| ctxt->userData = &scripts; | |
| if (xslparams) | |
| { | |
| /* push parameters to user context */ | |
| xsltQuoteUserParams(ctxt, xslparams); | |
| result = xsltApplyStylesheetUser(xsltSS, node_doc, NULL, NULL, NULL, ctxt); | |
| for (i = 0; i < params->count*2; i++) | |
| free((char*)xslparams[i]); | |
| free(xslparams); | |
| } | |
| else | |
| { | |
| result = xsltApplyStylesheetUser(xsltSS, node_doc, NULL, NULL, NULL, ctxt); | |
| } | |
| for (size_t i = 0; i < scripts.count; ++i) | |
| { | |
| if (scripts.entries[i].script) | |
| IActiveScript_Release(scripts.entries[i].script); | |
| } | |
| free(scripts.entries); | |
| ctxt->userData = NULL; | |
| xsltFreeTransformContext(ctxt); | |
| if (result) | |
| { | |
| if (stream) | |
| hr = node_transform_write_to_stream(xsltSS, result, stream); | |
| else | |
| hr = node_transform_write_to_bstr(xsltSS, result, p); | |
| xmlFreeDoc(result); | |
| } | |
| xsltFreeStylesheet(xsltSS); | |
| } | |
| else | |
| xmlFreeDoc(sheet_doc); | |
| xmlFreeDoc(node_doc); | |
| if (p && !*p) *p = SysAllocStringLen(NULL, 0); | |
| IXMLDOMDocument_Release(owner_doc); | |
| return hr; | |
| } | |
| HRESULT node_transform_node(struct domnode *node, IXMLDOMNode *stylesheet, BSTR *p) | |
| { | |
| return node_transform_node_params(node, stylesheet, p, NULL, NULL); | |
| } | |
| HRESULT node_select_nodes(struct domnode *node, BSTR query, IXMLDOMNodeList **list) | |
| { | |
| struct domnode *doc = node_get_doc(node); | |
| if (!query || !list) | |
| return E_INVALIDARG; | |
| return create_selection(node, query, doc->properties->XPath, list); | |
| } | |
| HRESULT node_select_singlenode(struct domnode *node, BSTR query, IXMLDOMNode **ret) | |
| { | |
| IXMLDOMNodeList *list; | |
| HRESULT hr; | |
| if (ret) | |
| *ret = NULL; | |
| hr = node_select_nodes(node, query, &list); | |
| if (hr == S_OK) | |
| { | |
| hr = IXMLDOMNodeList_nextNode(list, ret); | |
| IXMLDOMNodeList_Release(list); | |
| } | |
| return hr; | |
| } | |
| HRESULT node_get_namespaceURI(struct domnode *node, BSTR *uri) | |
| { | |
| if (!uri) | |
| return E_INVALIDARG; | |
| *uri = NULL; | |
| return node->uri ? return_bstr(node->uri, uri) : S_FALSE; | |
| } | |
| HRESULT node_attribute_get_namespace_uri(struct domnode *node, BSTR *uri) | |
| { | |
| struct domnode *doc = node->owner; | |
| bool version6, defaultns; | |
| if (!uri) | |
| return E_INVALIDARG; | |
| *uri = NULL; | |
| version6 = doc->properties->version == MSXML6; | |
| defaultns = !wcscmp(node->qname, L"xmlns"); | |
| if (defaultns || is_same_namespace_prefix(node, L"xmlns")) | |
| { | |
| if (version6) | |
| *uri = SysAllocString(L"http://www.w3.org/2000/xmlns/"); | |
| else if (!node->uri || !defaultns) | |
| *uri = SysAllocStringLen(NULL, 0); | |
| else | |
| *uri = SysAllocString(L"xmlns"); | |
| return *uri ? S_OK : E_OUTOFMEMORY; | |
| } | |
| return node_get_namespaceURI(node, uri); | |
| } | |
| HRESULT node_get_prefix(struct domnode *node, BSTR *prefix) | |
| { | |
| if (!prefix) | |
| return E_INVALIDARG; | |
| *prefix = NULL; | |
| return node->prefix ? return_bstr(node->prefix, prefix) : S_FALSE; | |
| } | |
| HRESULT node_get_attribute(const struct domnode *node, const WCHAR *name, IXMLDOMAttribute **attribute) | |
| { | |
| struct domnode *attr; | |
| IUnknown *obj; | |
| HRESULT hr; | |
| if (attribute) | |
| *attribute = NULL; | |
| if (!parser_is_valid_qualified_name(name)) | |
| return E_FAIL; | |
| if (!attribute) | |
| return S_FALSE; | |
| if (domnode_get_attribute(node, name, &attr) != S_OK) | |
| return S_FALSE; | |
| hr = create_attribute(attr, &obj); | |
| if (SUCCEEDED(hr)) | |
| { | |
| hr = IUnknown_QueryInterface(obj, &IID_IXMLDOMAttribute, (void **)attribute); | |
| IUnknown_Release(obj); | |
| } | |
| return hr; | |
| } | |
| HRESULT node_get_qualified_attribute(const struct domnode *node, const WCHAR *name, const WCHAR *uri, IXMLDOMNode **ret) | |
| { | |
| struct domnode *attr; | |
| if (!ret) | |
| return S_FALSE; | |
| if (ret) | |
| *ret = NULL; | |
| if (domnode_get_qualified_attribute(node, name, uri, &attr) != S_OK) | |
| return S_FALSE; | |
| return create_node(attr, ret); | |
| } | |
| HRESULT node_remove_qualified_attribute(struct domnode *node, const WCHAR *name, const WCHAR *uri, IXMLDOMNode **ret) | |
| { | |
| struct domnode *attr; | |
| HRESULT hr; | |
| if (!name) | |
| return E_INVALIDARG; | |
| if (ret) | |
| *ret = NULL; | |
| if ((hr = domnode_get_qualified_attribute(node, name, uri, &attr)) != S_OK) | |
| return hr; | |
| if (ret) | |
| hr = create_node(attr, ret); | |
| if (SUCCEEDED(hr)) | |
| domnode_unlink_attribute(attr); | |
| return hr; | |
| } | |
| HRESULT node_get_attribute_by_index(const struct domnode *node, LONG index, IXMLDOMNode **attr) | |
| { | |
| struct domnode *n, *curr = NULL; | |
| *attr = NULL; | |
| if (index < 0) | |
| return S_FALSE; | |
| LIST_FOR_EACH_ENTRY(n, &node->attributes, struct domnode, entry) | |
| { | |
| curr = n; | |
| if (!index--) break; | |
| curr = NULL; | |
| } | |
| if (!curr) | |
| return S_FALSE; | |
| return create_node(curr, attr); | |
| } | |
| HRESULT node_get_attribute_value(struct domnode *node, const WCHAR *name, VARIANT *value) | |
| { | |
| struct string_buffer buffer; | |
| struct domnode *attr; | |
| if (!name || !value) | |
| return E_INVALIDARG; | |
| VariantInit(value); | |
| if (!parser_is_valid_qualified_name(name)) | |
| return E_FAIL; | |
| if (domnode_get_attribute(node, name, &attr) != S_OK) | |
| { | |
| V_VT(value) = VT_NULL; | |
| return S_FALSE; | |
| } | |
| string_buffer_init(&buffer); | |
| domnode_get_attribute_value(attr, &buffer); | |
| V_VT(value) = VT_BSTR; | |
| return string_to_bstr(&buffer, &V_BSTR(value)); | |
| } | |
| HRESULT node_set_attribute(struct domnode *node, IXMLDOMNode *attribute, IXMLDOMNode **ret) | |
| { | |
| struct domnode *attr, *old_attr; | |
| HRESULT hr; | |
| if (!attribute) | |
| return E_INVALIDARG; | |
| if (!(attr = get_node_obj(attribute))) | |
| return E_FAIL; | |
| if (attr->type != NODE_ATTRIBUTE) | |
| return E_FAIL; | |
| if (attr->parent) | |
| return E_FAIL; | |
| if (ret) | |
| *ret = NULL; | |
| if (domnode_get_attribute(node, attr->qname, &old_attr) == S_OK) | |
| { | |
| if (ret) | |
| { | |
| if (FAILED(hr = create_node(old_attr, ret))) | |
| return hr; | |
| } | |
| domnode_insert_attribute(node, attr, old_attr); | |
| domnode_unlink_attribute(old_attr); | |
| } | |
| else | |
| { | |
| domnode_insert_attribute(node, attr, NULL); | |
| } | |
| return S_OK; | |
| } | |
| static bool is_namespace_definition_name(const struct parsed_name *name) | |
| { | |
| if (!name->prefix && !wcscmp(name->local, L"xmlns")) | |
| return true; | |
| return name->prefix && !wcscmp(name->prefix, L"xmlns"); | |
| } | |
| HRESULT node_set_attribute_value(struct domnode *node, const WCHAR *name, const VARIANT *value) | |
| { | |
| struct parsed_name attr_name; | |
| struct domnode *attr; | |
| BSTR attr_value; | |
| bool match; | |
| HRESULT hr; | |
| VARIANT v; | |
| if (FAILED(parse_qualified_name(name, &attr_name))) | |
| return E_FAIL; | |
| if (FAILED(hr = variant_get_str_value(value, &v, &attr_value))) | |
| { | |
| parsed_name_cleanup(&attr_name); | |
| return hr; | |
| } | |
| /* Check for conflict with element namespace. It's possible to have no uri set on element, | |
| while still having qualified name. */ | |
| match = node->uri | |
| && is_namespace_definition_name(&attr_name) | |
| && is_same_namespace_prefix(node, attr_name.local) | |
| && !is_same_uri(node, attr_value); | |
| parsed_name_cleanup(&attr_name); | |
| if (match) | |
| { | |
| VariantClear(&v); | |
| return E_INVALIDARG; | |
| } | |
| if (domnode_get_attribute(node, name, &attr) != S_OK) | |
| { | |
| if (SUCCEEDED(hr = domnode_create(NODE_ATTRIBUTE, name, wcslen(name), NULL, 0, node->owner, &attr))) | |
| domnode_insert_attribute(node, attr, NULL); | |
| } | |
| if (SUCCEEDED(hr)) | |
| hr = node_put_data(attr, attr_value); | |
| VariantClear(&v); | |
| /* Allow setting namespace definition node once. */ | |
| if (attr && is_namespace_definition(attr)) | |
| attr->flags |= DOMNODE_READONLY_VALUE; | |
| return hr; | |
| } | |
| HRESULT node_get_base_name(struct domnode *node, BSTR *name) | |
| { | |
| return return_bstr(node->name, name); | |
| } | |
| HRESULT create_node(struct domnode *node, IXMLDOMNode **ret) | |
| { | |
| IUnknown *obj; | |
| HRESULT hr; | |
| *ret = NULL; | |
| if (!node) | |
| return S_FALSE; | |
| switch (node->type) | |
| { | |
| case NODE_ELEMENT: | |
| hr = create_element(node, &obj); | |
| break; | |
| case NODE_ATTRIBUTE: | |
| hr = create_attribute(node, &obj); | |
| break; | |
| case NODE_TEXT: | |
| hr = create_text(node, &obj); | |
| break; | |
| case NODE_CDATA_SECTION: | |
| hr = create_cdata(node, &obj); | |
| break; | |
| case NODE_ENTITY_REFERENCE: | |
| hr = create_entity_ref(node, &obj); | |
| break; | |
| case NODE_PROCESSING_INSTRUCTION: | |
| hr = create_pi(node, &obj); | |
| break; | |
| case NODE_COMMENT: | |
| hr = create_comment(node, &obj); | |
| break; | |
| case NODE_DOCUMENT: | |
| hr = create_domdoc(node, &obj); | |
| break; | |
| case NODE_DOCUMENT_FRAGMENT: | |
| hr = create_doc_fragment(node, &obj); | |
| break; | |
| case NODE_DOCUMENT_TYPE: | |
| hr = create_doc_type(node, &obj); | |
| break; | |
| case NODE_ENTITY: | |
| case NODE_NOTATION: | |
| FIXME("Unsupported node type %d.\n", node->type); | |
| return E_NOTIMPL; | |
| default: | |
| WARN("Invalid node type %d\n", node->type); | |
| return E_FAIL; | |
| } | |
| if (hr == S_OK) | |
| { | |
| hr = IUnknown_QueryInterface(obj, &IID_IXMLDOMNode, (void **)ret); | |
| IUnknown_Release(obj); | |
| } | |
| return hr; | |
| } | |
| struct parse_context | |
| { | |
| ISAXExtensionHandler extension_handler; | |
| ISAXContentHandler content_handler; | |
| ISAXLexicalHandler lexical_handler; | |
| ISAXXMLReaderExtension *reader_extension; | |
| ISAXXMLReader *reader; | |
| struct domnode *node; | |
| struct string_buffer buffer; | |
| /* Parsed output */ | |
| struct domnode *root; | |
| MSXML_VERSION version; | |
| HRESULT status; | |
| int max_depth; | |
| int depth; | |
| }; | |
| static void parse_context_node_create(struct parse_context *context, DOMNodeType type, | |
| const WCHAR *name, int name_len, const WCHAR *uri, int uri_len, struct domnode *owner, | |
| struct domnode **node) | |
| { | |
| *node = NULL; | |
| if (context->status != S_OK) | |
| return; | |
| context->status = domnode_create(type, name, name_len, uri, uri_len, owner, node); | |
| } | |
| static void parse_context_append_child(struct parse_context *context, struct domnode *parent, struct domnode *child) | |
| { | |
| if (context->status == S_OK) | |
| domnode_append_child(parent, child); | |
| } | |
| static void parse_context_append_attribute(struct parse_context *context, struct domnode *node, struct domnode *attribute) | |
| { | |
| if (context->status == S_OK) | |
| domnode_insert_attribute(node, attribute, NULL); | |
| } | |
| static void parse_context_node_put_data(struct parse_context *context, struct domnode *node, const WCHAR *data, int data_len) | |
| { | |
| BSTR str; | |
| if (context->status != S_OK) | |
| return; | |
| if (!(str = SysAllocStringLen(data, data_len))) | |
| { | |
| context->status = E_OUTOFMEMORY; | |
| return; | |
| } | |
| context->status = node_put_data(node, str); | |
| SysFreeString(str); | |
| } | |
| static HRESULT parse_context_create_text_node(struct parse_context *c, DOMNodeType type) | |
| { | |
| bool preserve = is_preserving_whitespace(c->node), space = true; | |
| bool ignored_whitespace = false; | |
| struct domnode *node; | |
| if (c->status != S_OK) | |
| return c->status; | |
| if (type == NODE_CDATA_SECTION) | |
| { | |
| parse_context_node_create(c, type, NULL, 0, NULL, 0, c->root, &node); | |
| parse_context_node_put_data(c, node, c->buffer.data, c->buffer.count); | |
| parse_context_append_child(c, c->node, node); | |
| c->buffer.count = 0; | |
| return c->status; | |
| } | |
| if (c->buffer.count == 0) | |
| return S_OK; | |
| if (!preserve) | |
| { | |
| for (size_t i = 0; i < c->buffer.count; ++i) | |
| { | |
| if (!xml_is_space(c->buffer.data[i])) | |
| { | |
| space = false; | |
| break; | |
| } | |
| } | |
| if (space) | |
| { | |
| c->buffer.count = 0; | |
| ignored_whitespace = true; | |
| } | |
| } | |
| if (c->buffer.count) | |
| { | |
| parse_context_node_create(c, type, NULL, 0, NULL, 0, c->root, &node); | |
| parse_context_node_put_data(c, node, c->buffer.data, c->buffer.count); | |
| parse_context_append_child(c, c->node, node); | |
| c->buffer.count = 0; | |
| } | |
| else if (ignored_whitespace) | |
| { | |
| if (list_empty(&c->node->children)) | |
| { | |
| c->node->flags |= DOMNODE_IGNORED_WS_AFTER_STARTTAG; | |
| } | |
| else | |
| { | |
| struct domnode *last_child = node_from_entry(list_tail(&c->node->children)); | |
| last_child->flags |= DOMNODE_IGNORED_WS; | |
| } | |
| } | |
| return c->status; | |
| } | |
| static struct parse_context *impl_from_ISAXContentHandler(ISAXContentHandler *iface) | |
| { | |
| return CONTAINING_RECORD(iface, struct parse_context, content_handler); | |
| } | |
| static struct parse_context *impl_from_ISAXExtensionHandler(ISAXExtensionHandler *iface) | |
| { | |
| return CONTAINING_RECORD(iface, struct parse_context, extension_handler); | |
| } | |
| static HRESULT WINAPI parse_content_handler_QueryInterface(ISAXContentHandler *iface, REFIID riid, void **obj) | |
| { | |
| if (IsEqualGUID(riid, &IID_ISAXContentHandler) | |
| || IsEqualGUID(riid, &IID_IUnknown)) | |
| { | |
| *obj = iface; | |
| ISAXContentHandler_AddRef(iface); | |
| return S_OK; | |
| } | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI parse_content_handler_AddRef(ISAXContentHandler *iface) | |
| { | |
| return 2; | |
| } | |
| static ULONG WINAPI parse_content_handler_Release(ISAXContentHandler *iface) | |
| { | |
| return 1; | |
| } | |
| static HRESULT WINAPI parse_content_handler_putDocumentLocator(ISAXContentHandler *iface, ISAXLocator *locator) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_content_handler_startDocument(ISAXContentHandler *iface) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_content_handler_endDocument(ISAXContentHandler *iface) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_content_handler_startPrefixMapping(ISAXContentHandler *iface, | |
| const WCHAR *prefix, int prefix_len, const WCHAR *uri, int uri_len) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_content_handler_endPrefixMapping(ISAXContentHandler *iface, | |
| const WCHAR *prefix, int prefix_len) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_content_handler_startElement(ISAXContentHandler *iface, const WCHAR *uri, int uri_len, | |
| const WCHAR *name, int name_len, const WCHAR *qname, int qname_len, ISAXAttributes *attrs) | |
| { | |
| struct parse_context *c = impl_from_ISAXContentHandler(iface); | |
| struct domnode *element, *attr; | |
| int max_depth = c->max_depth; | |
| int count, length; | |
| const WCHAR *str; | |
| ++c->depth; | |
| if (max_depth && c->depth > max_depth) | |
| return c->version < MSXML6 ? E_ABORT : E_DOM_MAX_ELEMENT_DEPTH; | |
| parse_context_create_text_node(c, NODE_TEXT); | |
| parse_context_node_create(c, NODE_ELEMENT, qname, qname_len, uri, uri_len, c->root, &element); | |
| parse_context_append_child(c, c->node, element); | |
| c->node = element; | |
| /* TODO: error handling */ | |
| if (attrs) | |
| { | |
| ISAXAttributes_getLength(attrs, &count); | |
| for (int i = 0; i < count; ++i) | |
| { | |
| ISAXAttributes_getQName(attrs, i, &str, &length); | |
| ISAXAttributes_getURI(attrs, i, &uri, &uri_len); | |
| parse_context_node_create(c, NODE_ATTRIBUTE, str, length, uri, uri_len, c->root, &attr); | |
| parse_context_append_attribute(c, element, attr); | |
| ISAXAttributes_getValue(attrs, i, &str, &length); | |
| parse_context_node_put_data(c, attr, str, length); | |
| if (attr) | |
| { | |
| attr->flags |= DOMNODE_PARSED_VALUE; | |
| if (is_namespace_definition(attr)) | |
| attr->flags |= DOMNODE_READONLY_VALUE; | |
| } | |
| } | |
| } | |
| return c->status; | |
| } | |
| static HRESULT WINAPI parse_content_handler_endElement(ISAXContentHandler *iface, const WCHAR *uri, int uri_len, | |
| const WCHAR *name, int name_len, const WCHAR *qname, int qname_len) | |
| { | |
| struct parse_context *c = impl_from_ISAXContentHandler(iface); | |
| --c->depth; | |
| parse_context_create_text_node(c, NODE_TEXT); | |
| c->node = c->node->parent; | |
| return c->status; | |
| } | |
| static HRESULT WINAPI parse_content_handler_characters(ISAXContentHandler *iface, const WCHAR *chars, int count) | |
| { | |
| struct parse_context *c = impl_from_ISAXContentHandler(iface); | |
| string_append(&c->buffer, chars, count); | |
| return c->status; | |
| } | |
| static HRESULT WINAPI parse_content_handler_ignorableWhitespace(ISAXContentHandler *iface, const WCHAR *chars, int count) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_content_handler_processingInstruction(ISAXContentHandler *iface, const WCHAR *target, | |
| int target_len, const WCHAR *data, int data_len) | |
| { | |
| struct parse_context *c = impl_from_ISAXContentHandler(iface); | |
| struct domnode *pi; | |
| parse_context_create_text_node(c, NODE_TEXT); | |
| parse_context_node_create(c, NODE_PROCESSING_INSTRUCTION, target, target_len, NULL, 0, c->root, &pi); | |
| parse_context_append_child(c, c->node, pi); | |
| parse_context_node_put_data(c, pi, data, data_len); | |
| return c->status; | |
| } | |
| static HRESULT WINAPI parse_content_handler_skippedEntity(ISAXContentHandler *iface, const WCHAR *name, int name_len) | |
| { | |
| return S_OK; | |
| } | |
| static const ISAXContentHandlerVtbl parse_content_handler_vtbl = | |
| { | |
| parse_content_handler_QueryInterface, | |
| parse_content_handler_AddRef, | |
| parse_content_handler_Release, | |
| parse_content_handler_putDocumentLocator, | |
| parse_content_handler_startDocument, | |
| parse_content_handler_endDocument, | |
| parse_content_handler_startPrefixMapping, | |
| parse_content_handler_endPrefixMapping, | |
| parse_content_handler_startElement, | |
| parse_content_handler_endElement, | |
| parse_content_handler_characters, | |
| parse_content_handler_ignorableWhitespace, | |
| parse_content_handler_processingInstruction, | |
| parse_content_handler_skippedEntity, | |
| }; | |
| static HRESULT WINAPI parse_extension_handler_QueryInterface(ISAXExtensionHandler *iface, REFIID riid, void **obj) | |
| { | |
| if (IsEqualGUID(riid, &IID_ISAXExtensionHandler) | |
| || IsEqualGUID(riid, &IID_IUnknown)) | |
| { | |
| *obj = iface; | |
| ISAXExtensionHandler_AddRef(iface); | |
| return S_OK; | |
| } | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI parse_extension_handler_AddRef(ISAXExtensionHandler *iface) | |
| { | |
| return 2; | |
| } | |
| static ULONG WINAPI parse_extension_handler_Release(ISAXExtensionHandler *iface) | |
| { | |
| return 1; | |
| } | |
| static HRESULT WINAPI parse_extension_handler_xmldecl(ISAXExtensionHandler *iface, | |
| BSTR version, BSTR encoding, BSTR standalone) | |
| { | |
| struct parse_context *c = impl_from_ISAXExtensionHandler(iface); | |
| struct domnode *pi, *node; | |
| parse_context_node_create(c, NODE_PROCESSING_INSTRUCTION, L"xml", 3, NULL, 0, c->root, &pi); | |
| parse_context_append_child(c, c->root, pi); | |
| parse_context_node_create(c, NODE_ATTRIBUTE, L"version", 7, NULL, 0, c->root, &node); | |
| parse_context_node_put_data(c, node, version, SysStringLen(version)); | |
| parse_context_append_attribute(c, pi, node); | |
| if (encoding) | |
| { | |
| parse_context_node_create(c, NODE_ATTRIBUTE, L"encoding", 8, NULL, 0, c->root, &node); | |
| parse_context_node_put_data(c, node, encoding, SysStringLen(encoding)); | |
| parse_context_append_attribute(c, pi, node); | |
| } | |
| if (standalone) | |
| { | |
| parse_context_node_create(c, NODE_ATTRIBUTE, L"standalone", 10, NULL, 0, c->root, &node); | |
| parse_context_node_put_data(c, node, standalone, SysStringLen(standalone)); | |
| parse_context_append_attribute(c, pi, node); | |
| } | |
| return c->status; | |
| } | |
| static HRESULT WINAPI parse_extension_handler_dtd(ISAXExtensionHandler *iface, | |
| BSTR data, struct dtd *dtd) | |
| { | |
| struct parse_context *c = impl_from_ISAXExtensionHandler(iface); | |
| if (c->node && c->node->type == NODE_DOCUMENT_TYPE) | |
| { | |
| if (!(c->node->data = SysAllocStringLen(data, SysStringLen(data)))) | |
| c->status = E_OUTOFMEMORY; | |
| c->node->dtd = parser_dtd_addref(dtd); | |
| } | |
| return c->status; | |
| } | |
| static const ISAXExtensionHandlerVtbl parse_extension_handler_vtbl = | |
| { | |
| parse_extension_handler_QueryInterface, | |
| parse_extension_handler_AddRef, | |
| parse_extension_handler_Release, | |
| parse_extension_handler_xmldecl, | |
| parse_extension_handler_dtd, | |
| }; | |
| static struct parse_context *impl_from_ISAXLexicalHandler(ISAXLexicalHandler *iface) | |
| { | |
| return CONTAINING_RECORD(iface, struct parse_context, lexical_handler); | |
| } | |
| static HRESULT WINAPI parse_lexical_handler_QueryInterface(ISAXLexicalHandler *iface, REFIID riid, void **obj) | |
| { | |
| if (IsEqualGUID(riid, &IID_ISAXLexicalHandler) | |
| || IsEqualGUID(riid, &IID_IUnknown)) | |
| { | |
| *obj = iface; | |
| ISAXLexicalHandler_AddRef(iface); | |
| return S_OK; | |
| } | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI parse_lexical_handler_AddRef(ISAXLexicalHandler *iface) | |
| { | |
| return 2; | |
| } | |
| static ULONG WINAPI parse_lexical_handler_Release(ISAXLexicalHandler *iface) | |
| { | |
| return 1; | |
| } | |
| static HRESULT WINAPI parse_lexical_handler_startDTD(ISAXLexicalHandler *iface, | |
| const WCHAR *name, int name_len, const WCHAR *pubid, int pubid_len, | |
| const WCHAR *sysid, int sysid_len) | |
| { | |
| struct parse_context *c = impl_from_ISAXLexicalHandler(iface); | |
| struct domnode *dtd; | |
| parse_context_node_create(c, NODE_DOCUMENT_TYPE, name, name_len, NULL, 0, c->root, &dtd); | |
| parse_context_append_child(c, c->root, dtd); | |
| c->node = dtd; | |
| return c->status; | |
| } | |
| static HRESULT WINAPI parse_lexical_handler_endDTD(ISAXLexicalHandler *iface) | |
| { | |
| struct parse_context *c = impl_from_ISAXLexicalHandler(iface); | |
| if (c->node) | |
| c->node = c->node->parent; | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_lexical_handler_startEntity(ISAXLexicalHandler *iface, | |
| const WCHAR *name, int name_len) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_lexical_handler_endEntity(ISAXLexicalHandler *iface, | |
| const WCHAR *name, int name_len) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI parse_lexical_handler_startCDATA(ISAXLexicalHandler *iface) | |
| { | |
| struct parse_context *c = impl_from_ISAXLexicalHandler(iface); | |
| return parse_context_create_text_node(c, NODE_TEXT); | |
| } | |
| static HRESULT WINAPI parse_lexical_handler_endCDATA(ISAXLexicalHandler *iface) | |
| { | |
| struct parse_context *c = impl_from_ISAXLexicalHandler(iface); | |
| return parse_context_create_text_node(c, NODE_CDATA_SECTION); | |
| } | |
| static HRESULT WINAPI parse_lexical_handler_comment(ISAXLexicalHandler *iface, const WCHAR *chars, int count) | |
| { | |
| struct parse_context *c = impl_from_ISAXLexicalHandler(iface); | |
| struct domnode *comment; | |
| parse_context_create_text_node(c, NODE_TEXT); | |
| parse_context_node_create(c, NODE_COMMENT, NULL, 0, NULL, 0, c->root, &comment); | |
| parse_context_append_child(c, c->node, comment); | |
| parse_context_node_put_data(c, comment, chars, count); | |
| return c->status; | |
| } | |
| static const ISAXLexicalHandlerVtbl parse_lexical_handler_vtbl = | |
| { | |
| parse_lexical_handler_QueryInterface, | |
| parse_lexical_handler_AddRef, | |
| parse_lexical_handler_Release, | |
| parse_lexical_handler_startDTD, | |
| parse_lexical_handler_endDTD, | |
| parse_lexical_handler_startEntity, | |
| parse_lexical_handler_endEntity, | |
| parse_lexical_handler_startCDATA, | |
| parse_lexical_handler_endCDATA, | |
| parse_lexical_handler_comment, | |
| }; | |
| struct domdoc_properties *domdoc_create_properties(MSXML_VERSION version); | |
| static HRESULT parse_context_init(struct parse_context *c, const struct domdoc_properties *properties) | |
| { | |
| IUnknown *unk; | |
| HRESULT hr; | |
| VARIANT v; | |
| memset(c, 0, sizeof(*c)); | |
| c->content_handler.lpVtbl = &parse_content_handler_vtbl; | |
| c->extension_handler.lpVtbl = &parse_extension_handler_vtbl; | |
| c->lexical_handler.lpVtbl = &parse_lexical_handler_vtbl; | |
| c->buffer.status = &c->status; | |
| c->max_depth = properties->max_element_depth; | |
| c->version = properties->version; | |
| if (FAILED(hr = SAXXMLReader_create(MSXML3, (void **)&unk))) | |
| return hr; | |
| IUnknown_QueryInterface(unk, &IID_ISAXXMLReader, (void **)&c->reader); | |
| IUnknown_QueryInterface(unk, &IID_ISAXXMLReaderExtension, (void **)&c->reader_extension); | |
| IUnknown_Release(unk); | |
| ISAXXMLReader_putContentHandler(c->reader, &c->content_handler); | |
| V_VT(&v) = VT_UNKNOWN; | |
| V_UNKNOWN(&v) = (IUnknown *)&c->lexical_handler; | |
| ISAXXMLReader_putProperty(c->reader, L"http://xml.org/sax/properties/lexical-handler", v); | |
| V_VT(&v) = VT_UNKNOWN; | |
| V_UNKNOWN(&v) = (IUnknown *)&c->extension_handler; | |
| ISAXXMLReader_putProperty(c->reader, L"http://winehq.org/sax/properties/extension-handler", v); | |
| V_VT(&v) = VT_I4; | |
| V_I4(&v) = 0; | |
| ISAXXMLReader_putProperty(c->reader, L"max-element-depth", v); | |
| ISAXXMLReader_putFeature(c->reader, L"prohibit-dtd", properties->prohibit_dtd ? VARIANT_TRUE : VARIANT_FALSE); | |
| ISAXXMLReader_putFeature(c->reader, L"normalize-attribute-values", VARIANT_FALSE); | |
| domnode_create(NODE_DOCUMENT, NULL, 0, NULL, 0, NULL, &c->root); | |
| c->root->properties = domdoc_create_properties(MSXML_DEFAULT); | |
| c->root->properties->preserving = properties->preserving; | |
| c->node = c->root; | |
| return hr; | |
| } | |
| static void parse_context_cleanup(struct parse_context *c) | |
| { | |
| free(c->buffer.data); | |
| if (c->reader) | |
| ISAXXMLReader_Release(c->reader); | |
| if (c->reader_extension) | |
| ISAXXMLReaderExtension_Release(c->reader_extension); | |
| } | |
| HRESULT parse_stream(ISequentialStream *stream, bool utf16, const struct domdoc_properties *properties, struct domnode **tree) | |
| { | |
| struct parse_context context; | |
| HRESULT hr; | |
| VARIANT v; | |
| *tree = NULL; | |
| if (FAILED(hr = parse_context_init(&context, properties))) | |
| return hr; | |
| if (utf16) | |
| { | |
| hr = ISAXXMLReaderExtension_parseUTF16(context.reader_extension, stream); | |
| } | |
| else | |
| { | |
| V_VT(&v) = VT_UNKNOWN; | |
| V_UNKNOWN(&v) = (IUnknown *)stream; | |
| hr = ISAXXMLReader_parse(context.reader, v); | |
| } | |
| if (hr == S_OK) | |
| { | |
| *tree = context.root; | |
| context.root = NULL; | |
| } | |
| parse_context_cleanup(&context); | |
| return hr; | |
| } | |
| struct xmldoc_context | |
| { | |
| xmlDocPtr xmldoc; | |
| struct domnode *node; | |
| xmlNodePtr *xmlnode; | |
| xmlNodePtr tree; | |
| }; | |
| static xmlNsPtr xmlnode_get_ns(xmlNodePtr tree, struct domnode *node, xmlNodePtr element) | |
| { | |
| xmlChar *uri, *prefix; | |
| xmlNsPtr ns; | |
| if (!node->uri) | |
| return NULL; | |
| uri = xmlchar_from_wchar(node->uri); | |
| prefix = node->prefix ? xmlchar_from_wchar(node->prefix) : NULL; | |
| if ((ns = xmlSearchNs(tree->doc, element, prefix))) | |
| { | |
| if (!xmlStrEqual(ns->href, uri)) | |
| ns = xmlNewNs(element, uri, prefix); | |
| } | |
| else | |
| { | |
| ns = xmlNewNs(element, uri, prefix); | |
| } | |
| free(uri); | |
| free(prefix); | |
| return ns; | |
| } | |
| static xmlNodePtr create_xmlnode_element(struct xmldoc_context *context, struct domnode *node) | |
| { | |
| xmlChar *name, *value, *uri, *prefix; | |
| struct domnode *attr; | |
| xmlNodePtr xmlnode; | |
| xmlAttrPtr xmlattr; | |
| xmlNsPtr ns; | |
| BSTR text; | |
| name = xmlchar_from_wchar(node->name); | |
| xmlnode = xmlNewDocNode(context->xmldoc, NULL, name, NULL); | |
| xmlAddChild(context->tree, xmlnode); | |
| free(name); | |
| ns = xmlnode_get_ns(context->tree, node, xmlnode); | |
| xmlSetNs(xmlnode, ns); | |
| /* Add explicit definitions first. */ | |
| LIST_FOR_EACH_ENTRY(attr, &node->attributes, struct domnode, entry) | |
| { | |
| if ((attr->prefix && !wcscmp(attr->prefix, L"xmlns")) | |
| || !wcscmp(attr->qname, L"xmlns")) | |
| { | |
| node_get_text(attr, &text); | |
| uri = xmlchar_from_wchar(text); | |
| prefix = wcscmp(attr->qname, L"xmlns") ? xmlchar_from_wchar(attr->name) : NULL; | |
| xmlNewNs(xmlnode, uri, prefix); | |
| SysFreeString(text); | |
| free(prefix); | |
| free(uri); | |
| } | |
| } | |
| LIST_FOR_EACH_ENTRY(attr, &node->attributes, struct domnode, entry) | |
| { | |
| if ((attr->prefix && !wcscmp(attr->prefix, L"xmlns")) | |
| || !wcscmp(attr->qname, L"xmlns")) | |
| { | |
| continue; | |
| } | |
| node_get_text(attr, &text); | |
| name = xmlchar_from_wchar(attr->name); | |
| value = xmlchar_from_wchar(text); | |
| ns = xmlnode_get_ns(xmlnode, attr, xmlnode); | |
| xmlattr = xmlNewProp(xmlnode, name, value); | |
| xmlattr->_private2 = attr; | |
| xmlSetNs((xmlNodePtr)xmlattr, ns); | |
| if (context->node == attr) | |
| *context->xmlnode = (xmlNodePtr)xmlattr; | |
| SysFreeString(text); | |
| free(value); | |
| free(name); | |
| } | |
| return xmlnode; | |
| } | |
| static xmlDtdPtr create_xmldtd_from_domnode(struct domnode *node) | |
| { | |
| struct string_buffer buffer; | |
| xmlDtdPtr dtd = NULL; | |
| xmlDocPtr doc; | |
| BSTR str; | |
| string_buffer_init(&buffer); | |
| string_append(&buffer, L"<?xml version=\"1.0\"?>", 21); | |
| string_append(&buffer, node->data, SysStringLen(node->data)); | |
| string_append(&buffer, L"<a/>", 4); | |
| string_to_bstr(&buffer, &str); | |
| doc = xmlParseMemory((const char *)str, SysStringByteLen(str)); | |
| SysFreeString(str); | |
| if ((dtd = xmlGetIntSubset(doc))) | |
| dtd = xmlCopyDtd(dtd); | |
| xmlFreeDoc(doc); | |
| return dtd; | |
| } | |
| static xmlNodePtr create_xmlnode_from_domnode(struct xmldoc_context *context, struct domnode *node) | |
| { | |
| xmlNodePtr xmlnode = NULL, old_tree; | |
| xmlChar *name, *data; | |
| struct domnode *n; | |
| xmlDtdPtr dtd; | |
| name = node->name ? xmlchar_from_wchar(node->name) : NULL; | |
| data = node->data ? xmlchar_from_wchar(node->data) : NULL; | |
| switch (node->type) | |
| { | |
| case NODE_ELEMENT: | |
| xmlnode = create_xmlnode_element(context, node); | |
| break; | |
| case NODE_COMMENT: | |
| xmlnode = xmlNewDocComment(context->xmldoc, data); | |
| xmlAddChild(context->tree, xmlnode); | |
| break; | |
| case NODE_TEXT: | |
| xmlnode = xmlNewDocText(context->xmldoc, data); | |
| xmlAddChild(context->tree, xmlnode); | |
| break; | |
| case NODE_PROCESSING_INSTRUCTION: | |
| xmlnode = xmlNewDocPI(context->xmldoc, name, data); | |
| xmlAddChild(context->tree, xmlnode); | |
| break; | |
| case NODE_CDATA_SECTION: | |
| xmlnode = xmlNewCDataBlock(context->xmldoc, data, xmlStrlen(data)); | |
| xmlAddChild(context->tree, xmlnode); | |
| break; | |
| case NODE_DOCUMENT_TYPE: | |
| dtd = create_xmldtd_from_domnode(node); | |
| context->xmldoc->intSubset = dtd; | |
| break; | |
| default: | |
| FIXME("Not supported for node type %d.\n", node->type); | |
| break; | |
| } | |
| free(name); | |
| free(data); | |
| if (!xmlnode) | |
| return NULL; | |
| xmlnode->_private2 = node; | |
| if (context->node == node) | |
| *context->xmlnode = xmlnode; | |
| old_tree = context->tree; | |
| if (node->type == NODE_ELEMENT) context->tree = xmlnode; | |
| LIST_FOR_EACH_ENTRY(n, &node->children, struct domnode, entry) | |
| { | |
| create_xmlnode_from_domnode(context, n); | |
| } | |
| context->tree = old_tree; | |
| return xmlnode; | |
| } | |
| /* Create a whole libxml document tree, return pointer to corresponding node as well */ | |
| xmlDocPtr create_xmldoc_from_domdoc(struct domnode *node, xmlNodePtr *xmlnode) | |
| { | |
| struct xmldoc_context context = { 0 }; | |
| struct domnode *n, *doc; | |
| xmlDocPtr xmldoc; | |
| *xmlnode = NULL; | |
| doc = node_get_doc(node); | |
| xmldoc = xmlNewDoc(NULL); | |
| xmldoc->_private2 = doc; | |
| context.xmldoc = xmldoc; | |
| context.node = node; | |
| context.xmlnode = xmlnode; | |
| if (node == doc) | |
| *context.xmlnode = (xmlNodePtr)xmldoc; | |
| context.tree = (xmlNodePtr)xmldoc; | |
| LIST_FOR_EACH_ENTRY(n, &doc->children, struct domnode, entry) | |
| { | |
| create_xmlnode_from_domnode(&context, n); | |
| } | |
| return xmldoc; | |
| } | |
| HRESULT node_save(struct domnode *doc, IStream *stream) | |
| { | |
| struct node_dump_context context = { 0 }; | |
| struct domnode *child, *attr, *node; | |
| const WCHAR *encoding = NULL; | |
| UINT codepage; | |
| assert(doc->type == NODE_DOCUMENT); | |
| /* Get desired encoding from declaration. */ | |
| if ((child = domnode_get_first_child(doc))) | |
| { | |
| if (child->type == NODE_PROCESSING_INSTRUCTION && !wcscmp(child->name, L"xml")) | |
| { | |
| if (domnode_get_attribute(child, L"encoding", &attr) == S_OK) | |
| { | |
| if ((node = domnode_get_first_child(attr))) | |
| encoding = node->data; | |
| } | |
| } | |
| } | |
| if (!encoding) | |
| encoding = L"UTF-8"; | |
| TRACE("Using %s encoding.\n", debugstr_w(encoding)); | |
| if (!(codepage = get_codepage_for_encoding(encoding))) | |
| { | |
| FIXME("Unrecognized output encoding %s.\n", debugstr_w(encoding)); | |
| return E_FAIL; | |
| } | |
| node_dump_context_init(&context, codepage, stream); | |
| LIST_FOR_EACH_ENTRY(node, &doc->children, struct domnode, entry) | |
| { | |
| node_dump(node, &context); | |
| node_dump_append(&context, L"\r\n", 2); | |
| } | |
| return node_dump_context_cleanup(&context); | |
| } | |
| static HRESULT create_xpath_query_for_tagname(const BSTR name, BSTR *query) | |
| { | |
| struct string_buffer buffer; | |
| const WCHAR *p, *end; | |
| *query = NULL; | |
| string_buffer_init(&buffer); | |
| /* Special case - empty tagname - means select all nodes, | |
| except document itself. */ | |
| if (!*name) | |
| { | |
| string_append(&buffer, L"/descendant::node()", 19); | |
| return string_to_bstr(&buffer, query); | |
| } | |
| string_append(&buffer, L"descendant::", 12); | |
| p = name; | |
| while (p && *p) | |
| { | |
| switch (*p) | |
| { | |
| case '/': | |
| case '*': | |
| string_append(&buffer, p, 1); | |
| ++p; | |
| break; | |
| default: | |
| string_append(&buffer, L"*[local-name()='", 16); | |
| end = p; | |
| while (*end && *end != '/') | |
| ++end; | |
| string_append(&buffer, p, end - p); | |
| p = end; | |
| string_append(&buffer, L"']", 2); | |
| } | |
| } | |
| return string_to_bstr(&buffer, query); | |
| } | |
| HRESULT node_get_elements_by_tagname(struct domnode *node, BSTR name, IXMLDOMNodeList **list) | |
| { | |
| BSTR query; | |
| HRESULT hr; | |
| if (!name || !list) | |
| return E_INVALIDARG; | |
| hr = create_xpath_query_for_tagname(name, &query); | |
| if (SUCCEEDED(hr)) | |
| hr = create_selection(node, query, true, list); | |
| SysFreeString(query); | |
| return hr; | |
| } | |
| XDR_DT node_get_data_type(const struct domnode *node) | |
| { | |
| struct domnode *doc = node->owner, *attr; | |
| IXMLDOMSchemaCollection2 *schema; | |
| XDR_DT dt = DT_INVALID; | |
| HRESULT hr; | |
| BSTR value; | |
| if (node->type != NODE_ELEMENT) | |
| return dt; | |
| if (is_same_uri(node, L"urn:schemas-microsoft-com:datatypes")) | |
| { | |
| dt = bstr_to_dt(node->name, -1); | |
| } | |
| else | |
| { | |
| domnode_get_qualified_attribute(node, L"dt", L"urn:schemas-microsoft-com:datatypes", &attr); | |
| if (attr) | |
| { | |
| if (FAILED(hr = node_get_text(attr, &value))) | |
| return dt; | |
| dt = bstr_to_dt(value, -1); | |
| SysFreeString(value); | |
| } | |
| else if ((schema = doc->properties->schemaCache)) | |
| { | |
| dt = SchemaCache_get_node_dt(schema, node->name, node->uri); | |
| } | |
| } | |
| TRACE("=> dt:%s\n", debugstr_dt(dt)); | |
| return dt; | |
| } | |
| HRESULT create_attribute_node(const WCHAR *name, const WCHAR *uri, struct domnode *owner, IXMLDOMNode **ret) | |
| { | |
| struct domnode *attr; | |
| HRESULT hr; | |
| *ret = NULL; | |
| if (FAILED(hr = domnode_create(NODE_ATTRIBUTE, name, wcslen(name), uri, wcslen(uri), owner, &attr))) | |
| return hr; | |
| return create_node(attr, ret); | |
| } | |
| static void LIBXML2_LOG_CALLBACK validate_error(void* ctx, char const* msg, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, msg); | |
| LIBXML2_CALLBACK_ERR(domdoc_validateNode, msg, ap); | |
| va_end(ap); | |
| } | |
| static void LIBXML2_LOG_CALLBACK validate_warning(void* ctx, char const* msg, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, msg); | |
| LIBXML2_CALLBACK_WARN(domdoc_validateNode, msg, ap); | |
| va_end(ap); | |
| } | |
| HRESULT node_validate(struct domnode *doc, IXMLDOMNode *node_obj, IXMLDOMParseError **err) | |
| { | |
| IXMLDOMSchemaCollection2 *schema; | |
| struct domnode *node; | |
| LONG err_code = 0; | |
| HRESULT hr = S_OK; | |
| int validated = 0; | |
| xmlDocPtr xmldoc; | |
| xmlNodePtr xmlnode; | |
| if (!(node = get_node_obj(node_obj))) | |
| return E_FAIL; | |
| if (node->owner != doc && node != doc) | |
| { | |
| if (err) | |
| *err = create_parseError(err_code, NULL, NULL, NULL, 0, 0, 0); | |
| return E_FAIL; | |
| } | |
| /* TODO: for now simply treat empty document as not well-formed */ | |
| if (list_empty(&node->children)) | |
| { | |
| if (err) | |
| *err = create_parseError(E_XML_NOTWF, NULL, NULL, NULL, 0, 0, 0); | |
| return S_FALSE; | |
| } | |
| xmldoc = create_xmldoc_from_domdoc(node, &xmlnode); | |
| /* DTD validation */ | |
| if (xmldoc->intSubset || xmldoc->extSubset) | |
| { | |
| xmlValidCtxtPtr vctx = xmlNewValidCtxt(); | |
| int ret; | |
| vctx->error = validate_error; | |
| vctx->warning = validate_warning; | |
| ++validated; | |
| ret = node == doc ? xmlValidateDocument(vctx, xmldoc) : xmlValidateElement(vctx, xmldoc, xmlnode); | |
| if (!ret) | |
| { | |
| /* TODO: get a real error code here */ | |
| TRACE("DTD validation failed\n"); | |
| err_code = E_XML_INVALID; | |
| hr = S_FALSE; | |
| } | |
| xmlFreeValidCtxt(vctx); | |
| } | |
| /* Schema validation */ | |
| schema = doc->properties->schemaCache; | |
| if (hr == S_OK && schema) | |
| { | |
| hr = SchemaCache_validate_tree(schema, xmlnode); | |
| if (SUCCEEDED(hr)) | |
| { | |
| ++validated; | |
| /* TODO: get a real error code here */ | |
| if (hr == S_OK) | |
| { | |
| TRACE("schema validation succeeded\n"); | |
| } | |
| else | |
| { | |
| WARN("schema validation failed\n"); | |
| err_code = E_XML_INVALID; | |
| } | |
| } | |
| else | |
| { | |
| /* not really OK, just didn't find a schema for the ns */ | |
| hr = S_OK; | |
| } | |
| } | |
| xmlFreeDoc(xmldoc); | |
| if (!validated) | |
| { | |
| WARN("no DTD or schema found\n"); | |
| err_code = E_XML_NODTD; | |
| hr = S_FALSE; | |
| } | |
| if (err) | |
| *err = create_parseError(err_code, NULL, NULL, NULL, 0, 0, 0); | |
| return hr; | |
| } | |
| /* The only use case for this for loading documents. Original document node is preserved, | |
| together with its properties, and unlinked owned nodes. Current children nodes are | |
| unlinked, and replaced with children from the source. Such pattern makes sense only | |
| for document nodes. The key feature is to preserved owned unlinked nodes. */ | |
| void node_move_children(struct domnode *dest, struct domnode *src) | |
| { | |
| struct domnode *child, *next; | |
| domnode_unlink_children(dest); | |
| LIST_FOR_EACH_ENTRY_SAFE(child, next, &src->children, struct domnode, entry) | |
| { | |
| domnode_append_child(dest, child); | |
| } | |
| } | |
| HRESULT node_split_text(struct domnode *node, LONG offset, IXMLDOMText **out) | |
| { | |
| struct domnode *child; | |
| ULONG length; | |
| HRESULT hr; | |
| BSTR head; | |
| if (!out || offset < 0) | |
| return E_INVALIDARG; | |
| *out = NULL; | |
| length = SysStringLen(node->data); | |
| if (offset > length) | |
| return E_INVALIDARG; | |
| if (offset == length) | |
| return S_FALSE; | |
| if (FAILED(hr = domnode_create(node->type, NULL, 0, NULL, 0, node->owner, &child))) | |
| return hr; | |
| if (offset) | |
| { | |
| child->data = SysAllocStringLen(node->data + offset, length - offset); | |
| head = SysAllocStringLen(node->data, offset); | |
| SysFreeString(node->data); | |
| node->data = head; | |
| } | |
| else | |
| { | |
| child->data = node->data; | |
| node->data = NULL; | |
| } | |
| if (node->parent) | |
| domnode_insert_domnode(node->parent, child, domnode_get_next_sibling(node)); | |
| if (FAILED(hr = create_node(child, (IXMLDOMNode **)out))) | |
| domnode_unlink_child(child); | |
| return hr; | |
| } | |
| HRESULT node_delete_data(struct domnode *node, LONG offset, LONG count) | |
| { | |
| ULONG length = SysStringLen(node->data); | |
| BSTR str = NULL; | |
| if (offset < 0 || count < 0 || offset > length) | |
| return E_INVALIDARG; | |
| if (length == 0 || count == 0) | |
| return S_OK; | |
| if (offset == 0) | |
| { | |
| if (count < length) | |
| { | |
| str = SysAllocStringLen(node->data + count, length - count); | |
| if (!str) return E_OUTOFMEMORY; | |
| } | |
| } | |
| else | |
| { | |
| if (offset + count >= length) | |
| { | |
| str = SysAllocStringLen(node->data, offset); | |
| if (!str) return E_OUTOFMEMORY; | |
| } | |
| else | |
| { | |
| str = SysAllocStringLen(NULL, length - count); | |
| if (!str) return E_OUTOFMEMORY; | |
| memcpy(str, node->data, offset * sizeof(WCHAR)); | |
| memcpy(str + offset, node->data + offset + count, (length - count - offset) * sizeof(WCHAR)); | |
| str[length - count] = 0; | |
| } | |
| } | |
| SysFreeString(node->data); | |
| node->data = str; | |
| return S_OK; | |
| } | |
| HRESULT node_substring_data(struct domnode *node, LONG offset, LONG count, BSTR *p) | |
| { | |
| LONG length = SysStringLen(node->data); | |
| if (!p) | |
| return E_INVALIDARG; | |
| *p = NULL; | |
| if (offset < 0 || count < 0) | |
| return E_INVALIDARG; | |
| if (count == 0 || offset >= length) | |
| return S_FALSE; | |
| if (offset + count > length) | |
| *p = SysAllocString(node->data + offset); | |
| else | |
| *p = SysAllocStringLen(node->data + offset, count); | |
| return *p ? S_OK : E_OUTOFMEMORY; | |
| } | |
| HRESULT node_insert_data(struct domnode *node, LONG offset, BSTR data) | |
| { | |
| LONG length = SysStringLen(node->data); | |
| LONG data_length = SysStringLen(data); | |
| BSTR str; | |
| if (data_length == 0) | |
| return S_OK; | |
| if (offset < 0 || length < offset) | |
| return E_INVALIDARG; | |
| if (!(str = SysAllocStringLen(NULL, length + data_length))) return E_OUTOFMEMORY; | |
| memcpy(str, node->data, offset * sizeof(WCHAR)); | |
| memcpy(str + offset, data, data_length * sizeof(WCHAR)); | |
| memcpy(str + offset + data_length, node->data + offset, (length - offset) * sizeof(WCHAR)); | |
| str[length + data_length] = 0; | |
| SysFreeString(node->data); | |
| node->data = str; | |
| return S_OK; | |
| } | |
| /* TODO: might be worth it to unroll to avoid intermediate allocations */ | |
| HRESULT node_replace_data(struct domnode *node, LONG offset, LONG count, BSTR data) | |
| { | |
| HRESULT hr; | |
| if (FAILED(hr = node_delete_data(node, offset, count))) | |
| return hr; | |
| return node_insert_data(node, offset, data); | |
| } | |
| HRESULT node_get_data_length(struct domnode *node, LONG *length) | |
| { | |
| if (!length) | |
| return E_INVALIDARG; | |
| *length = SysStringLen(node->data); | |
| return S_OK; | |
| } |