记录一下MSXML6是如何对xml文件进行读写和解析的。
参考文章
microsoft官方文档,里面有基础的代码示例。
拷贝示例代码到Virtual Studio 控制台工程,直接可以运行。
MSXML6 可以调用raw api也可以使用智能指针,我选择的智能指针。
Program with DOM in C-C++ Using Smart Pointer Class Wrappers
这篇文章有对官方示例代码和接口的解释,增删改查都有示例。
MSXM简单的使用
可以在vimsky搜索具体的代码实现示例
C++ IXMLDOMElementPtr::getAttribute方法代码示例
我的代码
因为官方的示例xml层级比较简单,我自己弄了个层级稍微深一点的xml,在测试用xml部分。
我使用的是Virtual Studio 2022。
↓头文件、导入MSXML库、以及全局变量等
#include <stdio.h>
#include <tchar.h>
#import <msxml6.dll>
enum LoadType
{
eLoad_NoPrint = 0, // load 时不进行打印
eLoad_Print, //load 时打印到控制台
};
class FileInfo
{
public:
FileInfo() {}
~FileInfo() {}
public:
MSXML2::IXMLDOMDocumentPtr m_pXMLDom;
const char* m_pfilename = NULL;
};
void loadDOMsmart(FileInfo* fileinfo, enum LoadType lt);
void queryNodesSmart(FileInfo* fileinfo);
void PhaseSubNodes(MSXML2::IXMLDOMNodePtr& pNode, int level);
void ChangeXMLContent(FileInfo* fileinfo);
void SaveXMLContent(FileInfo* fileinfo);
↓主函数
int main(int argc, _TCHAR* argv[])
{
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr)) {
FileInfo fileinfo;
//申请接口
HRESULT hr = fileinfo.m_pXMLDom.CreateInstance(__uuidof(MSXML2::DOMDocument60), NULL, CLSCTX_INPROC_SERVER);
if (FAILED(hr)) {
printf("Failed to instantiate an XML DOM.\n");
return -1;
}
fileinfo.m_pfilename = "Property.xml";
loadDOMsmart(&fileinfo, eLoad_NoPrint); //载入xml文件
queryNodesSmart(&fileinfo); //解析xml文件
ChangeXMLContent(&fileinfo);//修改xml文件
SaveXMLContent(&fileinfo); //保存xml文件
//释放接口
fileinfo.m_pXMLDom.Release();
CoUninitialize();
}
return 0;
}
载入xml文件
//Load xml时,使用的是 MSXML2::IXMLDOMDocumentPtr
//IXMLDOMDocument: DOM树结构的根结点, IXMLDOMDocumentPtr是它的智能指针
void loadDOMsmart(FileInfo* fileinfo, enum LoadType lt)
{
try {
//默认参数设置
fileinfo->m_pXMLDom->async = VARIANT_FALSE;//是否异步执行load函数, 默认false
fileinfo->m_pXMLDom->validateOnParse = VARIANT_FALSE;//是否使用内部或外部 DTD 来验证文档。 默认值为 false。
fileinfo->m_pXMLDom->resolveExternals = VARIANT_FALSE;//是否进行外部解析,默认为false
#if 0 //使用LoadXML() 获取内存中的xml源
//获取xml文件大小
struct stat statbuff;
//stat("D:\\SourceCode\\soap_test\\soap_test\\x64\\Debug\\Property.xml", &statbuff);
stat(fileinfo->m_pfilename, &statbuff);
int filesize = statbuff.st_size;
if (filesize < 0) {
printf("get %s size failed\n", fileinfo->m_pfilename);
return;
}
fileinfo->m_ixmldatasize = filesize + 2;
fileinfo->m_pxml_data = new char[fileinfo->m_ixmldatasize];
memset((char*)fileinfo->m_pxml_data, 0x00 ,fileinfo->m_ixmldatasize);
FILE* fp = NULL;
errno_t err = fopen_s(&fp, fileinfo->m_pfilename, "rb"); //读取UTF16编码的文件,使用_wfopen_s更好
if (err != 0) {
printf("open %s failed\n", fileinfo->m_pfilename);
return;
}
fseek(fp,2, SEEK_SET);//跳过bom:FFFE
size_t fsize = fread_s(fileinfo->m_pxml_data, fileinfo->m_ixmldatasize, sizeof(char), filesize, fp);
wchar_t* wptr = (wchar_t*)fileinfo->m_pxml_data;
printf("read from %s, filesize %d, read size:%d\n", fileinfo->m_pfilename, filesize, (int)fsize);
_bstr_t xml_data(fileinfo->m_pxml_data);
VARIANT_BOOL ret = fileinfo->m_pXMLDom->loadXML(BSTR(wptr)); //xml 文件加载
#else //使用Load() 读取xml文件
VARIANT_BOOL ret = fileinfo->m_pXMLDom->load(fileinfo->m_pfilename); //xml 文件加载
#endif
if (ret == VARIANT_TRUE) {
if (lt == eLoad_Print)//打印xml数据流
printf("XML DOM loaded from %s:\n%s\n", fileinfo->m_pfilename, (LPCSTR)fileinfo->m_pXMLDom->xml);
}
else { // Failed to load xml
printf("Failed to load DOM from %s. %s\n", fileinfo->m_pfilename, (LPCSTR)fileinfo->m_pXMLDom->parseError->Getreason());
}
}
catch (_com_error errorObject) {
printf("Exception thrown, HRESULT: 0x%08x", errorObject.Error());
}
}
解析(遍历)
//解析一个xml文件,提取其中的元素
void queryNodesSmart(FileInfo* fileinfo)
{
try {
//遍历node
BSTR bstr;
long iNodesCount;
MSXML2::IXMLDOMElementPtr pRootNode;
MSXML2::IXMLDOMNodeListPtr pRootSubNodes;
MSXML2::IXMLDOMNodePtr pNode;
pRootNode = fileinfo->m_pXMLDom->GetdocumentElement();//获取根节点
if (pRootNode == NULL) {
printf("get root node failed\n");
return;
}
bstr = pRootNode->GetnodeName();//输出根节点信息
wprintf(L"Root:%s\n", (TCHAR*)bstr);
//这里是通过GetchildNodes() 获取所有子节点,然后递归遍历
//也可以使用GetfirstChild() + get_nextSibling() 来进行遍历。示例代码:
//https://vimsky.com/examples/detail/cpp-ex-msxml2-IXMLDOMNodePtr-GetnextSibling-method.html
pRootSubNodes = pRootNode->GetchildNodes(); //获取根节点的子节点列表
if (FAILED(pRootSubNodes->get_length(&iNodesCount))) { //获取根节点的子节点列表的长度
printf("get nodes list length failed\n");
return ;
}
for (int i = 0; i < iNodesCount; i++) { //遍历子节点列表
pRootSubNodes->get_item(i, (MSXML2::IXMLDOMNode**)&pNode);
PhaseSubNodes(pNode, 1); //进入sub node 的递归
}
}
catch (_com_error errorObject) {
printf("Exception thrown, HRESULT: 0x%08x", errorObject.Error());
}
return;
}
递归函数:
void PhaseSubNodes(MSXML2::IXMLDOMNodePtr& pNode, int level)
{
BSTR bstr;
long icount;
MSXML2::IXMLDOMNodePtr pSubNode;
MSXML2::IXMLDOMNodePtr pNamedItem;
MSXML2::IXMLDOMNodeListPtr pSubNodes;
MSXML2::IXMLDOMNamedNodeMapPtr pAttributes;
bstr = pNode->GetnodeName();
TCHAR tabstring[64] = { 0x00 };
for (int i = 0; i < level; i++) {
wcscat_s(tabstring, 64, L" "); //wcscat_s 第二个参数是TCHAR数量,不是字节数
}
//对不同节点名进行解析
if (wcscmp((TCHAR*)bstr, L"PropertyMap") == 0) { //这个节点有属性,获取属性
pNode->get_attributes((MSXML2::IXMLDOMNamedNodeMap**) & pAttributes);
if (pAttributes != NULL) {
pNamedItem = pAttributes->getNamedItem(BSTR(L"attrInfo"));
wprintf(L"%sNode: % s attr:%s\n", tabstring, (TCHAR*)bstr, (TCHAR*)pNamedItem->text);
}
else
wprintf(L"%sNode: % s\n", tabstring, (TCHAR*)bstr);
}
else if (wcscmp((TCHAR*)bstr, L"#text") == 0) { //这个节点名,表示已经最后一级,取出text,并且返回
wprintf(L"%s#text:%s\n", tabstring, (TCHAR*)pNode->text);
return;
}
else
wprintf(L"%sNode: % s\n", tabstring, (TCHAR*)bstr);
pNode->get_childNodes((MSXML2::IXMLDOMNodeList**)&pSubNodes);
pSubNodes->get_length(&icount);
for (int i = 0; i < icount; i++) { //遍历子节点列表
pSubNodes->get_item(i, (MSXML2::IXMLDOMNode**)&pSubNode);
PhaseSubNodes(pSubNode, level + 1); //调用递归函数
}
}
遍历后的输出结果:
修改xml文件
//把第一个DeviceTypeID改成“unkonw”
void ChangeXMLContent(FileInfo* fileinfo)
{
MSXML2::IXMLDOMNodePtr pSubNode;
MSXML2::IXMLDOMNodeListPtr pSubNodes;
MSXML2::IXMLDOMNamedNodeMapPtr pAttributes;
//**修改指定节点的内容
//从任意节点选择 Node名为:PropertyMap,获取第一个
pSubNode = fileinfo->m_pXMLDom->selectSingleNode(L"//DeviceTypeID");
if (pSubNode) {
pSubNode->put_text(BSTR(L"unkonw"));
}
else {
printf("No node is fetched.\n");
}
//**给指定节点增加内容
//从任意节点选择 Node名为:PropertyMap, 拥有attrInfo属性, 属性值为'Structure/PrivateBox/Origin/ReadOnly/的节点
pSubNodes = fileinfo->m_pXMLDom->selectNodes(L"//PropertyMap[@attrInfo='Structure/PrivateBox/Origin/ReadOnly/']");
if (pSubNodes) {
for (long i = 0; i < pSubNodes->length; i++) {
pSubNode = pSubNodes->item[i];
//从当前节点选择 AttributeValue 节点
MSXML2::IXMLDOMNodePtr pNode_AttributeValue = pSubNode->selectSingleNode(L"AttributeValue");
if (pNode_AttributeValue) {
//从 AttributeValue 节点选择 Length 节点
MSXML2::IXMLDOMNodePtr pNode_Length = pNode_AttributeValue->selectSingleNode(L"Length");
if (pNode_Length) {
//Length 数值改为2
pNode_Length->put_text(BSTR(L"2"));
}
//从 AttributeValue 节点选择 AttributeType 节点
MSXML2::IXMLDOMNodePtr pNode_AttributeType = pNode_AttributeValue->selectSingleNode(L"AttributeType");
if (pNode_AttributeType) {
// 给AttributeType 节点增加内容
//创建一个新的Element
MSXML2::IXMLDOMElementPtr pNode_PropertyMap_new = fileinfo->m_pXMLDom->createElement(L"PropertyMap");
if (pNode_PropertyMap_new) {
//创建一个新的Attribute
MSXML2::IXMLDOMAttributePtr pAttribute = fileinfo->m_pXMLDom->createAttribute(L"attrInfo");
if (pAttribute) {
//设置Attribute值
_variant_t value = L"Boolean/newadd/Origin/ReadOnly/";
pAttribute->put_value(value);
//把Attribute添加到Element
pNode_PropertyMap_new->setAttributeNode(pAttribute);
}
//把新的Element添加到AttributeType节点
pNode_AttributeType->appendChild(pNode_PropertyMap_new);
}
}
}
}
}
else
{
printf("No node set is fetched.\n");
}
}
保存xml文件
void SaveXMLContent(FileInfo* fileinfo)
{
try {
if (FAILED(fileinfo->m_pXMLDom->save("Property_new.xml"))) {
printf("Save fail: %s\n", (LPCSTR)fileinfo->m_pXMLDom->parseError->Getreason());
}
printf("Save succeed\n");
}
catch (_com_error e) {
printf("Exception thrown, HRESULT: 0x%08x", e.Error());
}
}
测试用xml
文件名:Property.xml
<?xml version="1.0" encoding="utf-8" ?>
<DeviceTypeDefaultList>
<DeviceTypeDefault>
<DeviceTypeIDList>
<DeviceTypeID>1.3.6.1</DeviceTypeID>
<DeviceTypeID>1.3.6.5</DeviceTypeID>
<DeviceTypeID>1.3.6.9</DeviceTypeID>
</DeviceTypeIDList>
<PropertyMap attrInfo="Structure/Root/Origin/ReadOnly/">
<AttributeValue>
<Length>1</Length>
<AttributeType>
<PropertyMap attrInfo="List/Origin/ReadOnly/">
<AttributeValue>
<Length>0</Length>
<AttributeType></AttributeType>
</AttributeValue>
</PropertyMap>
<PropertyMap attrInfo="Structure/Origin/ReadOnly/">
<AttributeValue>
<Length>2</Length>
<AttributeType>
<PropertyMap attrInfo="Boolean/IsMD/Origin/ReadOnly/">
<AttributeValue>false</AttributeValue>
</PropertyMap>
<PropertyMap attrInfo="Boolean/IsEOn/Origin/ReadOnly/">
<AttributeValue>true</AttributeValue>
</PropertyMap>
</AttributeType>
</AttributeValue>
</PropertyMap>
</AttributeType>
</AttributeValue>
</PropertyMap>
<ListDefault>
<PropertyMap attrInfo="Structure/PrivateBox/Origin/ReadOnly/">
<AttributeValue>
<Length>1</Length>
<AttributeType>
<PropertyMap attrInfo="Boolean/PwdExist/Origin/ReadOnly/">
<AttributeValue>false</AttributeValue>
<AttributeValueOld>false</AttributeValueOld>
</PropertyMap>
</AttributeType>
</AttributeValue>
</PropertyMap>
</ListDefault>
</DeviceTypeDefault>
</DeviceTypeDefaultList>
输入输出xml对比
的确修改了上面代码中修改的3个目标节点。