史上最最靠譜,又雙叒叒簡單的基於MSXML的XML解析指南-C++

史上最最靠譜,又雙叒叒簡單的基於MSXML的XML解析指南-C++

最近做C++相關的項目,遇到同時使用COM和MSXML來解析XML文件中信息的問題,這類問題如果做MFC開發也會經常用到。
在網上搜了一整圈,確實很難找到可用的code,總算自己研究出高效而簡單的方法,藉此機會總結一下,並分享給大家。

附 VS Project鏡像:
SimpleParser4MSXML-cpp: C++語言寫的MSXML的簡單使用示例, COM 和 MFC 開發中比較常用。
https://github.com/yanglr/Sim...
點擊”Raw”可看到源碼,歡迎fork或star~



首先簡要列舉一下MSXML技術的基本特點。

基於 COM 的技術,用於處理 Windows 操作系統隨附的 XML。
MSXML 提供 DOM 本機實現,同時支持 XPath 和 XSLT。
包含 SAX2 基於事件的分析器。

流程設計

首先簡要介紹一下大概流程:

  • 初始化COM
  • 創建一個IDOMDocument對象xmlDoc,使用xmlDoc -> load() 或 loadXML()方法讀入 XML源
  • 調用selectNodes()或者selectSingleNode()函數,選取指定的節點對象。
  • 通過IXMLDOMNode對象的屬性和方法讀取節點對象的內容。
  • 通過IXMLDOMNode對象的屬性和方法設置節點對象的內容。
  • 通過調用xmlDoc -> save()保存XML文件。
  • 關閉COM


需要解決的問題:

  • xml信息有哪幾種讀取形式(xml文件或wchar)
  • 如何選取節點,and取節點屬性有哪些方法?
  • IXMLDOMNode 與 IXMLDOMElement 接口有什麼聯繫和區別?
  • 節點如果是數組,怎麼操作?
  • 如何爲屬性插入屬性
  • 字符串的轉換


xml信息有哪幾種讀取形式(xml文件或wchar)

  • xml文件

從文件中導入xml內容,使用url或filePath

VARIANT_BOOL bSuccess = false;
HRESULT hr = iXMLDoc->load(CComVariant(L"./test.xml"), &bSuccess); // 此處的L可以省略

當已變量方式傳人filePath時,需要使用c_str()函數轉換一下,代碼如下:

VARIANT_BOOL bSuccess = false;
filePath = "./test.xml";
HRESULT hr = iXMLDoc->load(CComVariant(filePath.c_str()), &bSuccess);


  • 已以字符串格式讀入的xml完整代碼

先定義一個<font color=blue>BSTR常量

const wchar_t *src = L""
L"<?xml version=\"1.0\" encoding=\"utf-16\"?>\r\n"
L"<root desc=\"Great\">\r\n"
L"  <text>Hey</text>\r\n"
L"    <layouts>\r\n"
L"    <lay index=\"15\" bold=\"true\"/>\r\n"
L"    <layoff index=\"12\"/>\r\n"
L"    <layin index=\"17\"/>\r\n"
L"  </layouts>\r\n"
L"</root>\r\n";

然後從<font color=blue>BSTR導入xml內容:

VARIANT_BOOL bSuccess = false;
iXMLDoc->loadXML(CComBSTR(src), &bSuccess);

注: BSTR字符串是用於COM組件對象模型的字符串格式, 字符串以表示字符串長度的4字節整數開始, 然後跟上UTF-16編碼的wchar_t字符串(包括0結束標誌)。BSTR類型的變量是一個指針, 指向字符串的第一個字符處。

如何選取節點,and取節點屬性有哪些方法?

  • 搜索節點名字
CComBSTR sstrRoot(L"root"); // sstrRoot("root");
CComPtr<IXMLDOMNode> rootNode;
HRESULT hr = iXMLDoc->selectSingleNode(sstrRoot, &rootNode);
CComPtr<IXMLDOMNode> textNode;
hr = rootNode->selectSingleNode(CComBSTR(L"text"), &textNode); // 搜索第一個"text"節點

IXMLDOMNode 與 IXMLDOMElement 接口有什麼聯繫和區別

IXMLDOMElement接口繼承於IXMLDOMNode接口,但除了從IXMLDOMNode接口繼承的方法之外,IXMLDOMElement接口還向外暴露以下方法:

方法 說明
get_tagName 檢索元素名稱(在tag之間的文本)。
getAttribute 檢索所指定名字的屬性的值。
getAttributeNode 檢索所指定名字的屬性的節點
getElementsByTagName 檢索與提供的名稱匹配的所有子元素的列表。
removeAttribute 移動或替換給定名稱的屬性
removeAttributeNode 從這個元素中移除指定的屬性
setAttribute 爲給定名稱的屬性設置值
setAttributeNode 在此元素上添加或替換提供的屬性節點。


