// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"


// Exit macros
#define XmlExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
#define XmlExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
#define XmlExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
#define XmlExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
#define XmlExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
#define XmlExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
#define XmlExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_XMLUTIL, p, x, e, s, __VA_ARGS__)
#define XmlExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_XMLUTIL, p, x, s, __VA_ARGS__)
#define XmlExitOnNullDebugTrace(p, x, e, s, ...)  ExitOnNullDebugTraceSource(DUTIL_SOURCE_XMLUTIL, p, x, e, s, __VA_ARGS__)
#define XmlExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_XMLUTIL, p, x, s, __VA_ARGS__)
#define XmlExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_XMLUTIL, e, x, s, __VA_ARGS__)
#define XmlExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_XMLUTIL, g, x, s, __VA_ARGS__)

// intialization globals
CLSID vclsidXMLDOM = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0} };
static volatile LONG vcXmlInitialized = 0;
static BOOL vfMsxml40 = FALSE;
static BOOL fComInitialized = FALSE;
BOOL vfMsxml30 = FALSE;

/********************************************************************
 XmlInitialize - finds an appropriate version of the XML DOM

*********************************************************************/
extern "C" HRESULT DAPI XmlInitialize(
    )
{
    HRESULT hr = S_OK;

    if (!fComInitialized)
    {
        hr = ::CoInitialize(0);
        if (RPC_E_CHANGED_MODE != hr)
        {
            XmlExitOnFailure(hr, "failed to initialize COM");
            fComInitialized = TRUE;
        }
    }

    LONG cInitialized = ::InterlockedIncrement(&vcXmlInitialized);
    if (1 == cInitialized)
    {
        // NOTE: 4.0 behaves differently than 3.0 so there may be problems doing this
#if 0
        hr = ::CLSIDFromProgID(L"Msxml2.DOMDocument.4.0", &vclsidXMLDOM);
        if (S_OK == hr)
        {
            vfMsxml40 = TRUE;
            Trace(REPORT_VERBOSE, "found Msxml2.DOMDocument.4.0");
            ExitFunction();
        }
#endif
        hr = ::CLSIDFromProgID(L"Msxml2.DOMDocument", &vclsidXMLDOM);
        if (FAILED(hr))
        {
            // try to fall back to old MSXML
            hr = ::CLSIDFromProgID(L"MSXML.DOMDocument", &vclsidXMLDOM);
        }
        XmlExitOnFailure(hr, "failed to get CLSID for XML DOM");

        Assert(IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument) ||
               IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument20) ||
               IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument26) ||
               IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument30) ||
               IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument40) ||
               IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument50) ||
               IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument60));
    }

    hr = S_OK;
LExit:
    return hr;
}


/********************************************************************
 XmUninitialize -

*********************************************************************/
extern "C" void DAPI XmlUninitialize(
    )
{
    AssertSz(vcXmlInitialized, "XmlUninitialize called when not initialized");

    LONG cInitialized = ::InterlockedDecrement(&vcXmlInitialized);

    if (0 == cInitialized)
    {
        memset(&vclsidXMLDOM, 0, sizeof(vclsidXMLDOM));

        if (fComInitialized)
        {
            ::CoUninitialize();
        }
    } 
}

extern "C" HRESULT DAPI XmlCreateElement(
    __in IXMLDOMDocument *pixdDocument,
    __in_z LPCWSTR wzElementName,
    __out IXMLDOMElement **ppixnElement
    )
{
    if (!ppixnElement || !pixdDocument)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;
    BSTR bstrElementName = ::SysAllocString(wzElementName);
    XmlExitOnNull(bstrElementName, hr, E_OUTOFMEMORY, "failed SysAllocString");
    hr = pixdDocument->createElement(bstrElementName, ppixnElement);
LExit:
    ReleaseBSTR(bstrElementName);
    return hr;
}


/********************************************************************
 XmlCreateDocument -

*********************************************************************/
extern "C" HRESULT DAPI XmlCreateDocument(
    __in_opt LPCWSTR pwzElementName,
    __out IXMLDOMDocument** ppixdDocument,
    __out_opt IXMLDOMElement** ppixeRootElement
    )
{
    HRESULT hr = S_OK;
    BOOL (WINAPI *pfnDisableWow64)(__out PVOID* ) = NULL;
    BOOLEAN (WINAPI *pfnEnableWow64)(__in BOOLEAN ) = NULL;
    BOOL (WINAPI *pfnRevertWow64)(__in PVOID ) = NULL;
    BOOL fWow64Available = FALSE;
    void *pvWow64State = NULL;

    // RELEASEME
    IXMLDOMElement* pixeRootElement = NULL;
    IXMLDOMDocument *pixdDocument = NULL;

    // Test if we have access to the Wow64 API, and store the result in fWow64Available
    HMODULE hKernel32 = ::GetModuleHandleA("kernel32.dll");
    XmlExitOnNullWithLastError(hKernel32, hr, "failed to get handle to kernel32.dll");

    // This will test if we have access to the Wow64 API
    if (NULL != GetProcAddress(hKernel32, "IsWow64Process"))
    {
        pfnDisableWow64 = (BOOL (WINAPI *)(PVOID *))::GetProcAddress(hKernel32, "Wow64DisableWow64FsRedirection");
        pfnEnableWow64 = (BOOLEAN (WINAPI *)(BOOLEAN))::GetProcAddress(hKernel32, "Wow64EnableWow64FsRedirection");
        pfnRevertWow64 = (BOOL (WINAPI *)(PVOID))::GetProcAddress(hKernel32, "Wow64RevertWow64FsRedirection");

        fWow64Available = pfnDisableWow64 && pfnEnableWow64 && pfnRevertWow64;
    }

    // create the top level XML document
    AssertSz(vcXmlInitialized, "XmlInitialize() was not called");

    // Enable Wow64 Redirection, if possible
    if (fWow64Available)
    {
        // We want to enable Wow64 redirection, but the Wow64 API requires us to disable it first to get its current state (so we can revert it later)
        pfnDisableWow64(&pvWow64State);
        // If we fail to enable it, don't bother trying to disable it later on
        fWow64Available = pfnEnableWow64(TRUE);
    }

    hr = ::CoCreateInstance(vclsidXMLDOM, NULL, CLSCTX_INPROC_SERVER, XmlUtil_IID_IXMLDOMDocument, (void**)&pixdDocument);
    XmlExitOnFailure(hr, "failed to create XML DOM Document");
    Assert(pixdDocument);

    if (IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument30) || IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument20))
    {
        vfMsxml30 = TRUE;
    }

    if (pwzElementName)
    {
        hr = XmlCreateElement(pixdDocument, pwzElementName, &pixeRootElement);
        XmlExitOnFailure(hr, "failed XmlCreateElement");
        hr = pixdDocument->appendChild(pixeRootElement, NULL);
        XmlExitOnFailure(hr, "failed appendChild");
    }

    *ppixdDocument = pixdDocument;
    pixdDocument = NULL;

    if (ppixeRootElement)
    {
        *ppixeRootElement = pixeRootElement;
        pixeRootElement = NULL;
    }

