text: /*
   * Schema cache implementation
   *
   * Copyright 2007 Huw Davies
   * Copyright 2010 Adam Martinson for CodeWeavers
   *
   * 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 <assert.h>
  #include <stdarg.h>
  #include <libxml/xmlerror.h>
  #include <libxml/tree.h>
  #include <libxml/xmlschemas.h>
  #include <libxml/schemasInternals.h>
  #include <libxml/hash.h>
  #include <libxml/parser.h>
  #include <libxml/parserInternals.h>
  #include <libxml/xmlIO.h>
  #include <libxml/xmlversion.h>
  #include <libxml/xpath.h>
  
  #include "windef.h"
  #include "winbase.h"
  #include "winuser.h"
  #include "ole2.h"
  #include "msxml6.h"
  
  #include "wine/debug.h"
  
  #include "msxml_private.h"
  
  WINE_DEFAULT_DEBUG_CHANNEL(msxml);
  
  /* We use a chained hashtable, which can hold any number of schemas
   * TODO: grow/shrink hashtable depending on load factor
   * TODO: implement read-only where appropriate
   */
  
  /* This is just the number of buckets, should be prime */
  #define DEFAULT_HASHTABLE_SIZE 17
  
  xmlDocPtr XDR_to_XSD_doc(xmlDocPtr xdr_doc, xmlChar const* nsURI);
  
  static const xmlChar XSD_schema[] = "schema";
  static const xmlChar XSD_nsURI[] = "http://www.w3.org/2001/XMLSchema";
  static const xmlChar XDR_schema[] = "Schema";
  static const xmlChar XDR_nsURI[] = "urn:schemas-microsoft-com:xml-data";
  static const xmlChar DT_nsURI[] = "urn:schemas-microsoft-com:datatypes";
  
  static xmlChar *        datatypes_src;
  static int              datatypes_len;
  static HGLOBAL          datatypes_handle;
  static HRSRC            datatypes_rsrc;
  static xmlSchemaPtr     datatypes_schema;
  
  static const WCHAR      emptyW[] = {0};
  
  /* Supported types:
   * msxml3 - XDR only
   * msxml4 - XDR & XSD
   * msxml5 - XDR & XSD
   * mxsml6 - XSD only
   *
   * CacheType_NS is a special type used for read-only collection build with
   * IXMLDOMDocument2::namespaces()
   */
  typedef enum  {
      CacheEntryType_Invalid,
      CacheEntryType_XDR,
      CacheEntryType_XSD,
      CacheEntryType_NS
  } CacheEntryType;
  
  typedef struct
  {
      DispatchEx dispex;
      IXMLDOMSchemaCollection2 IXMLDOMSchemaCollection2_iface;
      LONG ref;
  
      MSXML_VERSION version;
      xmlHashTablePtr cache;
      xmlChar **uris;
      int allocated;
      int count;
  
      VARIANT_BOOL validateOnLoad;
      int read_only;
  } schema_cache;
  
  typedef struct
  {
      CacheEntryType type;
      xmlSchemaPtr schema;
      xmlDocPtr doc;
      LONG ref;
  } cache_entry;
  
  static const tid_t schema_cache_se_tids[] = {
      IXMLDOMSchemaCollection_tid,
      IXMLDOMSchemaCollection2_tid,
      NULL_tid
  };
  
  /* datatypes lookup stuff
   * generated with help from gperf */
  #define DT_MIN_STR_LEN 2
  #define DT_MAX_STR_LEN 11
  #define DT_MIN_HASH_VALUE 2
  #define DT_MAX_HASH_VALUE 115
  
  static const xmlChar DT_bin_base64[] = "bin.base64";
  static const xmlChar DT_bin_hex[] = "bin.hex";
  static const xmlChar DT_boolean[] = "boolean";
  static const xmlChar DT_char[] = "char";
  static const xmlChar DT_date[] = "date";
  static const xmlChar DT_date_tz[] = "date.tz";
  static const xmlChar DT_dateTime[] = "dateTime";
  static const xmlChar DT_dateTime_tz[] = "dateTime.tz";
  static const xmlChar DT_entity[] = "entity";
  static const xmlChar DT_entities[] = "entities";
  static const xmlChar DT_enumeration[] = "enumeration";
  static const xmlChar DT_fixed_14_4[] = "fixed.14.4";
  static const xmlChar DT_float[] = "float";
  static const xmlChar DT_i1[] = "i1";
  static const xmlChar DT_i2[] = "i2";
  static const xmlChar DT_i4[] = "i4";
  static const xmlChar DT_i8[] = "i8";
  static const xmlChar DT_id[] = "id";
  static const xmlChar DT_idref[] = "idref";
  static const xmlChar DT_idrefs[] = "idrefs";
  static const xmlChar DT_int[] = "int";
  static const xmlChar DT_nmtoken[] = "nmtoken";
  static const xmlChar DT_nmtokens[] = "nmtokens";
  static const xmlChar DT_notation[] = "notation";
  static const xmlChar DT_number[] = "number";
  static const xmlChar DT_r4[] = "r4";
  static const xmlChar DT_r8[] = "r8";
  static const xmlChar DT_string[] = "string";
  static const xmlChar DT_time[] = "time";
  static const xmlChar DT_time_tz[] = "time.tz";
  static const xmlChar DT_ui1[] = "ui1";
  static const xmlChar DT_ui2[] = "ui2";
  static const xmlChar DT_ui4[] = "ui4";
  static const xmlChar DT_ui8[] = "ui8";
  static const xmlChar DT_uri[] = "uri";
  static const xmlChar DT_uuid[] = "uuid";
  
  static const OLECHAR wDT_bin_base64[] = {'b','i','n','.','b','a','s','e','6','4',0};
  static const OLECHAR wDT_bin_hex[] = {'b','i','n','.','h','e','x',0};
  static const OLECHAR wDT_boolean[] = {'b','o','o','l','e','a','n',0};
  static const OLECHAR wDT_char[] = {'c','h','a','r',0};
  static const OLECHAR wDT_date[] = {'d','a','t','e',0};
  static const OLECHAR wDT_date_tz[] = {'d','a','t','e','.','t','z',0};
  static const OLECHAR wDT_dateTime[] = {'d','a','t','e','T','i','m','e',0};
  static const OLECHAR wDT_dateTime_tz[] = {'d','a','t','e','T','i','m','e','.','t','z',0};
  static const OLECHAR wDT_entity[] = {'e','n','t','i','t','y',0};
  static const OLECHAR wDT_entities[] = {'e','n','t','i','t','i','e','s',0};
  static const OLECHAR wDT_enumeration[] = {'e','n','u','m','e','r','a','t','i','o','n',0};
  static const OLECHAR wDT_fixed_14_4[] = {'f','i','x','e','d','.','1','4','.','4',0};
  static const OLECHAR wDT_float[] = {'f','l','o','a','t',0};
  static const OLECHAR wDT_i1[] = {'i','1',0};
  static const OLECHAR wDT_i2[] = {'i','2',0};
  static const OLECHAR wDT_i4[] = {'i','4',0};
  static const OLECHAR wDT_i8[] = {'i','8',0};
  static const OLECHAR wDT_id[] = {'i','d',0};
  static const OLECHAR wDT_idref[] = {'i','d','r','e','f',0};
  static const OLECHAR wDT_idrefs[] = {'i','d','r','e','f','s',0};
  static const OLECHAR wDT_int[] = {'i','n','t',0};
  static const OLECHAR wDT_nmtoken[] = {'n','m','t','o','k','e','n',0};
  static const OLECHAR wDT_nmtokens[] = {'n','m','t','o','k','e','n','s',0};
  static const OLECHAR wDT_notation[] = {'n','o','t','a','t','i','o','n',0};
  static const OLECHAR wDT_number[] = {'n','u','m','b','e','r',0};
  static const OLECHAR wDT_r4[] = {'r','4',0};
  static const OLECHAR wDT_r8[] = {'r','8',0};
  static const OLECHAR wDT_string[] = {'s','t','r','i','n','g',0};
  static const OLECHAR wDT_time[] = {'t','i','m','e',0};
  static const OLECHAR wDT_time_tz[] = {'t','i','m','e','.','t','z',0};
  static const OLECHAR wDT_ui1[] = {'u','i','1',0};
  static const OLECHAR wDT_ui2[] = {'u','i','2',0};
  static const OLECHAR wDT_ui4[] = {'u','i','4',0};
  static const OLECHAR wDT_ui8[] = {'u','i','8',0};
  static const OLECHAR wDT_uri[] = {'u','r','i',0};
  static const OLECHAR wDT_uuid[] = {'u','u','i','d',0};
  
  static const BYTE hash_assoc_values[] =
  {
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116,  10, 116, 116,  55,
       45, 116,   5, 116,   0, 116,   0, 116, 116, 116,
      116, 116, 116, 116, 116,   5,   0,   0,  20,   0,
        0,  10,   0,   0, 116,   0,   0,   0,  15,   5,
      116, 116,  10,   0,   0,   0, 116, 116,   0,   0,
       10, 116, 116, 116, 116, 116, 116,   5,   0,   0,
       20,   0,   0,  10,   0,   0, 116,   0,   0,   0,
       15,   5, 116, 116,  10,   0,   0,   0, 116, 116,
        0,   0,  10, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
      116, 116, 116, 116, 116, 116
  };
  
  static void LIBXML2_LOG_CALLBACK parser_error(void* ctx, char const* msg, ...)
  {
      va_list ap;
      va_start(ap, msg);
      LIBXML2_CALLBACK_ERR(Schema_parse, msg, ap);
      va_end(ap);
  }
  
  static void LIBXML2_LOG_CALLBACK parser_warning(void* ctx, char const* msg, ...)
  {
      va_list ap;
      va_start(ap, msg);
      LIBXML2_CALLBACK_WARN(Schema_parse, msg, ap);
      va_end(ap);
  }
  
  static void parser_serror(void* ctx, xmlErrorPtr err)
  {
      LIBXML2_CALLBACK_SERROR(Schema_parse, err);
  }
  
  static inline xmlSchemaPtr Schema_parse(xmlSchemaParserCtxtPtr spctx)
  {
      TRACE("(%p)\n", spctx);
  
      xmlSchemaSetParserErrors(spctx, parser_error, parser_warning, NULL);
      xmlSchemaSetParserStructuredErrors(spctx, parser_serror, NULL);
      return xmlSchemaParse(spctx);
  }
  
  static void LIBXML2_LOG_CALLBACK validate_error(void* ctx, char const* msg, ...)
  {
      va_list ap;
      va_start(ap, msg);
      LIBXML2_CALLBACK_ERR(Schema_validate_tree, 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(Schema_validate_tree, msg, ap);
      va_end(ap);
  }
  
  static void validate_serror(void* ctx, xmlErrorPtr err)
  {
      LIBXML2_CALLBACK_SERROR(Schema_validate_tree, err);
  }
  
  static HRESULT schema_cache_get_item(IUnknown *iface, LONG index, VARIANT *item)
  {
      V_VT(item) = VT_BSTR;
      return IXMLDOMSchemaCollection2_get_namespaceURI((IXMLDOMSchemaCollection2*)iface, index, &V_BSTR(item));
  }
  
  static const struct enumvariant_funcs schemacache_enumvariant = {
      schema_cache_get_item,
      NULL
  };
  
  static inline HRESULT Schema_validate_tree(xmlSchemaPtr schema, xmlNodePtr tree)
  {
      xmlSchemaValidCtxtPtr svctx;
      int err;
  
      TRACE("(%p, %p)\n", schema, tree);
      /* TODO: if validateOnLoad property is false,
       *       we probably need to validate the schema here. */
      svctx = xmlSchemaNewValidCtxt(schema);
      xmlSchemaSetValidErrors(svctx, validate_error, validate_warning, NULL);
      xmlSchemaSetValidStructuredErrors(svctx, validate_serror, NULL);
  
      if (tree->type == XML_DOCUMENT_NODE)
          err = xmlSchemaValidateDoc(svctx, (xmlDocPtr)tree);
      else
          err = xmlSchemaValidateOneElement(svctx, tree);
  
      xmlSchemaFreeValidCtxt(svctx);
      return err? S_FALSE : S_OK;
  }
  
  static DWORD dt_hash(xmlChar const* str, int len /* calculated if -1 */)
  {
      DWORD hval = (len == -1)? xmlStrlen(str) : len;
  
      switch (hval)
      {
          default:
              hval += hash_assoc_values[str[10]];
              /*FALLTHROUGH*/
          case 10:
              hval += hash_assoc_values[str[9]];
              /*FALLTHROUGH*/
          case 9:
              hval += hash_assoc_values[str[8]];
              /*FALLTHROUGH*/
          case 8:
              hval += hash_assoc_values[str[7]];
              /*FALLTHROUGH*/
          case 7:
              hval += hash_assoc_values[str[6]];
              /*FALLTHROUGH*/
          case 6:
              hval += hash_assoc_values[str[5]];
              /*FALLTHROUGH*/
          case 5:
              hval += hash_assoc_values[str[4]];
              /*FALLTHROUGH*/
          case 4:
              hval += hash_assoc_values[str[3]];
              /*FALLTHROUGH*/
          case 3:
              hval += hash_assoc_values[str[2]];
              /*FALLTHROUGH*/
          case 2:
              hval += hash_assoc_values[str[1]];
              /*FALLTHROUGH*/
          case 1:
              hval += hash_assoc_values[str[0]];
              break;
      }
      return hval;
  }
  
  static DWORD dt_hash_bstr(OLECHAR const* bstr, int len /* calculated if -1 */)
  {
      DWORD hval = (len == -1)? lstrlenW(bstr) : len;
  
      switch (hval)
      {
          default:
              hval += (bstr[10] & 0xFF00)? 116 : hash_assoc_values[bstr[10]];
              /*FALLTHROUGH*/
          case 10:
              hval += (bstr[9] & 0xFF00)? 116 : hash_assoc_values[bstr[9]];
              /*FALLTHROUGH*/
          case 9:
              hval += (bstr[8] & 0xFF00)? 116 : hash_assoc_values[bstr[8]];
              /*FALLTHROUGH*/
          case 8:
              hval += (bstr[7] & 0xFF00)? 116 : hash_assoc_values[bstr[7]];
              /*FALLTHROUGH*/
          case 7:
              hval += (bstr[6] & 0xFF00)? 116 : hash_assoc_values[bstr[6]];
              /*FALLTHROUGH*/
          case 6:
              hval += (bstr[5] & 0xFF00)? 116 : hash_assoc_values[bstr[5]];
              /*FALLTHROUGH*/
          case 5:
              hval += (bstr[4] & 0xFF00)? 116 : hash_assoc_values[bstr[4]];
              /*FALLTHROUGH*/
          case 4:
              hval += (bstr[3] & 0xFF00)? 116 : hash_assoc_values[bstr[3]];
              /*FALLTHROUGH*/
          case 3:
              hval += (bstr[2] & 0xFF00)? 116 : hash_assoc_values[bstr[2]];
              /*FALLTHROUGH*/
          case 2:
              hval += (bstr[1] & 0xFF00)? 116 : hash_assoc_values[bstr[1]];
              /*FALLTHROUGH*/
          case 1:
              hval += (bstr[0] & 0xFF00)? 116 : hash_assoc_values[bstr[0]];
              break;
      }
      return hval;
  }
  
  static const xmlChar *const DT_string_table[LAST_DT] =
  {
      DT_bin_base64,
      DT_bin_hex,
      DT_boolean,
      DT_char,
      DT_date,
      DT_date_tz,
      DT_dateTime,
      DT_dateTime_tz,
      DT_entity,
      DT_entities,
      DT_enumeration,
      DT_fixed_14_4,
      DT_float,
      DT_i1,
      DT_i2,
      DT_i4,
      DT_i8,
      DT_id,
      DT_idref,
      DT_idrefs,
      DT_int,
      DT_nmtoken,
      DT_nmtokens,
      DT_notation,
      DT_number,
      DT_r4,
      DT_r8,
      DT_string,
      DT_time,
      DT_time_tz,
      DT_ui1,
      DT_ui2,
      DT_ui4,
      DT_ui8,
      DT_uri,
      DT_uuid
  };
  
  static const WCHAR *const DT_wstring_table[LAST_DT] =
  {
      wDT_bin_base64,
      wDT_bin_hex,
      wDT_boolean,
      wDT_char,
      wDT_date,
      wDT_date_tz,
      wDT_dateTime,
      wDT_dateTime_tz,
      wDT_entity,
      wDT_entities,
      wDT_enumeration,
      wDT_fixed_14_4,
      wDT_float,
      wDT_i1,
      wDT_i2,
      wDT_i4,
      wDT_i8,
      wDT_id,
      wDT_idref,
      wDT_idrefs,
      wDT_int,
      wDT_nmtoken,
      wDT_nmtokens,
      wDT_notation,
      wDT_number,
      wDT_r4,
      wDT_r8,
      wDT_string,
      wDT_time,
      wDT_time_tz,
      wDT_ui1,
      wDT_ui2,
      wDT_ui4,
      wDT_ui8,
      wDT_uri,
      wDT_uuid
  };
  
  static const XDR_DT DT_lookup_table[] =
  {
      -1, -1,
      DT_I8,
      DT_UI8,
      DT_TIME,
      -1, -1,
      DT_I4,
      DT_UI4,
      -1, -1, -1,
      DT_R8,
      DT_URI,
      -1,
      DT_FLOAT,
      -1,
      DT_R4,
      DT_INT,
      DT_CHAR,
      -1,
      DT_ENTITY,
      DT_ID,
      DT_ENTITIES,
      DT_UUID,
      -1, -1,
      DT_TIME_TZ,
      -1,
      DT_DATE,
      -1,
      DT_NUMBER,
      DT_BIN_HEX,
      DT_DATETIME,
      -1,
      DT_IDREF,
      DT_IDREFS,
      DT_BOOLEAN,
      -1, -1, -1,
      DT_STRING,
      DT_NMTOKEN,
      DT_NMTOKENS,
      -1,
      DT_BIN_BASE64,
      -1,
      DT_I2,
      DT_UI2,
      -1, -1, -1,
      DT_DATE_TZ,
      DT_NOTATION,
      -1, -1,
      DT_DATETIME_TZ,
      DT_I1,
      DT_UI1,
      -1, -1,
      DT_ENUMERATION,
      -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1,
      DT_FIXED_14_4
  };
  
  XDR_DT str_to_dt(xmlChar const* str, int len /* calculated if -1 */)
  {
      DWORD hash = dt_hash(str, len);
      XDR_DT dt = DT_INVALID;
  
      if (hash <= DT_MAX_HASH_VALUE)
          dt = DT_lookup_table[hash];
  
      if (dt != DT_INVALID && xmlStrcasecmp(str, DT_string_table[dt]) == 0)
          return dt;
  
      return DT_INVALID;
  }
  
  XDR_DT bstr_to_dt(OLECHAR const* bstr, int len /* calculated if -1 */)
  {
      DWORD hash = dt_hash_bstr(bstr, len);
      XDR_DT dt = DT_INVALID;
  
      if (hash <= DT_MAX_HASH_VALUE)
          dt = DT_lookup_table[hash];
  
      if (dt != DT_INVALID && lstrcmpiW(bstr, DT_wstring_table[dt]) == 0)
          return dt;
  
      return DT_INVALID;
  }
  
  xmlChar const* dt_to_str(XDR_DT dt)
  {
      if (dt == DT_INVALID)
          return NULL;
  
      return DT_string_table[dt];
  }
  
  OLECHAR const* dt_to_bstr(XDR_DT dt)
  {
      if (dt == DT_INVALID)
          return NULL;
  
      return DT_wstring_table[dt];
  }
  
  const char* debugstr_dt(XDR_DT dt)
  {
      return debugstr_a(dt != DT_INVALID ? (const char*)DT_string_table[dt] : NULL);
  }
  
  HRESULT dt_validate(XDR_DT dt, xmlChar const* content)
  {
      xmlDocPtr tmp_doc;
      xmlNodePtr node;
      xmlNsPtr ns;
      HRESULT hr;
  
      TRACE("(dt:%s, %s)\n", debugstr_dt(dt), debugstr_a((char const*)content));
  
      if (!datatypes_schema)
      {
          xmlSchemaParserCtxtPtr spctx;
          assert(datatypes_src != NULL);
          spctx = xmlSchemaNewMemParserCtxt((char const*)datatypes_src, datatypes_len);
          datatypes_schema = Schema_parse(spctx);
          xmlSchemaFreeParserCtxt(spctx);
      }
  
      switch (dt)
      {
          case DT_INVALID:
              return E_FAIL;
          case DT_BIN_BASE64:
          case DT_BIN_HEX:
          case DT_BOOLEAN:
          case DT_CHAR:
          case DT_DATE:
          case DT_DATE_TZ:
          case DT_DATETIME:
          case DT_DATETIME_TZ:
          case DT_FIXED_14_4:
          case DT_FLOAT:
          case DT_I1:
          case DT_I2:
          case DT_I4:
          case DT_I8:
          case DT_INT:
          case DT_NMTOKEN:
          case DT_NMTOKENS:
          case DT_NUMBER:
          case DT_R4:
          case DT_R8:
          case DT_STRING:
          case DT_TIME:
          case DT_TIME_TZ:
          case DT_UI1:
          case DT_UI2:
          case DT_UI4:
          case DT_UI8:
          case DT_URI:
          case DT_UUID:
              if (!datatypes_schema)
              {
                  ERR("failed to load schema for urn:schemas-microsoft-com:datatypes, "
                      "you're probably using an old version of libxml2: " LIBXML_DOTTED_VERSION "\n");
  
                  /* Hopefully they don't need much in the way of XDR datatypes support... */
                  return S_OK;
              }
  
              if (content && xmlStrlen(content))
              {
                  tmp_doc = xmlNewDoc(NULL);
                  node = xmlNewChild((xmlNodePtr)tmp_doc, NULL, dt_to_str(dt), content);
                  ns = xmlNewNs(node, DT_nsURI, BAD_CAST "dt");
                  xmlSetNs(node, ns);
                  xmlDocSetRootElement(tmp_doc, node);
  
                  hr = Schema_validate_tree(datatypes_schema, (xmlNodePtr)tmp_doc);
                  xmlFreeDoc(tmp_doc);
              }
              else
              {   /* probably the node is being created manually and has no content yet */
                  hr = S_OK;
              }
              return hr;
          default:
              FIXME("need to handle dt:%s\n", debugstr_dt(dt));
              return S_OK;
      }
  }
  
  static inline xmlChar const* get_node_nsURI(xmlNodePtr node)
  {
      return (node->ns != NULL)? node->ns->href : NULL;
  }
  
  static inline cache_entry* get_entry(schema_cache* This, xmlChar const* nsURI)
  {
      return (!nsURI)? xmlHashLookup(This->cache, BAD_CAST "") :
                       xmlHashLookup(This->cache, nsURI);
  }
  
  static inline xmlSchemaPtr get_node_schema(schema_cache* This, xmlNodePtr node)
  {
      cache_entry* entry = get_entry(This, get_node_nsURI(node));
      return (!entry)? NULL : entry->schema;
  }
  
  static xmlExternalEntityLoader _external_entity_loader;
  
  static xmlParserInputPtr external_entity_loader(const char *URL, const char *ID,
                                                  xmlParserCtxtPtr ctxt)
  {
      xmlParserInputPtr input;
  
      TRACE("(%s %s %p)\n", debugstr_a(URL), debugstr_a(ID), ctxt);
  
      assert(MSXML_hInstance != NULL);
      assert(datatypes_rsrc != NULL);
      assert(datatypes_handle != NULL);
      assert(datatypes_src != NULL);
  
      /* TODO: if the desired schema is in the cache, load it from there */
      if (lstrcmpA(URL, "urn:schemas-microsoft-com:datatypes") == 0)
      {
          TRACE("loading built-in schema for %s\n", URL);
          input = xmlNewStringInputStream(ctxt, datatypes_src);
      }
      else
      {
          input = _external_entity_loader(URL, ID, ctxt);
      }
  
      return input;
  }
  
  void schemasInit(void)
  {
      xmlChar* buf;
      if (!(datatypes_rsrc = FindResourceA(MSXML_hInstance, "DATATYPES", "XML")))
      {
          FIXME("failed to find resource for %s\n", DT_nsURI);
          return;
      }
  
      if (!(datatypes_handle = LoadResource(MSXML_hInstance, datatypes_rsrc)))
      {
          FIXME("failed to load resource for %s\n", DT_nsURI);
          return;
      }
      buf = LockResource(datatypes_handle);
      datatypes_len = SizeofResource(MSXML_hInstance, datatypes_rsrc);
  
      /* Resource is loaded as raw data,
       * need a null-terminated string */
      while (buf[datatypes_len - 1] != '>') datatypes_len--;
      datatypes_src = heap_alloc(datatypes_len + 1);
      memcpy(datatypes_src, buf, datatypes_len);
      datatypes_src[datatypes_len] = 0;
  
      if (xmlGetExternalEntityLoader() != external_entity_loader)
      {
          _external_entity_loader = xmlGetExternalEntityLoader();
          xmlSetExternalEntityLoader(external_entity_loader);
      }
  }
  
  void schemasCleanup(void)
  {
      xmlSchemaFree(datatypes_schema);
      heap_free(datatypes_src);
      xmlSetExternalEntityLoader(_external_entity_loader);
  }
  
  static LONG cache_entry_add_ref(cache_entry* entry)
  {
      LONG ref = InterlockedIncrement(&entry->ref);
      TRACE("(%p)->(%d)\n", entry, ref);
      return ref;
  }
  
  static LONG cache_entry_release(cache_entry* entry)
  {
      LONG ref = InterlockedDecrement(&entry->ref);
      TRACE("(%p)->(%d)\n", entry, ref);
  
      if (ref == 0)
      {
          if (entry->type == CacheEntryType_XSD)
          {
              xmldoc_release(entry->doc);
              entry->schema->doc = NULL;
              xmlSchemaFree(entry->schema);
          }
          else if (entry->type == CacheEntryType_XDR)
          {
              xmldoc_release(entry->doc);
              xmldoc_release(entry->schema->doc);
              entry->schema->doc = NULL;
              xmlSchemaFree(entry->schema);
          }
  
          heap_free(entry);
      }
      return ref;
  }
  
  static const struct IXMLDOMSchemaCollection2Vtbl XMLDOMSchemaCollection2Vtbl;
  
  static inline schema_cache* impl_from_IXMLDOMSchemaCollection2(IXMLDOMSchemaCollection2* iface)
  {
      return CONTAINING_RECORD(iface, schema_cache, IXMLDOMSchemaCollection2_iface);
  }
  
  static inline schema_cache* impl_from_IXMLDOMSchemaCollection(IXMLDOMSchemaCollection* iface)
  {
      return CONTAINING_RECORD(iface, schema_cache, IXMLDOMSchemaCollection2_iface);
  }
  
  static inline schema_cache* unsafe_impl_from_IXMLDOMSchemaCollection(IXMLDOMSchemaCollection *iface)
  {
      return iface->lpVtbl == (void*)&XMLDOMSchemaCollection2Vtbl ? impl_from_IXMLDOMSchemaCollection(iface) : NULL;
  }
  
  static inline CacheEntryType cache_type_from_xmlDocPtr(xmlDocPtr schema)
  {
      xmlNodePtr root = NULL;
      if (schema)
          root = xmlDocGetRootElement(schema);
      if (root && root->ns)
      {
  
          if (xmlStrEqual(root->name, XDR_schema) &&
              xmlStrEqual(root->ns->href, XDR_nsURI))
          {
              return CacheEntryType_XDR;
          }
          else if (xmlStrEqual(root->name, XSD_schema) &&
                   xmlStrEqual(root->ns->href, XSD_nsURI))
          {
              return CacheEntryType_XSD;
          }
      }
      return CacheEntryType_Invalid;
  }
  
  static BOOL link_datatypes(xmlDocPtr schema)
  {
      xmlNodePtr root, next, child;
      xmlNsPtr ns;
  
      assert(xmlGetExternalEntityLoader() == external_entity_loader);
      root = xmlDocGetRootElement(schema);
      if (!root)
          return FALSE;
  
      for (ns = root->nsDef; ns != NULL; ns = ns->next)
      {
          if (xmlStrEqual(ns->href, DT_nsURI))
              break;
      }
  
      if (!ns)
          return FALSE;
  
      next = xmlFirstElementChild(root);
      child = xmlNewChild(root, NULL, BAD_CAST "import", NULL);
      if (next) child = xmlAddPrevSibling(next, child);
      xmlSetProp(child, BAD_CAST "namespace", DT_nsURI);
      xmlSetProp(child, BAD_CAST "schemaLocation", DT_nsURI);
  
      return TRUE;
  }
  
  static cache_entry* cache_entry_from_xsd_doc(xmlDocPtr doc, xmlChar const* nsURI, MSXML_VERSION v)
  {
      cache_entry* entry = heap_alloc(sizeof(cache_entry));
      xmlSchemaParserCtxtPtr spctx;
      xmlDocPtr new_doc = xmlCopyDoc(doc, 1);
  
      link_datatypes(new_doc);
  
      /* TODO: if the nsURI is different from the default xmlns or targetNamespace,
       *       do we need to do something special here? */
      entry->type = CacheEntryType_XSD;
      entry->ref = 0;
      spctx = xmlSchemaNewDocParserCtxt(new_doc);
  
      if ((entry->schema = Schema_parse(spctx)))
      {
          xmldoc_init(entry->schema->doc, v);
          entry->doc = entry->schema->doc;
          xmldoc_add_ref(entry->doc);
      }
      else
      {
          FIXME("failed to parse doc\n");
          xmlFreeDoc(new_doc);
          heap_free(entry);
          entry = NULL;
      }
      xmlSchemaFreeParserCtxt(spctx);
      return entry;
  }
  
  static cache_entry* cache_entry_from_xdr_doc(xmlDocPtr doc, xmlChar const* nsURI, MSXML_VERSION version)
  {
      cache_entry* entry = heap_alloc(sizeof(cache_entry));
      xmlSchemaParserCtxtPtr spctx;
      xmlDocPtr new_doc = xmlCopyDoc(doc, 1), xsd_doc = XDR_to_XSD_doc(doc, nsURI);
  
      link_datatypes(xsd_doc);
  
      entry->type = CacheEntryType_XDR;
      entry->ref = 0;
      spctx = xmlSchemaNewDocParserCtxt(xsd_doc);
  
      if ((entry->schema = Schema_parse(spctx)))
      {
          entry->doc = new_doc;
          xmldoc_init(entry->schema->doc, version);
          xmldoc_init(entry->doc, version);
          xmldoc_add_ref(entry->doc);
          xmldoc_add_ref(entry->schema->doc);
      }
      else
      {
          FIXME("failed to parse doc\n");
          xmlFreeDoc(new_doc);
          xmlFreeDoc(xsd_doc);
          heap_free(entry);
          entry = NULL;
      }
      xmlSchemaFreeParserCtxt(spctx);
  
      return entry;
  }
  
  static cache_entry* cache_entry_from_url(VARIANT url, xmlChar const* nsURI, MSXML_VERSION version)
  {
      cache_entry* entry;
      IXMLDOMDocument3* domdoc = NULL;
      xmlDocPtr doc = NULL;
      HRESULT hr = dom_document_create(version, (void **)&domdoc);
      VARIANT_BOOL b = VARIANT_FALSE;
      CacheEntryType type = CacheEntryType_Invalid;
  
      if (hr != S_OK)
      {
          FIXME("failed to create domdoc\n");
          return NULL;
      }
      assert(domdoc != NULL);
      assert(V_VT(&url) == VT_BSTR);
  
      hr = IXMLDOMDocument3_load(domdoc, url, &b);
      if (hr != S_OK)
      {
          ERR("IXMLDOMDocument3_load() returned 0x%08x\n", hr);
          if (b != VARIANT_TRUE)
          {
              FIXME("Failed to load doc at %s\n", debugstr_w(V_BSTR(&url)));
              IXMLDOMDocument3_Release(domdoc);
              return NULL;
          }
      }
      doc = xmlNodePtr_from_domnode((IXMLDOMNode*)domdoc, XML_DOCUMENT_NODE)->doc;
      type = cache_type_from_xmlDocPtr(doc);
  
      switch (type)
      {
          case CacheEntryType_XSD:
              entry = cache_entry_from_xsd_doc(doc, nsURI, version);
              break;
          case CacheEntryType_XDR:
              entry = cache_entry_from_xdr_doc(doc, nsURI, version);
              break;
          default:
              entry = NULL;
              FIXME("invalid schema\n");
              break;
      }
      IXMLDOMDocument3_Release(domdoc);
  
      return entry;
  }
  
  static void cache_free(void* data, const xmlChar* name /* ignored */)
  {
      cache_entry_release((cache_entry*)data);
  }
  
  /* returns index or -1 if not found */
  static int cache_free_uri(schema_cache *cache, const xmlChar *uri)
  {
      int i;
  
      for (i = 0; i < cache->count; i++)
          if (xmlStrEqual(cache->uris[i], uri))
          {
              heap_free(cache->uris[i]);
              return i;
          }
  
      return -1;
  }
  
  static void cache_add_entry(schema_cache *cache, const xmlChar *uri, cache_entry *entry)
  {
      int i;
  
      /* meaning no entry found with this name */
      if (xmlHashRemoveEntry(cache->cache, uri, cache_free))
      {
          if (cache->count == cache->allocated)
          {
              cache->allocated *= 2;
              cache->uris = heap_realloc(cache->uris, cache->allocated*sizeof(xmlChar*));
          }
          i = cache->count++;
      }
      else
          i = cache_free_uri(cache, uri);
  
      cache->uris[i] = heap_strdupxmlChar(uri);
      xmlHashAddEntry(cache->cache, uri, entry);
  }
  
  static void cache_remove_entry(schema_cache *cache, const xmlChar *uri)
  {
      /* adjust index if entry was really removed */
      if (xmlHashRemoveEntry(cache->cache, uri, cache_free) == 0)
      {
          int i = cache_free_uri(cache, uri);
          if (i == -1) return;
          /* shift array */
          if (i != --cache->count)
              memmove(&cache->uris[i], &cache->uris[i+1], (cache->count-i)*sizeof(xmlChar*));
      }
  }
  
  /* This one adds all namespaces defined in document to a cache, without anything
     associated with uri obviously.
     Unfortunately namespace:: axis implementation in libxml2 differs from what we need,
     it uses additional node type to describe namespace definition attribute while
     in msxml it's expected to be a normal attribute - as a workaround document is
     queried at libxml2 level here. */
  HRESULT cache_from_doc_ns(IXMLDOMSchemaCollection2 *iface, xmlnode *node)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      static const xmlChar query[] = "//*/namespace::*";
      xmlXPathObjectPtr nodeset;
      xmlXPathContextPtr ctxt;
  
      This->read_only = 1;
  
      ctxt = xmlXPathNewContext(node->node->doc);
  
      nodeset = xmlXPathEvalExpression(query, ctxt);
      xmlXPathFreeContext(ctxt);
  
      if (nodeset)
      {
          int pos = 0, len = xmlXPathNodeSetGetLength(nodeset->nodesetval);
  
          while (pos < len)
          {
              xmlNodePtr node = xmlXPathNodeSetItem(nodeset->nodesetval, pos);
              if (node->type == XML_NAMESPACE_DECL)
              {
                  static const xmlChar defns[] = "http://www.w3.org/XML/1998/namespace";
                  xmlNsPtr ns = (xmlNsPtr)node;
                  cache_entry *entry;
  
                  /* filter out default uri */
                  if (xmlStrEqual(ns->href, defns))
                  {
                      pos++;
                      continue;
                  }
  
                  entry = heap_alloc(sizeof(cache_entry));
                  entry->type = CacheEntryType_NS;
                  entry->ref = 1;
                  entry->schema = NULL;
                  entry->doc = NULL;
  
                  cache_add_entry(This, ns->href, entry);
              }
              pos++;
          }
  
          xmlXPathFreeObject(nodeset);
      }
  
      return S_OK;
  }
  
  static HRESULT WINAPI schema_cache_QueryInterface(IXMLDOMSchemaCollection2* iface,
                                                    REFIID riid, void** ppvObject)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
  
      TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppvObject);
  
      if ( IsEqualIID(riid, &IID_IUnknown) ||
           IsEqualIID(riid, &IID_IDispatch) ||
           IsEqualIID(riid, &IID_IXMLDOMSchemaCollection) ||
           IsEqualIID(riid, &IID_IXMLDOMSchemaCollection2) )
      {
          *ppvObject = iface;
      }
      else if(This->version == MSXML6 && IsEqualIID(riid, &CLSID_XMLSchemaCache60))
      {
          /*
           * Version 6 can be queried for an interface with IID equal to CLSID.
           * There is no public interface with that IID and returned pointer
           * is equal to returned IXMLDOMSchemaCollection2 iface. We assume
           * that it's just another way for querying IXMLDOMSchemaCollection2
           * interface. Office 2013 ClickToRun installer uses this.
           */
          WARN("riid CLSID_XMLSchemaCache60, returning IXMLDOMSchemaCollection2 interface.\n");
          *ppvObject = iface;
      }
      else if (dispex_query_interface(&This->dispex, riid, ppvObject))
      {
          return *ppvObject ? S_OK : E_NOINTERFACE;
      }
      else if(IsEqualGUID( riid, &IID_ISupportErrorInfo ))
      {
          return node_create_supporterrorinfo(schema_cache_se_tids, ppvObject);
      }
      else
      {
          FIXME("interface %s not implemented\n", debugstr_guid(riid));
          *ppvObject = NULL;
          return E_NOINTERFACE;
      }
  
      IXMLDOMSchemaCollection2_AddRef(iface);
  
      return S_OK;
  }
  
  static ULONG WINAPI schema_cache_AddRef(IXMLDOMSchemaCollection2* iface)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      LONG ref = InterlockedIncrement(&This->ref);
      TRACE("(%p)->(%d)\n", This, ref);
      return ref;
  }
  
  static ULONG WINAPI schema_cache_Release(IXMLDOMSchemaCollection2* iface)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      LONG ref = InterlockedDecrement(&This->ref);
      TRACE("(%p)->(%d)\n", This, ref);
  
      if (ref == 0)
      {
          int i;
  
          for (i = 0; i < This->count; i++)
              heap_free(This->uris[i]);
          heap_free(This->uris);
          xmlHashFree(This->cache, cache_free);
          heap_free(This);
      }
  
      return ref;
  }
  
  static HRESULT WINAPI schema_cache_GetTypeInfoCount(IXMLDOMSchemaCollection2* iface,
                                                      UINT* pctinfo)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      return IDispatchEx_GetTypeInfoCount(&This->dispex.IDispatchEx_iface, pctinfo);
  }
  
  static HRESULT WINAPI schema_cache_GetTypeInfo(IXMLDOMSchemaCollection2* iface,
                                                 UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      return IDispatchEx_GetTypeInfo(&This->dispex.IDispatchEx_iface,
          iTInfo, lcid, ppTInfo);
  }
  
  static HRESULT WINAPI schema_cache_GetIDsOfNames(IXMLDOMSchemaCollection2* iface,
                                                   REFIID riid, LPOLESTR* rgszNames,
                                                   UINT cNames, LCID lcid, DISPID* rgDispId)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      return IDispatchEx_GetIDsOfNames(&This->dispex.IDispatchEx_iface,
          riid, rgszNames, cNames, lcid, rgDispId);
  }
  
  static HRESULT WINAPI schema_cache_Invoke(IXMLDOMSchemaCollection2* iface,
                                            DISPID dispIdMember, REFIID riid, LCID lcid,
                                            WORD wFlags, DISPPARAMS* pDispParams,
                                            VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
                                            UINT* puArgErr)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      return IDispatchEx_Invoke(&This->dispex.IDispatchEx_iface,
          dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
  }
  
  static HRESULT WINAPI schema_cache_add(IXMLDOMSchemaCollection2* iface, BSTR uri, VARIANT var)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      xmlChar* name;
  
      TRACE("(%p)->(%s %s)\n", This, debugstr_w(uri), debugstr_variant(&var));
  
      if (This->read_only) return E_FAIL;
  
      name = uri ? xmlchar_from_wchar(uri) : xmlchar_from_wchar(emptyW);
  
      switch (V_VT(&var))
      {
          case VT_NULL:
              {
                  cache_remove_entry(This, name);
              }
              break;
  
          case VT_BSTR:
              {
                  cache_entry* entry = cache_entry_from_url(var, name, This->version);
  
                  if (entry)
                  {
                      cache_entry_add_ref(entry);
                  }
                  else
                  {
                      heap_free(name);
                      return E_FAIL;
                  }
  
                  cache_add_entry(This, name, entry);
              }
              break;
  
          case VT_DISPATCH:
          case VT_UNKNOWN:
              {
                  xmlDocPtr doc = NULL;
                  cache_entry* entry;
                  CacheEntryType type;
                  IXMLDOMNode* domnode = NULL;
                  IUnknown_QueryInterface(V_UNKNOWN(&var), &IID_IXMLDOMNode, (void**)&domnode);
  
                  if (domnode)
                  {
                      DOMNodeType type;
  
                      IXMLDOMNode_get_nodeType(domnode, &type);
                      switch (type)
                      {
                      case NODE_ELEMENT:
                      {
                          IXMLDOMDocument *domdoc;
                          VARIANT_BOOL b;
                          BSTR xml;
  
                          IXMLDOMNode_get_xml(domnode, &xml);
                          dom_document_create(This->version, (void **)&domdoc);
                          IXMLDOMDocument_loadXML(domdoc, xml, &b);
                          SysFreeString(xml);
                          doc = xmlNodePtr_from_domnode((IXMLDOMNode*)domdoc, XML_DOCUMENT_NODE)->doc;
                          break;
                      }
                      default:
                          doc = xmlNodePtr_from_domnode(domnode, XML_DOCUMENT_NODE)->doc;
                          break;
                      }
                  }
  
                  if (!doc)
                  {
                      IXMLDOMNode_Release(domnode);
                      heap_free(name);
                      return E_INVALIDARG;
                  }
                  type = cache_type_from_xmlDocPtr(doc);
  
                  if (type == CacheEntryType_XSD)
                  {
                      entry = cache_entry_from_xsd_doc(doc, name, This->version);
                  }
                  else if (type == CacheEntryType_XDR)
                  {
                      entry = cache_entry_from_xdr_doc(doc, name, This->version);
                  }
                  else
                  {
                      WARN("invalid schema!\n");
                      entry = NULL;
                  }
  
                  IXMLDOMNode_Release(domnode);
  
                  if (entry)
                  {
                      cache_entry_add_ref(entry);
                  }
                  else
                  {
                      heap_free(name);
                      return E_FAIL;
                  }
  
                  cache_add_entry(This, name, entry);
              }
              break;
  
          default:
              FIXME("arg type is not supported, %s\n", debugstr_variant(&var));
              heap_free(name);
              return E_INVALIDARG;
      }
      heap_free(name);
      return S_OK;
  }
  
  static HRESULT WINAPI schema_cache_get(IXMLDOMSchemaCollection2* iface, BSTR uri,
                                         IXMLDOMNode** node)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      cache_entry* entry;
      xmlChar* name;
  
      TRACE("(%p)->(%s %p)\n", This, debugstr_w(uri), node);
  
      if (This->version == MSXML6)
      {
          if (node) *node = NULL;
          return E_NOTIMPL;
      }
  
      if (!node)
          return E_POINTER;
  
      *node = NULL;
  
      name = uri ? xmlchar_from_wchar(uri) : xmlchar_from_wchar(emptyW);
      entry = (cache_entry*) xmlHashLookup(This->cache, name);
      heap_free(name);
  
      /* TODO: this should be read-only */
      if (entry && entry->doc)
          return get_domdoc_from_xmldoc(entry->doc, (IXMLDOMDocument3**)node);
  
      return S_OK;
  }
  
  static HRESULT WINAPI schema_cache_remove(IXMLDOMSchemaCollection2* iface, BSTR uri)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      xmlChar* name;
  
      TRACE("(%p)->(%s)\n", This, debugstr_w(uri));
  
      if (This->version == MSXML6) return E_NOTIMPL;
  
      name = uri ? xmlchar_from_wchar(uri) : xmlchar_from_wchar(emptyW);
      cache_remove_entry(This, name);
      heap_free(name);
      return S_OK;
  }
  
  static HRESULT WINAPI schema_cache_get_length(IXMLDOMSchemaCollection2* iface, LONG* length)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      TRACE("(%p)->(%p)\n", This, length);
  
      if (!length)
          return E_POINTER;
  
      *length = This->count;
      return S_OK;
  }
  
  static HRESULT WINAPI schema_cache_get_namespaceURI(IXMLDOMSchemaCollection2* iface,
                                                      LONG index, BSTR* uri)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
  
      TRACE("(%p)->(%i %p)\n", This, index, uri);
  
      if (!uri)
          return E_POINTER;
  
      if (This->version == MSXML6)
          *uri = NULL;
  
      if (index >= This->count)
          return E_FAIL;
  
      *uri = bstr_from_xmlChar(This->uris[index]);
      return S_OK;
  }
  
  static void cache_copy(void* data, void* dest, const xmlChar* name)
  {
      schema_cache* This = (schema_cache*) dest;
      cache_entry* entry = (cache_entry*) data;
  
      if (xmlHashLookup(This->cache, name) == NULL)
      {
          cache_entry_add_ref(entry);
          cache_add_entry(This, name, entry);
      }
  }
  
  static HRESULT WINAPI schema_cache_addCollection(IXMLDOMSchemaCollection2* iface,
                                                   IXMLDOMSchemaCollection* collection)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      schema_cache* That;
  
      TRACE("(%p)->(%p)\n", This, collection);
  
      if (!collection)
          return E_POINTER;
  
      That = unsafe_impl_from_IXMLDOMSchemaCollection(collection);
      if (!That)
      {
          ERR("external collection implementation\n");
          return E_FAIL;
      }
  
      /* TODO: detect errors while copying & return E_FAIL */
      xmlHashScan(That->cache, cache_copy, This);
  
      return S_OK;
  }
  
  static HRESULT WINAPI schema_cache_get__newEnum(IXMLDOMSchemaCollection2* iface, IUnknown** enumv)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      TRACE("(%p)->(%p)\n", This, enumv);
      return create_enumvariant((IUnknown*)iface, TRUE, &schemacache_enumvariant, (IEnumVARIANT**)enumv);
  }
  
  static HRESULT WINAPI schema_cache_validate(IXMLDOMSchemaCollection2* iface)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      FIXME("(%p): stub\n", This);
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI schema_cache_put_validateOnLoad(IXMLDOMSchemaCollection2* iface,
                                                        VARIANT_BOOL value)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      FIXME("(%p)->(%d): stub\n", This, value);
  
      This->validateOnLoad = value;
      /* it's ok to disable it, cause we don't validate on load anyway */
      if (value == VARIANT_FALSE) return S_OK;
  
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI schema_cache_get_validateOnLoad(IXMLDOMSchemaCollection2* iface,
                                                        VARIANT_BOOL* value)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      TRACE("(%p)->(%p)\n", This, value);
  
      if (!value) return E_POINTER;
      *value = This->validateOnLoad;
  
      return S_OK;
  }
  
  static HRESULT WINAPI schema_cache_getSchema(IXMLDOMSchemaCollection2* iface,
                                               BSTR namespaceURI, ISchema** schema)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      FIXME("(%p)->(%s %p): stub\n", This, debugstr_w(namespaceURI), schema);
      if (schema)
          *schema = NULL;
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI schema_cache_getDeclaration(IXMLDOMSchemaCollection2* iface,
                                                    IXMLDOMNode* node, ISchemaItem** item)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      FIXME("(%p)->(%p %p): stub\n", This, node, item);
      if (item)
          *item = NULL;
      return E_NOTIMPL;
  }
  
  static const struct IXMLDOMSchemaCollection2Vtbl XMLDOMSchemaCollection2Vtbl =
  {
      schema_cache_QueryInterface,
      schema_cache_AddRef,
      schema_cache_Release,
      schema_cache_GetTypeInfoCount,
      schema_cache_GetTypeInfo,
      schema_cache_GetIDsOfNames,
      schema_cache_Invoke,
      schema_cache_add,
      schema_cache_get,
      schema_cache_remove,
      schema_cache_get_length,
      schema_cache_get_namespaceURI,
      schema_cache_addCollection,
      schema_cache_get__newEnum,
      schema_cache_validate,
      schema_cache_put_validateOnLoad,
      schema_cache_get_validateOnLoad,
      schema_cache_getSchema,
      schema_cache_getDeclaration
  };
  
  static xmlSchemaElementPtr lookup_schema_elemDecl(xmlSchemaPtr schema, xmlNodePtr node)
  {
      xmlSchemaElementPtr decl = NULL;
      xmlChar const* nsURI = get_node_nsURI(node);
  
      TRACE("(%p, %p)\n", schema, node);
  
      if (xmlStrEqual(nsURI, schema->targetNamespace))
          decl = xmlHashLookup(schema->elemDecl, node->name);
  
      if (!decl && xmlHashSize(schema->schemasImports) > 1)
      {
          FIXME("declaration not found in main schema - need to check schema imports!\n");
          /*xmlSchemaImportPtr import;
          if (nsURI == NULL)
              import = xmlHashLookup(schema->schemasImports, XML_SCHEMAS_NO_NAMESPACE);
          else
              import = xmlHashLookup(schema->schemasImports, node->ns->href);
  
          if (import != NULL)
              decl = xmlHashLookup(import->schema->elemDecl, node->name);*/
      }
  
      return decl;
  }
  
  static inline xmlNodePtr lookup_schema_element(xmlSchemaPtr schema, xmlNodePtr node)
  {
      xmlSchemaElementPtr decl = lookup_schema_elemDecl(schema, node);
      while (decl != NULL && decl->refDecl != NULL)
          decl = decl->refDecl;
      return (decl != NULL)? decl->node : NULL;
  }
  
  HRESULT SchemaCache_validate_tree(IXMLDOMSchemaCollection2* iface, xmlNodePtr tree)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      xmlSchemaPtr schema;
  
      TRACE("(%p, %p)\n", This, tree);
  
      if (!tree)
          return E_POINTER;
  
      if (tree->type == XML_DOCUMENT_NODE)
          tree = xmlDocGetRootElement(tree->doc);
  
      schema = get_node_schema(This, tree);
      /* TODO: if the ns is not in the cache, and it's a URL,
       *       do we try to load from that? */
      if (schema)
          return Schema_validate_tree(schema, tree);
      else
          WARN("no schema found for xmlns=%s\n", get_node_nsURI(tree));
  
      return E_FAIL;
  }
  
  XDR_DT SchemaCache_get_node_dt(IXMLDOMSchemaCollection2* iface, xmlNodePtr node)
  {
      schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
      xmlSchemaPtr schema = get_node_schema(This, node);
      XDR_DT dt = DT_INVALID;
  
      TRACE("(%p, %p)\n", This, node);
  
      if (node->ns && xmlStrEqual(node->ns->href, DT_nsURI))
      {
          dt = str_to_dt(node->name, -1);
      }
      else if (schema)
      {
          xmlChar* str;
          xmlNodePtr schema_node = lookup_schema_element(schema, node);
  
          str = xmlGetNsProp(schema_node, BAD_CAST "dt", DT_nsURI);
          if (str)
          {
              dt = str_to_dt(str, -1);
              xmlFree(str);
          }
      }
  
      return dt;
  }
  
  static const tid_t schemacache_iface_tids[] = {
      IXMLDOMSchemaCollection2_tid,
      0
  };
  
  static dispex_static_data_t schemacache_dispex = {
      NULL,
      IXMLDOMSchemaCollection2_tid,
      NULL,
      schemacache_iface_tids
  };
  
  HRESULT SchemaCache_create(MSXML_VERSION version, void** obj)
  {
      schema_cache* This = heap_alloc(sizeof(schema_cache));
      if (!This)
          return E_OUTOFMEMORY;
  
      TRACE("(%d %p)\n", version, obj);
  
      This->IXMLDOMSchemaCollection2_iface.lpVtbl = &XMLDOMSchemaCollection2Vtbl;
      This->cache = xmlHashCreate(DEFAULT_HASHTABLE_SIZE);
      This->allocated = 10;
      This->count = 0;
      This->uris = heap_alloc(This->allocated*sizeof(xmlChar*));
      This->ref = 1;
      This->version = version;
      This->validateOnLoad = VARIANT_TRUE;
      This->read_only = 0;
      init_dispex(&This->dispex, (IUnknown*)&This->IXMLDOMSchemaCollection2_iface, &schemacache_dispex);
  
      *obj = &This->IXMLDOMSchemaCollection2_iface;
      return S_OK;
  }
  