// XmlDocFragment.cpp
//
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2002 Robert Cunnings. All rights reserved.
//
// You may copy, modify, distribute or publish this code free of charge.
//
// NO WARRANTY: This software is provided AS IS, without warranty of any kind.
//
//////////////////////////////////////////////////////////////////////
//
// WMXMLDocFragment class - a class representing an XML fragment.
// Contains methods to serialize and deserialize element content
// to native types. Allows parsed XML to be queried using XPATH like syntax.
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "XMLDocFragment.h"
#include "charencode.h"
#include "xmlparser.h"
#include "base64.h"
#include "hexcode.h"
#include "tmconv.h"

WMXMLDocFragment::WMXMLDocFragment(void)
{
    m_pRoot = NULL;
}

WMXMLDocFragment::WMXMLDocFragment(WMXMLElement& Root)
{
    m_pRoot = new WMXMLElement(Root);
}

WMXMLDocFragment::~WMXMLDocFragment(void)
{
    if (m_pRoot != NULL)
    {
        delete m_pRoot;
    }
}

void WMXMLDocFragment::SetRootElem(WMXMLElement *pElem)
{
    if (m_pRoot != NULL)
    {
        delete m_pRoot;
    }
    m_pRoot = pElem;
}

bool WMXMLDocFragment::IsNilElement(WMXMLElement *pElem)
{
	// process any xsi:nil attribute
    wstring wstrValue;

	bool bRtn = pElem->GetAttribute(wstring(L"http://www.w3.org/2001/XMLSchema-instance"),
						            wstring(L"nil"),
						            wstrValue);
    if (bRtn == false)
    {
	    bRtn = pElem->GetAttribute( wstring(L"http://www.w3.org/1999/XMLSchema-instance"),
						            wstring(L"null"),
						            wstrValue);
        if (bRtn == false)
        {
	        bRtn = pElem->GetAttribute( wstring(L"http://www.w3.org/2000/10/XMLSchema-instance"),
						                wstring(L"nil"),
						                wstrValue);
        }
    }
    if (bRtn == false)
    {
        return false;
    }
    if (wstrValue == L"true" || wstrValue == L"1")
    {
        return true;
    }
    return false;
}

bool WMXMLDocFragment::IsEmptyElement(WMXMLElement *pElem)
{
    return pElem->GetContent().empty();
}

bool WMXMLDocFragment::SerializeString(const wstring& wstrValue, WMXMLElement *pElem)
{
    pElem->SetContent(wstrValue);
    return true;
}

bool WMXMLDocFragment::SerializeString(const string& strValue, WMXMLElement *pElem)
{
    wstring wstrTemp;
    UTF8ToWideString(strValue, wstrTemp);
    pElem->SetContent(wstrTemp);
    return true;
}

bool WMXMLDocFragment::DeserializeString(wstring& wstrValue, WMXMLElement *pElem)
{
    wstrValue = pElem->GetContent();
    return true;
}

bool WMXMLDocFragment::DeserializeString(string& strValue, WMXMLElement *pElem)
{
    WideStringToUTF8(pElem->GetContent(), strValue);
    return true;
}

bool WMXMLDocFragment::SerializeInt(int nValue, WMXMLElement *pElem)
{
    wchar_t buff[128];
    if (_snwprintf(buff, 128, L"%d", nValue) < 0)
    {
        return false;
    }
    pElem->SetContent(wstring(buff));
    return true;
}

bool WMXMLDocFragment::DeserializeInt(int& nValue, WMXMLElement *pElem)
{
    nValue = _wtoi(pElem->GetContent().c_str());
    return true;
}

bool WMXMLDocFragment::SerializeUInt(unsigned int uValue, WMXMLElement *pElem)
{
    wchar_t buff[128];
    if (_snwprintf(buff, 128, L"%u", uValue) < 0)
    {
        return false;
    }
    pElem->SetContent(wstring(buff));
    return true;
}

bool WMXMLDocFragment::DeserializeUInt(unsigned int& uValue, WMXMLElement *pElem)
{
    uValue = wcstoul(pElem->GetContent().c_str(), NULL, 10);
    return true;
}