LExit:
    // Re-disable Wow64 Redirection, if appropriate
    if (fWow64Available && !pfnRevertWow64(pvWow64State))
    {
        // If we expected to be able to revert, and couldn't, fail in the only graceful way we can
        ::ExitProcess(1);
    }

    ReleaseObject(pixeRootElement);
    ReleaseObject(pixdDocument);
    return hr;
}


/********************************************************************
 XmlLoadDocument -

*********************************************************************/
extern "C" HRESULT DAPI XmlLoadDocument(
    __in_z LPCWSTR wzDocument,
    __out IXMLDOMDocument** ppixdDocument
    )
{
    return XmlLoadDocumentEx(wzDocument, 0, ppixdDocument);
}


/********************************************************************
 XmlReportParseError -

*********************************************************************/
static void XmlReportParseError(
    __in IXMLDOMParseError* pixpe
    )
{
    HRESULT hr = S_OK;
    long lNumber = 0;
    BSTR bstr = NULL;

    Trace(REPORT_STANDARD, "Failed to parse XML. IXMLDOMParseError reports:");

    hr = pixpe->get_errorCode(&lNumber);
    XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.errorCode.");
    Trace(REPORT_STANDARD, "errorCode = 0x%x", lNumber);

    hr = pixpe->get_filepos(&lNumber);
    XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.filepos.");
    Trace(REPORT_STANDARD, "filepos = %d", lNumber);

    hr = pixpe->get_line(&lNumber);
    XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.line.");
    Trace(REPORT_STANDARD, "line = %d", lNumber);

    hr = pixpe->get_linepos(&lNumber);
    XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.linepos.");
    Trace(REPORT_STANDARD, "linepos = %d", lNumber);

    hr = pixpe->get_reason(&bstr);
    XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.reason.");
    Trace(REPORT_STANDARD, "reason = %ls", bstr);
    ReleaseNullBSTR(bstr);

    hr = pixpe->get_srcText (&bstr);
    XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.srcText .");
    Trace(REPORT_STANDARD, "srcText = %ls", bstr);
    ReleaseNullBSTR(bstr);

LExit:
    ReleaseBSTR(bstr);
}

/********************************************************************
 XmlLoadDocumentEx -

*********************************************************************/
extern "C" HRESULT DAPI XmlLoadDocumentEx(
    __in_z LPCWSTR wzDocument,
    __in DWORD dwAttributes,
    __out IXMLDOMDocument** ppixdDocument
    )
{
    HRESULT hr = S_OK;
    VARIANT_BOOL vbSuccess = 0;

    // RELEASEME
    IXMLDOMDocument* pixd = NULL;
    IXMLDOMParseError* pixpe = NULL;
    BSTR bstrLoad = NULL;

    if (!wzDocument || !*wzDocument)
    {
        hr = E_UNEXPECTED;
        XmlExitOnFailure(hr, "string must be non-null");
    }

    hr = XmlCreateDocument(NULL, &pixd);
    if (hr == S_FALSE)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "failed XmlCreateDocument");

    if (dwAttributes & XML_LOAD_PRESERVE_WHITESPACE)
    {
        hr = pixd->put_preserveWhiteSpace(VARIANT_TRUE);
        XmlExitOnFailure(hr, "failed put_preserveWhiteSpace");
    }

    // Security issue.  Avoid triggering anything external.
    hr = pixd->put_validateOnParse(VARIANT_FALSE);
    XmlExitOnFailure(hr, "failed put_validateOnParse");
    hr = pixd->put_resolveExternals(VARIANT_FALSE);
    XmlExitOnFailure(hr, "failed put_resolveExternals");

    bstrLoad = ::SysAllocString(wzDocument);
    XmlExitOnNull(bstrLoad, hr, E_OUTOFMEMORY, "failed to allocate bstr for Load in XmlLoadDocumentEx");

    hr = pixd->loadXML(bstrLoad, &vbSuccess);
    if (S_FALSE == hr)
    {
        hr = HRESULT_FROM_WIN32(ERROR_OPEN_FAILED);
    }

    if (FAILED(hr) && S_OK == pixd->get_parseError(&pixpe))
    {
        XmlReportParseError(pixpe);
    }

    XmlExitOnFailure(hr, "failed loadXML");


    hr = S_OK;
