Qt之XML文件解析SAX

XML简介

​ 和HTML的语法很相似,但不同之处在于: HTML 被设计用来显示数据,其关注的是数据的外观,XML 被设计用来传输和存储数据,其关注的是数据的内容,因此,XML主要用来作为数据的存储和共享。

​ XML文档是一种树的结构,从根部扩展到枝叶。以下是一个XML示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!--写入矩形图元的信息-->
<class name="Rect">
<object name="obj1">
<x1>10</x1>
<y1>10</y1>
<x2>50</x2>
<y1>50</y1>
<linewidth>2</linewidth>
<scale>0</scale>
<rotate>0</rotate>
</object>
</class>
</root>

其中第一行 是XML 声明。它定义 XML 的版本和所使用的编码格式,<root> </root>为根节点的起始(在XML中可以自定义节点名称),<class> </class>为子元素,其中name为其属性,值为Rect;每一个子元素都可以拥有子元素,故class的子元素为object,依次类推; 所有的元素都可以有文本内容和属性,如x1的文本为10,x2的文本为50。

写XML文件

​ 使用QT自带的模块QXmlStreamWriter

1. 构造函数

​ QXmlStreamWriter有三种构造方式,分别是

1
2
3
QXmlStreamWriter::QXmlStreamWriter([QString](qstring.html) **string*);
QXmlStreamWriter::QXmlStreamWriter([QByteArray](qbytearray.html) **array*);
QXmlStreamWriter::QXmlStreamWriter([QIODevice](qiodevice.html) **device*)

第一种和第二种分别是创建一个QString和QByteArray对象,然后将xml流写入到其中,第三种则是创建一个文件设备将xml流写入其中,一般来说,第三种常用。

2. 属性

autoFormatting:bool量,表示是否自动格式化文档,其相关读写函数为:

1
2
void setAutoFormatting(bool enable)
bool autoFormatting() const

autoFormattingIndent:int变量,当自动格式化为真时,它表示缩进的空格或者制表符的数量,相关读写函数为:

1
2
int autoFormattingIndent() const
void setAutoFormattingIndent(int spacesOrTabs)

一般4个空格等于一个制表符,也就是说setAutoFormattingIndent(4)的效果与setAutoFormattingIndent(-1)的效果是一样的。

3. 方法

​ 这里只介绍常用的一些方法。

1、自动格式化代码

1
void QXmlStreamWriter::setAutoFormatting(bool enable)

2、 设置文档编码

1
2
void QXmlStreamWriter::setCodec(QTextCodec *codec);
void QXmlStreamWriter::setCodec(const char *codecName)

​ 默认是utf-8,其它支持的编码格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Big5
Big5-HKSCS
CP949
EUC-JP
EUC-KR
GB18030
HP-ROMAN8
IBM 850
IBM 866
IBM 874
ISO 2022-JP
ISO 8859-1 to 10
ISO 8859-13 to 16
Iscii-Bng, Dev, Gjr, Knd, Mlm, Ori, Pnj, Tlg, and Tml
KOI8-R
KOI8-U
Macintosh
Shift-JIS
TIS-620
TSCII
UTF-8
UTF-16
UTF-16BE
UTF-16LE
UTF-32
UTF-32BE
UTF-32LE
Windows-1250 to 1258

3、设置自动缩进值

1
void setAutoFormattingIndent(int spacesOrTabs)

4、开始写xml文档时,需要先设置xml的版本和所使用的编码,一般为1.0版本,编码为utf-8,相关函数为:

1
2
3
4
void QXmlStreamWriter::writeStartDocument()
void QXmlStreamWriter::writeStartDocument(const QString &version)
void QXmlStreamWriter::writeStartDocument(const QString &version, bool standalone)

与其相对应的,关闭文档写入需要调用函数

1
2
void QXmlStreamWriter::writeEndDocument();

5、xml文档为树结构,其有一个根节点,根节点下面都挂在着许多元素,节点元素设置都为以下函数

1
2
3
void QXmlStreamWriter::writeStartElement(const QString &namespaceUri, const QString &name);
void QXmlStreamWriter::writeStartElement(const QString &qualifiedName)