bool WMXMLDocFragment::SerializeFloat(double fltValue, WMXMLElement *pElem)
{
    wchar_t buff[128];
    if (_snwprintf(buff, 128, L"%g", fltValue) < 0)
    {
        return false;
    }
    pElem->SetContent(wstring(buff));
    return true;
}

bool WMXMLDocFragment::DeserializeFloat(double& fltValue, WMXMLElement *pElem)
{
    fltValue = _wtof(pElem->GetContent().c_str());
    return true;
}

bool WMXMLDocFragment::SerializeTime(COleDateTime& dt, WMXMLElement *pElem)
{
	struct tm t;
	if (TmFromOleDate((DATE)dt, t) == false)
	{
		return false;
	}
	TmConvertToStandardFormat(t);
	// format
	wchar_t wszBuffer[64];
	wcsftime(wszBuffer, 64, L"%H:%M:%SZ", &t);
	pElem->AppendContent(wstring(wszBuffer));
	return true;
}

bool WMXMLDocFragment::DeserializeTime(COleDateTime& dt, WMXMLElement *pElem)
{
	wstring wstrTemp = pElem->GetContent();
	// expected format is "HH:MM:SS[Z]" or "HH:MM:SS[+-]HH:MM"
	BSTR bstrTime = SysAllocString(wstrTemp.substr(0, 8).c_str());

	DATE t;
	HRESULT hRes = VarDateFromStr(bstrTime, LANG_USER_DEFAULT, VAR_TIMEVALUEONLY, &t);
	SysFreeString(bstrTime);
	if (FAILED(hRes))
	{
		return false;
	}
	// check for time zone
	wstring::size_type pos;
	if ((pos = wstrTemp.find(wchar_t('Z'))) == wstring::npos)
	{
		HRESULT hRes;
		BSTR bstrZone;
		bool bPositiveOffset;
		bool bTimeZoneFound = true;
		// not UTC
		if ((pos = wstrTemp.find(wchar_t('+'))) != wstring::npos)
		{
			// positive time zone offset
			bPositiveOffset = true;
		}
		else if ((pos = wstrTemp.find(wchar_t('-'))) != wstring::npos)
		{
			// negative time zone offset
			bPositiveOffset = false;
		}
		else
		{
			bTimeZoneFound = false;
		}
		if (bTimeZoneFound = true)
		{
			// normalize to UTC
			wstrTemp = wstrTemp.substr(pos + 1, 5);
			wstrTemp += L":00";
			DATE z;
			bstrZone = SysAllocString(wstrTemp.c_str());
			hRes = VarDateFromStr(bstrZone, LANG_USER_DEFAULT, VAR_TIMEVALUEONLY, &z);
			SysFreeString(bstrZone);
			if (FAILED(hRes))
			{
				return false;
			}
			if (bPositiveOffset == false)
			{
				t = t + z;
				if (t > 1.0)
				{
					t = t - 1.0;
				}
			}
			else
			{
				t = t - z;
				if (t < 1.0)
				{
					t = t + 1.0;
				}
			}
		}
	}
	dt = COleDateTime(t);
	return true;
}

bool WMXMLDocFragment::SerializeDateTime(COleDateTime& dt, WMXMLElement *pElem)
{
	struct tm t;
	if (TmFromOleDate((DATE)dt, t) == false)
	{
		return false;
	}
	TmConvertToStandardFormat(t);
	// format
	wchar_t wszBuffer[64];
	wcsftime(wszBuffer, 64, L"%Y-%m-%dT%H:%M:%SZ", &t);
	pElem->AppendContent(wstring(wszBuffer));
	return true;
}

