TinyXML2让VC++中操作XML,如鱼得水,就像一个小型的数据库,特别方便。从WeChat安装目录,可发现腾讯开发微信时,也在使用TinyXML。
本篇主要介绍在VC++ 2019的MFC项目中,如何利用TinyXML2,创建、插入、查询、更新、删除节点或数据。也顺便介绍下UNICODE转UTF8,因TinyXML2生成的XML文件是UTF-8编码,VS2019开发工具是UNICODE编码,所以,中文不转换会写入乱码。
一、创建MFC项目 MFCTinyXML2
- 应用程序类型选择“基于对话框”
- 主框架样式仅选择“粗框架”,其他样式无需选择
- 高级功能处全部全部不要选
详细教程见:https://blog.csdn.net/yixiao0307/article/details/119837989
二、下载源码并复制到MFC项目中
1、源码地址:https://github.com/leethomason/tinyxml2
2、将版本库中的tinyxml2.cpp和tinyxml2.h复制到项目中(源码中的文件很多,但,只需要这两个文件~,其他文件直接删掉就好了)
在Dialog界面增加几个按钮,最终项目目录结构如下
在主Dlg类中引用tinyxml2/tinyxml2.h
在项目中,选中tinyxml2.cpp文件,点击右键查看其属性,将“C/C++” - “预编译头” - 设置为“不使用预编译头”(如不设置,VS2019会“温馨”提示你没有使用pch.h这个预编译头文件)
OK,至此已可正常使用tinyxml2中的类和方法了。
三、创建XML,并保存到XML文件中
//使用以下内容建立XMLDocument
const char* xmlContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
tinyxml2::XMLDocument docXml;
docXml.Parse(xmlContent);
//添加root节点
tinyxml2::XMLElement* root = docXml.NewElement("root");
docXml.InsertEndChild(root);
//添加message节点
tinyxml2::XMLElement* messageNode = docXml.NewElement("message");
messageNode->SetAttribute("username", "zhangsan");//属性
messageNode->SetText("hello");//内容
root->InsertEndChild(messageNode);
//保存成XML文件
docXml.SaveFile("XMLFile.xml");
生成的XML文件,如下
<?xml version="1.0" encoding="utf-8"?>
<root>
<message username="zhangsan">hello</message>
</root>
四、读取XML
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");//读取上边生成的XML文件,如果还没有生成,会报错:load xml file failed.
if (xmlError == tinyxml2::XML_SUCCESS) {
//获取root节点
tinyxml2::XMLElement* root = docXml.RootElement();
//获取message节点
tinyxml2::XMLElement* messageNode = root->FirstChildElement("message");
//获取message->username属性的值
const char* username = messageNode->FindAttribute("username")->Value();
//获取message的内容
const char* content = messageNode->GetText();
//弹窗展示读取到的信息
CString str;
str.Format(_T("<message username=\"%s\">%s</message>"), Char2Wchar(username), Char2Wchar(content));
AfxMessageBox(str);
}
else {
AfxMessageBox(_T("load xml file failed."));
}
Char2Wchar转宽字节方法代码
wchar_t* Char2Wchar(const char* str, UINT CodePage) {
//先获取转换成宽字符后的长度
int nLen = MultiByteToWideChar(CodePage, MB_PRECOMPOSED, str, -1, NULL, 0);
//声明一个宽字符类型变量,用于存放转换后的字符
wchar_t* wstr = new wchar_t[nLen];
//利用微软ANSI转宽字符的函数(name:ANSI字符,wname:宽字符)
MultiByteToWideChar(CodePage, MB_PRECOMPOSED, str, -1, wstr, nLen);
return wstr;
}
效果:XML文件不存在时,会提示
效果:读取成功后
五、新增节点
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");
if (xmlError == tinyxml2::XML_SUCCESS) {
//获取root节点
tinyxml2::XMLElement* root = docXml.RootElement();
//循环增加3次message节点
const char* arrUsername[3] = {"lisi","wangwu","zhaoliu"};
for (int i = 0; i < 3; i++)
{
//生成新的message节点(无论在哪个节点中New,都直接在doc.NewElement)
tinyxml2::XMLElement* messageNode = docXml.NewElement("message");
messageNode->SetAttribute("username", arrUsername[i]);//属性
messageNode->SetText(i);//内容
root->InsertEndChild(messageNode);
}
//不要忘记保存一下
docXml.SaveFile("XMLFile.xml");
}
else {
AfxMessageBox(_T("load xml file failed."));
}
效果:增加了0/1/2三条message节点记录
六、查询
1、根据属性值(如:username = wangwu)查找
方法
/// <summary>
/// 根据属性/值查询节点
/// </summary>
/// <param name="root">xml root节点</param>
/// <param name="elementName">要查询的节点,一般是多行的</param>
/// <param name="attributeName">节点的属性名</param>
/// <param name="attributeValue">节点的属性值</param>
/// <returns></returns>
tinyxml2::XMLElement* queryXMLElementByAttribute(tinyxml2::XMLElement* root, const char* elementName, const char* attributeName, const char* attributeValue) {
tinyxml2::XMLElement* node = root->FirstChildElement(elementName);
while (node != NULL)
{
const char* value = node->Attribute(attributeName);
//比较两个字符串char*是否相等(const char*,不能直接使用等号比较)
if (strcmp(value, attributeValue) == 0)
break;
node = node->NextSiblingElement();
}
return node;
}
调用
const char* whereUsername = "wangwu";
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");
if (xmlError == tinyxml2::XML_SUCCESS) {
//获取root节点
tinyxml2::XMLElement* root = docXml.RootElement();
tinyxml2::XMLElement* messageNode = tinyxml2::XMLElement* messageNode = queryXMLElementByAttribute(root, "message", "username", "wangwu");
//最后保留下的这个节点就是查询到的节点
//弹窗展示读取到的信息
CString str;
str.Format(_T("<message username=\"%s\">%s</message>"), Char2Wchar(messageNode->Attribute("username")), Char2Wchar(messageNode->GetText()));
AfxMessageBox(str);
}
else {
AfxMessageBox(_T("load xml file failed."));
}
效果:
2、根据值查找(同理)
七、更新
1、更新属性值
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");
if (xmlError == tinyxml2::XML_SUCCESS) {
//获取root节点
tinyxml2::XMLElement* root = docXml.RootElement();
//查询
tinyxml2::XMLElement* messageNode = queryXMLElementByAttribute(root, "message", "username", "lisi");
//更新
messageNode->SetAttribute("username", "lisi1234");//改成lisi1234
//不要忘记保存一下
docXml.SaveFile("XMLFile.xml");
}
else {
AfxMessageBox(_T("load xml file failed."));
}
效果:
2、更新内容(同理)
八、删除
1、删除指定节点
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");
if (xmlError == tinyxml2::XML_SUCCESS) {
//获取root节点
tinyxml2::XMLElement* root = docXml.RootElement();
//查询
tinyxml2::XMLElement* messageNode = queryXMLElementByAttribute(root, "message", "username", "zhaoliu");
//删除查询到的节点
root->DeleteChild(messageNode);
//不要忘记保存一下
docXml.SaveFile("XMLFile.xml");
}
else {
AfxMessageBox(_T("load xml file failed."));
}
2、删除全部子节点
代码:
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");
if (xmlError == tinyxml2::XML_SUCCESS) {
//获取root节点
tinyxml2::XMLElement* root = docXml.RootElement();
//删除查询到的节点
root->DeleteChildren();
//不要忘记保存一下
docXml.SaveFile("XMLFile.xml");
}
else {
AfxMessageBox(_T("load xml file failed."));
}
效果:
九、获取XML文件全部内容
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");
if (xmlError == tinyxml2::XML_SUCCESS) {
tinyxml2::XMLPrinter printer;
docXml.Print(&printer);
AfxMessageBox(Char2Wchar(printer.CStr()));
}
else {
AfxMessageBox(_T("load xml file failed."));
}
效果:
十、UTF-8编码的XML中,增加中文节点(中文乱码处理办法)
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");
if (xmlError == tinyxml2::XML_SUCCESS) {
//获取root节点
tinyxml2::XMLElement* root = docXml.RootElement();
//生成新的message节点(无论在哪个节点中New,都直接在doc.NewElement)
tinyxml2::XMLElement* messageNode = docXml.NewElement("message");
messageNode->SetAttribute("username", "王二麻子");//属性
messageNode->SetText("中文内容测试");//内容
root->InsertEndChild(messageNode);
//不要忘记保存一下
docXml.SaveFile("XMLFile.xml");
}
else {
AfxMessageBox(_T("load xml file failed."));
}
使用以上代码,不做任何处理,会出现如下情况(乱码)
之所以中文会乱码,分析原因为:VS是使用UNICODE编码(UTF16),直接用写入UTF8编码的XML文件中,当然会乱码,因此,需要做如下转换:
- ASCII(char*)转宽字节(wchar_t*/UNICODE/CP_ACP,因为微软有提供函数MultiByteToWideChar)
- UNICODE以UTF8(CP_UTF8)编码转ASCII
转换成UTF8编码的代码:
char* Ascii2Unicode2Utf8(const char* str) {
//ASCII to Unicode(CP_ACP)
int nLen = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
wchar_t* wstr = new wchar_t[nLen];
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstr, nLen);
//Unicode to UTF-8
int nLenUTF8 = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* strUTF8 = new char[nLenUTF8];
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, strUTF8, nLenUTF8, NULL, NULL);
return strUTF8;
}
使用了转UTF8编码的方法后,生成XML不再乱码了
tinyxml2::XMLDocument docXml;
tinyxml2::XMLError xmlError = docXml.LoadFile("XMLFile.xml");
if (xmlError == tinyxml2::XML_SUCCESS) {
//获取root节点
tinyxml2::XMLElement* root = docXml.RootElement();
//生成新的message节点(无论在哪个节点中New,都直接在doc.NewElement)
tinyxml2::XMLElement* messageNode = docXml.NewElement("message");
messageNode->SetAttribute("username", Ascii2Unicode2Utf8("王二麻子"));//属性
messageNode->SetText(Ascii2Unicode2Utf8("中文内容测试"));//内容
root->InsertEndChild(messageNode);
//不要忘记保存一下
docXml.SaveFile("XMLFile.xml");
}
else {
AfxMessageBox(_T("load xml file failed."));
}
效果:中文字内容写入正常