大白技術控
目錄
-
-
- 史上最最靠譜,又雙叒叒簡單的基于MSXML的XML解析指南
- 流程設計
-
- xml資訊有哪幾種讀取形式(xml檔案或wchar)
- 如何選取節點,and取節點屬性有哪些方法?
- IXMLDOMNode與IXMLDOMElement接口有何聯系、差別
- 節點如果是數組,怎麼操作?
- 如何為屬性插入屬性
- 字元串的轉換與輸出
- 主要代碼
- 文末彩蛋
- 主要代碼
最近做C++相關的項目,遇到同時使用COM和MSXML來解析XML檔案中資訊的問題,這類問題如果做MFC開發也會經常用到。
在網上搜了一整圈,确實很難找到可用的code,總算自己研究出高效而簡單的方法,借此機會總結一下,并分享給大家。
附 VS Project鏡像:
SimpleParser4MSXML-cpp: C++語言寫的MSXML的簡單使用示例, COM 和 MFC 開發中比較常用。
https://github.com/yanglr/SimpleParser4MSXML-cpp
點選”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
需要解決的問題:
- IXMLDOMNode 與 IXMLDOMElement 接口有什麼聯系和差別?
- 字元串的轉換
-
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完整代碼
先定義一個
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";
然後從
BSTR
導入xml内容:
VARIANT_BOOL bSuccess = false;
iXMLDoc->loadXML(CComBSTR(src), &bSuccess);
注: BSTR字元串是用于COM元件對象模型的字元串格式, 字元串以表示字元串長度的4位元組整數開始, 然後跟上UTF-16編碼的wchar_t字元串(包括\0結束标志)。BSTR類型的變量是一個指針, 指向字元串的第一個字元處。
- 搜尋節點名字
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"節點
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"
- 直接使用
函數+“%ls”或printf
函數+“%s”列印wprintf
類字元串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);
- 将
類字元串的内容複制到CComBSTR
中,然後使用wstring
輸出wcout
CComBSTR ssName;
wstring bstrText(ssName);
wcout << bstrText << endl;
- 先使用将bstr轉為
,然後std::wstring
wcout
std::wstring wstringName(ssName, SysStringLen(ssName));
wcout << wstringName << endl;
- 先将
類字元串強轉為CComBSTR
類型後,然後使用LPCTSTR
對wcout
類字元串而言,這已經是一種比較簡單的方式了。CStringW
CComBSTR ssName;
CString cstring(ssName);
wcout << (LPCTSTR)cstring << endl;
-
CComBSTR
類字元串(多位元組字元串)中,然後使用CW2A
wcout
CComBSTR ssName;
CW2A printstr(ssName);
cout << printstr << endl;
- 先使用宏W2A将bstr轉為std::string,然後cout
USES_CONVERSION;
std::string stringName = std::string(W2A(ssName));
cout << stringName << 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;
}
運作結果:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLyQDMxgTM3kjNtgDOxkTM1QzMxEjMxATOxAjMtgzM5YzM08CXxATOxAjMvwFOzkjNzQzLcd2bsJ2Lc12bj5ycn9Gbi52YugTMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
運作完,得到的update.xml内容為:
https://raw.githubusercontent.com/yanglr/SimpleParser4MSXML-cpp/master/msxmlDemo/updated.xml
參考資料:
- IXMLDOMElement接口
- Using the MSXML Parser
- MFC C++ XML Parse - Using MSXML
- 如何:各種字元串類型之間轉換 | Microsoft Docs
本文原載于本人的CSDN部落格:
史上最最靠譜,又雙叒叒(ruò,zhuó)簡單的基于MSXML的XML解析指南-C++ - Bravo Yeung-羊較瘦之自留地
作者簡介:Bravo Yeung,計算機碩士,知乎幹貨答主(獲81K 贊同, 38K 感謝, 235K 收藏)。曾在國内 Top3網際網路視訊直播公司工作過,後加入一家外企做軟體開發至今。
如需轉載,請加微信 iMath7 申請開白!
歡迎在留言區留下你的觀點,一起讨論提高。如果今天的文章讓你有新的啟發,學習能力的提升上有新的認識,歡迎轉發分享給更多人。
歡迎各位讀者加入 .NET技術交流群,在公衆号背景回複“加群”或者“學習”即可。
微信背景回複“asp”,給你:一份全網最強的ASP.NET學習路線圖。
回複“cs”,給你:一整套 C# 和 WPF 學習資源!
回複“core”,給你:2019年dotConf大會上釋出的.NET core 3.0學習視訊!