LExit:
    if (ppixdDocument)
    {
        *ppixdDocument = pixd;
        pixd = NULL;
    }
    ReleaseBSTR(bstrLoad);
    ReleaseObject(pixd);
    ReleaseObject(pixpe);

    return hr;
}


/*******************************************************************
 XmlLoadDocumentFromFile

********************************************************************/
extern "C" HRESULT DAPI XmlLoadDocumentFromFile(
    __in_z LPCWSTR wzPath,
    __out IXMLDOMDocument** ppixdDocument
    )
{
    return XmlLoadDocumentFromFileEx(wzPath, 0, ppixdDocument);
}


/*******************************************************************
 XmlLoadDocumentFromFileEx

********************************************************************/
extern "C" HRESULT DAPI XmlLoadDocumentFromFileEx(
    __in_z LPCWSTR wzPath,
    __in DWORD dwAttributes,
    __out IXMLDOMDocument** ppixdDocument
    )
{
    HRESULT hr = S_OK;
    VARIANT varPath;
    VARIANT_BOOL vbSuccess = 0;

    IXMLDOMDocument* pixd = NULL;
    IXMLDOMParseError* pixpe = NULL;

    ::VariantInit(&varPath);
    varPath.vt = VT_BSTR;
    varPath.bstrVal = ::SysAllocString(wzPath);
    XmlExitOnNull(varPath.bstrVal, hr, E_OUTOFMEMORY, "failed to allocate bstr for Path in XmlLoadDocumentFromFileEx");

    hr = XmlCreateDocument(NULL, &pixd);
    if (hr == S_FALSE)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "failed XmlCreateDocument");

    if (dwAttributes & XML_LOAD_PRESERVE_WHITESPACE)
    {
        hr = pixd->put_preserveWhiteSpace(VARIANT_TRUE);
        XmlExitOnFailure(hr, "failed put_preserveWhiteSpace");
    }

    // Avoid triggering anything external.
    hr = pixd->put_validateOnParse(VARIANT_FALSE);
    XmlExitOnFailure(hr, "failed put_validateOnParse");
    hr = pixd->put_resolveExternals(VARIANT_FALSE);
    XmlExitOnFailure(hr, "failed put_resolveExternals");

    pixd->put_async(VARIANT_FALSE);
    hr = pixd->load(varPath, &vbSuccess);
    if (S_FALSE == hr)
    {
        hr = HRESULT_FROM_WIN32(ERROR_OPEN_FAILED);
    }

    if (FAILED(hr) && S_OK == pixd->get_parseError(&pixpe))
    {
        XmlReportParseError(pixpe);
    }

    XmlExitOnFailure(hr, "failed to load XML from: %ls", wzPath);

    if (ppixdDocument)
    {
        *ppixdDocument = pixd;
        pixd = NULL;
    }

    hr = S_OK;
LExit:
    ReleaseVariant(varPath);
    ReleaseObject(pixd);
    ReleaseObject(pixpe);

    return hr;
}


/********************************************************************
 XmlLoadDocumentFromBuffer

*********************************************************************/
extern "C" HRESULT DAPI XmlLoadDocumentFromBuffer(
    __in_bcount(cbSource) const BYTE* pbSource,
    __in SIZE_T cbSource,
    __out IXMLDOMDocument** ppixdDocument
    )
{
    HRESULT hr = S_OK;
    IXMLDOMDocument* pixdDocument = NULL;
    SAFEARRAY sa = { };
    VARIANT vtXmlSource;
    VARIANT_BOOL vbSuccess = 0;

    ::VariantInit(&vtXmlSource);

    // create document
    hr = XmlCreateDocument(NULL, &pixdDocument);
    if (hr == S_FALSE)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "failed XmlCreateDocument");

    // Security issue.  Avoid triggering anything external.
    hr = pixdDocument->put_validateOnParse(VARIANT_FALSE);
    XmlExitOnFailure(hr, "failed put_validateOnParse");
    hr = pixdDocument->put_resolveExternals(VARIANT_FALSE);
    XmlExitOnFailure(hr, "failed put_resolveExternals");

    // load document
    sa.cDims = 1;
    sa.fFeatures = FADF_STATIC | FADF_FIXEDSIZE;
    sa.cbElements = 1;
    sa.pvData = (PVOID)pbSource;
    sa.rgsabound[0].cElements = (ULONG)cbSource;
    vtXmlSource.vt = VT_ARRAY | VT_UI1;
    vtXmlSource.parray = &sa;

    hr = pixdDocument->load(vtXmlSource, &vbSuccess);
    if (S_FALSE == hr)
    {
        hr = HRESULT_FROM_WIN32(ERROR_OPEN_FAILED);
    }
    XmlExitOnFailure(hr, "failed loadXML");

    // return value
    *ppixdDocument = pixdDocument;
    pixdDocument = NULL;

LExit:
    ReleaseObject(pixdDocument);
    return hr;
}