与其相对应的,关闭节点或元素需要调用函数

1
2
void QXmlStreamWriter::writeEndElement()

6、每一个元素都可以有若干个属性,写入带有名称和值的属性

1
2
3
4
5
void QXmlStreamWriter::writeAttribute(const QString &namespaceUri, const QString &name, const QString &value);
void QXmlStreamWriter::writeAttribute(const QString &qualifiedName, const QString &value);
void QXmlStreamWriter::writeAttribute(const QXmlStreamAttribute &attribute);
void QXmlStreamWriter::writeAttributes(const QXmlStreamAttributes &attributes);

7、写文本

1
2
void QXmlStreamWriter::writeCharacters(const QString &text);

8、写入带有名称和文本的元素

1
2
3
void QXmlStreamWriter::writeTextElement(const QString &namespaceUri, const QString &name, const QString &text);
void QXmlStreamWriter::writeTextElement(const QString &qualifiedName, const QString &text);

9、写注释

1
2
void QXmlStreamWriter::writeComment(const QString &text);

还有一些方法就不一一介绍了,以上已经可以写出一个完整的xml文档。

4. 示例

​ 将一些图元的信息写入到xml文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
void MainWindow::on_btnWrite_clicked()
{
QString fileName = QFileDialog::getSaveFileName(this, "Save File",
"./untitled.xml","Xml(*.xml)");
qDebug() << fileName;

QFile file(fileName);

if (!file.open(QIODevice::WriteOnly))
{
qDebug() << "Save failed";
return;
}

/* 打开文件成功,开始写入 */
QXmlStreamWriter writer(&file);

writer.setCodec("utf-8");
writer.writeStartDocument("1.0");
writer.setAutoFormatting(true);
writer.setAutoFormattingIndent(-1);

writer.writeStartElement("root");
writer.writeComment("写入矩形图元的信息");
writer.writeStartElement("class");
writer.writeAttribute("name", "Rect");
writer.writeStartElement("object");
writer.writeAttribute("name", "obj1");
writer.writeTextElement("x1", QString::number(10));
writer.writeTextElement("y1", QString::number(10));
writer.writeTextElement("x2", QString::number(50));
writer.writeTextElement("y1", QString::number(50));
writer.writeTextElement("linewidth", QString::number(2));
writer.writeTextElement("scale", QString::number(0));
writer.writeTextElement("rotate", QString::number(0));
writer.writeEndElement();

writer.writeStartElement("object");
writer.writeAttribute("name", "obj2");
writer.writeTextElement("x1", QString::number(20));
writer.writeTextElement("y1", QString::number(30));
writer.writeTextElement("x2", QString::number(50));
writer.writeTextElement("y1", QString::number(60));
writer.writeTextElement("linewidth", QString::number(2));
writer.writeTextElement("scale", QString::number(0));
writer.writeTextElement("rotate", QString::number(90));

writer.writeEndElement();
writer.writeEndElement();

writer.writeComment("写入线图元的信息");
writer.writeStartElement("class");
writer.writeAttribute("name", "Line");
writer.writeStartElement("object");
writer.writeAttribute("name", "obj1");
writer.writeTextElement("x1", QString::number(10));
writer.writeTextElement("y1", QString::number(10));
writer.writeTextElement("x2", QString::number(50));
writer.writeTextElement("y1", QString::number(50));
writer.writeTextElement("linewidth", QString::number(2));
writer.writeTextElement("scale", QString::number(0));
writer.writeTextElement("rotate", QString::number(0));
writer.writeEndElement();

writer.writeStartElement("object");
writer.writeAttribute("name", "obj2");
writer.writeTextElement("x1", QString::number(10));
writer.writeTextElement("y1", QString::number(10));
writer.writeTextElement("x2", QString::number(50));
writer.writeTextElement("y1", QString::number(50));
writer.writeTextElement("linewidth", QString::number(2));
writer.writeTextElement("scale", QString::number(0));
writer.writeTextElement("rotate", QString::number(0));
writer.writeEndElement();
writer.writeEndElement();

writer.writeComment("写入椭圆图元信息");
writer.writeStartElement("class");
writer.writeAttribute("name", "Ellipse");
writer.writeCharacters("无椭圆图元");
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();
file.close();
}

