2 * Schema cache implementation
4 * Copyright 2007 Huw Davies
5 * Copyright 2010 Adam Martinson for CodeWeavers
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
34 #include "wine/debug.h"
36 #include "msxml_private.h"
38 WINE_DEFAULT_DEBUG_CHANNEL(msxml);
40 /* We use a chained hashtable, which can hold any number of schemas
41 * TODO: versioned constructor
42 * TODO: grow/shrink hashtable depending on load factor
43 * TODO: implement read-only where appropriate
46 /* This is just the number of buckets, should be prime */
47 #define DEFAULT_HASHTABLE_SIZE 17
51 #include <libxml/tree.h>
52 #include <libxml/xmlschemas.h>
53 #include <libxml/schemasInternals.h>
54 #include <libxml/hash.h>
55 #include <libxml/parser.h>
56 #include <libxml/parserInternals.h>
57 #include <libxml/xmlIO.h>
59 xmlDocPtr XDR_to_XSD_doc(xmlDocPtr xdr_doc, xmlChar const* nsURI);
61 static const xmlChar XSD_schema[] = "schema";
62 static const xmlChar XSD_nsURI[] = "http://www.w3.org/2001/XMLSchema";
63 static const xmlChar XDR_schema[] = "Schema";
64 static const xmlChar XDR_nsURI[] = "urn:schemas-microsoft-com:xml-data";
65 static const xmlChar DT_nsURI[] = "urn:schemas-microsoft-com:datatypes";
67 static xmlChar const* datatypes_schema = NULL;
68 static HGLOBAL datatypes_handle = NULL;
69 static HRSRC datatypes_rsrc = NULL;
77 typedef enum _SCHEMA_TYPE {
83 typedef struct _schema_cache
85 const struct IXMLDOMSchemaCollection2Vtbl* lpVtbl;
86 xmlHashTablePtr cache;
90 typedef struct _cache_entry
98 typedef struct _cache_index_data
104 xmlExternalEntityLoader _external_entity_loader = NULL;
106 static xmlParserInputPtr external_entity_loader(const char *URL, const char *ID,
107 xmlParserCtxtPtr ctxt)
109 xmlParserInputPtr input;
111 TRACE("(%s, %s, %p)\n", wine_dbgstr_a(URL), wine_dbgstr_a(ID), ctxt);
113 assert(MSXML_hInstance != NULL);
114 assert(datatypes_rsrc != NULL);
115 assert(datatypes_handle != NULL);
116 assert(datatypes_schema != NULL);
118 /* TODO: if the desired schema is in the cache, load it from there */
119 if (lstrcmpA(URL, "urn:schemas-microsoft-com:datatypes") == 0)
121 TRACE("loading built-in schema for %s\n", URL);
122 input = xmlNewStringInputStream(ctxt, datatypes_schema);
126 input = _external_entity_loader(URL, ID, ctxt);
132 void schemasInit(void)
136 if (!(datatypes_rsrc = FindResourceA(MSXML_hInstance, "DATATYPES", "XML")))
138 FIXME("failed to find resource for %s\n", DT_nsURI);
142 if (!(datatypes_handle = LoadResource(MSXML_hInstance, datatypes_rsrc)))
144 FIXME("failed to load resource for %s\n", DT_nsURI);
147 buf = LockResource(datatypes_handle);
148 len = SizeofResource(MSXML_hInstance, datatypes_rsrc) - 1;
150 /* Resource is loaded as raw data,
151 * need a null-terminated string */
152 while (buf[len] != '>')
154 datatypes_schema = BAD_CAST buf;
156 if ((void*)xmlGetExternalEntityLoader() != (void*)external_entity_loader)
158 _external_entity_loader = xmlGetExternalEntityLoader();
159 xmlSetExternalEntityLoader(external_entity_loader);
163 void schemasCleanup(void)
165 if (datatypes_handle)
166 FreeResource(datatypes_handle);
167 xmlSetExternalEntityLoader(_external_entity_loader);
170 static LONG cache_entry_add_ref(cache_entry* entry)
172 LONG ref = InterlockedIncrement(&entry->ref);
173 TRACE("%p new ref %d\n", entry, ref);
177 static LONG cache_entry_release(cache_entry* entry)
179 LONG ref = InterlockedDecrement(&entry->ref);
180 TRACE("%p new ref %d\n", entry, ref);
184 if (entry->type == SCHEMA_TYPE_XSD)
186 xmldoc_release(entry->doc);
187 entry->schema->doc = NULL;
188 xmlSchemaFree(entry->schema);
191 else /* SCHEMA_TYPE_XDR */
193 xmldoc_release(entry->doc);
194 xmldoc_release(entry->schema->doc);
195 entry->schema->doc = NULL;
196 xmlSchemaFree(entry->schema);
203 static inline schema_cache* impl_from_IXMLDOMSchemaCollection2(IXMLDOMSchemaCollection2* iface)
205 return (schema_cache*)((char*)iface - FIELD_OFFSET(schema_cache, lpVtbl));
208 static inline SCHEMA_TYPE schema_type_from_xmlDocPtr(xmlDocPtr schema)
212 root = xmlDocGetRootElement(schema);
213 if (root && root->ns)
216 if (xmlStrEqual(root->name, XDR_schema) &&
217 xmlStrEqual(root->ns->href, XDR_nsURI))
219 return SCHEMA_TYPE_XDR;
221 else if (xmlStrEqual(root->name, XSD_schema) &&
222 xmlStrEqual(root->ns->href, XSD_nsURI))
224 return SCHEMA_TYPE_XSD;
227 return SCHEMA_TYPE_INVALID;
230 static BOOL link_datatypes(xmlDocPtr schema)
232 xmlNodePtr root, next, child;
235 assert((void*)xmlGetExternalEntityLoader() == (void*)external_entity_loader);
236 root = xmlDocGetRootElement(schema);
240 for (ns = root->nsDef; ns != NULL; ns = ns->next)
242 if (xmlStrEqual(ns->href, DT_nsURI))
249 next = xmlFirstElementChild(root);
250 child = xmlNewChild(root, NULL, BAD_CAST "import", NULL);
251 if (next) child = xmlAddPrevSibling(next, child);
252 xmlSetProp(child, BAD_CAST "namespace", DT_nsURI);
253 xmlSetProp(child, BAD_CAST "schemaLocation", DT_nsURI);
258 static cache_entry* cache_entry_from_url(char const* url, xmlChar const* nsURI)
260 cache_entry* entry = heap_alloc(sizeof(cache_entry));
261 xmlSchemaParserCtxtPtr spctx = xmlSchemaNewParserCtxt(url);
262 entry->type = SCHEMA_TYPE_XSD;
266 if((entry->schema = xmlSchemaParse(spctx)))
268 /* TODO: if the nsURI is different from the default xmlns or targetNamespace,
269 * do we need to do something special here? */
270 xmldoc_init(entry->schema->doc, &CLSID_DOMDocument40);
271 entry->doc = entry->schema->doc;
272 xmldoc_add_ref(entry->doc);
279 xmlSchemaFreeParserCtxt(spctx);
283 FIXME("schema for nsURI %s not found\n", wine_dbgstr_a(url));
290 static cache_entry* cache_entry_from_xsd_doc(xmlDocPtr doc, xmlChar const* nsURI)
292 cache_entry* entry = heap_alloc(sizeof(cache_entry));
293 xmlSchemaParserCtxtPtr spctx;
294 xmlDocPtr new_doc = xmlCopyDoc(doc, 1);
296 link_datatypes(new_doc);
298 /* TODO: if the nsURI is different from the default xmlns or targetNamespace,
299 * do we need to do something special here? */
300 entry->type = SCHEMA_TYPE_XSD;
302 spctx = xmlSchemaNewDocParserCtxt(new_doc);
304 if ((entry->schema = xmlSchemaParse(spctx)))
306 xmldoc_init(entry->schema->doc, &CLSID_DOMDocument40);
307 entry->doc = entry->schema->doc;
308 xmldoc_add_ref(entry->doc);
312 FIXME("failed to parse doc\n");
317 xmlSchemaFreeParserCtxt(spctx);
321 static cache_entry* cache_entry_from_xdr_doc(xmlDocPtr doc, xmlChar const* nsURI)
323 cache_entry* entry = heap_alloc(sizeof(cache_entry));
324 xmlSchemaParserCtxtPtr spctx;
325 xmlDocPtr new_doc = xmlCopyDoc(doc, 1), xsd_doc = XDR_to_XSD_doc(doc, nsURI);
327 link_datatypes(xsd_doc);
329 entry->type = SCHEMA_TYPE_XDR;
331 spctx = xmlSchemaNewDocParserCtxt(xsd_doc);
333 if ((entry->schema = xmlSchemaParse(spctx)))
335 entry->doc = new_doc;
336 xmldoc_init(entry->schema->doc, &CLSID_DOMDocument30);
337 xmldoc_init(entry->doc, &CLSID_DOMDocument30);
338 xmldoc_add_ref(entry->doc);
339 xmldoc_add_ref(entry->schema->doc);
343 FIXME("failed to parse doc\n");
349 xmlSchemaFreeParserCtxt(spctx);
354 static HRESULT WINAPI schema_cache_QueryInterface(IXMLDOMSchemaCollection2* iface,
355 REFIID riid, void** ppvObject)
357 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
359 TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppvObject);
361 if ( IsEqualIID(riid, &IID_IUnknown) ||
362 IsEqualIID(riid, &IID_IDispatch) ||
363 IsEqualIID(riid, &IID_IXMLDOMSchemaCollection) ||
364 IsEqualIID(riid, &IID_IXMLDOMSchemaCollection2) )
370 FIXME("interface %s not implemented\n", debugstr_guid(riid));
371 return E_NOINTERFACE;
374 IXMLDOMSchemaCollection2_AddRef(iface);
379 static ULONG WINAPI schema_cache_AddRef(IXMLDOMSchemaCollection2* iface)
381 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
382 LONG ref = InterlockedIncrement(&This->ref);
383 TRACE("%p new ref %d\n", This, ref);
387 static void cache_free(void* data, xmlChar* name /* ignored */)
389 cache_entry_release((cache_entry*)data);
392 static ULONG WINAPI schema_cache_Release(IXMLDOMSchemaCollection2* iface)
394 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
395 LONG ref = InterlockedDecrement(&This->ref);
396 TRACE("%p new ref %d\n", This, ref);
400 xmlHashFree(This->cache, cache_free);
407 static HRESULT WINAPI schema_cache_GetTypeInfoCount(IXMLDOMSchemaCollection2* iface,
410 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
412 TRACE("(%p)->(%p)\n", This, pctinfo);
419 static HRESULT WINAPI schema_cache_GetTypeInfo(IXMLDOMSchemaCollection2* iface,
420 UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo)
422 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
425 TRACE("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
427 hr = get_typeinfo(IXMLDOMSchemaCollection_tid, ppTInfo);
432 static HRESULT WINAPI schema_cache_GetIDsOfNames(IXMLDOMSchemaCollection2* iface,
433 REFIID riid, LPOLESTR* rgszNames,
434 UINT cNames, LCID lcid, DISPID* rgDispId)
436 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
440 TRACE("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames,
443 if(!rgszNames || cNames == 0 || !rgDispId)
446 hr = get_typeinfo(IXMLDOMSchemaCollection_tid, &typeinfo);
449 hr = ITypeInfo_GetIDsOfNames(typeinfo, rgszNames, cNames, rgDispId);
450 ITypeInfo_Release(typeinfo);
456 static HRESULT WINAPI schema_cache_Invoke(IXMLDOMSchemaCollection2* iface,
457 DISPID dispIdMember, REFIID riid, LCID lcid,
458 WORD wFlags, DISPPARAMS* pDispParams,
459 VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
462 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
466 TRACE("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
467 lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
469 hr = get_typeinfo(IXMLDOMSchemaCollection_tid, &typeinfo);
472 hr = ITypeInfo_Invoke(typeinfo, &(This->lpVtbl), dispIdMember, wFlags, pDispParams,
473 pVarResult, pExcepInfo, puArgErr);
474 ITypeInfo_Release(typeinfo);
480 static HRESULT WINAPI schema_cache_add(IXMLDOMSchemaCollection2* iface, BSTR uri, VARIANT var)
482 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
483 xmlChar* name = xmlChar_from_wchar(uri);
484 TRACE("(%p)->(%s, var(vt %x))\n", This, debugstr_w(uri), V_VT(&var));
490 xmlHashRemoveEntry(This->cache, name, cache_free);
496 xmlChar* url = xmlChar_from_wchar(V_BSTR(&var));
497 cache_entry* entry = cache_entry_from_url((char const*)url, name);
502 cache_entry_add_ref(entry);
510 xmlHashRemoveEntry(This->cache, name, cache_free);
511 xmlHashAddEntry(This->cache, name, entry);
517 xmlDocPtr doc = NULL;
520 IXMLDOMNode* domnode = NULL;
521 IDispatch_QueryInterface(V_DISPATCH(&var), &IID_IXMLDOMNode, (void**)&domnode);
524 doc = xmlNodePtr_from_domnode(domnode, XML_DOCUMENT_NODE)->doc;
528 IXMLDOMNode_Release(domnode);
532 type = schema_type_from_xmlDocPtr(doc);
534 if (type == SCHEMA_TYPE_XSD)
536 entry = cache_entry_from_xsd_doc(doc, name);
538 else if (type == SCHEMA_TYPE_XDR)
540 entry = cache_entry_from_xdr_doc(doc, name);
544 WARN("invalid schema!\n");
548 IXMLDOMNode_Release(domnode);
552 cache_entry_add_ref(entry);
560 xmlHashRemoveEntry(This->cache, name, cache_free);
561 xmlHashAddEntry(This->cache, name, entry);
575 static HRESULT WINAPI schema_cache_get(IXMLDOMSchemaCollection2* iface, BSTR uri,
578 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
581 TRACE("(%p)->(%s, %p)\n", This, wine_dbgstr_w(uri), node);
586 name = xmlChar_from_wchar(uri);
587 entry = (cache_entry*) xmlHashLookup(This->cache, name);
590 /* TODO: this should be read-only */
592 return DOMDocument_create_from_xmldoc(entry->doc, (IXMLDOMDocument3**)node);
598 static HRESULT WINAPI schema_cache_remove(IXMLDOMSchemaCollection2* iface, BSTR uri)
600 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
601 xmlChar* name = xmlChar_from_wchar(uri);
602 TRACE("(%p)->(%s)\n", This, wine_dbgstr_w(uri));
604 xmlHashRemoveEntry(This->cache, name, cache_free);
609 static HRESULT WINAPI schema_cache_get_length(IXMLDOMSchemaCollection2* iface, LONG* length)
611 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
612 TRACE("(%p)->(%p)\n", This, length);
616 *length = xmlHashSize(This->cache);
620 static void cache_index(void* data /* ignored */, void* index, xmlChar* name)
622 cache_index_data* index_data = (cache_index_data*)index;
624 if (index_data->index-- == 0)
625 *index_data->out = bstr_from_xmlChar(name);
628 static HRESULT WINAPI schema_cache_get_namespaceURI(IXMLDOMSchemaCollection2* iface,
629 LONG index, BSTR* len)
631 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
632 cache_index_data data = {index,len};
633 TRACE("(%p)->(%i, %p)\n", This, index, len);
639 if (index >= xmlHashSize(This->cache))
642 xmlHashScan(This->cache, cache_index, &data);
646 static void cache_copy(void* data, void* dest, xmlChar* name)
648 schema_cache* This = (schema_cache*) dest;
649 cache_entry* entry = (cache_entry*) data;
651 if (xmlHashLookup(This->cache, name) == NULL)
653 cache_entry_add_ref(entry);
654 xmlHashAddEntry(This->cache, name, entry);
658 static HRESULT WINAPI schema_cache_addCollection(IXMLDOMSchemaCollection2* iface,
659 IXMLDOMSchemaCollection* otherCollection)
661 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
662 schema_cache* That = impl_from_IXMLDOMSchemaCollection2((IXMLDOMSchemaCollection2*)otherCollection);
663 TRACE("(%p)->(%p)\n", This, That);
665 if (!otherCollection)
668 /* TODO: detect errors while copying & return E_FAIL */
669 xmlHashScan(That->cache, cache_copy, This);
674 static HRESULT WINAPI schema_cache_get__newEnum(IXMLDOMSchemaCollection2* iface,
683 static HRESULT WINAPI schema_cache_validate(IXMLDOMSchemaCollection2* iface)
689 static HRESULT WINAPI schema_cache_put_validateOnLoad(IXMLDOMSchemaCollection2* iface,
690 VARIANT_BOOL validateOnLoad)
696 static HRESULT WINAPI schema_cache_get_validateOnLoad(IXMLDOMSchemaCollection2* iface,
697 VARIANT_BOOL* validateOnLoad)
703 static HRESULT WINAPI schema_cache_getSchema(IXMLDOMSchemaCollection2* iface,
704 BSTR namespaceURI, ISchema** schema)
712 static HRESULT WINAPI schema_cache_getDeclaration(IXMLDOMSchemaCollection2* iface,
713 IXMLDOMNode* node, ISchemaItem** item)
721 static const struct IXMLDOMSchemaCollection2Vtbl schema_cache_vtbl =
723 schema_cache_QueryInterface,
725 schema_cache_Release,
726 schema_cache_GetTypeInfoCount,
727 schema_cache_GetTypeInfo,
728 schema_cache_GetIDsOfNames,
733 schema_cache_get_length,
734 schema_cache_get_namespaceURI,
735 schema_cache_addCollection,
736 schema_cache_get__newEnum,
737 schema_cache_validate,
738 schema_cache_put_validateOnLoad,
739 schema_cache_get_validateOnLoad,
740 schema_cache_getSchema,
741 schema_cache_getDeclaration
744 static void LIBXML2_LOG_CALLBACK validate_error(void* ctx, char const* msg, ...)
748 LIBXML2_CALLBACK_ERR(SchemaCache_validate_tree, msg, ap);
752 static void LIBXML2_LOG_CALLBACK validate_warning(void* ctx, char const* msg, ...)
756 LIBXML2_CALLBACK_WARN(SchemaCache_validate_tree, msg, ap);
760 #ifdef HAVE_XMLSCHEMASSETVALIDSTRUCTUREDERRORS
761 static void validate_serror(void* ctx, xmlErrorPtr err)
763 LIBXML2_CALLBACK_SERROR(SchemaCache_validate_tree, err);
767 HRESULT SchemaCache_validate_tree(IXMLDOMSchemaCollection2* iface, xmlNodePtr tree)
769 schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
771 xmlChar const* ns = NULL;
772 TRACE("(%p, %p)\n", This, tree);
777 if ((xmlNodePtr)tree->doc == tree)
779 xmlNodePtr root = xmlDocGetRootElement(tree->doc);
780 if (root && root->ns)
788 entry = (ns != NULL)? xmlHashLookup(This->cache, ns) :
789 xmlHashLookup(This->cache, BAD_CAST "");
790 /* TODO: if the ns is not in the cache, and it's a URL,
791 * do we try to load from that? */
794 xmlSchemaValidCtxtPtr svctx;
796 /* TODO: if validateOnLoad property is false,
797 * we probably need to validate the schema here. */
798 svctx = xmlSchemaNewValidCtxt(entry->schema);
799 xmlSchemaSetValidErrors(svctx, validate_error, validate_warning, NULL);
800 #ifdef HAVE_XMLSCHEMASSETVALIDSTRUCTUREDERRORS
801 xmlSchemaSetValidStructuredErrors(svctx, validate_serror, NULL);
804 if ((xmlNodePtr)tree->doc == tree)
805 err = xmlSchemaValidateDoc(svctx, (xmlDocPtr)tree);
807 err = xmlSchemaValidateOneElement(svctx, tree);
809 xmlSchemaFreeValidCtxt(svctx);
810 return err? S_FALSE : S_OK;
816 HRESULT SchemaCache_create(IUnknown* pUnkOuter, void** ppObj)
818 schema_cache* This = heap_alloc(sizeof(schema_cache));
820 return E_OUTOFMEMORY;
822 This->lpVtbl = &schema_cache_vtbl;
823 This->cache = xmlHashCreate(DEFAULT_HASHTABLE_SIZE);
826 *ppObj = &This->lpVtbl;
832 HRESULT SchemaCache_create(IUnknown* pUnkOuter, void** ppObj)
834 MESSAGE("This program tried to use a SchemaCache object, but\n"
835 "libxml2 support was not present at compile time.\n");