/********************************************************************
 XmlSetAttribute -

*********************************************************************/
extern "C" HRESULT DAPI XmlSetAttribute(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute,
    __in_z LPCWSTR pwzAttributeValue
    )
{
    HRESULT hr = S_OK;
    VARIANT varAttributeValue;
    ::VariantInit(&varAttributeValue);

    // RELEASEME
    IXMLDOMDocument* pixdDocument = NULL;
    IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
    IXMLDOMAttribute* pixaAttribute = NULL;
    IXMLDOMNode* pixaNode = NULL;
    BSTR bstrAttributeName = ::SysAllocString(pwzAttribute);
    XmlExitOnNull(bstrAttributeName, hr, E_OUTOFMEMORY, "failed to allocate bstr for AttributeName in XmlSetAttribute");

    hr = pixnNode->get_attributes(&pixnnmAttributes);
    XmlExitOnFailure(hr, "failed get_attributes in XmlSetAttribute(%ls)", pwzAttribute);

    hr = pixnNode->get_ownerDocument(&pixdDocument);
    if (hr == S_FALSE)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "failed get_ownerDocument in XmlSetAttribute");

    hr = pixdDocument->createAttribute(bstrAttributeName, &pixaAttribute);
    XmlExitOnFailure(hr, "failed createAttribute in XmlSetAttribute(%ls)", pwzAttribute);

    varAttributeValue.vt = VT_BSTR;
    varAttributeValue.bstrVal = ::SysAllocString(pwzAttributeValue);
    if (!varAttributeValue.bstrVal)
    {
        hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
    }
    XmlExitOnFailure(hr, "failed SysAllocString in XmlSetAttribute");

    hr = pixaAttribute->put_nodeValue(varAttributeValue);
    XmlExitOnFailure(hr, "failed put_nodeValue in XmlSetAttribute(%ls)", pwzAttribute);

    hr = pixnnmAttributes->setNamedItem(pixaAttribute, &pixaNode);
    XmlExitOnFailure(hr, "failed setNamedItem in XmlSetAttribute(%ls)", pwzAttribute);

LExit:
    ReleaseObject(pixdDocument);
    ReleaseObject(pixnnmAttributes);
    ReleaseObject(pixaAttribute);
    ReleaseObject(pixaNode);
    ReleaseBSTR(varAttributeValue.bstrVal);
    ReleaseBSTR(bstrAttributeName);

    return hr;
}


/********************************************************************
 XmlSelectSingleNode -

*********************************************************************/
extern "C" HRESULT DAPI XmlSelectSingleNode(
    __in IXMLDOMNode* pixnParent,
    __in_z LPCWSTR wzXPath,
    __out IXMLDOMNode **ppixnChild
    )
{
    HRESULT hr = S_OK;

    BSTR bstrXPath = NULL;

    XmlExitOnNull(pixnParent, hr, E_UNEXPECTED, "pixnParent parameter was null in XmlSelectSingleNode");
    XmlExitOnNull(ppixnChild, hr, E_UNEXPECTED, "ppixnChild parameter was null in XmlSelectSingleNode");

    bstrXPath = ::SysAllocString(wzXPath ? wzXPath : L"");
    XmlExitOnNull(bstrXPath, hr, E_OUTOFMEMORY, "failed to allocate bstr for XPath expression in XmlSelectSingleNode");

    hr = pixnParent->selectSingleNode(bstrXPath, ppixnChild);

LExit:
    ReleaseBSTR(bstrXPath);

    return hr;
}


/********************************************************************
 XmlCreateTextNode -

*********************************************************************/
extern "C" HRESULT DAPI XmlCreateTextNode(
    __in IXMLDOMDocument *pixdDocument,
    __in_z LPCWSTR wzText,
    __out IXMLDOMText **ppixnTextNode
    )
{
    if (!ppixnTextNode || !pixdDocument)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;
    BSTR bstrText = ::SysAllocString(wzText);
    XmlExitOnNull(bstrText, hr, E_OUTOFMEMORY, "failed SysAllocString");
    hr = pixdDocument->createTextNode(bstrText, ppixnTextNode);
LExit:
    ReleaseBSTR(bstrText);

    return hr;
}


/********************************************************************
 XmlGetText

*********************************************************************/
extern "C" HRESULT DAPI XmlGetText(
    __in IXMLDOMNode* pixnNode,
    __deref_out_z BSTR* pbstrText
    )
{
    return pixnNode->get_text(pbstrText);
}


/********************************************************************
 XmlGetAttribute

*********************************************************************/
extern "C" HRESULT DAPI XmlGetAttribute(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute,
    __deref_out_z BSTR* pbstrAttributeValue
    )
{
    Assert(pixnNode);
    HRESULT hr = S_OK;

    // RELEASEME
    IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
    IXMLDOMNode* pixnAttribute = NULL;
    VARIANT varAttributeValue;
    BSTR bstrAttribute = SysAllocString(pwzAttribute);

    // INIT
    ::VariantInit(&varAttributeValue);

    // get attribute value from source
    hr = pixnNode->get_attributes(&pixnnmAttributes);
    XmlExitOnFailure(hr, "failed get_attributes");

    hr = XmlGetNamedItem(pixnnmAttributes, bstrAttribute, &pixnAttribute);
    if (S_FALSE == hr)
    {
        // hr = E_FAIL;
        ExitFunction();
    }
    XmlExitOnFailure(hr, "failed getNamedItem in XmlGetAttribute(%ls)", pwzAttribute);

    hr = pixnAttribute->get_nodeValue(&varAttributeValue);
    XmlExitOnFailure(hr, "failed get_nodeValue in XmlGetAttribute(%ls)", pwzAttribute);

    // steal the BSTR from the VARIANT
    if (S_OK == hr && pbstrAttributeValue)
    {
        *pbstrAttributeValue = varAttributeValue.bstrVal;
        varAttributeValue.bstrVal = NULL;
    }

LExit:
    ReleaseObject(pixnnmAttributes);
    ReleaseObject(pixnAttribute);
    ReleaseVariant(varAttributeValue);
    ReleaseBSTR(bstrAttribute);

    return hr;
}


