/*
 * XML test
 *
 * Copyright 2005 Mike McCormack for CodeWeavers
 * Copyright 2007-2008 Alistair Leslie-Hughes
 * Copyright 2010-2011 Adam Martinson for CodeWeavers
 * Copyright 2010-2013 Nikolay Sivov for CodeWeavers
 * Copyright 2023 Daniel Lehman
 *
 * 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
#define CONST_VTABLE

#include <stdio.h>
#include <assert.h>

#include "windows.h"

#include "initguid.h"
#include "msxml2.h"

#include "wine/test.h"

static BSTR alloced_bstrs[256];
static int alloced_bstrs_count;

static BSTR _bstr_(const WCHAR *str)
{
    assert(alloced_bstrs_count < ARRAY_SIZE(alloced_bstrs));
    alloced_bstrs[alloced_bstrs_count] = SysAllocString(str);
    return alloced_bstrs[alloced_bstrs_count++];
}

static void free_bstrs(void)
{
    int i;
    for (i = 0; i < alloced_bstrs_count; i++)
        SysFreeString(alloced_bstrs[i]);
    alloced_bstrs_count = 0;
}

struct attrtest_t {
    const WCHAR *name;
    const WCHAR *uri;
    const WCHAR *prefix;
    const WCHAR *href;
};

static struct attrtest_t attrtests[] = {
    { L"xmlns", L"http://www.w3.org/2000/xmlns/", L"xmlns", L"xmlns" },
    { L"xmlns", L"nondefaulturi", L"xmlns", L"xmlns" },
    { L"c", L"http://www.w3.org/2000/xmlns/", NULL, L"http://www.w3.org/2000/xmlns/" },
    { L"c", L"nsref1", NULL, L"nsref1" },
    { L"ns:c", L"nsref1", L"ns", L"nsref1" },
    { L"xmlns:c", L"http://www.w3.org/2000/xmlns/", L"xmlns", L"" },
    { L"xmlns:c", L"nondefaulturi", L"xmlns", L"" },
    { 0 }
};

/* see dlls/msxml[36]/tests/domdoc.c */
static void test_create_attribute(void)
{
    struct attrtest_t *ptr = attrtests;
    IXMLDOMElement *el;
    IXMLDOMDocument2 *doc;
    IXMLDOMNode *node;
    VARIANT var;
    HRESULT hr;
    int i = 0;
    BSTR str;

    hr = CoCreateInstance(&CLSID_DOMDocument40, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument2, (void **)&doc);
    ok(hr == S_OK, "Failed to create DOMDocument40, hr %#lx.\n", hr);

    while (ptr->name)
    {
        V_VT(&var) = VT_I1;
        V_I1(&var) = NODE_ATTRIBUTE;
        hr = IXMLDOMDocument2_createNode(doc, var, _bstr_(ptr->name), _bstr_(ptr->uri), &node);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        str = NULL;
        hr = IXMLDOMNode_get_prefix(node, &str);
        if (ptr->prefix)
        {
            /* MSXML4 can report different results with different service packs */
            ok(hr == S_OK || broken(hr == S_FALSE), "Failed to get prefix, hr %#lx.\n", hr);
            ok(!lstrcmpW(str, _bstr_(ptr->prefix)) || broken(!str), "got %s\n", wine_dbgstr_w(str));
        }
        else
        {
            ok(hr == S_FALSE, "%d: unexpected hr %#lx\n", i, hr);
            ok(str == NULL, "%d: got prefix %s\n", i, wine_dbgstr_w(str));
        }
        SysFreeString(str);

        str = NULL;
        hr = IXMLDOMNode_get_namespaceURI(node, &str);
        ok(hr == S_OK, "%d: unexpected hr %#lx\n", i, hr);
        ok(!lstrcmpW(str, _bstr_(ptr->href)), "%d: got uri %s, expected %s\n",
            i, wine_dbgstr_w(str), wine_dbgstr_w(ptr->href));
        SysFreeString(str);

        IXMLDOMNode_Release(node);
        free_bstrs();

        i++;
        ptr++;
    }

    V_VT(&var) = VT_I1;
    V_I1(&var) = NODE_ELEMENT;
    hr = IXMLDOMDocument2_createNode(doc, var, _bstr_(L"e"), NULL, &node);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void**)&el);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    IXMLDOMNode_Release(node);

    V_VT(&var) = VT_I1;
    V_I1(&var) = NODE_ATTRIBUTE;
    hr = IXMLDOMDocument2_createNode(doc, var, _bstr_(L"xmlns:a"),
        _bstr_(L"http://www.w3.org/2000/xmlns/"), &node);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IXMLDOMElement_setAttributeNode(el, (IXMLDOMAttribute*)node, NULL);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    /* for some reason default namespace uri is not reported */
    hr = IXMLDOMNode_get_namespaceURI(node, &str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(str, L""), "got uri %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

    IXMLDOMNode_Release(node);
    IXMLDOMElement_Release(el);
    IXMLDOMDocument2_Release(doc);
    free_bstrs();
}