節點如果是數組,怎麼操作?

先使用get_childNodes函數獲得子節點列表,然後遍歷之用get_item依次取出每一項進行處理。

    CComPtr<IXMLDOMElement> pRootElement;
    CComPtr<IXMLDOMNodeList> pNodeList;
    pRootElement->get_childNodes(&pNodeList); // Child node list
    long nLen;
    pNodeList->get_length(&nLen);    // Child node list
    for (long index = 0; i != nLen; ++index) // Traverse
    {
        CComPtr<IXMLDOMNode> pCurNode;
        hr = pNodeList->get_item(index, &pCurNode);
        do();  // 此處可做任何你想做的事情
    }


如何爲屬性插入屬性

使用Element->setAttribute()即可,具體如下:

CComPtr<IXMLDOMElement> imageElement;
xmlDocData->createElement(CComBSTR(L"Image"), &imageElement); // 創建節點"Image"
imageElement->setAttribute(CComBSTR(L"Type"), CComVariant(CComBSTR(imageType.c_str())));  // 添加屬性"Type"


字符串的轉換與輸出

  • 直接使用<font color=blue>printf函數+“%ls”或<font color=blue>wprintf函數+“%s”打印<font color=blue>BSTR類字符串
    CComBSTR ssName;
    printf("Node name:%ls\n", ssName);   // 用%ls打印BSTR字符串內容
    SysFreeString(ssName);               // 用完字符串後必須釋放      

    CComBSTR ssName;
    wprintf(L"Node name:%s\n", ssName);   // 這裏的L不能省略
    SysFreeString(ssName);
    • <font color=blue>CComBSTR類字符串的內容複製到<font color=blue>wstring中,然後使用<font color=blue>wcout輸出
       CComBSTR ssName;
       wstring bstrText(ssName);
       wcout << bstrText << endl;
    • 先將<font color=blue>CComBSTR類字符串強轉爲<font color=blue>LPCTSTR類型後,然後使用<font color=blue>wcout輸出

    <font color=blue>CStringW類字符串而言,這已經是一種比較簡單的方式了。

       CComBSTR ssName;
       CString cstring(ssName);
       wcout << (LPCTSTR)cstring << endl;
    • <font color=blue>CComBSTR類字符串的內容複製到<font color=blue>CW2A類字符串(多字節字符串)中,然後使用<font color=blue>wcout輸出
    CComBSTR ssName;
    CW2A printstr(ssName);
    cout << printstr << endl;


    主要代碼

    #include <msxml6.h>   // 含有 MSXML最新版
    #include <atlbase.h>
    #include "atlstr.h"  // 含有CString, CStringW和CW2A
    #include <iostream>  // 包含wcout函數
    #include <string>    // 包含 c_str()函數, wcout
    #include "comutil.h" // 包含_bstr_t
    using namespace std;
    
    const wchar_t *src = L""
    L"<?xml version=\"1.0\" encoding=\"utf-16\"?>\r\n"
    L"<root desc=\"Great\">\r\n"
    L"  <text>Hey</text>\r\n"
    L"    <layouts>\r\n"
    L"    <lay index=\"15\" bold=\"true\"/>\r\n"
    L"    <layoff index=\"12\"/>\r\n"
    L"    <layin index=\"17\"/>\r\n"
    L"  </layouts>\r\n"
    L"</root>\r\n";
    
    int main()
    {
        CoInitialize(NULL); // Initialize COM
    
        CComPtr<IXMLDOMDocument> iXMLDoc;  // Or use CComPtr<IXMLDOMDocument2>, CComPtr<IXMLDOMDocument3>
    
        try
        {
            HRESULT hr = iXMLDoc.CoCreateInstance(__uuidof(DOMDocument));
            //     iXMLDoc.CoCreateInstance(__uuidof(DOMDocument60));
    
            // Load the file. 
            VARIANT_BOOL bSuccess = false;
    
            // Load it from a url/filename...
            hr = iXMLDoc->load(CComVariant(L"./test.xml"), &bSuccess);
            // filePath = "./test.xml";
            // hr = iXMLDoc->load(CComVariant(filePath.c_str()), &bSuccess);
    
            // or from a BSTR...
            // iXMLDoc->loadXML(CComBSTR(src), &bSuccess);
    
            // Get a smart pointer (sp) to the root
            CComPtr<IXMLDOMElement> pRootElement;
            hr = iXMLDoc->get_documentElement(&pRootElement); // Root elements
    
            // Get Attribute value of the note "root"
            CComBSTR ssDesc("desc");
            CComVariant deVal(VT_EMPTY);
            hr = pRootElement->getAttribute(ssDesc, &deVal);
    
            CComBSTR sstrRoot(L"root"); // sstrRoot("root");
            CComPtr<IXMLDOMNode> rootNode;
            hr = iXMLDoc->selectSingleNode(sstrRoot, &rootNode);  // Search "root"
    
            CComBSTR rootText;
            hr = rootNode->get_text(&rootText);
            if (SUCCEEDED(hr))
            {
                wstring bstrText(rootText);
                wcout << "Text of root: " << bstrText << endl;
            }
    
            CComPtr<IXMLDOMNode> descAttribute;
            hr = rootNode->selectSingleNode(CComBSTR("@desc"), &descAttribute); // Atrribute需要用@, 而各個節點不能使用@作爲前綴來搜索
            CComBSTR descVal;
            hr = descAttribute->get_text(&descVal);
            if (SUCCEEDED(hr))
            {
                wstring bstrText(descVal);
                wcout << "Desc Attribute: " << bstrText << endl;
            }
    
            if (!FAILED(hr))
            {
                wstring strVal;
                if (deVal.vt == VT_BSTR)
                    strVal = deVal.bstrVal;
    
                wcout << "desc: " << strVal << endl;
            }
    
            CComPtr<IXMLDOMNodeList> pNodeList;
            pRootElement->get_childNodes(&pNodeList); // Child node list
            long nLen;
            pNodeList->get_length(&nLen);    // Child node list
            for (long i = 0; i != nLen; ++i) // Traverse
            {
                CComPtr<IXMLDOMNode> pNode;
                hr = pNodeList->get_item(i, &pNode);
    
                CComBSTR ssName;
                CComVariant val(VT_EMPTY);
                hr = pNode->get_nodeName(&ssName);
                if (SUCCEEDED(hr))
                {
                    wstring bstrText(ssName);
                    wcout << "Name of node " << (i + 1) << ": " << bstrText << endl;
    
                    CString cstring(ssName);
                    // To display a CStringW correctly, use wcout and cast cstring to (LPCTSTR), an easier way to display wide character strings.
                    wcout << (LPCTSTR)cstring << endl;
    
                    // CW2A converts the string in ccombstr to a multi-byte string in printstr, used for display output.
                    CW2A printstr(ssName);
                    cout << printstr << endl;
                }
            }
    
            /// Add(Append) node
            CComPtr<IXMLDOMDocument>& xmlDocData(iXMLDoc);
            CComPtr<IXMLDOMElement> imageElement;
            CComPtr<IXMLDOMNode> newImageNode;
            string imageType = "jpeg";
            char buffer[MAX_PATH];
            GetCurrentDirectory(MAX_PATH, buffer);  //  Get Current Directory
            string path(buffer); // Copy content of char*, generate a string
            string imagePath = path + "\\com.jpg";
    
            xmlDocData->createElement(CComBSTR(L"Image"), &imageElement);
            imageElement->setAttribute(CComBSTR(L"Type"), CComVariant(CComBSTR(imageType.c_str()))); // 爲當前節點添加屬性
            imageElement->setAttribute(CComBSTR(L"FileName"), CComVariant(CComBSTR(imagePath.c_str())));
            rootNode->appendChild(imageElement, &newImageNode);
    
            /// Remove "text" node under "root" node
            CComPtr<IXMLDOMNode> xmlOldNode;
            CComPtr<IXMLDOMNode> textNode;
            hr = rootNode->selectSingleNode(CComBSTR(L"text"), &textNode); // Search "text" node        
            hr = rootNode->removeChild(textNode, &xmlOldNode);
    
            /// Update XML
            hr = iXMLDoc->save(CComVariant("updated.xml"));
        }
        catch (char* pStrErr) {
            // Some error...
            std::cout << pStrErr << std::endl << std::endl;
        } // catch
        catch (...) {
            // Unknown error...
            std::cout << "Unknown error..." << std::endl << std::endl;
        }
    
        // Release() - that gets done automatically, also can manually do for each opened node or elements.
        // iXMLDoc.Release();
    
        // Stop COM
        CoUninitialize();
    
        system("pause");
        return 0;
    }

    運行結果:
    run Result

    運行完,得到的update.xml內容爲:
    https://raw.githubusercontent...


    參考資料:

    1. IXMLDOMElement接口
    2. Using the MSXML Parser
    3. MFC C++ XML Parse - Using MSXML
    4. 如何:各種字符串類型之間轉換 | Microsoft Docs

    本文原載於本人csdn博客 →
    https://blog.csdn.net/lzuacm/...

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章