/********************************************************************
 XmlGetAttributeEx

*********************************************************************/
HRESULT DAPI XmlGetAttributeEx(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR wzAttribute,
    __deref_out_z LPWSTR* psczAttributeValue
    )
{
    Assert(pixnNode);
    HRESULT hr = S_OK;
    IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
    IXMLDOMNode* pixnAttribute = NULL;
    VARIANT varAttributeValue;
    BSTR bstrAttribute = NULL;

    ::VariantInit(&varAttributeValue);

    // get attribute value from source
    hr = pixnNode->get_attributes(&pixnnmAttributes);
    XmlExitOnFailure(hr, "Failed get_attributes.");

    bstrAttribute = ::SysAllocString(wzAttribute);
    XmlExitOnNull(bstrAttribute, hr, E_OUTOFMEMORY, "Failed to allocate attribute name BSTR.");

    hr = XmlGetNamedItem(pixnnmAttributes, bstrAttribute, &pixnAttribute);
    if (S_FALSE == hr)
    {
        ExitFunction1(hr = E_NOTFOUND);
    }
    XmlExitOnFailure(hr, "Failed getNamedItem in XmlGetAttribute(%ls)", wzAttribute);

    hr = pixnAttribute->get_nodeValue(&varAttributeValue);
    if (S_FALSE == hr)
    {
        ExitFunction1(hr = E_NOTFOUND);
    }
    XmlExitOnFailure(hr, "Failed get_nodeValue in XmlGetAttribute(%ls)", wzAttribute);

    // copy value
    hr = StrAllocString(psczAttributeValue, varAttributeValue.bstrVal, 0);
    XmlExitOnFailure(hr, "Failed to copy attribute value.");

LExit:
    ReleaseObject(pixnnmAttributes);
    ReleaseObject(pixnAttribute);
    ReleaseVariant(varAttributeValue);
    ReleaseBSTR(bstrAttribute);

    return hr;
}


/********************************************************************
 XmlGetYesNoAttribute

*********************************************************************/
HRESULT DAPI XmlGetYesNoAttribute(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR wzAttribute,
    __out BOOL* pfYes
    )
{
    HRESULT hr = S_OK;
    LPWSTR sczValue = NULL;

    hr = XmlGetAttributeEx(pixnNode, wzAttribute, &sczValue);
    if (E_NOTFOUND != hr)
    {
        XmlExitOnFailure(hr, "Failed to get attribute.");

        *pfYes = CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczValue, -1, L"yes", -1);
    }

LExit:
    ReleaseStr(sczValue);

    return hr;
}



/********************************************************************
 XmlGetAttributeNumber

*********************************************************************/
extern "C" HRESULT DAPI XmlGetAttributeNumber(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute,
    __out DWORD* pdwValue
    )
{
    HRESULT hr = XmlGetAttributeNumberBase(pixnNode, pwzAttribute, 10, pdwValue);
    return hr;
}


/********************************************************************
 XmlGetAttributeNumberBase

*********************************************************************/
extern "C" HRESULT DAPI XmlGetAttributeNumberBase(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute,
    __in int nBase,
    __out DWORD* pdwValue
    )
{
    HRESULT hr = S_OK;
    BSTR bstrPointer = NULL;

    hr = XmlGetAttribute(pixnNode, pwzAttribute, &bstrPointer);
    XmlExitOnFailure(hr, "Failed to get value from attribute.");

    if (S_OK == hr)
    {
        *pdwValue = wcstoul(bstrPointer, NULL, nBase);
    }

LExit:
    ReleaseBSTR(bstrPointer);
    return hr;
}


/********************************************************************
 XmlGetAttributeUInt16

*********************************************************************/
extern "C" HRESULT DAPI XmlGetAttributeUInt16(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute,
    __out WORD* pwValue
    )
{
    HRESULT hr = S_OK;
    BSTR bstrValue = NULL;

    hr = XmlGetAttribute(pixnNode, pwzAttribute, &bstrValue);
    XmlExitOnFailure(hr, "failed XmlGetAttribute");

    if (S_OK == hr)
    {
        WORD w = 0;
        hr = StrStringToUInt16(bstrValue, 0, &w);
        XmlExitOnFailure(hr, "Failed to treat attribute value as UInt16.");

        *pwValue = w;
    }
    else
    {
        *pwValue = 0;
    }

LExit:
    ReleaseBSTR(bstrValue);
    return hr;
}


/********************************************************************
 XmlGetAttributeInt32

*********************************************************************/
extern "C" HRESULT DAPI XmlGetAttributeInt32(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute,
    __out int* piValue
    )
{
    HRESULT hr = S_OK;
    BSTR bstrValue = NULL;

    hr = XmlGetAttribute(pixnNode, pwzAttribute, &bstrValue);
    XmlExitOnFailure(hr, "failed XmlGetAttribute");

    if (S_OK == hr)
    {
        int i = 0;
        hr = StrStringToInt32(bstrValue, 0, &i);
        XmlExitOnFailure(hr, "Failed to treat attribute value as Int32.");

        *piValue = i;
    }
    else
    {
        *piValue = 0;
    }

LExit:
    ReleaseBSTR(bstrValue);
    return hr;
}


