Qt 笔记3--Qt 操作 XML
XML 可扩展标记语言(eXtensible Markup Language),被设计用来传输和存储数据,是当前使用最广泛的网络数据传输格式之一。
Qt提供了一系列基于DOM的XML处理类,包括:QDomComment、QDomProcessingInstruction、QDomComment、QDomElement、QDomText、QDomDocumentType、QDomNodeList等 ,以下为笔者最近学习Qt XML解析功能时写的一些用例,相应学习笔记分享在此处,以便于后续参考。若后续有新的功能或者案例,也会在此处加以补充!
1、XML基础知识
XML 可扩展标记语言(eXtensible Markup Language),被设计用来传输和存储数据。
1.1 XML 结构
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
第一行是 XML 声明。它定义 XML 的版本(1.0)和所使用的编码;
第二行描述文档的根元素;
接下来 4 行描述根的 4 个子元素(to, from, heading 以及 body);
最后一行定义根元素的结尾。
XML 文档形成一种树结构;
XML 文档必须包含根元素,该元素是所有其他元素的父元素;
XML 文档中的元素形成了一棵文档树。这棵树从根部开始,并扩展到树的最底端;
所有的元素都可以有子元素;父、子以及同胞等术语用于描述元素之间的关系,父元素拥有子元素,相同层级上的子元素成为同胞(兄弟或姐妹),所有的元素都可以有文本内容和属性。
1.2 XML语法规则
XML 必须包含根元素,它是所有其他元素的父元素;
XML的声明是可选部分,如果存在需要放在文档的第一行,一般标注版本、编码等信息;
在XML中,省略关闭标签是非法的,所有元素都必须有关闭标签,其中声明不是XML正文的一部分,它没有标签;
XML标签对大小写敏感,必须使用相同的大小写来编写打开标签和关闭标签;
XML中所有元素都必须彼此正确地嵌套,若A嵌套在B中,那么A的开始和结束都需要在B中;
XML 属性值必须加引号;XML中,一些字符拥有特殊的意义,如果把字符 "<" 放在XML元素中,会发生错误,这是因为解析器会把它当作新元素的开始,为了避免这个错误,请用实体引用来代替 "<" 字符,例如:为了避免<字符带来的错误,需要将
<message>if salary < 1000 then</message>
替换为:
<message>if salary < 1000 then</message>
在XML中,有 5 个预定义的实体引用,如下表所示:
< | < | less than |
> | > | greater than |
& | & | ampersand |
' | ' | apostrophe |
" | " | quotation mark |
在 XML 中编写注释的语法与 HTML 的语法很相似,采用<!-- 注释内容 -->的方式;
在 XML 中,文档中的空格不会被删减。
2、功能说明
当前根据需要初步实现如下功能,后续会根据需要会添加一些新功能和案例!
1)从XML文档/字符串读取数据--F1GetDocumentFromFile、F2GetDocumentFromStr
2)输出XML数据--M1LoadXmlFromDoc
3)获取某元素最外层所有属性--M2GetAllAttributes
4)获取某元素子层的所有元素--M3GetAllSubNodes
5)创建一个XML--M4CreateXML
6)将XML保存到本地--M5SaveXML
3、源代码
3.1 XML
代码包括三个主要文件:xmlclass.h、xmlclass.cpp、main.cpp,源码如下:
xmlclass.h
#ifndef XMLCLASS_H
#define XMLCLASS_H
#include <QObject>
#include <QDebug>
#include <QFile>
#include <QList>
#include <QDomDocument>
#include <QDomDocumentType>
#include <QDomElement>
#include <QDomNodeList>
class XmlClass
{
public:
XmlClass();
QDomDocument F1GetDocumentFromFile(QString PathName);
QDomDocument F2GetDocumentFromStr(QString XMLstr);
void TestF2GetDocumentFromStr();
void M1LoadXmlFromDoc(QString PathName);
void TestM1LoadXmlFromDoc();
void M2GetAllAttributes(QDomElement de);
void TestM2GetAllAttributes();
QList<QDomElement> M3GetAllSubNodes(QDomElement de);
void TestM3GetAllSubNodes();
QDomDocument M4CreateXML();
void TestM4CreateXML();
void M5SaveXML(QDomDocument de,QString PathName);
void TestM5SaveXML();
};
#endif // XMLCLASS_H
xmlclass.cpp
#include "xmlclass.h"
XmlClass::XmlClass()
{
qDebug()<<"Initial XmlClass!";
}
QDomDocument XmlClass::F1GetDocumentFromFile(QString PathName)
{
/*
* 该函数用于从某个文件读取XML,并返回document对象
*/
QDomDocument doc;
QFile file(PathName);
if(!file.open(QIODevice::ReadOnly))
{
qDebug()<<"Read file error!";
}
if(!doc.setContent(&file))
{
qDebug()<<"SetContent Error!";
file.close();
}
file.close();
return doc;
}
QDomDocument XmlClass::F2GetDocumentFromStr(QString XMLstr)
{
/*
* 该函数用于从XMLStr中读取XML,并返回QDomDocument对象
*/
qDebug()<<"\nF2:Get Document From Str!";
qDebug().noquote()<<"XMLStr:"<<XMLstr;
QDomDocument doc;
doc.setContent(XMLstr);
return doc;
}
void XmlClass::TestF2GetDocumentFromStr()
{
QString xmlStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><note><to>Tove</to><from>Jani</from><heading>Reminder</heading><body>Don't forget me this weekend!</body></note>";
QDomDocument doc = F2GetDocumentFromStr(xmlStr);
if(!doc.isNull()){
qDebug().noquote()<<doc.toString(2);//default indent is 1
}else{
qDebug()<<"Load XML From String Error!";
}
}
void XmlClass::M1LoadXmlFromDoc(QString PathName)
{
/*
* 该函数用于从某个文件读取XML并输出
*/
qDebug()<<"\nM1:Load XML!";
QDomDocument doc = F1GetDocumentFromFile(PathName);
if(!doc.isNull()){
qDebug().noquote()<<doc.toString(2);//default indent is 1
}else{
qDebug()<<"Load XML Error!";
}
}
void XmlClass::TestM1LoadXmlFromDoc()
{
M1LoadXmlFromDoc("20150609094727.jdf");
}
void XmlClass::M2GetAllAttributes(QDomElement de)
{
/*
* 该函数用于获取某个元素的最外层所有属性
*/
qDebug()<<"\nM2:Get All Attributes!";
if(!de.isNull())
{
qDebug().noquote()<<de.tagName()<<" attributes:";
QDomNamedNodeMap dnn = de.attributes();
for(int i=0;i<dnn.size();i++)
{
QDomNode dn = dnn.item(i);
qDebug().noquote()<<dn.nodeName()<<":"<<dn.nodeValue();
}
}
}
void XmlClass::TestM2GetAllAttributes()
{
QString PathName = "20150609094727.jdf";
QDomDocument doc = F1GetDocumentFromFile(PathName);
if(!doc.isNull()){
QDomElement de = doc.documentElement();
M2GetAllAttributes(de);
}else
{
qDebug()<<"Load XML Error!";
}
}
QList<QDomElement> XmlClass::M3GetAllSubNodes(QDomElement de)
{
/*
* 该函数用于获取某个元素的子层所元素,并输出子层所有元素的tagname
*/
qDebug()<<"\nM3:Get All SubNodes!";
QList<QDomElement> DeList;
for(QDomNode n=de.firstChild();!n.isNull();n=n.nextSibling())
{
QDomElement tmp = n.toElement();
DeList.append(tmp);
}
return DeList;
}
void XmlClass::TestM3GetAllSubNodes()
{
QString PathName = "20150609094727.jdf";
QDomDocument doc = F1GetDocumentFromFile(PathName);
if(!doc.isNull()){
QDomElement de = doc.documentElement();
QList<QDomElement> DeList = M3GetAllSubNodes(de);
qDebug()<<"DeList Size:"<<DeList.size();
for(int i=0;i<DeList.size();i++)
{
QDomElement tmp = DeList[i];
if(tmp.text()!=NULL){
qDebug().noquote()<<tmp.tagName()<<":"<<tmp.text();
}else{
qDebug().noquote()<<tmp.tagName();
}
}
}else
{
qDebug()<<"Load XML Error!";
}
}
QDomDocument XmlClass::M4CreateXML()
{
/*
* 该函数用于创建一个XML
* 创建内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a comment -->
<note Attr="note attr">
<to>Tove</to>
<from>Jani</from>
<date ymd="2019-03-29" />
<heading>Reminder</heading>
<body AttrH="300" AttrW="600">Don't forget me this weekend!</body>
</note>
*/
qDebug()<<"\nM4:Create XML!";
QDomDocument doc;
//1)添加声明
QDomProcessingInstruction DPI = doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\"");
doc.appendChild(DPI);
//2)添加注释
QDomComment DC = doc.createComment("This is a comment");
doc.appendChild(DC);
//3)添加根元素note
QDomElement DE = doc.createElement("note");
DE.setAttribute("Atte","note attr");//添加属性
doc.appendChild(DE);
//4-1)添加子元素to
QDomElement DE1 = doc.createElement("to");
QDomText DT1 = doc.createTextNode("Tove");
DE1.appendChild(DT1);
DE.appendChild(DE1);
//4-2)添加子元素from
QDomElement DE2 = doc.createElement("from");
QDomText DT2 = doc.createTextNode("Jani");
DE2.appendChild(DT2);
DE.appendChild(DE2);
//4-3)添加子元素date
QDomElement DE3 = doc.createElement("date");
DE3.setAttribute("ymd","2019-03-29");
DE.appendChild(DE3);
//4-4)添加子元素heading
QDomElement DE4 = doc.createElement("heading");
QDomText DT4 = doc.createTextNode("Reminder");
DE4.appendChild(DT4);
DE.appendChild(DE4);
//4-5)添加子元素body
//<body AttrH="300" AttrW="600">Don't forget me this weekend!</body>
QDomElement DE5 = doc.createElement("body");
DE5.setAttribute("AttrH","300");
DE5.setAttribute("AttrW","600");
QDomText DT5 = doc.createTextNode("Don't forget me this weekend!");
DE4.appendChild(DT5);
DE.appendChild(DE5);
return doc;
}
void XmlClass::TestM4CreateXML()
{
QDomDocument doc = M4CreateXML();
qDebug().noquote()<<doc.documentElement().tagName()<<"info:";
qDebug().noquote()<<doc.toString(2);
}
void XmlClass::M5SaveXML(QDomDocument de,QString PathName)
{
/*
* 该函数用于将XML保存到本地
*/
QFile file(PathName);
if(file.open(QFile::WriteOnly|QFile::Text))
{
QTextStream out(&file);
de.save(out,QDomNode::EncodingFromDocument);
file.close();
qDebug()<<"\nM5:Save XML Done!";
}else
{
qDebug()<<"M5:Open File Error!";
}
}
void XmlClass::TestM5SaveXML()
{
qDebug()<<"\nM5:Save XML!";
QString xmlStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><note><to>Tove</to><from>Jani</from><heading>Reminder</heading><body>Don't forget me this weekend!</body></note>";
QDomDocument doc = F2GetDocumentFromStr(xmlStr);
if(!doc.isNull()){
M5SaveXML(doc,"Tmp.xml");
}else{
qDebug()<<"Load XML From String Error!";
}
}
main.cpp
#include <QCoreApplication>
#include "xmlclass.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
XmlClass *XCobj = new XmlClass();
XCobj->TestF2GetDocumentFromStr();
XCobj->TestM1LoadXmlFromDoc();
XCobj->TestM2GetAllAttributes();
XCobj->TestM3GetAllSubNodes();
XCobj->TestM4CreateXML();
XCobj->TestM5SaveXML();
delete XCobj;
return a.exec();
}
4、测试结果
1)从XML文档/字符串读取数据--F1GetDocumentFromFile、F2GetDocumentFromStr
F2:Get Document From Str!
XMLStr: <?xml version="1.0" encoding="UTF-8"?><note><to>Tove</to><from>Jani</fro
m><heading>Reminder</heading><body>Don't forget me this weekend!</body></note>
<?xml version='1.0' encoding='UTF-8'?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
2)输出XML数据--M1LoadXmlFromDoc
M1:Load XML!
<?xml version='1.0' encoding='utf-8'?>
<JDF ICSVersions="Base_L1-1.4" **此处省略部分内容** Activation
="Active">
<Comment Name="JobDescription"/>
** 此处省略一部分内容**
<AuditPool>
<Created AgentName="Caldera JDF" AgentVersion="10.0" TimeStamp="2015-06-09T0
9:47:27+02:00"/>
</AuditPool>
</JDF>
3)获取某元素最外层所有属性--M2GetAllAttributes
M2:Get All Attributes!
JDF attributes:
ICSVersions : Base_L1-1.4
Type : DigitalPrinting
Version : 1.4
DescriptiveName : 20150609094727
Status : Waiting
xmlns:xsi : http://www.w3.org/2001/XMLSchema-instance
ID : 20150609094727
xmlns:cal : http://www.caldera.com/jdf
JobPartID : 1
xmlns : http://www.CIP4.org/JDFSchema_1_1
JobID : 20150609094727
Activation : Active
4)获取某元素子层的所有元素--M3GetAllSubNodes
M3:Get All SubNodes!
DeList Size: 4
Comment
ResourcePool
ResourceLinkPool
AuditPool
5)创建一个XML--M4CreateXML
M4:Create XML!
note info:
<?xml version="1.0" encoding="UTF-8"?>
<!--This is a comment-->
<note Atte="note attr">
<to>Tove</to>
<from>Jani</from>
<date ymd="2019-03-29"/>
<heading>ReminderDon't forget me this weekend!</heading>
<body AttrW="600" AttrH="300"/>
</note>
6)将XML保存到本地--M5SaveXML
M5:Save XML!
F2:Get Document From Str!
XMLStr: <?xml version="1.0" encoding="UTF-8"?><note><to>Tove</to><from>Jani</fro
m><heading>Reminder</heading><body>Don't forget me this weekend!</body></note>
M5:Save XML Done!
5、XML案例
5.1、构造XML案例1
该案例主要用于生成如下XML文档,其具体内容和源码如下:
XML文档内容:
<?xml version="1.0" encoding="UTF-8"?>
<!--This is Two Jobs Nested->JDF_INPUT->20150609112433.jdf-->
<JDF JobID="20150609112433" JobPartID="1" Activation="Active" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cal="http://www.caldera.com/jdf" xmlns="http://www.CIP4.org/JDFSchema_1_1" Version="1.4" Type="DigitalPrinting" ID="20150609112433" Status="Waiting" ICSVersions="Base_L1-1.4" DescriptiveName="20150609112433">
<Comment Name="JobDescription"/>
<ResourcePool>
<RunList Class="Parameter" DocCopies="2" ID="RL001" Status="Available">
<LayoutElement>
<FileSpec URL="file://FILES/20150609112433/FlameBoy_Cut.eps"/>
</LayoutElement>
</RunList>
<DigitalPrintingParams Class="Parameter" ID="DPP001" Status="Available"/>
<CustomerInfo CustomerOrderID="" Class="Parameter" CustomerID="ORDER001" ID="CI001" Status="Available">
<Contact ContactTypes="Sales">
<Address Street="1 rue des frères lumières" City="xxx" Country="France" PostalCode="xxx"/>
<Company OrganizationName=""/>
<ComChannel ChannelType="Phone" Locator="tel:+xxxx"/>
<ComChannel ChannelType="Fax" Locator="tel:+xxx"/>
<Person Languages="EN" JobTitle="Manager" FirstName="">
<Address Street="1 rue des xxx" City="Strasbourg" Country="France" PostalCode="xxxx"/>
<ComChannel ChannelType="Email" Locator="mailto:xxx@xxx.com"/>
</Person>
</Contact>
</CustomerInfo>
<Device Class="Implementation" DeviceID="CalderaJet:Nest" ID="DEV001" Status="Available"/>
<Component Class="Quantity" ID="C001" Status="Unavailable" ComponentType="FinalProduct"/>
</ResourcePool>
<ResourceLinkPool>
<DigitalPrintingParamsLink Usage="DPP001"/>
<RunListLink Usage="Input" rRef="RL001"/>
<CustomerInfoLink Usage="Input" ref="CI001"/>
<DeviceLink Usage="Input" rRef="DEV001"/>
<ComponentLink Usage="Input" rRef="C001"/>
</ResourceLinkPool>
<AuditPool>
<Created AgentVersion="10.0" AgentName="Caldera JDF" TimeStamp="2015-06-09T11:24:33+02:00"/>
</AuditPool>
</JDF>
源码:
QDomDocument JtCreateJDF();
void JtTestCreateJDF();
void JtSaveJDF();
QDomDocument XmlClass::JtCreateJDF()
{
QDomDocument doc;
//1)添加声明
QDomProcessingInstruction DPI = doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\"");
doc.appendChild(DPI);
//2)添加注释
QDomComment DC = doc.createComment("This is Two Jobs Nested->JDF_INPUT->20150609112433.jdf");
doc.appendChild(DC);
//3)添加根元素JDF
QDomElement DE = doc.createElement("JDF");
DE.setAttribute("xmlns:cal","http://www.caldera.com/jdf");//添加属性
DE.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
DE.setAttribute("ID","20150609112433");
DE.setAttribute("JobID","20150609112433");
DE.setAttribute("JobPartID","1");
DE.setAttribute("Status","Waiting");
DE.setAttribute("Activation","Active");
DE.setAttribute("Type","DigitalPrinting");
DE.setAttribute("Version","1.4");
DE.setAttribute("ICSVersions","Base_L1-1.4");
DE.setAttribute("DescriptiveName","20150609112433");
DE.setAttribute("xmlns","http://www.CIP4.org/JDFSchema_1_1");
doc.appendChild(DE);
//4-1)添加子元素Comment
QDomElement DE1 = doc.createElement("Comment");
DE1.setAttribute("Name","JobDescription");
DE.appendChild(DE1);
//4-2)添加子元素ResourcePool
QDomElement DE2 = doc.createElement("ResourcePool");
QDomElement DE21 = doc.createElement("RunList");
DE21.setAttribute("Class","Parameter");
DE21.setAttribute("ID","RL001");
DE21.setAttribute("Status","Available");
DE21.setAttribute("DocCopies","2");
QDomElement DE211 = doc.createElement("LayoutElement");
QDomElement DE2111 = doc.createElement("FileSpec");
DE2111.setAttribute("URL","file://FILES/20150609112433/FlameBoy_Cut.eps");
DE211.appendChild(DE2111);
DE21.appendChild(DE211);
QDomElement DE22 = doc.createElement("DigitalPrintingParams");
DE22.setAttribute("Class","Parameter");
DE22.setAttribute("ID","DPP001");
DE22.setAttribute("Status","Available");
QDomElement DE23 = doc.createElement("CustomerInfo");
DE23.setAttribute("Class","Parameter");
DE23.setAttribute("ID","CI001");
DE23.setAttribute("Status","Available");
DE23.setAttribute("CustomerID","ORDER001");
DE23.setAttribute("CustomerOrderID","");
QDomElement DE231 = doc.createElement("Contact");
DE231.setAttribute("ContactTypes","Sales");
QDomElement DE2311 = doc.createElement("Address");
DE2311.setAttribute("City","xxx");
DE2311.setAttribute("Country","France" );
DE2311.setAttribute("PostalCode","xxx" );
DE2311.setAttribute("Street","1 rue des frères lumières");
QDomElement DE2312 = doc.createElement("Company");
DE2312.setAttribute("OrganizationName","");
QDomElement DE2313 = doc.createElement("ComChannel");
DE2313.setAttribute("ChannelType","Phone");
DE2313.setAttribute("Locator","tel:+xxxx");
QDomElement DE2314 = doc.createElement("ComChannel");
DE2314.setAttribute("ChannelType","Fax");
DE2314.setAttribute("Locator","tel:+xxx");
QDomElement DE2315 = doc.createElement("Person");
DE2315.setAttribute("FirstName","");
DE2315.setAttribute("JobTitle","Manager");
DE2315.setAttribute("Languages","EN");
QDomElement DE23151 = doc.createElement("Address");
DE23151.setAttribute("City","Strasbourg");
DE23151.setAttribute("Country","France");
DE23151.setAttribute("PostalCode","xxxx");
DE23151.setAttribute("Street","1 rue des xxx");
QDomElement DE23152 = doc.createElement("ComChannel");
DE23152.setAttribute("ChannelType","Email");
DE23152.setAttribute("Locator","mailto:xxx@xxx.com");
DE2315.appendChild(DE23151);
DE2315.appendChild(DE23152);
DE231.appendChild(DE2311);
DE231.appendChild(DE2312);
DE231.appendChild(DE2313);
DE231.appendChild(DE2314);
DE231.appendChild(DE2315);
DE23.appendChild(DE231);
QDomElement DE24 = doc.createElement("Device");
DE24.setAttribute("Class","Implementation");
DE24.setAttribute("ID","DEV001");
DE24.setAttribute("Status","Available");
DE24.setAttribute("DeviceID","CalderaJet:Nest");
QDomElement DE25 = doc.createElement("Component");
DE25.setAttribute("Class","Quantity");
DE25.setAttribute("ID","C001");
DE25.setAttribute("Status","Unavailable");
DE25.setAttribute("ComponentType","FinalProduct");
DE2.appendChild(DE21);
DE2.appendChild(DE22);
DE2.appendChild(DE23);
DE2.appendChild(DE24);
DE2.appendChild(DE25);
DE.appendChild(DE2);
//4-3)添加子元素ResourceLinkPool
QDomElement DE3 = doc.createElement("ResourceLinkPool");
QDomElement DE31 = doc.createElement("DigitalPrintingParamsLink");
DE31.setAttribute("Usage","Input");
DE31.setAttribute("Usage","DPP001");
QDomElement DE32 = doc.createElement("RunListLink");
DE32.setAttribute("Usage","Input");
DE32.setAttribute("rRef","RL001");
QDomElement DE33 = doc.createElement("CustomerInfoLink");
DE33.setAttribute("Usage","Input");
DE33.setAttribute("ref","CI001");
QDomElement DE34 = doc.createElement("DeviceLink");
DE34.setAttribute("Usage","Input");
DE34.setAttribute("rRef","DEV001");
QDomElement DE35 = doc.createElement("ComponentLink");
DE35.setAttribute("Usage","Input");
DE35.setAttribute("rRef","C001");
DE3.appendChild(DE31);
DE3.appendChild(DE32);
DE3.appendChild(DE33);
DE3.appendChild(DE34);
DE3.appendChild(DE35);
DE.appendChild(DE3);
//4-4)添加子元素AuditPool
QDomElement DE4 = doc.createElement("AuditPool");
QDomElement DE41 = doc.createElement("Created");
DE41.setAttribute("AgentName","Caldera JDF");
DE41.setAttribute("AgentVersion","10.0");
DE41.setAttribute("TimeStamp","2015-06-09T11:24:33+02:00");
DE4.appendChild(DE41);
DE.appendChild(DE4);
return doc;
}
void XmlClass::JtTestCreateJDF()
{
QDomDocument doc = JtCreateJDF();
//qDebug().noquote()<<'\n\n'<<doc.documentElement().tagName()<<":";
qDebug().noquote()<<doc.toString(2);
}
void XmlClass::JtSaveJDF()
{
/*
* 该函数用于将JDF保存到本地
*/
QString PathName="JDF_I_20150609112433.jdf";
QDomDocument doc = JtCreateJDF();
if(!doc.isNull()){
QFile file(PathName);
if(file.open(QFile::WriteOnly|QFile::Text))
{
QTextStream out(&file);
doc.save(out,4,QDomNode::EncodingFromDocument);
file.close();
qDebug()<<"\nSave JDF Done!";
}else
{
qDebug()<<"Open File Error!";
}
}else{
qDebug()<<"doc is NULL!";
}
}
6、说明
以上代码默认测试环境为Qt 5.7,测试系统为Windows7 x64
参考文献:
[1] Qt 之 XML(DOM): https://blog.youkuaiyun.com/liang19890820/article/details/52981488
[2] QtDocumentation QDom* : https://doc.qt.io/qt-5/classes.html#d
[3] XML 教程 : http://www.runoob.com/xml/xml-tutorial.html
[4] XML 在线解析工具 : https://c.runoob.com/front-end/710