bool WMXMLDocFragment::DeserializeDateTime(COleDateTime& dt, WMXMLElement *pElem)
{
	wstring wstrTemp = pElem->GetContent();
	// find delimiter 'T' expected format is "CCYY-MM-DDTHH:MM:SS[Z]" or "CCYY-MM-DDTHH:MM:SS[+-]HH:MM"
	wstring::size_type pos;
	pos = wstrTemp.find(wchar_t('T'));
	if (pos == wstring::npos)
	{
		// invalid
		return false;
	}
	BSTR bstrDate = SysAllocString(wstrTemp.substr(0, pos).c_str());
	BSTR bstrTime = SysAllocString(wstrTemp.substr(pos + 1, 8).c_str());

	DATE d, t;
	HRESULT hRes = VarDateFromStr(bstrDate, LOCALE_SYSTEM_DEFAULT, VAR_DATEVALUEONLY, &d);
	SysFreeString(bstrDate);
	if (FAILED(hRes))
	{
		return false;
	}
	hRes = VarDateFromStr(bstrTime, LOCALE_SYSTEM_DEFAULT, VAR_TIMEVALUEONLY, &t);
	SysFreeString(bstrTime);
	if (FAILED(hRes))
	{
		return false;
	}
	// check for time zone
	wstrTemp = wstrTemp.substr(pos + 1);
	if ((pos = wstrTemp.find(wchar_t('Z'))) == wstring::npos)
	{
		HRESULT hRes;
		BSTR bstrZone;
		bool bPositiveOffset;
		bool bTimeZoneFound = true;
		// not UTC
		if ((pos = wstrTemp.find(wchar_t('+'))) != wstring::npos)
		{
			// positive time zone offset
			bPositiveOffset = true;
		}
		else if ((pos = wstrTemp.find(wchar_t('-'))) != wstring::npos)
		{
			// negative time zone offset
			bPositiveOffset = false;
		}
		else
		{
			bTimeZoneFound = false;
		}
		if (bTimeZoneFound == true)
		{
			// normalize to UTC
			wstrTemp = wstrTemp.substr(pos + 1, 5);
			wstrTemp += L":00";
			DATE z;
			bstrZone = SysAllocString(wstrTemp.c_str());
			hRes = VarDateFromStr(bstrZone, LANG_USER_DEFAULT, VAR_TIMEVALUEONLY, &z);
			SysFreeString(bstrZone);
			if (FAILED(hRes))
			{
				return false;
			}
			if (bPositiveOffset == false)
			{
				t = t + z;
				if (t > 1.0)
				{
					d = d + 1.0;
					t = t - 1.0;
				}
			}
			else
			{
				t = t - z;
				if (t < 1.0)
				{
					d = d - 1.0;
					t = t + 1.0;
				}
			}
		}
	}
	dt = COleDateTime(d + t);
	return true;
}

bool WMXMLDocFragment::SerializeDecimal(_variant_t& decValue, WMXMLElement *pElem)
{
	_variant_t varTemp(decValue);
	if (FAILED(VariantChangeType(&varTemp, &varTemp, 0, VT_BSTR)))
    {
        return false;
    }
    pElem->SetContent(wstring(varTemp.bstrVal));
    return true;
}

bool WMXMLDocFragment::DeserializeDecimal(_variant_t& decValue, WMXMLElement *pElem)
{
    decValue = pElem->GetContent().c_str();
	if (FAILED(VariantChangeType(&decValue, &decValue, 0, VT_DECIMAL)))
    {
        return false;
    }
    return true;
}


bool WMXMLDocFragment::SerializeBoolean(bool bValue, WMXMLElement *pElem)
{
    pElem->SetContent(bValue == true ? wstring(L"true") : wstring(L"false"));
    return true;
}

bool WMXMLDocFragment::DeserializeBoolean(bool& bValue, WMXMLElement *pElem)
{
    if (pElem->GetContent() == L"1" || pElem->GetContent() == L"true")
    {
        bValue = true;
    }
    else if (pElem->GetContent() == L"0" || pElem->GetContent() == L"false")
    {
        bValue = false;
    }
    else
    {
        return false;
    }
    return true;
}

bool WMXMLDocFragment::SerializeBase64(const unsigned char *pValue, int nValueLength, WMXMLElement *pElem)
{
    WMBase64 base64;
    string strEncoded;
    if (base64.Encode(pValue, nValueLength, strEncoded) == false)
    {
        return false;
    }
    wstring wstrContent;
    UTF8ToWideString(strEncoded, wstrContent);
    pElem->SetContent(wstrContent);
    return true;
}