/* see dlls/msxml[36]/tests/domdoc.c */
static void test_namespaces_as_attributes(void)
{
    struct test
    {
        const WCHAR *xml;
        int explen;
        const WCHAR *names[3];
        const WCHAR *prefixes[3];
        const WCHAR *basenames[3];
        const WCHAR *uris[3];
        const WCHAR *texts[3];
        const WCHAR *xmls[3];
    };
    static const struct test tests[] =
    {
        {
            L"<a ns:b=\"b attr\" d=\"d attr\" xmlns:ns=\"nshref\" />", 3,
            { L"ns:b",   L"d",      L"xmlns:ns" }, /* nodeName */
            { L"ns",     NULL,      L"xmlns" },    /* prefix */
            { L"b",      L"d",      L"ns" },       /* baseName */
            { L"nshref", NULL,      L"" },         /* namespaceURI */
            { L"b attr", L"d attr", L"nshref" },   /* text */
            { L"ns:b=\"b attr\"", L"d=\"d attr\"", L"xmlns:ns=\"nshref\"" }, /* xml */
        },
        /* property only */
        {
            L"<a d=\"d attr\" />", 1,
            { L"d" },                   /* nodeName */
            { NULL },                   /* prefix */
            { L"d" },                   /* baseName */
            { NULL },                   /* namespaceURI */
            { L"d attr" },              /* text */
            { L"d=\"d attr\"" },        /* xml */
        },
        /* namespace only */
        {
            L"<a xmlns:ns=\"nshref\" />", 1,
            { L"xmlns:ns" },            /* nodeName */
            { L"xmlns" },               /* prefix */
            { L"ns" },                  /* baseName */
            { L"" },                    /* namespaceURI */
            { L"nshref" },              /* text */
            { L"xmlns:ns=\"nshref\"" }, /* xml */
        },
        /* default namespace */
        {
            L"<a xmlns=\"nshref\" />", 1,
            { L"xmlns" },               /* nodeName */
            { L"xmlns" },               /* prefix */
            { L"" },                    /* baseName */
            { L"" },                    /* namespaceURI */
            { L"nshref" },              /* text */
            { L"xmlns=\"nshref\"" },    /* xml */
        },
        /* no properties or namespaces */
        {
            L"<a />", 0,
        },

        { NULL }
    };
    const struct test *test;
    IXMLDOMNamedNodeMap *map;
    IXMLDOMNode *node, *item;
    IXMLDOMDocument2 *doc;
    VARIANT_BOOL b;
    LONG len, i;
    HRESULT hr;
    BSTR str;

    test = tests;
    while (test->xml)
    {
        hr = CoCreateInstance(&CLSID_DOMDocument40, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument2, (void **)&doc);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        hr = IXMLDOMDocument2_loadXML(doc, _bstr_(test->xml), &b);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        node = NULL;
        hr = IXMLDOMDocument2_get_firstChild(doc, &node);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        hr = IXMLDOMNode_get_attributes(node, &map);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        len = -1;
        hr = IXMLDOMNamedNodeMap_get_length(map, &len);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        ok(len == test->explen, "got %ld\n", len);

        item = NULL;
        hr = IXMLDOMNamedNodeMap_get_item(map, test->explen+1, &item);
        ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr);
        ok(!item, "Item should be NULL\n");

        for (i = 0; i < len; i++)
        {
            item = NULL;
            hr = IXMLDOMNamedNodeMap_get_item(map, i, &item);
            ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

            str = NULL;
            hr = IXMLDOMNode_get_nodeName(item, &str);
            ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
            ok(!lstrcmpW(str, test->names[i]), "got %s\n", wine_dbgstr_w(str));
            SysFreeString(str);

            str = NULL;
            hr = IXMLDOMNode_get_prefix(item, &str);
            if (test->prefixes[i])
            {
                /* MSXML4 can report different results with different service packs */
                ok(hr == S_OK || broken(hr == S_FALSE), "Unexpected hr %#lx.\n", hr);
                ok(!lstrcmpW(str, test->prefixes[i]) || broken(!str),
                   "got %s\n", wine_dbgstr_w(str));
                SysFreeString(str);
            }
            else
                ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr );

            str = NULL;
            hr = IXMLDOMNode_get_baseName(item, &str);
            /* MSXML4 can report different results with different service packs */
            ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
            ok(!lstrcmpW(str, test->basenames[i]) || broken(!lstrcmpW(str, L"xmlns")),
                "got %s\n", wine_dbgstr_w(str));
            SysFreeString(str);

            str = NULL;
            hr = IXMLDOMNode_get_namespaceURI(item, &str);
            if (test->uris[i])
            {
                ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
                if (test->prefixes[i] && !lstrcmpW(test->prefixes[i], L"xmlns"))
                    ok(!lstrcmpW(str, L""), "got %s\n", wine_dbgstr_w(str));
                else
                    ok(!lstrcmpW(str, test->uris[i]), "got %s\n", wine_dbgstr_w(str));
                SysFreeString(str);
            }
            else
                ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr );

            str = NULL;
            hr = IXMLDOMNode_get_text(item, &str);
            ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
            ok(!lstrcmpW(str, test->texts[i]), "got %s\n", wine_dbgstr_w(str));
            SysFreeString(str);

            str = NULL;
            hr = IXMLDOMNode_get_xml(item, &str);
            ok(SUCCEEDED(hr), "Failed to get node xml, hr %#lx.\n", hr);
            ok(!lstrcmpW(str, test->xmls[i]), "got %s\n", wine_dbgstr_w(str));
            SysFreeString(str);

            IXMLDOMNode_Release(item);
        }

        IXMLDOMNamedNodeMap_Release(map);
        IXMLDOMNode_Release(node);
        IXMLDOMDocument2_Release(doc);

        test++;
    }
    free_bstrs();
}