/********************************************************************
 XmlGetAttributeUInt32

*********************************************************************/
extern "C" HRESULT DAPI XmlGetAttributeUInt32(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute,
    __out DWORD* pdwValue
    )
{
    HRESULT hr = S_OK;
    BSTR bstrValue = NULL;

    hr = XmlGetAttribute(pixnNode, pwzAttribute, &bstrValue);
    XmlExitOnFailure(hr, "failed XmlGetAttribute");

    if (S_OK == hr)
    {
        UINT dw = 0;
        hr = StrStringToUInt32(bstrValue, 0, &dw);
        XmlExitOnFailure(hr, "Failed to treat attribute value as UInt32.");

        *pdwValue = dw;
    }
    else
    {
        *pdwValue = 0;
    }

LExit:
    ReleaseBSTR(bstrValue);
    return hr;
}


/********************************************************************
 XmlGetAttributeUInt64

*********************************************************************/
extern "C" HRESULT DAPI XmlGetAttributeUInt64(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute,
    __out DWORD64* pqwValue
    )
{
    HRESULT hr = S_OK;
    BSTR bstrValue = NULL;

    hr = XmlGetAttribute(pixnNode, pwzAttribute, &bstrValue);
    XmlExitOnFailure(hr, "failed XmlGetAttribute");

    if (S_OK == hr)
    {
        DWORD64 qw = 0;
        hr = StrStringToUInt64(bstrValue, 0, &qw);
        XmlExitOnFailure(hr, "Failed to treat attribute value as UInt64.");

        *pqwValue = qw;
    }
    else
    {
        *pqwValue = 0;
    }

LExit:
    ReleaseBSTR(bstrValue);
    return hr;
}


/********************************************************************
 XmlGetNamedItem -

*********************************************************************/
extern "C" HRESULT DAPI XmlGetNamedItem(
    __in IXMLDOMNamedNodeMap *pixnmAttributes,
    __in_opt LPCWSTR wzName,
    __out IXMLDOMNode **ppixnNamedItem
    )
{
    if (!pixnmAttributes || !ppixnNamedItem)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;
    BSTR bstrName = ::SysAllocString(wzName);
    XmlExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString");

    hr = pixnmAttributes->getNamedItem(bstrName, ppixnNamedItem);

LExit:
    ReleaseBSTR(bstrName);
    return hr;
}


/********************************************************************
 XmlSetText -

*********************************************************************/
extern "C" HRESULT DAPI XmlSetText(
    __in IXMLDOMNode *pixnNode,
    __in_z LPCWSTR pwzText
    )
{
    Assert(pixnNode && pwzText);
    HRESULT hr = S_OK;
    DOMNodeType dnType;

    // RELEASEME
    IXMLDOMDocument* pixdDocument = NULL;
    IXMLDOMNodeList* pixnlNodeList = NULL;
    IXMLDOMNode* pixnChildNode = NULL;
    IXMLDOMText* pixtTextNode = NULL;
    VARIANT varText;

    ::VariantInit(&varText);

    // find the text node
    hr = pixnNode->get_childNodes(&pixnlNodeList);
    XmlExitOnFailure(hr, "failed to get child nodes");

    while (S_OK == (hr = pixnlNodeList->nextNode(&pixnChildNode)))
    {
        hr = pixnChildNode->get_nodeType(&dnType);
        XmlExitOnFailure(hr, "failed to get node type");

        if (NODE_TEXT == dnType)
            break;
        ReleaseNullObject(pixnChildNode);
    }
    if (S_FALSE == hr)
    {
        hr = S_OK;
    }

    if (pixnChildNode)
    {
        varText.vt = VT_BSTR;
        varText.bstrVal = ::SysAllocString(pwzText);
        if (!varText.bstrVal)
        {
            hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
        }
        XmlExitOnFailure(hr, "failed SysAllocString in XmlSetText");

        hr = pixnChildNode->put_nodeValue(varText);
        XmlExitOnFailure(hr, "failed IXMLDOMNode::put_nodeValue");
    }
    else
    {
        hr = pixnNode->get_ownerDocument(&pixdDocument);
        if (hr == S_FALSE)
        {
            hr = E_FAIL;
        }
        XmlExitOnFailure(hr, "failed get_ownerDocument in XmlSetAttribute");

        hr = XmlCreateTextNode(pixdDocument, pwzText, &pixtTextNode);
        XmlExitOnFailure(hr, "failed createTextNode in XmlSetText(%ls)", pwzText);

        hr = pixnNode->appendChild(pixtTextNode, NULL);
        XmlExitOnFailure(hr, "failed appendChild in XmlSetText(%ls)", pwzText);
    }

    hr = *pwzText ? S_OK : S_FALSE;

LExit:
    ReleaseObject(pixnlNodeList);
    ReleaseObject(pixnChildNode);
    ReleaseObject(pixdDocument);
    ReleaseObject(pixtTextNode);
    ReleaseVariant(varText);
    return hr;
}


/********************************************************************
 XmlSetTextNumber -

*********************************************************************/
extern "C" HRESULT DAPI XmlSetTextNumber(
    __in IXMLDOMNode *pixnNode,
    __in DWORD dwValue
    )
{
    HRESULT hr = S_OK;
    WCHAR wzValue[12];

    hr = ::StringCchPrintfW(wzValue, countof(wzValue), L"%u", dwValue);
    XmlExitOnFailure(hr, "Failed to format numeric value as string.");

    hr = XmlSetText(pixnNode, wzValue);

LExit:
    return hr;
}