bool WMXMLDocFragment::DeserializeBase64(unsigned char *pValue, int& nValueLength, WMXMLElement *pElem)
{
    WMBase64 base64;
    string strTemp;
    WideStringToUTF8(pElem->GetContent(), strTemp);
	return base64.Decode(strTemp, pValue, nValueLength);
}

bool WMXMLDocFragment::SerializeHexBinary(const unsigned char *pValue, int nValueLength, WMXMLElement *pElem)
{
    WMHexCode hex;
    string strEncoded;
    if (hex.Encode(pValue, nValueLength, strEncoded) == false)
    {
        return false;
    }
    wstring wstrContent;
    UTF8ToWideString(strEncoded, wstrContent);
    pElem->SetContent(wstrContent);
    return true;
}

bool WMXMLDocFragment::DeserializeHexBinary(unsigned char *pValue,
                                            int& nValueLength,
                                            WMXMLElement *pElem)
{
    WMHexCode hex;
    string strTemp;
    WideStringToUTF8(pElem->GetContent(), strTemp);
	return hex.Decode(strTemp, pValue, nValueLength);
}

bool WMXMLDocFragment::SerializeXML(string& strXML, bool bPrettyPrint)
{
    if (m_pRoot == NULL)
    {
        return false;
    }
	wstring wstrXML;
    if (bPrettyPrint == true)
    {
	    m_pRoot->Serialize(NULL, wstrXML, 0);
    }
    else
    {
	    m_pRoot->Serialize(NULL, wstrXML);
    }
	WideStringToUTF8(wstrXML, strXML);
    return true;
}

bool WMXMLDocFragment::SerializeXML(wstring& wstrXML, bool bPrettyPrint)
{
    if (m_pRoot == NULL)
    {
        return false;
    }
    if (bPrettyPrint == true)
    {
	    m_pRoot->Serialize(NULL, wstrXML, 0);
    }
    else
    {
	    m_pRoot->Serialize(NULL, wstrXML);
    }
    return true;
}

bool WMXMLDocFragment::SerializeXML(BSTR *pbstrXML, bool bPrettyPrint)
{
    if (m_pRoot == NULL)
    {
        return false;
    }
    if (bPrettyPrint == true)
    {
	    *pbstrXML = m_pRoot->Serialize(NULL, 0);
    }
    else
    {
	    *pbstrXML = m_pRoot->Serialize(NULL);
    }
    return true;
}

bool WMXMLDocFragment::DeserializeXML(const char *pszXML, unsigned long ulLength)
{
    if (m_pRoot != NULL)
    {
        delete m_pRoot;
        m_pRoot = NULL;
    }
	// set up for parsing
	WMXMLParser Parser;
	bool bRtn = true;
	// parse the XML response
	try
	{
		Parser.ParseXML(pszXML, ulLength, NULL);
	}
	catch(int e)
	{
		int x = e;
		return false;
	}
	// get the WMXMLElement pointer representing root node of parse tree
	m_pRoot = Parser.DetachDocRoot();
	return m_pRoot != NULL;
}

bool WMXMLDocFragment::DeserializeXML(const string& strXML)
{
    if (m_pRoot != NULL)
    {
        delete m_pRoot;
        m_pRoot = NULL;
    }
	// set up for parsing
	WMXMLParser Parser;
	bool bRtn = true;
	// parse the XML response
	try
	{
		Parser.ParseXML(strXML.c_str(), (unsigned long)strXML.length(), NULL);
	}
	catch(int e)
	{
		int x = e;
		return false;
	}
	// get the WMXMLElement pointer representing root node of parse tree
	m_pRoot = Parser.DetachDocRoot();
	return m_pRoot != NULL;
}

bool WMXMLDocFragment::DeserializeXML(const BSTR bstrXML)
{
    if (m_pRoot != NULL)
    {
        delete m_pRoot;
        m_pRoot = NULL;
    }
	// set up for parsing
	WMXMLParser Parser;
	bool bRtn = true;
	// parse the XML response
	try
	{
		Parser.ParseXML_WideString(bstrXML, NULL);
	}
	catch(int e)
	{
		int x = e;
		return false;
	}
	// get the WMXMLElement pointer representing root node of parse tree
	m_pRoot = Parser.DetachDocRoot();
	return m_pRoot != NULL;
}