最后构成的xml文档如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!--写入矩形图元的信息-->
<class name="Rect">
<object name="obj1">
<x1>10</x1>
<y1>10</y1>
<x2>50</x2>
<y1>50</y1>
<linewidth>2</linewidth>
<scale>0</scale>
<rotate>0</rotate>
</object>
<object name="obj2">
<x1>20</x1>
<y1>30</y1>
<x2>50</x2>
<y1>60</y1>
<linewidth>2</linewidth>
<scale>0</scale>
<rotate>90</rotate>
</object>
</class>
<!--写入线图元的信息-->
<class name="Line">
<object name="obj1">
<x1>10</x1>
<y1>10</y1>
<x2>50</x2>
<y1>50</y1>
<linewidth>2</linewidth>
<scale>0</scale>
<rotate>0</rotate>
</object>
<object name="obj2">
<x1>10</x1>
<y1>10</y1>
<x2>50</x2>
<y1>50</y1>
<linewidth>2</linewidth>
<scale>0</scale>
<rotate>0</rotate>
</object>
</class>
<!--写入椭圆图元信息-->
<class name="Ellipse">无椭圆图元</class>
</root>

读XML文件

​ 使用QT自带的模块QXmlStreamReader

1. 构造函数

可以通过

1
2
3
4
5
6
QXmlStreamReader(const char *data)
QXmlStreamReader(const QString &data)
QXmlStreamReader(const QByteArray &data)
QXmlStreamReader(QIODevice *device)
QXmlStreamReader()

2. 属性

namespaceProcessing:bool变量,指是否处理命名空间,相关读写函数为

1
2
3
bool namespaceProcessing() const
void setNamespaceProcessing(bool)

3. 方法

1、设置文件设备,如果你使用了无参的构造,则可以通过下面这个函数来设置要读取的文件

1
2
void setDevice(QIODevice *device)

2、读取下一个下一个标记

1
2
QXmlStreamReader::TokenType QXmlStreamReader::readNext();

读取xml文档主要就用这一个函数,通过其返回值 QXmlStreamReader::TokenType来判断读取的是什么元素,枚举TokenType如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum TokenType {
NoToken = 0,
Invalid,
StartDocument,
EndDocument,
StartElement,
EndElement,
Characters,
Comment,
DTD,
EntityReference,
ProcessingInstruction
};

  • NoToken:没有读取到任何信息
  • Invalid:读取失败,错误信息保存在error()errorString()
  • StartDocument:读取到的为xml文档版本号documentVersion()和编码格式documentEncoding()
  • EndDocument:读取到文档结束的位置
  • StartElement:读取到的为一个节点,节点命名空间和名字分别保存在namespaceUri()name()中,其属性值保存在attribute()中。
  • EndElement:读取到该节点的结束位置
  • Characters:读取的内容为字符,保存在text()
  • Comment:读取的内容为注释,保存在text()
  • DTD:读取的内容为DTD,保存在text()
  • EntityReference:无法解析的实体引用
  • ProcessingInstruction

3、读取开始节点中的文本

1
2
QString QXmlStreamReader::readElementText(QXmlStreamReader::ReadElementTextBehaviour behaviour = ErrorOnUnexpectedElement)

这个函数用在当读取的标记为StartElement之后,直接调用这个函数可以更方便的获取其内容

示例

​ 读取上面写入的文档,示例代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
void MainWindow::on_btnRead_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, "Save this file As"
, "./", "Xml(*.xml)");
qDebug() << fileName;

QFile file(fileName);

if (!file.open(QIODevice::ReadOnly))
{
qDebug() << "Save failed";
return;
}

