QT_XML文件操作
XML(可扩展标记语言)是标准通用标记语言(SGML)的子集。作为一种允许用户自定义标记语言的源语言,它主要用于标记数据和定义数据类型。XML 具备扩展性良好、内容与形式分离、遵循严格语法要求以及保值性良好等显著优点【有且只有一个根元素】。
文章目录
- QT_XML文件操作
-
- XML数据结构
- 项目配置
- [一、DOM 方式核心类速查](#一、DOM 方式核心类速查)
- [二、XML 各节点类型判断方法](#二、XML 各节点类型判断方法)
- 三、中文乱码问题(重要)
- [四、常用 API 汇总](#四、常用 API 汇总)
- 五、增删改查常用场景
-
- [1. 追加/新增节点](#1. 追加/新增节点)
- [2. 修改某一节点文本](#2. 修改某一节点文本)
- [3. 删除指定节点](#3. 删除指定节点)
- [4. 属性读写](#4. 属性读写)
- [六、QXmlStreamReader / Writer](#六、QXmlStreamReader / Writer)
- 七、常见坑与注意事项
- 八、一个更精简的「读+写」完整示例
- 九、Qt读取写入配置文件案例
XML数据结构
cpp
<?xml version="1.0" encoding="UTF-8"?>
<factory>
<worker>
<WNo>cs001</WNo>
<WName>吴又可</WName>
<WSex>男</WSex>
<WEdu>带专</WEdu>
<WDep>技术部</WDep>
<WSalay>2000</WSalay>
</worker>
</factory>
第一行:XML文档声明;它定义 XML 的1.0版本,以及UTF-8编码;
第一行:XML文档根元素,在文档中唯一;
第三行:XML文档元素;
项目配置
.pro文件里面添加QT += xml

一、DOM 方式核心类速查
| 类 | 作用 |
|---|---|
| QDomDocument | XML 文档对象,代表整棵树,解析/构建的入口 |
| QDomElement | 元素节点,如 <worker> |
| QDomNode | 所有节点的基类(元素、文本、注释、属性等) |
| QDomNodeList | 子节点集合 |
| QDomText | 文本节点,即标签之间的内容 |
| QDomAttr | 属性节点,如 name="xxx" |
| QDomComment | 注释节点 <!-- --> |
| QDomProcessingInstruction | 处理指令,如 XML 声明 <?xml ...?> |
| QDomNamedNodeMap | 属性集合 |
二、XML 各节点类型判断方法
遍历节点时经常踩到「空白文本节点」的坑(XML 标签之间的换行/缩进会被当成 text 节点),导致 toElement() 得到空元素。常用判断:
cpp
QDomNode node = root.firstChild();
while (!node.isNull()) {
if (node.isElement()) { // 只处理元素节点
QDomElement e = node.toElement();
QString tag = e.tagName();
// ...
}
node = node.nextSibling();
}
常用判断方法:
node.isElement()--- 是否元素节点node.isText()--- 是否文本节点node.isComment()--- 是否注释节点node.isAttr()--- 是否属性节点node.nodeType()/node.nodeName()/node.nodeValue()
注意:也可用
QDomNode::ElementNode、TextNode等枚举配合nodeType()判断。
三、中文乱码问题(重要)
读取/写入含中文的 XML 常见乱码,原因和解决:
- 写入时指定 UTF-8 ,用
toUtf8()写入,且文件头声明 UTF-8:
cpp
QString xml = doc.toString();
file.write(xml.toUtf8()); // 不要直接 write(xml.toLocal8Bit())
- 读取解析时,setContent 的字符串应使用 UTF-8 字节:
cpp
QByteArray data = file.readAll();
QString xmlStr = QString::fromUtf8(data);
doc.setContent(xmlStr);
- 如果必须兼容 GBK 中文,用
QTextCodec(Qt5):
cpp
QTextCodec *codec = QTextCodec::codecForName("GBK");
QString xmlStr = codec->toUnicode(data);
Qt6 已移除
QTextCodec,建议统一用 UTF-8。
四、常用 API 汇总
cpp
// 解析
bool ok = doc.setContent(&file, &errorMsg, &errorLine, &errorColumn); // 带错误定位
bool ok = doc.setContent(QString); // 从字符串解析
// 获取根元素(等价于 doc.firstChildElement())
QDomElement root = doc.documentElement();
// 查找元素
QDomElement e = root.firstChildElement("worker"); // 第一个匹配子元素
QDomElement next = e.nextSiblingElement("worker"); // 下一个同标签兄弟元素
QDomNodeList list = doc.elementsByTagName("worker"); // 文档级按标签名查找全部
// 创建元素/文本/属性
QDomElement el = doc.createElement("name");
QDomText t = doc.createTextNode("张三");
el.appendChild(t);
QDomAttr attr = doc.createAttribute("id");
attr.setValue("1");
el.setAttributeNode(attr);
// 或简写:el.setAttribute("id", "1");
// 读取/修改元素文本
QString name = e.text(); // 取元素文本(含所有子文本拼接)
e.firstChild().setNodeValue("新值"); // 修改文本节点值
// 删除节点
QDomNode old = e.parentNode().removeChild(e);
// 输出
QString xml = doc.toString(4); // 参数 4 表示缩进空格数,美化输出
五、增删改查常用场景
1. 追加/新增节点
cpp
QDomElement root = doc.documentElement();
QDomElement newWorker = doc.createElement("worker");
root.appendChild(newWorker);
2. 修改某一节点文本
cpp
QDomNodeList list = doc.elementsByTagName("WName");
if (!list.isEmpty()) {
QDomElement e = list.at(0).toElement();
// 方法一:直接改第一个子文本节点
QDomNode oldText = e.firstChild();
e.firstChild().setNodeValue("新姓名");
// 方法二:清空后重建(更安全)
while (e.hasChildNodes()) e.removeChild(e.firstChild());
e.appendChild(doc.createTextNode("新姓名"));
}
3. 删除指定节点
cpp
QDomNodeList list = doc.elementsByTagName("worker");
for (int i = list.size() - 1; i >= 0; --i) { // 倒序遍历,避免删除时索引错乱
QDomNode n = list.at(i);
if (n.toElement().attribute("id") == "3") {
n.parentNode().removeChild(n);
}
}
删除集合节点时必须倒序遍历,否则删一个后面索引全乱了。
4. 属性读写
cpp
QString id = e.attribute("id"); // 读
bool has = e.hasAttribute("id"); // 判断是否存在
e.setAttribute("id", "10"); // 写(不存在则自动新建)
e.removeAttribute("id"); // 删
六、QXmlStreamReader / Writer
DOM 会把整棵树读进内存,大文件(几百 MB)会卡。此时用流式 API:
cpp
// 读取
QFile file(path);
file.open(QIODevice::ReadOnly | QIODevice::Text);
QXmlStreamReader reader(&file);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement()) {
QString name = reader.name().toString();
if (name == "WNo") {
QString val = reader.readElementText();
// 处理 val
}
}
}
if (reader.hasError()) {
qDebug() << reader.errorString() << reader.lineNumber();
}
file.close();
cpp
// 写入
QFile out(path);
out.open(QIODevice::WriteOnly | QIODevice::Text);
QXmlStreamWriter writer(&out);
writer.setAutoFormatting(true); // 自动换行缩进
writer.setAutoFormattingIndent(4); // 缩进 4 空格
writer.writeStartDocument("1.0", false); // 第二个参数 false 表示不写独立声明,可省 encoding
writer.writeStartElement("factory");
writer.writeStartElement("worker");
writer.writeTextElement("WNo", "cs001"); // 一步写 <WNo>cs001</WNo>
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();
out.close();
DOM 适合:小文件、需要随机增删改查树结构。Stream 适合:大文件、顺序读写、内存敏感场景。
七、常见坑与注意事项
- 空白文本节点 :
firstChild()可能返回换行文本节点,务必用isElement()或firstChildElement()过滤。 - ReadWrite 打开已有文件不清空 :覆盖写前必须
resize(0)或QIODevice::Truncate,否则旧内容残留导致非法 XML。 documentElement()必须先确保解析成功:setContent 失败时 documentElement 为空,直接访问会崩溃。- 内存泄漏 :Qt DOM 用引用计数自动管理,一般无需手动 delete;
ui对象在析构里 delete 即可。 - 编码:XML 头声明编码必须与实际写入字节一致,否则中文乱码。
- 关闭文件 :解析/写入完成后记得
close(),否则文件句柄占用,后续操作可能失败。 - Qt6 差异 :
QTextCodec被移除,改用QStringConverter;toString()基本用法不变。
八、一个更精简的「读+写」完整示例
cpp
#include <QDomDocument>
#include <QFile>
// 写:把一张 map 写入 config.xml
void writeConfig(const QMap<QString, QString> &cfg, const QString &path) {
QDomDocument doc;
QDomProcessingInstruction pi = doc.createProcessingInstruction(
"xml", "version=\"1.0\" encoding=\"UTF-8\"");
doc.appendChild(pi);
QDomElement root = doc.createElement("config");
doc.appendChild(root);
for (auto it = cfg.begin(); it != cfg.end(); ++it) {
QDomElement item = doc.createElement("item");
item.setAttribute("key", it.key());
item.setAttribute("value", it.value());
root.appendChild(item);
}
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
return;
file.write(doc.toString(4).toUtf8()); // 4 空格缩进美化
file.close();
}
// 读:把 config.xml 读回 map
QMap<QString, QString> readConfig(const QString &path) {
QMap<QString, QString> cfg;
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return cfg;
QDomDocument doc;
QString err; int line = -1, col = -1;
if (!doc.setContent(&file, &err, &line, &col)) {
qDebug() << "XML parse error:" << err << "line" << line << "col" << col;
file.close();
return cfg;
}
file.close();
QDomElement root = doc.documentElement();
for (QDomElement e = root.firstChildElement("item");
!e.isNull(); e = e.nextSiblingElement("item")) {
cfg.insert(e.attribute("key"), e.attribute("value"));
}
return cfg;
}
九、Qt读取写入配置文件案例
widget.ui
cpp
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>444</width>
<height>244</height>
</rect>
</property>
<property name="windowTitle">
<string>读取xml文件测试</string>
</property>
<widget class="QPushButton" name="writeBtn">
<property name="geometry">
<rect>
<x>70</x>
<y>190</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>写入XML文件</string>
</property>
</widget>
<widget class="QPushButton" name="readBtn">
<property name="geometry">
<rect>
<x>270</x>
<y>190</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>读取XML文件</string>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>426</width>
<height>166</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>职工编号:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="no"/>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>职工姓名:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="name"/>
</item>
</layout>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>职工性别:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="sex"/>
</item>
</layout>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>职工学历:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edu"/>
</item>
</layout>
</item>
<item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>职工部门:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="dep"/>
</item>
</layout>
</item>
<item row="5" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>职工薪资:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="salay"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>职工编号:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="no_2"/>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>职工姓名:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="name_2"/>
</item>
</layout>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>职工性别:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="sex_2"/>
</item>
</layout>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>职工学历:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edu_2"/>
</item>
</layout>
</item>
<item row="5" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_12">
<property name="text">
<string>职工薪资:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="salay_2"/>
</item>
</layout>
</item>
<item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>职工部门:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="dep_2"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
widget.h
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QDomComment> // Qt所封装的类,专门用来操作XML文件,专门用来操作XML文件,用来存储一些配置数据信息
#include <QDomNodeList> // 子节点集合类
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QMessageBox>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QFile m_qfiles;
QString strcurrentfilepath; // 表示当前文件的路径
QString strcurrentfilename; //表示当前文件名
public:
bool openxmlfile(QString filesnamepath); //打开指定的文件
void readxmlfile(); //读取XML文件
void writexmlfiles(); // 写入xml文件
void readrootxml(QDomNodeList sonnodelist); // 读取子节点数据
private slots:
void on_writeBtn_clicked();
void on_readBtn_clicked();
};
#endif // WIDGET_H
widget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
// ========================== 构造函数 ==========================
// 初始化界面,并设置 XML 文件的默认保存路径与文件名
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this); // 加载 designer 中设计的 UI 界面
QDir dirs; // 可用来检查/创建目录,此处未实际使用
strcurrentfilepath = "D:/xmlfiles"; // XML 文件所在目录
strcurrentfilename = "/factoryworkersxml.xml"; // 文件名
}
// ========================== 析构函数 ==========================
// 释放 ui 对象,避免内存泄漏
Widget::~Widget() { delete ui; }
// ========================== 打开 XML 文件 ==========================
// 功能:按指定文件名打开(不存在则自动创建)XML 文件
// 参数:filesnamepath ------ 相对文件名(如 "/factoryworkersxml.xml")
// 返回:true 表示打开成功,false 表示失败
bool Widget::openxmlfile(QString filesnamepath) {
m_qfiles.setFileName(strcurrentfilepath + filesnamepath); // 拼接完整路径
// ReadWrite:可读可写;QFile::Text:以文本模式打开
return m_qfiles.open(QIODevice::ReadWrite | QFile::Text);
}
// ========================== 写入 XML 文件 ==========================
// 功能:将界面输入框中的内容写入 XML 文件
void Widget::writexmlfiles() {
// 1. 打开(或创建)XML 文件,失败则终止
if (!openxmlfile(strcurrentfilename)) {
qDebug() << "打开XML文件失败,请重新检查";
return;
}
qDebug() << "写入:打开XML文件成功,请认真操作XML";
// 截断文件,清空旧内容。因为 ReadWrite 模式打开已有文件不会清空,若不截断,末尾会残留旧字节,导致新内容 + 旧残留拼成非法 XML,读取时 setContent 解析失败。
m_qfiles.resize(0);
// 2. 创建 QDomDocument 对象,用于构建 XML 文档
QDomDocument domdoct;
// 创建 XML 头部处理指令(声明版本号与编码方式)
QDomProcessingInstruction qversion;
qversion = domdoct.createProcessingInstruction(
"xml", "version=\"1.0\" encoding=\"UTF-8\"");
domdoct.appendChild(qversion); // 将头部声明追加到文档中
// 3. 创建顶层节点 <factory>
QDomElement domrootelement = domdoct.createElement("factory");
domdoct.appendChild(domrootelement);
// 4. 创建父节点 <worker>,并在其下挂接各个子节点
QDomElement itemrootelement = domdoct.createElement("worker");
{
// ---- 节点1:工号 <WNo> ----
QDomElement node1 = domdoct.createElement("WNo"); // 创建元素节点
QDomText domtext1 = domdoct.createTextNode("WNO"); // 创建文本节点
domtext1.setData(ui->no->text()); // 写入界面输入框的工号
node1.appendChild(domtext1); // 将文本节点绑定到元素节点
itemrootelement.appendChild(node1); // 将元素节点绑定到父节点 <worker>
// ---- 节点2:姓名 <WName> ----
QDomElement node2 = domdoct.createElement("WName");
QDomText domtext2 = domdoct.createTextNode("WName");
domtext2.setData(ui->name->text()); // 写入界面输入框的姓名
node2.appendChild(domtext2);
itemrootelement.appendChild(node2);
// ---- 节点3:性别 <WSex> ----
QDomElement node3 = domdoct.createElement("WSex");
QDomText domtext3 = domdoct.createTextNode("WSex");
domtext3.setData(ui->sex->text()); // 写入界面输入框的性别
node3.appendChild(domtext3);
itemrootelement.appendChild(node3);
// ---- 节点4:学历 <WEdu> ----
QDomElement node4 = domdoct.createElement("WEdu");
QDomText domtext4 = domdoct.createTextNode("WEdu");
domtext4.setData(ui->edu->text()); // 写入界面输入框的学历
node4.appendChild(domtext4);
itemrootelement.appendChild(node4);
// ---- 节点5:部门 <WDep> ----
QDomElement node5 = domdoct.createElement("WDep");
QDomText domtext5 = domdoct.createTextNode("WDep");
domtext5.setData(ui->dep->text()); // 写入界面输入框的部门
node5.appendChild(domtext5);
itemrootelement.appendChild(node5);
// ---- 节点6:工资 <WSalay> ----
QDomElement node6 = domdoct.createElement("WSalay");
QDomText domtext6 = domdoct.createTextNode("WSalay");
domtext6.setData(ui->salay->text()); // 写入界面输入框的工资
node6.appendChild(domtext6);
itemrootelement.appendChild(node6);
}
// 5. 将 <worker> 节点挂接到顶层节点 <factory> 下
domrootelement.appendChild(itemrootelement);
// 6. 将整棵文档树转成字符串,以 UTF-8 编码写入文件,并关闭文件
m_qfiles.write(domdoct.toString().toUtf8());
m_qfiles.close();
// 7. 提示用户写入成功
QMessageBox::information(this, "提示", "恭喜你XML写入成功,请查看目录文件",
QMessageBox::Yes);
}
// ========================== 读取 XML 文件 ==========================
// 功能:解析并读取 XML 文件内容
void Widget::readxmlfile() {
if (!openxmlfile(strcurrentfilename)) {
qDebug() << "读取:打开xml文件失败,请重新检查";
return;
}
qDebug() << "读取:打开XML文件成功";
QDomDocument docs;
// 使用带错误信息参数的重载,解析失败时可以输出具体错误行/列,便于排查
QString errorMsg;
int errorLine = -1, errorColumn = -1;
if (!docs.setContent(&m_qfiles, &errorMsg, &errorLine, &errorColumn)) {
qDebug() << "读取:操作setContent失败,请重新检查";
qDebug() << "错误信息:" << errorMsg << ",行:" << errorLine
<< ",列:" << errorColumn;
m_qfiles.close();
return;
}
qDebug() << "读取:操作setContent成功";
m_qfiles.close(); // 解析完成后关闭文件,避免文件句柄被占用
// 读取根节点
QDomElement root = docs.documentElement();
// 读取第一个父亲节点
QDomNode node = root.firstChild();
while (!node.isNull()) {
QDomNodeList sonlist = node.childNodes(); // 读取子节点集合信息
QString rootname = node.toElement().tagName(); // 读取父亲节点名称
if (rootname.compare("worker") == 0) {
readrootxml(sonlist); // 将子节点集合信息传递到读取子节点数据
}
node = node.nextSibling(); // 读取下一个父节点
}
}
// ========================== 读取子节点数据 ==========================
// 功能:遍历 <worker> 的子节点集合,将各节点文本显示到右侧编辑框
// 参数:sonnodelist ------ <worker> 节点的全部子节点集合
void Widget::readrootxml(QDomNodeList sonnodelist) {
// 循环遍历子节点集合,逐个取出节点
for (int sonnode = 0; sonnode < sonnodelist.size(); sonnode++) {
// 获取当前子节点并转为元素节点
QDomElement sonelement = sonnodelist.at(sonnode).toElement();
// 根据节点标签名,将文本内容填入对应的右侧显示框
if (sonelement.tagName().compare("WNo") == 0) {
ui->no_2->setText(sonelement.text()); // 职工编号
} else if (sonelement.tagName().compare("WName") == 0) {
ui->name_2->setText(sonelement.text()); // 职工姓名
} else if (sonelement.tagName().compare("WSex") == 0) {
ui->sex_2->setText(sonelement.text()); // 职工性别
} else if (sonelement.tagName().compare("WEdu") == 0) {
ui->edu_2->setText(sonelement.text()); // 职工学历
} else if (sonelement.tagName().compare("WDep") == 0) {
ui->dep_2->setText(sonelement.text()); // 职工部门
} else if (sonelement.tagName().compare("WSalay") == 0) {
ui->salay_2->setText(sonelement.text()); // 职工薪资
}
}
}
// ========================== 槽函数 ==========================
// 点击"写入"按钮时调用,将界面数据写入 XML 文件
void Widget::on_writeBtn_clicked() { writexmlfiles(); }
void Widget::on_readBtn_clicked() {
readxmlfile(); // 点击"读取XML文件"按钮,解析文件并回显到右侧编辑框
}
效果