bool WMXMLDocFragment::DeserializeXML(const wstring& wstrXML)
{
    if (m_pRoot != NULL)
    {
        delete m_pRoot;
        m_pRoot = NULL;
    }
	// set up for parsing
	WMXMLParser Parser;
	bool bRtn = true;
	// parse the XML response
	try
	{
		Parser.ParseXML_WideString(wstrXML, NULL);
	}
	catch(int e)
	{
		int x = e;
		return false;
	}
	// get the WMXMLElement pointer representing root node of parse tree
	m_pRoot = Parser.DetachDocRoot();
	return m_pRoot != NULL;
}

WMXMLElement *WMXMLDocFragment::CreateRootElem( const wstring& wstrNamespace,
                                                const wstring& wstrPrefix,
                                                const wstring& wstrLocalName)
{
    if (m_pRoot != NULL)
    {
        delete m_pRoot;
        m_pRoot = NULL;
    }
    wstring wstrName;
    if (wstrNamespace.empty() == false && wstrPrefix.empty() == false)
    {
        wstrName = wstrPrefix;
        wstrName += L":";
        wstrName += wstrLocalName;
    }
    else
    {
        wstrName = wstrLocalName;
    }
    WMXMLElement *pElem = new WMXMLElement(wstrName);
	if (pElem == NULL)
	{
        return NULL;
    }
    if (wstrNamespace.empty() == false)
    {
        if (wstrPrefix.empty() == false)
        {
    	    pElem->SetNSPrefix(wstrPrefix, wstrNamespace);
        }
        else
        {
            // default namespace
    	    pElem->SetAttribute(wstring(L"xmlns"), wstrNamespace);
        }
    }
	m_pRoot = pElem;
    return pElem;
}

WMXMLElement *WMXMLDocFragment::CreateChildElem(WMXMLElement *pParent,
                                                const wstring& wstrNamespace,
                                                const wstring& wstrPrefix,
                                                const wstring& wstrLocalName)
{
    wstring wstrName;
    if (wstrNamespace.empty() == false && wstrPrefix.empty() == false)
    {
        wstrName = wstrPrefix;
        wstrName += L":";
        wstrName += wstrLocalName;
    }
    else
    {
        wstrName = wstrLocalName;
    }
    WMXMLElement *pElem = new WMXMLElement(wstrName);
	if (pElem == NULL)
	{
        return NULL;
    }
    if (wstrNamespace.empty() == false)
    {
        if (wstrPrefix.empty() == false)
        {
    	    pElem->SetNSPrefix(wstrPrefix, wstrNamespace);
        }
        else
        {
            // default namespace
    	    pElem->SetAttribute(wstring(L"xmlns"), wstrNamespace);
        }
    }
    if (pParent != NULL)
    {
	    pParent->AddChildElement(pElem);
    }
    return pElem;
}

bool WMXMLDocFragment::SelectElements(  const wstring& wstrPath,
                                        list<WMXMLElement *>& listElements,
                                        map<wstring, wstring>& mapNamespaces)
{
    if (wstrPath[0] != wchar_t('/') || m_pRoot == NULL)
    {
        return false;
    }
    // select root?
    if (wstrPath.length() == 1)
    {
        listElements.push_back(m_pRoot);
        return true;
    }
    return SelectElements(  m_pRoot,
                            wstrPath.substr(1),
                            listElements,
                            mapNamespaces);
}