QXmlStreamReader reader(&file);
QXmlStreamReader::TokenType token;
QXmlStreamAttributes attribute;
QString str, attrStr;
while (!reader.atEnd()) {
token = reader.readNext();
if(token == QXmlStreamReader::StartElement)
{
str = reader.name().toString();
if(QString::compare(str, "root") == 0)
{
qDebug() << "这是根节点:"<< str;
qDebug() << "----------------";
}
else if(QString::compare(str, "class") == 0)
{

attribute = reader.attributes();
if(attribute.hasAttribute("name"))
{
attrStr.clear();
attrStr = attribute.value("name").toString();
}
qDebug() << "************************************************";
qDebug() << "这是元素:"<< str << ", 属性为" << attrStr;
}
else if(!QString::compare(str, "object"))
{
attribute = reader.attributes();
if(attribute.hasAttribute("name"))
{
attrStr.clear();
attrStr = attribute.value("name").toString();
}
qDebug() << "这是元素:" << str << ", 属性为" << attrStr;

}
else if(QString::compare(str, "x1") == 0)
{

qDebug() << attrStr << "对象的x1值为:" << reader.readElementText();
}
else if(QString::compare(str, "x2") == 0)
{
qDebug() << attrStr << "对象的x2值为:" << reader.readElementText();
}
else if(QString::compare(str, "y1") == 0)
{
qDebug() << attrStr << "对象的y1值为:" << reader.readElementText();
}
else if(QString::compare(str, "y2") == 0)
{
qDebug() << attrStr << "对象的y2值为:" << reader.readElementText();
}
else if(QString::compare(str, "linewidth") == 0)
{
qDebug() << attrStr << "对象的linewidth值为:" << reader.readElementText();
}
else if(QString::compare(str, "scale") == 0)
{
qDebug() << attrStr << "对象的scale值为:" << reader.readElementText();
}
else if(QString::compare(str, "rotate") == 0)
{
qDebug() << attrStr << "对象的rotate值为:" << reader.readElementText();
qDebug() << "----------------";
}
}
else if(token == QXmlStreamReader::Characters)
{
// qDebug() << reader.text().toString(); /* 会把换行符和制表符也给读出来 */
}
else if(token == QXmlStreamReader::Comment)
{
qDebug() << reader.text().toString(); /* xml注释 */
}
else if(token == QXmlStreamReader::Invalid)
{
qDebug() << "读取失败:" << reader.errorString();
}
}
}

打印输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
"H:/robot_plus/tests/Xml/XML/bin/untitled1.xml"
这是根节点: "root"
----------------
"写入矩形图元的信息"
************************************************
这是元素: "class" , 属性为 "Rect"
这是元素: "object" , 属性为 "obj1"
"obj1" 对象的x1值为: "10"
"obj1" 对象的y1值为: "10"
"obj1" 对象的x2值为: "50"
"obj1" 对象的y1值为: "50"
"obj1" 对象的linewidth值为: "2"
"obj1" 对象的scale值为: "0"
"obj1" 对象的rotate值为: "0"
----------------
这是元素: "object" , 属性为 "obj2"
"obj2" 对象的x1值为: "20"
"obj2" 对象的y1值为: "30"
"obj2" 对象的x2值为: "50"
"obj2" 对象的y1值为: "60"
"obj2" 对象的linewidth值为: "2"
"obj2" 对象的scale值为: "0"
"obj2" 对象的rotate值为: "90"
----------------
"写入线图元的信息"
************************************************
这是元素: "class" , 属性为 "Line"
这是元素: "object" , 属性为 "obj1"
"obj1" 对象的x1值为: "10"
"obj1" 对象的y1值为: "10"
"obj1" 对象的x2值为: "50"
"obj1" 对象的y1值为: "50"
"obj1" 对象的linewidth值为: "2"
"obj1" 对象的scale值为: "0"
"obj1" 对象的rotate值为: "0"
----------------
这是元素: "object" , 属性为 "obj2"
"obj2" 对象的x1值为: "10"
"obj2" 对象的y1值为: "10"
"obj2" 对象的x2值为: "50"
"obj2" 对象的y1值为: "50"
"obj2" 对象的linewidth值为: "2"
"obj2" 对象的scale值为: "0"
"obj2" 对象的rotate值为: "0"
----------------
"写入椭圆图元信息"
************************************************
这是元素: "class" , 属性为 "Ellipse"

总结

​ 使用SAX方式进行读写XML文档更易上手,也更符合XML标准,不足之处在于仅支持迭代读写,如果你想要更改某个属性,只能重新开始写入。使用DOM方式进行解析则可以达到增删改查的操作。但是两者各有利弊。