/********************************************************************
 XmlCreateChild -

*********************************************************************/
extern "C" HRESULT DAPI XmlCreateChild(
    __in IXMLDOMNode* pixnParent,
    __in_z LPCWSTR pwzElementType,
    __out IXMLDOMNode** ppixnChild
    )
{
    HRESULT hr = S_OK;

    // RELEASEME
    IXMLDOMDocument* pixdDocument = NULL;
    IXMLDOMNode* pixnChild = NULL;

    hr = pixnParent->get_ownerDocument(&pixdDocument);
    if (hr == S_FALSE)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "failed get_ownerDocument");

    hr = XmlCreateElement(pixdDocument, pwzElementType, (IXMLDOMElement**) &pixnChild);
    if (hr == S_FALSE)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "failed createElement");

    pixnParent->appendChild(pixnChild,NULL);
    if (hr == S_FALSE)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "failed appendChild");

    if (ppixnChild)
    {
        *ppixnChild = pixnChild;
        pixnChild = NULL;
    }

LExit:
    ReleaseObject(pixdDocument);
    ReleaseObject(pixnChild);
    return hr;
}

/********************************************************************
 XmlRemoveAttribute -

*********************************************************************/
extern "C" HRESULT DAPI XmlRemoveAttribute(
    __in IXMLDOMNode* pixnNode,
    __in_z LPCWSTR pwzAttribute
    )
{
    HRESULT hr = S_OK;

    // RELEASEME
    IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
    BSTR bstrAttribute = ::SysAllocString(pwzAttribute);
    XmlExitOnNull(bstrAttribute, hr, E_OUTOFMEMORY, "failed to allocate bstr for attribute in XmlRemoveAttribute");

    hr = pixnNode->get_attributes(&pixnnmAttributes);
    XmlExitOnFailure(hr, "failed get_attributes in RemoveXmlAttribute(%ls)", pwzAttribute);

    hr = pixnnmAttributes->removeNamedItem(bstrAttribute, NULL);
    XmlExitOnFailure(hr, "failed removeNamedItem in RemoveXmlAttribute(%ls)", pwzAttribute);

LExit:
    ReleaseObject(pixnnmAttributes);
    ReleaseBSTR(bstrAttribute);

    return hr;
}


/********************************************************************
 XmlSelectNodes -

*********************************************************************/
extern "C" HRESULT DAPI XmlSelectNodes(
    __in IXMLDOMNode* pixnParent,
    __in_z LPCWSTR wzXPath,
    __out IXMLDOMNodeList **ppixnlChildren
    )
{
    HRESULT hr = S_OK;

    BSTR bstrXPath = NULL;

    XmlExitOnNull(pixnParent, hr, E_UNEXPECTED, "pixnParent parameter was null in XmlSelectNodes");
    XmlExitOnNull(ppixnlChildren, hr, E_UNEXPECTED, "ppixnChild parameter was null in XmlSelectNodes");

    bstrXPath = ::SysAllocString(wzXPath ? wzXPath : L"");
    XmlExitOnNull(bstrXPath, hr, E_OUTOFMEMORY, "failed to allocate bstr for XPath expression in XmlSelectNodes");

    hr = pixnParent->selectNodes(bstrXPath, ppixnlChildren);

LExit:
    ReleaseBSTR(bstrXPath);
    return hr;
}


/********************************************************************
 XmlNextAttribute - returns the next attribute in a node list

 NOTE: pbstrAttribute is optional
       returns S_OK if found an element
       returns S_FALSE if no element found
       returns E_* if something went wrong
********************************************************************/
extern "C" HRESULT DAPI XmlNextAttribute(
    __in IXMLDOMNamedNodeMap* pixnnm,
    __out IXMLDOMNode** pixnAttribute,
    __deref_opt_out_z_opt BSTR* pbstrAttribute
    )
{
    Assert(pixnnm && pixnAttribute);

    HRESULT hr = S_OK;
    IXMLDOMNode* pixn = NULL;
    DOMNodeType nt;

    // null out the return values
    *pixnAttribute = NULL;
    if (pbstrAttribute)
    {
        *pbstrAttribute = NULL;
    }

    hr = pixnnm->nextNode(&pixn);
    XmlExitOnFailure(hr, "Failed to get next attribute.");

    if (S_OK == hr)
    {
        hr = pixn->get_nodeType(&nt);
        XmlExitOnFailure(hr, "failed to get node type");

        if (NODE_ATTRIBUTE != nt)
        {
            hr = E_UNEXPECTED;
            XmlExitOnFailure(hr, "Failed to get expected node type back: attribute");
        }

        // if the caller asked for the attribute name
        if (pbstrAttribute)
        {
            hr = pixn->get_baseName(pbstrAttribute);
            XmlExitOnFailure(hr, "failed to get attribute name");
        }

        *pixnAttribute = pixn;
        pixn = NULL;
    }

LExit:
    ReleaseObject(pixn);
    return hr;
}