bool WMXMLDocFragment::SelectElements(  WMXMLElement *pContext,
                                        const wstring& wstrPath,
                                        list<WMXMLElement *>& listElements,
                                        map<wstring, wstring>& mapNamespaces)
{
    if (wstrPath[0] == wchar_t('/'))
    {
        return false;
    }
    bool bDone = false;
    // parse path
    wstring::size_type pos;
    wstring wstrInputPath(wstrPath);
    wstring wstrTemp;
    pos = wstrInputPath.find(wchar_t('/'));
    wstrTemp = wstrInputPath.substr(0, pos);
    if (pos == wstring::npos)
    {
        bDone = true;
    }
    else
    {
        wstrInputPath = wstrInputPath.substr(pos + 1);
    }
    // parse possible predicate
    int nPredicate = 0;
    bool bIsNumberPredicate = false;
	pos = wstrTemp.find(wchar_t('['));
    if (pos != wstring::npos)
    {
        wstring wstrPredicate = wstrTemp.substr(pos + 1);
        wstrTemp = wstrTemp.substr(0, pos);
        pos = wstrPredicate.find(wchar_t(']'));
        if (pos == wstring::npos)
        {
            // missing closing bracket
            return false;
        }
        wstrPredicate = wstrPredicate.substr(0, pos);
        if ((nPredicate = _wtoi(wstrPredicate.c_str())) == 0)
        {
            // only number predicates supported
            return false;
        }
        if (nPredicate < 1)
        {
            // must be positive integer
            return false;
        }
        bIsNumberPredicate = true;
    }
    // parse possible qname
    wstring wstrNS, wstrName;
	pos = wstrTemp.find(':');
	if (pos != wstring::npos)
	{
		// get NS prefix
        map<wstring, wstring>::iterator iM;
		if ((iM = mapNamespaces.find(wstrTemp.substr(0, pos))) != mapNamespaces.end())
		{
			wstrNS = (*iM).second;
			wstrName = wstrTemp.substr(++pos);
		}
		else
		{
			// namespace not found in NS map
            return false;
		}
	}
    else
    {
        wstrName = wstrTemp;
    }
    // seek element node(s)
    ChildElementsList::iterator iL;
    if (bDone == true)
    {
        int counter = 1;
        for (iL = pContext->GetChildElements().begin();
            iL != pContext->GetChildElements().end();
            iL++)
        {
            WMXMLElement *pElem = *iL;
            if (pElem == NULL)
            {
                continue;
            }
            if (wstrName == L"*" || (pElem->GetLocalName() == wstrName && pElem->GetURI() == wstrNS))
            {
                if (bIsNumberPredicate == true)
                {
                    if (counter++ == nPredicate)
                    {
                        listElements.push_back(pElem);
                        return true;
                    }
                    continue;
                }
                listElements.push_back(pElem);
            }
        }
        if (bIsNumberPredicate == true)
        {
            return false;
        }
        return true;
    }
    // descend
    int counter = 1;
    for (iL = pContext->GetChildElements().begin();
        iL != pContext->GetChildElements().end();
        iL++)
    {
        WMXMLElement *pElem = *iL;
        if (pElem == NULL)
        {
            continue;
        }
        if (wstrName == L"*" || (pElem->GetLocalName() == wstrName && pElem->GetURI() == wstrNS))
        {
            if (bIsNumberPredicate == true)
            {
                if (counter++ == nPredicate)
                {
                    return SelectElements(  pElem,
                                            wstrInputPath,
                                            listElements,
                                            mapNamespaces);
                }
                continue;
            }
            if (SelectElements( pElem,
                                wstrInputPath,
                                listElements,
                                mapNamespaces) == false)
            {
                return false;
            }
        }
    }
    if (bIsNumberPredicate == true)
    {
        return false;
    }
    return true;
}

bool WMXMLDocFragment::SelectElems( const wstring& wstrPath,
                                    map<wstring, wstring>& mapNamespaces)
{
    m_listSelection.clear();
    if (SelectElements( wstrPath,
                        m_listSelection,
                        mapNamespaces) == false)
    {
        m_iterSelect = m_listSelection.begin();
        return false;
    }
    m_iterSelect = m_listSelection.begin();
    return m_listSelection.size() != 0;
}

bool WMXMLDocFragment::SelectElems(WMXMLElement *pContext,
            const wstring& wstrPath,
            map<wstring, wstring>& mapNamespaces)
{
    m_listSelection.clear();
    if (SelectElements( pContext,
                        wstrPath,
                        m_listSelection,
                        mapNamespaces) == false)
    {
        m_iterSelect = m_listSelection.begin();
        return false;
    }
    m_iterSelect = m_listSelection.begin();
    return m_listSelection.size() != 0;
}

WMXMLElement *WMXMLDocFragment::NextElem()
{
    if (m_iterSelect == m_listSelection.end())
    {
        return NULL;
    }
    return *m_iterSelect++;
}