static void test_get_ownerDocument(void)
{
    IXMLDOMDocument *doc1;
    IXMLDOMDocument2 *doc;
    VARIANT_BOOL b;
    HRESULT hr;

    hr = CoCreateInstance(&CLSID_DOMDocument40, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument2, (void **)&doc);

    hr = IXMLDOMDocument2_get_ownerDocument(doc, NULL);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);

    doc1 = (void *)0xdead;
    hr = IXMLDOMDocument2_get_ownerDocument(doc, &doc1);
    todo_wine
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!doc1, "Unexpected pointer.\n");

    hr = IXMLDOMDocument2_loadXML(doc, _bstr_(L"<a>text</a>"), &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_TRUE, "Unexpected result %d.\n", b);

    hr = IXMLDOMDocument2_get_ownerDocument(doc, NULL);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);

    doc1 = (void *)0xdead;
    hr = IXMLDOMDocument2_get_ownerDocument(doc, &doc1);
    todo_wine
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!doc1, "Unexpected pointer.\n");

    IXMLDOMDocument2_Release(doc);
}

static void test_get_parentNode(void)
{
    IXMLDOMDocument *doc;
    IXMLDOMNode *node;
    HRESULT hr;

    hr = CoCreateInstance(&CLSID_DOMDocument40, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument, (void **)&doc);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IXMLDOMDocument_get_parentNode(doc, NULL);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);

    node = (void *)0x1;
    hr = IXMLDOMDocument_get_parentNode(doc, &node);
    ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr);
    ok(!node, "Unexpected node %p.\n", node);

    IXMLDOMDocument_Release(doc);
}

static void test_prohibitdtd(void)
{
    IXMLDOMDocument2 *doc;
    HRESULT hr;
    VARIANT v;

    hr = CoCreateInstance(&CLSID_DOMDocument40, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument2, (void **)&doc);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    VariantInit(&v);
    hr = IXMLDOMDocument2_getProperty(doc, _bstr_(L"ProhibitDTD"), &v);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(V_VT(&v) == VT_BOOL, "Unexpected type %d.\n", V_VT(&v));
    ok(!V_BOOL(&v), "Unexpected value %d.\n", V_BOOL(&v));

    IXMLDOMDocument2_Release(doc);
}

START_TEST(domdoc)
{
    HRESULT hr;
    IXMLDOMDocument2 *doc;

    hr = CoInitialize(NULL);
    ok(hr == S_OK, "failed to init com\n");

    hr = CoCreateInstance(&CLSID_DOMDocument40, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument2, (void **)&doc);
    if (hr != S_OK)
    {
        win_skip("class &CLSID_DOMDocument40 not supported\n");
        return;
    }
    IXMLDOMDocument2_Release(doc);

    test_namespaces_as_attributes();
    test_create_attribute();
    test_get_ownerDocument();
    test_get_parentNode();
    test_prohibitdtd();

    CoUninitialize();
}
