目錄
相關背景
pugi::xml簡概
將配置信息寫入xml文件
讀取xml文件中的配置信息
相關背景
????????當我們需要將某些配置信息寫入項目目錄下的xml文件,或者再程序啟動時,加載項目下已有的的配置信息(.xml),此時,我們可以使用輕量級C++ XML處理庫pugi::xml,來對xml文件進行操作。
pugi::xml簡概
首先我們通過如下鏈接,下載pugi::xml源碼的壓縮包
項目首頁 - pugixml:Light-weight, simple and fast XML parser for C++ with XPath support - GitCodehttps://gitcode.com/gh_mirrors/pu/pugixml/?utm_source=artical_gitcode&index=top&type=card&webUrl&isLogin=1解壓之后,將源碼中的src目錄下,三個文件,復制到工程中即可,三個文件如下:
在項目中引用頭文件即可正常使用
#include "pugixml.hpp "
常見節點類型
pugi::node_element? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 普通元素節點 <element>
pugi::node_text? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 文本節點
pugi::node_comment? ? ? ? ? ? ? ? ? ? ? ? ? ? // 注釋節點 <!-- comment -->
pugi::node_declaration? ? ? ? ? ? ? ? ? ? ? ? // XML 聲明 <?xml ...?>
pugi::node_cdata ? ? ? ?????????????????????????// CDATA 節點 <![CDATA[...]]>
pugi::node_pi? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 處理指令 <?target data?>
prepend_child() ????????????????????????????????// 在 最前面 添加子節點
append_child() ????????????????????????????????//在 最后面 添加子節點?
將配置信息寫入xml文件
1、構建存放xml文件的文件路徑
CString csXmlFile;
/*獲取項目根目錄路徑*/
/*#define GetProjectRootMgr() ((CEditorApp*)AfxGetApp())->m_prjRootMgr*/
csXmlFile = GetProjectRootMgr().getProjectDir().c_str();
/*拼接xml配置文件名*/
csXmlFile += L"\\BACnetMSTPServerConfig.xml";
這里需要注意的是 GetProjectRootMgr()是我們事先定義好的宏,用來獲取項目的根目錄,詳情如下:
#define GetProjectRootMgr() ((CEditorApp*)AfxGetApp())->m_prjRootMgr
?這個宏定義各部分的作用如下:
AfxGetApp() :獲取當前MFC應用程序實例
(CEditorApp*) :將應用程序實例轉換為具體的 CEditorApp 類型
->m_prjRootMgr :訪問該應用程序對象的項目根管理器成員變量
2、創建xml文檔對象,并嘗試加載現有的配置文件
/*創建xml文檔對象*/
pugi::xml_document xmlDoc;
/*嘗試加載現有的配置文件*/
pugi::xml_parse_result result = xmlDoc.load_file(csXmlFile);
xmlDoc.load_file嘗試加載我們所構建的文件路徑對應的文件,它的返回值是xml_parse_result,這是pugixml庫中用于表示XML解析結果的結構體。
struct xml_parse_result
{xml_parse_status status; // 解析狀態ptrdiff_t offset; // 錯誤位置偏移量xml_encoding encoding; // 文檔編碼// 轉換為bool,檢查是否成功operator bool() const;// 獲取錯誤描述const char* description() const;
};
這個結構體的成員含義如下:
1)status(解析狀態)
xml_parse_status是一個枚舉類型,常用值如下:
狀態值 | 含義 |
---|---|
status_ok | 解析成功 |
status_file_not_found | 文件未找到 |
status_io_error | I/O錯誤 |
status_out_of_memory | 內存不足 |
2)offset(錯誤位置)
當解析失敗時,指示錯誤發生的字節偏移量,成功時通常為0。
3、進行判斷,如果不存在現有的配置文件,則添加xml聲明頭,并創建根節點
if (result.status == pugi::status_file_not_found) {/*添加聲明*/pugi::xml_node declNode = xmlDoc.prepend_child(pugi::node_declaration);declNode.append_attribute(L"version").set_value(L"1.0");declNode.append_attribute(L"encoding").set_value(L"UTF-8");xmlDoc.root().append_child(L"Bacnet_MSTP_Server");xmlDoc.save_file(csXmlFile);result = xmlDoc.load_file(csXmlFile);
}
當配置文件不存在時 :
-
添加XML聲明頭: <?xml version="1.0" encoding="UTF-8"?>
-
創建根節點 <Bacnet_MSTP_Server>
-
保存文件并重新加載
4、檢查xml是否加載成功
if (result.status != pugi::status_ok) {return -1;
}
成功則繼續
5、清理和重置配置節點(可根據需求)
xml_node nodeBACnetMFTP, nodeServerMFTP;nodeBACnetMFTP = xmlDoc.root().child(L"Bacnet_MSTP_Server");nodeBACnetMFTP.remove_attributes();nodeBACnetMFTP.remove_children();nodeBACnetMFTP.append_attribute(L"Enabled").set_value(1);
-
獲取Bacnet_MSTP_Server根節點
-
清除所有現有屬性和子節點
-
添加 Enabled="1" 屬性
效果如下:
6、服務器配置信息保存(添加子節點)
/*nodeBACnetMFTP在第五步的時候有賦值*/nodeServerMFTP = nodeBACnetMFTP.append_child(L"Server");// 創建子節點而不是屬性pugi::xml_node nodeDeviceID = nodeServerMFTP.append_child(L"DeviceID");nodeDeviceID.text().set(m_serverInfo.nDataBit);pugi::xml_node nodeMacAddress = nodeServerMFTP.append_child(L"MacAddress");nodeMacAddress.text().set(m_serverInfo.nMacAdd);pugi::xml_node nodeComType = nodeServerMFTP.append_child(L"ComType");nodeComType.text().set(m_serverInfo.wsConnectType.data());pugi::xml_node nodeBaudRate = nodeServerMFTP.append_child(L"BaudRate");nodeBaudRate.text().set(m_serverInfo.nBaud);pugi::xml_node nodeTimeout = nodeServerMFTP.append_child(L"Timeout");nodeTimeout.text().set(m_serverInfo.nTimeout);pugi::xml_node nodeDesc = nodeServerMFTP.append_child(L"Desc");if (m_serverInfo.bMSTP) {nodeDesc.text().set(L"MSTP Server");}else {nodeDesc.text().set(L"a simple BACnet");}
?效果如下:
7、保存文件
最后,將文件保存
unsigned int nFlag = pugi::format_indent | pugi::format_write_hex_char | pugi::format_save_file_text;
if (!xmlDoc.save_file(csXmlFile, L"\t")) {return -2;
}
return 0;
-
設置XML格式化標志(縮進、十六進制字符、文本格式)
-
使用制表符縮進保存文件
-
保存失敗返回 -2 ,成功返回 0
最終結果如下:
讀取xml文件中的配置信息
1、構建xml文件路徑
與寫入xml文件一樣,在讀取之前需要先構建要讀取的xml文件路徑
CString csXmlFile;
csXmlFile = GetProjectRootMgr().getProjectDir().c_str();
csXmlFile += L"\\BACnetMSTPServerConfig.xml";
這里同樣在項目目錄下,構建完文件路徑之后,與寫入是不同的是,我們需要判斷是否存在這個配置文件,如果不存在,則不需要讀取加載了。
if (!PathFileExists(csXmlFile)) { return -1;
}
2、加載xml配置文件
如果xml配置文件已經存在,則創建文檔對象,并嘗試加載
pugi::xml_document xmlDoc;
pugi::xml_parse_result result = xmlDoc.load_file(csXmlFile, pugi::parse_default | pugi::parse_ws_pcdata);
if (result.status != pugi::status_ok) { return -2;
}
-
pugi::parse_default
?默認解析選項 ,包含:
? - parse_cdata - 解析 CDATA 節點
? - parse_escapes - 處理轉義字符(如 < , > )
? - parse_wconv_attribute - 屬性值的空白字符轉換
? - parse_eol - 行尾字符標準化
-
pugi::parse_ws_pcdata
- 保留文本節點中的空白字符
- 默認情況下,pugi::xml 會忽略純空白的文本節點
- 添加此選項后,空白字符(空格、制表符、換行符)會被保留
3、提取數據
根節點數據
獲取根節點
pugi::xml_node nodeBACnet = xmlDoc.root().first_child();
if (!nodeBACnet) { return -3;
}
獲取根節點屬性
int nEnabled = 0;
m_listObjects.clear();
if (nodeBACnet) { nEnabled = nodeBACnet.attribute(L"Enabled").as_int();
根節點的子節點數據
解析服務器(Server)信息
pugi::xml_node nodeServer = nodeBACnet.child(L"Server");
m_serverInfo.wsName = nodeServer.attribute(L"Name").as_string();
當一個節點有很多子節點的話,我們可以通過循環
pugi::xml_node nodeObjects = nodeBACnet.child(L"Objects");
m_nNum = 0;
if (nodeObjects) {for (pugi::xml_node nodeObject : nodeObjects.children(L"Object")) {BACNETSERVER_OBJECT_INFO_T tempObjectInfo;tempObjectInfo.wsObjectName = nodeObject.attribute(L"Name").as_string();tempObjectInfo.wsDesc = nodeObject.attribute(L"Desc").as_string();tempObjectInfo.nType = nodeObject.attribute(L"Type").as_int();tempObjectInfo.nInstance = nodeObject.attribute(L"Instance").as_int();tempObjectInfo.wsAddr = nodeObject.attribute(L"Addr").as_string();tempObjectInfo.nUnits = nodeObject.attribute(L"Units").as_int();pugi::xml_node nodeMultistateText = nodeObject.child(L"MultistateText");if (nodeMultistateText) {for (pugi::xml_node nodeState : nodeMultistateText.children(L"State")){BACNETSERVER_MULTISTATETEXT_INFO_T stateInfo;stateInfo.nNumber = nodeState.attribute(L"Number").as_int();stateInfo.wsText = nodeState.text().as_string();tempObjectInfo.vecMultiStateText.emplace_back(stateInfo);}}m_nNum++;/*讀取完一條,存儲起來*/m_listObjects.emplace_back(tempObjectInfo);}
}
這里要注意.child()和.children()的區別