/********************************************************************
 XmlNextElement - returns the next element in a node list

 NOTE: pbstrElement is optional
       returns S_OK if found an element
       returns S_FALSE if no element found
       returns E_* if something went wrong
********************************************************************/
extern "C" HRESULT DAPI XmlNextElement(
    __in IXMLDOMNodeList* pixnl,
    __out IXMLDOMNode** pixnElement,
    __deref_opt_out_z_opt BSTR* pbstrElement
    )
{
    Assert(pixnl && pixnElement);

    HRESULT hr = S_OK;
    IXMLDOMNode* pixn = NULL;
    DOMNodeType nt;

    // null out the return values
    *pixnElement = NULL;
    if (pbstrElement)
    {
        *pbstrElement = NULL;
    }

    //
    // find the next element in the list
    //
    while (S_OK == (hr = pixnl->nextNode(&pixn)))
    {
        hr = pixn->get_nodeType(&nt);
        XmlExitOnFailure(hr, "failed to get node type");

        if (NODE_ELEMENT == nt)
            break;

        ReleaseNullObject(pixn);
    }
    XmlExitOnFailure(hr, "failed to get next element");

    // if we have a node and the caller asked for the element name
    if (pixn && pbstrElement)
    {
        hr = pixn->get_baseName(pbstrElement);
        XmlExitOnFailure(hr, "failed to get element name");
    }

    *pixnElement = pixn;
    pixn = NULL;

    hr = *pixnElement ? S_OK : S_FALSE;
LExit:
    ReleaseObject(pixn);
    return hr;
}


/********************************************************************
 XmlRemoveChildren -

*********************************************************************/
extern "C" HRESULT DAPI XmlRemoveChildren(
    __in IXMLDOMNode* pixnSource,
    __in_z LPCWSTR pwzXPath
    )
{
    HRESULT hr = S_OK;

    // RELEASEME
    IXMLDOMNodeList* pixnlNodeList = NULL;
    IXMLDOMNode* pixnNode = NULL;
    IXMLDOMNode* pixnRemoveChild = NULL;

    if (pwzXPath)
    {
        hr = XmlSelectNodes(pixnSource, pwzXPath, &pixnlNodeList);
        XmlExitOnFailure(hr, "failed XmlSelectNodes");
    }
    else
    {
        hr = pixnSource->get_childNodes(&pixnlNodeList);
        XmlExitOnFailure(hr, "failed childNodes");
    }
    if (S_FALSE == hr)
    {
        ExitFunction();
    }

    while (S_OK == (hr = pixnlNodeList->nextNode(&pixnNode)))
    {
        hr = pixnSource->removeChild(pixnNode, &pixnRemoveChild);
        XmlExitOnFailure(hr, "failed removeChild");

        ReleaseNullObject(pixnRemoveChild);
        ReleaseNullObject(pixnNode);
    }
    if (S_FALSE == hr)
    {
        hr = S_OK;
    }

LExit:
    ReleaseObject(pixnlNodeList);
    ReleaseObject(pixnNode);
    ReleaseObject(pixnRemoveChild);

    return hr;
}


/********************************************************************
 XmlSaveDocument -

*********************************************************************/
extern "C" HRESULT DAPI XmlSaveDocument(
    __in IXMLDOMDocument* pixdDocument,
    __inout LPCWSTR wzPath
    )
{
    HRESULT hr = S_OK;

    // RELEASEME
    VARIANT varsDestPath;

    ::VariantInit(&varsDestPath);
    varsDestPath.vt = VT_BSTR;
    varsDestPath.bstrVal = ::SysAllocString(wzPath);
    if (!varsDestPath.bstrVal)
    {
        hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
    }
    XmlExitOnFailure(hr, "failed to create BSTR");

    hr = pixdDocument->save(varsDestPath);
    if (hr == S_FALSE)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "failed save in WriteDocument");

LExit:
    ReleaseVariant(varsDestPath);
    return hr;
}


/********************************************************************
 XmlSaveDocumentToBuffer

*********************************************************************/
extern "C" HRESULT DAPI XmlSaveDocumentToBuffer(
    __in IXMLDOMDocument* pixdDocument,
    __deref_out_bcount(*pcbDest) BYTE** ppbDest,
    __out DWORD* pcbDest
    )
{
    HRESULT hr = S_OK;
    IStream* pStream = NULL;
    LARGE_INTEGER li = { };
    STATSTG statstg = { };
    BYTE* pbDest = NULL;
    ULONG cbRead = 0;
    VARIANT vtDestination;

    ::VariantInit(&vtDestination);

    // create stream
    hr = ::CreateStreamOnHGlobal(NULL, TRUE, &pStream);
    XmlExitOnFailure(hr, "Failed to create stream.");

    // write document to stream
    vtDestination.vt = VT_UNKNOWN;
    vtDestination.punkVal = (IUnknown*)pStream;
    hr = pixdDocument->save(vtDestination);
    XmlExitOnFailure(hr, "Failed to save document.");

    // get stream size
    hr = pStream->Stat(&statstg, STATFLAG_NONAME);
    XmlExitOnFailure(hr, "Failed to get stream size.");

    // allocate buffer
    pbDest = static_cast<BYTE*>(MemAlloc(statstg.cbSize.LowPart, TRUE));
    XmlExitOnNull(pbDest, hr, E_OUTOFMEMORY, "Failed to allocate destination buffer.");

    // read data from stream
    li.QuadPart = 0;
    hr = pStream->Seek(li, STREAM_SEEK_SET, NULL);
    XmlExitOnFailure(hr, "Failed to seek stream.");

    hr = pStream->Read(pbDest, statstg.cbSize.LowPart, &cbRead);
    if (cbRead < statstg.cbSize.LowPart)
    {
        hr = E_FAIL;
    }
    XmlExitOnFailure(hr, "Failed to read stream content to buffer.");

    // return value
    *ppbDest = pbDest;
    pbDest = NULL;
    *pcbDest = statstg.cbSize.LowPart;

LExit:
    ReleaseObject(pStream);
    ReleaseMem(pbDest);
    return hr;
}