//////////////////////////////////////////////////////////////////////////////////// // // File: xml.cpp // // History: 16-Nov-00 markder Created. // // Desc: This file contains helper functions to manipulate // the MSXML's document object model (DOM). // //////////////////////////////////////////////////////////////////////////////////// #include "StdAfx.h" #include "xml.h" void __stdcall _com_issue_error(long) { SDBERROR(_T("Unknown COM error!!")); } //////////////////////////////////////////////////////////////////////////////////// // // XMLNodeList Implementation // // This class is a wrapper for the IXMLDOMNodeList interface. It simplifies // C++ access by exposing functions for executing XQL queries and iterating // through the elements in a node list. // //////////////////////////////////////////////////////////////////////////////////// XMLNodeList::XMLNodeList() { m_nSize = 0; } XMLNodeList::~XMLNodeList() { Clear(); } void XMLNodeList::Clear() { m_nSize = 0; m_csXQL.Empty(); if (m_cpList) { m_cpList.Release(); } } LONG XMLNodeList::GetSize() { return m_nSize; } BOOL XMLNodeList::Query(IXMLDOMNode* pNode, LPCTSTR szXQL) { BOOL bSuccess = FALSE; BSTR bsXQL = NULL; CString csXQL(szXQL); bsXQL = csXQL.AllocSysString(); Clear(); if (FAILED(pNode->selectNodes(bsXQL, &m_cpList))) { CString csFormat; csFormat.Format(_T("Error executing XQL \"%s\""), szXQL); SDBERROR(csFormat); goto eh; } if (FAILED(m_cpList->get_length(&m_nSize))) { CString csFormat; csFormat.Format(_T("Error executing XQL \"%s\""), szXQL); SDBERROR(csFormat); goto eh; } m_csXQL = szXQL; bSuccess = TRUE; eh: if (bsXQL != NULL) { SysFreeString(bsXQL); } if (!bSuccess) { Clear(); } return bSuccess; } BOOL XMLNodeList::GetChildNodes(IXMLDOMNode* pNode) { BOOL bSuccess = FALSE; Clear(); if (FAILED(pNode->get_childNodes(&m_cpList))) { SDBERROR(_T("Error retrieving child nodes")); goto eh; } if (FAILED(m_cpList->get_length(&m_nSize))) { SDBERROR(_T("Error retrieving child nodes")); goto eh; } bSuccess = TRUE; eh: if (!bSuccess) { Clear(); } return bSuccess; } BOOL XMLNodeList::GetItem(LONG nIndex, IXMLDOMNode** ppNode) { BOOL bSuccess = FALSE; if (nIndex < 0 || nIndex >= m_nSize) { CString csFormat; csFormat.Format(_T("XMLNodeList index %d out of range for XQL \"%s\""), nIndex, m_csXQL); SDBERROR(csFormat); goto eh; } if (FAILED(m_cpList->get_item(nIndex, ppNode))) { CString csFormat; csFormat.Format(_T("XMLNodeList get_item failed for XQL \"%s\""), m_csXQL); SDBERROR(csFormat); goto eh; } bSuccess = TRUE; eh: return bSuccess; } //////////////////////////////////////////////////////////////////////////////////// // // Func: OpenXML // // Desc: Opens an XML file or stream and returns the root node. // BOOL OpenXML( CString csFileOrStream, IXMLDOMNode** ppRootNode, BOOL bStream, IXMLDOMDocument** ppDoc) { long i; long nErrorLine = 0; long nErrorLinePos = 0; long nListCount = 0; BOOL bSuccess = FALSE; BSTR bsSrcText = NULL; BSTR bsErrorReason = NULL; HRESULT hr = E_FAIL; VARIANT vFileOrStream; VARIANT_BOOL vbSuccess = VARIANT_FALSE; IXMLDOMDocument* pDoc = NULL; IXMLDOMParseErrorPtr cpXMLParseError; VariantInit(&vFileOrStream); VariantClear(&vFileOrStream); if (ppDoc == NULL) { ppDoc = &pDoc; } if (*ppDoc == NULL) { if (FAILED(CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (LPVOID*)ppDoc))) { SDBERROR(_T("Could not instantiate MSXML object.\n")); goto eh; } } vFileOrStream.vt = VT_BSTR; vFileOrStream.bstrVal = csFileOrStream.AllocSysString(); // // This statement prevents XML parser from replacing white space // characters with tabs // if (bStream) { hr = (*ppDoc)->loadXML(vFileOrStream.bstrVal, &vbSuccess); } else { (*ppDoc)->put_preserveWhiteSpace(VARIANT_TRUE); (*ppDoc)->put_validateOnParse(g_bStrict ? VARIANT_TRUE : VARIANT_FALSE); hr = (*ppDoc)->load(vFileOrStream, &vbSuccess); } if (FAILED(hr) || vbSuccess == VARIANT_FALSE) { if (FAILED((*ppDoc)->get_parseError(&cpXMLParseError))) { SDBERROR(_T("Could not retrieve XMLDOMParseError object")); goto eh; } if (FAILED(cpXMLParseError->get_line(&nErrorLine))) { SDBERROR(_T("Could not retrieve line number from XMLDOMParseError object")); goto eh; } if (FAILED(cpXMLParseError->get_linepos(&nErrorLinePos))) { SDBERROR(_T("Could not retrieve line position from XMLDOMParseError object")); goto eh; } if (FAILED(cpXMLParseError->get_srcText(&bsSrcText))) { SDBERROR(_T("Could not retrieve source text from XMLDOMParseError object")); goto eh; } if (FAILED(cpXMLParseError->get_reason(&bsErrorReason))) { SDBERROR(_T("Could not retrieve error reason from XMLDOMParseError object")); goto eh; } CString csError; csError.Format(_T("XML parsing error on line %d:\n\n%ls\n\n%ls\n"), nErrorLine, bsErrorReason, bsSrcText); while (nErrorLinePos--) { csError += " "; } csError += _T("^----- Error\n\n"); SDBERROR(csError); goto eh; } if (FAILED((*ppDoc)->QueryInterface(IID_IXMLDOMNode, (LPVOID*)ppRootNode))) { SDBERROR(_T("Could not retrieve XMLDOMNode object from XMLDOMDocument interface")); goto eh; } bSuccess = TRUE; eh: if (pDoc) { pDoc->Release(); } if (bsSrcText) { SysFreeString(bsSrcText); } if (bsErrorReason) { SysFreeString(bsErrorReason); } VariantClear(&vFileOrStream); return bSuccess; } //////////////////////////////////////////////////////////////////////////////////// // // Func: SaveXMLFile // // Desc: Saves an XML file. // BOOL SaveXMLFile( CString csFile, IXMLDOMNode* pNode) { BOOL bSuccess = FALSE; DWORD dwAttr; DWORD dwErr; CString csFormat; VARIANT vFilename; HRESULT hr; IXMLDOMDocumentPtr cpDocument; VariantInit(&vFilename); // // Check file attributes // dwAttr = GetFileAttributes(csFile); if ((DWORD)-1 == dwAttr) { dwErr = GetLastError(); if (ERROR_FILE_NOT_FOUND != dwErr) { csFormat.Format(_T("Error accessing XML file: %s (0x%lx)\n"), dwErr); SDBERROR(csFormat); goto eh; } } else if (dwAttr & FILE_ATTRIBUTE_READONLY) { csFormat.Format(_T("File \"%s\" appears to be read-only and cannot be updated\n"), csFile); SDBERROR(csFormat); goto eh; } if (FAILED(pNode->get_ownerDocument(&cpDocument))) { SDBERROR(_T("Could not retrieve ownerDocument property of node.")); goto eh; } vFilename.vt = VT_BSTR; vFilename.bstrVal = csFile.AllocSysString(); hr = cpDocument->save(vFilename); if (FAILED(hr)) { csFormat.Format(_T("Could not update XML file: %s (0x%lx)\n"), csFile, (DWORD)hr); SDBERROR(csFormat); goto eh; } bSuccess = TRUE; eh: VariantClear(&vFilename); return bSuccess; } CString ReplaceAmp( LPCTSTR lpszXML) { LPTSTR pchStart = (LPTSTR)lpszXML; LPTSTR pchEnd; LPTSTR pchHRef; LPTSTR pchTag; TCHAR ch; CString csXML = ""; // << this is what we return CString csHRef; do { pchHRef = _tcsstr(pchStart, _T("href")); if (NULL == pchHRef) { pchHRef = _tcsstr(pchStart, _T("HREF")); } if (NULL != pchHRef) { // // Find the closing bracket // pchEnd = _tcschr(pchHRef, _T('>')); if (NULL == pchEnd) { csXML += pchStart; pchHRef = NULL; } else { // // Now see where this thing starts // ch = *pchHRef; *pchHRef = _T('\0'); // // Search back to the first '<' // pchTag = _tcsrchr(pchStart, _T('<')); *pchHRef = ch; if (NULL == pchTag) { pchTag = pchStart; } // // Now we have < > // csHRef = CString(pchTag, (int)(pchEnd - pchTag + 1)); csHRef.Replace(_T("%26"), _T("&")); csHRef.Replace(_T("&"), _T("&")); csXML += CString(pchStart, (int)(pchTag-pchStart)) + csHRef; pchStart = pchEnd + 1; } } else { csXML += pchStart; } } while (NULL != pchHRef); return csXML; } //////////////////////////////////////////////////////////////////////////////////// // // Func: GetInnerXML // // Desc: Returns the XML between the begin/end tag of pNode. // CString GetInnerXML( IXMLDOMNode* pNode) { USES_CONVERSION; long nIndex = 0; long nListLength = 0; IXMLDOMNode* pNodeChild = NULL; IXMLDOMNodeList* pNodeList = NULL; DOMNodeType NodeType; CString csNodeXML; CString csHRef; CString csFixedHRef; CString strXML; strXML.Empty(); if (FAILED(pNode->get_childNodes(&pNodeList)) || pNodeList == NULL) { SDBERROR(_T("get_childNodes failed while retrieving innerXML")); goto eh; } if (FAILED(pNodeList->get_length(&nListLength))) { SDBERROR(_T("get_length failed while retrieving innerXML")); goto eh; } while (nIndex < nListLength) { if (FAILED(pNodeList->get_item(nIndex, &pNodeChild))) { SDBERROR(_T("get_item failed while retrieving innerXML")); goto eh; } csNodeXML = GetXML(pNodeChild); strXML += csNodeXML; pNodeChild->Release(); pNodeChild = NULL; ++nIndex; } ReplaceStringNoCase(strXML, _T(" xmlns=\"x-schema:schema.xml\""), _T("")); eh: if (NULL != pNodeList) { pNodeList->Release(); } if (NULL != pNodeChild) { pNodeChild->Release(); } return strXML; } //////////////////////////////////////////////////////////////////////////////////// // // Func: GetAttribute // // Desc: Returns the text value of the attribute specified by lpszAttribute on node // pNode. If the the attribute doesn't exist, the function returns FALSE. // BOOL GetAttribute( LPCTSTR lpszAttribute, IXMLDOMNodePtr pNode, CString* pcsValue, BOOL bXML) { USES_CONVERSION; BOOL bSuccess = FALSE; BSTR bsQuery = NULL; CString csQuery; IXMLDOMNodePtr cpAttrNode; csQuery = _T("@"); csQuery += lpszAttribute; bsQuery = csQuery.AllocSysString(); // // g_csError will not be set in this function. It is up // to the caller to handle a FALSE return from this function // and report appropriately. // if (FAILED(pNode->selectSingleNode(bsQuery, &cpAttrNode))) { goto eh; } if (cpAttrNode == NULL) { goto eh; } if (bXML) { *pcsValue = GetXML(cpAttrNode); } else { *pcsValue = GetText(cpAttrNode); } bSuccess = TRUE; eh: if (bsQuery != NULL) { SysFreeString(bsQuery); } return bSuccess; } //////////////////////////////////////////////////////////////////////////////////// // // Func: RemoveAttribute // // Desc: Removes the specified attribute from the element. // BOOL RemoveAttribute( CString csName, IXMLDOMNodePtr pNode) { USES_CONVERSION; BOOL bSuccess = FALSE; BSTR bsName = NULL; IXMLDOMNamedNodeMap* pNodeMap = NULL; IXMLDOMNode* pAttrNode = NULL; // // g_csError will not be set in this function. It is up // to the caller to handle a FALSE return from this function // and report appropriately. // if (FAILED(pNode->get_attributes(&pNodeMap)) || pNodeMap == NULL) { goto eh; } bsName = csName.AllocSysString(); if (FAILED(pNodeMap->removeNamedItem(bsName, &pAttrNode))) { goto eh; } bSuccess = TRUE; eh: if (pNodeMap != NULL) { pNodeMap->Release(); } if (pAttrNode != NULL) { pAttrNode->Release(); } if (bsName != NULL) { SysFreeString(bsName); } return bSuccess; } //////////////////////////////////////////////////////////////////////////////////// // // Func: GetChild // // Desc: Returns the child node corresponding to the specified tag name. // BOOL GetChild( LPCTSTR lpszTag, IXMLDOMNode* pParentNode, IXMLDOMNode** ppChildNode) { BOOL bSuccess = FALSE; XMLNodeList XQL; if (!XQL.Query(pParentNode, lpszTag)) { goto eh; } if (XQL.GetSize() == 0) { goto eh; } if (XQL.GetSize() > 1) { goto eh; } if (!XQL.GetItem(0, ppChildNode)) { goto eh; } bSuccess = TRUE; eh: return bSuccess; } //////////////////////////////////////////////////////////////////////////////////// // // Func: GetText // // Desc: Returns the value of the text property on node pNode. // CString GetText( IXMLDOMNode* pNode) { CString csText; BSTR bsText = NULL; HRESULT hr; hr = pNode->get_text(&bsText); if (SUCCEEDED(hr)) { csText = bsText; if (bsText) { SysFreeString(bsText); } } // // If get_text fails, then csText is blank. // return csText; } //////////////////////////////////////////////////////////////////////////////////// // // Func: GetText // // Desc: Returns the value of the node pNode, in string form // CString GetNodeValue( IXMLDOMNode* pNode) { CString csVal; VARIANT var; VariantInit(&var); // BUGBUG: what if some of these calls fail! if (S_OK == pNode->get_nodeValue(&var)) { if (VT_BSTR == var.vt) { csVal = var.bstrVal; if (NULL != var.bstrVal) { SysFreeString(var.bstrVal); } } } return csVal; } //////////////////////////////////////////////////////////////////////////////////// // // Func: GetText // // Desc: Retrieves the value of the text property on node pNode. // excludes any comment text // Returns FALSE in case of an error // BOOL GetNodeText( IXMLDOMNode* pNode, CString& csNodeText ) { USES_CONVERSION; BOOL bSuccess = FALSE; long nIndex = 0; long nListLength = 0; IXMLDOMNode* pNodeText = NULL; IXMLDOMNodeList* pNodeList = NULL; DOMNodeType NodeType; CString csText; csNodeText.Empty(); if (FAILED(pNode->get_childNodes(&pNodeList)) || pNodeList == NULL) { // BUGBUG: display some error goto eh; } if (FAILED(pNodeList->get_length(&nListLength))) { // BUGBUG: display some error goto eh; } while (nIndex < nListLength) { if (FAILED(pNodeList->get_item(nIndex, &pNodeText))) { // BUGBUG: display some error goto eh; // can't get the item } if (FAILED(pNodeText->get_nodeType(&NodeType))) { // BUGBUG: display some error goto eh; // can't get node type } if (NODE_TEXT == NodeType) { // // now this node is a body text // csText = GetNodeValue(pNodeText); csText.TrimLeft(); csText.TrimRight(); if (!csText.IsEmpty()) { csNodeText += CString(_T(' ')) + csText; } } pNodeText->Release(); pNodeText = NULL; ++nIndex; } // // we have gathered all the text from this node // bSuccess = !csNodeText.IsEmpty(); eh: if (NULL != pNodeList) { pNodeList->Release(); } if (NULL != pNodeText) { pNodeText->Release(); } return bSuccess; } //////////////////////////////////////////////////////////////////////////////////// // // Func: GetNodeName // // Desc: Returns the nodeName value from the specified node. // CString GetNodeName( IXMLDOMNode* pNode) { CString csName; BSTR bsName = NULL; if (SUCCEEDED(pNode->get_nodeName(&bsName))) { csName = bsName; } if (bsName) SysFreeString(bsName); // // If get_nodeName fails, then csName is blank. // return csName; } CString GetParentNodeName( IXMLDOMNode* pNode) { CString csName; IXMLDOMNodePtr cpParent; if (FAILED(pNode->get_parentNode(&cpParent))) { return CString(); } return GetNodeName(cpParent); } //////////////////////////////////////////////////////////////////////////////////// // // Func: GetXML // // Desc: Returns the value of the xml property on node pNode. // CString GetXML( IXMLDOMNode* pNode) { CString csXML; BSTR bsXML = NULL; HRESULT hr; hr = pNode->get_xml(&bsXML); if (SUCCEEDED(hr)) { csXML = bsXML; if (bsXML) { SysFreeString(bsXML); } } // // If get_xml fails, then csXML is blank. // return csXML; } //////////////////////////////////////////////////////////////////////////////////// // // Func: MapStringToLangID // // Desc: Returns a LANGID corresponding to the passed in string. // LANGID MapStringToLangID( CString& csLang) { typedef struct _LANG_MAP { LPTSTR szLang; LANGID LangID; } LANG_MAP, *PLANG_MAP; static LANG_MAP s_LangMap[] = { { _T("usa"), MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US) }, { _T(""), NULL } }; long i; BOOL bSuccess = FALSE; LANGID LangID = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); if (csLang.Left(2) == _T("0x") || csLang.Left(2) == _T("0X")) { _stscanf(csLang, _T("0x%x"), &LangID); return LangID; } i = 0; while (TRUE) { if (s_LangMap[i].szLang[0] == _T('\0')) { // // End of map. // break; } if (0 == _tcsicmp(csLang, s_LangMap[i].szLang)) { // // Found string. // LangID = s_LangMap[i].LangID; bSuccess = TRUE; } if (bSuccess) { break; } i++; } if (!bSuccess) { // // Couldn't map it. Give a useful error; list all recognized values. // CString csError; CString csFormat; i = 0; csError = _T("LANG attribute on DATABASE is not one of recognized values:\n\n"); while (TRUE) { if (s_LangMap[i].szLang[0] == _T('\0')) { break; } csFormat.Format(_T(" %s\n"), s_LangMap[i].szLang); csError += csFormat; i++; } SDBERROR(csError); } return LangID; } //////////////////////////////////////////////////////////////////////////////////// // // Func: AddAttribute // // Desc: Adds an attribute to the specified XML node. // BOOL AddAttribute( IXMLDOMNode* pNode, CString csAttribute, CString csValue) { USES_CONVERSION; BOOL bSuccess = FALSE; BSTR bsAttribute = NULL; VARIANT vType; VARIANT vValue; IXMLDOMDocumentPtr cpDocument; IXMLDOMNamedNodeMapPtr cpNodeMap; IXMLDOMNodePtr cpAttrNode; IXMLDOMNodePtr cpNamedAttr; VariantInit(&vType); VariantInit(&vValue); vValue.bstrVal = csValue.AllocSysString(); if (vValue.bstrVal == NULL) { SDBERROR(_T("CString::AllocSysString failed")); goto eh; } vValue.vt = VT_BSTR; vType.vt = VT_I4; vType.lVal = NODE_ATTRIBUTE; bsAttribute = csAttribute.AllocSysString(); if (bsAttribute == NULL) { SDBERROR(_T("CString::AllocSysString failed")); goto eh; } if (FAILED(pNode->get_ownerDocument(&cpDocument))) { SDBERROR(_T("createNode failed while adding attribute")); goto eh; } if (FAILED(cpDocument->createNode(vType, bsAttribute, NULL, &cpAttrNode))) { SDBERROR(_T("createNode failed while adding attribute")); goto eh; } if (FAILED(cpAttrNode->put_nodeValue(vValue))) { SDBERROR(_T("put_nodeValue failed while adding attribute")); goto eh; } if (FAILED(pNode->get_attributes(&cpNodeMap))) { SDBERROR(_T("get_attributes failed while adding adding attribute")); goto eh; } if (FAILED(cpNodeMap->setNamedItem(cpAttrNode, &cpNamedAttr))) { SDBERROR(_T("setNamedItem failed while adding adding attribute")); goto eh; } bSuccess = TRUE; eh: VariantClear(&vType); VariantClear(&vValue); if (bsAttribute != NULL) { SysFreeString(bsAttribute); } return bSuccess; } //////////////////////////////////////////////////////////////////////////////////// // // Func: GenerateIDAttribute // // Desc: Adds an ID attribute to the specified XML node. The ID is in the // traditional Windows GUID format. // BOOL GenerateIDAttribute( IXMLDOMNode* pNode, CString* pcsGuid, GUID* pGuid) { BOOL bSuccess = FALSE; BSTR bsGUID = NULL; GUID id; // // Generate guid // if (FAILED(CoCreateGuid(&id))) { SDBERROR(_T("CoCreateGuid failed")); goto eh; } if (NULL != pGuid) { *pGuid = id; } bsGUID = SysAllocStringLen(NULL, 64); if (bsGUID == NULL) { SDBERROR(_T("SysAllocStringLen failed")); goto eh; } StringFromGUID2(id, bsGUID, 64); if (!AddAttribute( pNode, _T("ID"), CString(bsGUID) )) { SDBERROR_PROPOGATE(); goto eh; } *pcsGuid = bsGUID; bSuccess = TRUE; eh: if (bsGUID) { SysFreeString(bsGUID); } return bSuccess; } BOOL ReplaceXMLNode(IXMLDOMNode* pNode, IXMLDOMDocument* pDoc, BSTR bsText) { BOOL bSuccess = FALSE; IXMLDOMNodePtr cpNewTextNode; IXMLDOMNodePtr cpParentNode; IXMLDOMNodePtr cpOldNode; VARIANT vType; VariantInit(&vType); vType.vt = VT_I4; vType.lVal = NODE_TEXT; if (FAILED(pDoc->createNode(vType, NULL, NULL, &cpNewTextNode))) { SDBERROR(_T("createNode failed while adding attribute")); goto eh; } if (FAILED(cpNewTextNode->put_text(bsText))) { SDBERROR(_T("Could not set text property of object.")); goto eh; } if (FAILED(pNode->get_parentNode(&cpParentNode))) { SDBERROR(_T("Could not retrieve parent node of object.")); goto eh; } if (FAILED(cpParentNode->replaceChild(cpNewTextNode, pNode, &cpOldNode))) { SDBERROR(_T("Could not replace node with text node.")); goto eh; } bSuccess = TRUE; eh: VariantClear(&vType); return bSuccess; }