QXmlStreamReader
QXmlStreamReader类通过简单的流式API为我们提供了一种快速的读取xml文件的方式。他比Qt自己使用的SAX解析方式还要快。
所谓的流式读取即将一个xml文档读取成一系列标记的流,类似于SAX。而QXmlStreamReader类和SAX的主要区别就是解析这些标记的方式。使用SAX解析时,应用程序必须提供一些处理器(回调函数)来处理来自解析器的一系列的所谓的xml事件,不同的xml标记会触发不同的事件,从而进行相应的处理。而使用QXmlStreamReader,应用程序自己可以驱动整个循环,从解析器中一个接一个的拉取出xml标记。这个动作可以通过readNext()来完成,该函数会读取出下一个完整的标记,然后其tokenType()。然后,我们就可以使用一系列方便的函数,如isStartElement(),text()等来确定或得到具体所读取的内容。这种拉模式(pull)的解析方法的好处就在于,我们可以将对一个xml文档的解析分开到多个函数中来完成,对不同的标记使用一个单独的函数来处理。
QXmlStreamReader类的典型使用方法如下:
QXmlStreamReader xml;
...
while (!xml.atEnd()) {
xml.readNext();
... // do processing
}
if (xml.hasError()) {
... // do error handling
}
如果在解析的过程中出现了错误,atEnd()和hasError()会返回true,error()会返回所出现的具体错误类型。errorString(),lineNumber(),columnNumber()和characterOffset()函数可以用来得到错误的具体信息,一般我们使用这几个函数来构建一个错误字符串来提示用户具体的错误信息。同时,为了简化应用程序代码,QXmlStreamReader还提供了一个raiseError()的机制,可以让我们在必要时触发一个自定义的错误信息。
QXmlStreamReader是一个增量式的解析器。它可以处理文档不能被一下处理完的情况,比如该xml文件来自于多个文件或来自于网络。当QXmlStreamReader解析完所有的数据但该xml文档是不完整的,这时它会返回一个PrematureEndOfDocumentError类型的错误。然后,当有更多的数据到来时,它会从这个错误中恢复,然后继续调用readNext()来解析新的数据。
QXmlStreamReader是不太消耗内存的,因为它不会在内存中存储整个xml文档树,仅仅存储当前它所解析的标记。此外,QXmlStreamReader使用QStringRef来解析所有的字符串数据而不是真实的QString对象,这可以避免不必要的小字符串内存分配代价。QStringRef是对QString或其子串的一个简单包装,并提供了一些类似于QString类的API,但它不会进行内存的分配,并在底层使用了引用计数来共享数据。我们可以在需要时,调用QStringRef的toString()来得到一个真实的QString对象。
读xml文件
example.xml
<?xml version="1.0" encoding="UTF-8"?>
<labels map="demo1" ver="1.0">
<label id="1802232">
<x>1568</x>
<y>666</y>
</label>
<label id="1802230">
<x>1111</x>
<y>622</y>
</label>
</labels>
#ifndef XMLREAGER_H
#define XMLREAGER_H
#include <QXmlStreamReader>
class xmlreader
{
public:
xmlreader();
bool readFile(const QString &fileName);
private:
void readlabelsElement(); //读取label标签
void readlabelElement(); //读取label标签
void readxElement(); //读取x标签
void readyElement(); //读取y标签
void skipUnknownElement(); //跳过未知标签
QXmlStreamReader reader;
};
#endif // XMLREAGER_H
#include "xmlreader.h"
#include <iostream>
#include <QDebug>
#include <QFile>
xmlreader::xmlreader()
{
}
bool xmlreader::readFile(const QString &fileName)
{
//以只读和文本方式打开文件,如果打开失败输出错误日志,并且返回false
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
std::cerr << "Error: Cannot read file " << qPrintable(fileName)
<< ": " << qPrintable(file.errorString())
<< std::endl;
return false;
}
//将文件设置成xml阅读器的输入设备
reader.setDevice(&file);
reader.readNext(); //直接读取下一个节点,因为首先读到的标签是XML文件的头部(第一行)
while (!reader.atEnd()) //外部循环,未到文件结尾就一直循环读取
{
if (reader.isStartElement()) //外部分支,如果不是起始标签,则直接读取下一个节点
{
if (reader.name() == "labels") //内部分支,如果根节点不是 == labels,
//说明读取的文件是错误的
{
qDebug() << reader.name();//通过qDebug()输出当前节点的名字,这里输出labels
readlabelsElement(); //读取labels节点的内容
}
else
{ //raiseError()函数用来自定义输出错误日志的内容,这里输出Not a labels file
reader.raiseError(QObject::tr("Not a labels file"));
}
}
else
{
reader.readNext();
}
}
//关闭文件,如果读取发生错误(hasError())或者文件有错误,输出错误信息,返回false,
file.close();
if (reader.hasError()) {
std::cerr << "Error: Failed to parse file "
<< qPrintable(fileName) << ": "
<< qPrintable(reader.errorString()) << std::endl;
return false;
} else if (file.error() != QFile::NoError) {
std::cerr << "Error: Cannot read file " << qPrintable(fileName)
<< ": " << qPrintable(file.errorString())
<< std::endl;
return false;
}
return true;
}
void xmlreader::readlabelsElement()
{
reader.readNext();//读取了根节点labels后,继续读取下一个节点
while (!reader.atEnd())
{
if (reader.isEndElement())
{
reader.readNext();
break; //如果是结束节点,则结束循环
//循环执行下去,读到的第一个结束节点是</labels>,而不是</label>;
//这是执行readlabelElement()函数中得到的结果,当读到</label>时,
//该函数跳出循环并读取下一个节点,而下一个节点是<label>或者</labels>
}
if (reader.isStartElement())
{
if (reader.name() == "label")
{ //获得label的attributes()值,也就是id,转换成字符串输出
qDebug() << reader.attributes().value("id").toString();
qDebug() << reader.name();
readlabelElement();
}
else
{
skipUnknownElement();//未知节点直接跳过
}
}
else
{
reader.readNext();
}
}
}
void xmlreader::readlabelElement()
{
reader.readNext();
while (!reader.atEnd())
{
if (reader.isEndElement())
{
reader.readNext();
break;
}
if (reader.isStartElement())
{
if (reader.name() == "x")
{
readxElement();
}
else if (reader.name() == "y")
{
readyElement();
}
else
{
skipUnknownElement();
}
}
else
{
reader.readNext();
}
}
}
void xmlreader::readxElement()
{
QString x = reader.readElementText();
qDebug() <<"x:" << x;
if (reader.isEndElement())
reader.readNext();
}
void xmlreager::readyElement()
{
QString y = reader.readElementText();//执行这个函数以后,y获得了坐标值,并且当前节点
//自动变成结束节点</y>
qDebug() << "y:" << y;
if (reader.isEndElement())
reader.readNext(); //在这里,读取下一个节点,就是</label>
}
//是一个递归函数
void xmlreader::skipUnknownElement()
{
reader.readNext();
while (!reader.atEnd()) {
if (reader.isEndElement()) {
reader.readNext();
break;
}
if (reader.isStartElement()) {
skipUnknownElement();//函数的递归调用
} else {
reader.readNext();
}
}
}
#include <QtCore/QCoreApplication>
#include "xmlreader.h"
#include <iostream>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
xmlreader reader;
reader.readFile("labels.xml");
return a.exec();
}
读取结果如下图所示:
teachers.xml:
其中,学校(school)三楼(floor3)的老师信息,还有一个学生的信息。
6个老师,1个学生
<?xml version="1.0" ?>
<school>
<floor3 id="3" time="2019/10/11">
<teacher>
<entry name="Job">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<teacher>
<entry name="Job2">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<teacher>
<entry name="Job3">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<teacher>
<entry name="Job4">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<teacher>
<entry name="Job5">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<student>
<entry name="Lily">
<age>20</age>
<sport>dancing</sport>
</entry>
<entry name="Keith">
<age>21</age>
<sport>running</sport>
</entry>
</student>
<teacher>
<entry name="Job6">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
</floor3>
</school>
注意:
重点在函数中的代码,移植出来可以使用。
记得将“用来计数”的变量,进行整理。
还有文件所在地址,也要更换。
将XML的文件内容,首先读取到一个变量中,再分析这个变量的内容。
受XML文件的编码格式影响很大,如果有中文乱码的现象出现,要慎重使用这种方法,可能无法读取。
#include <QtCore/QCoreApplication>
#include <QXmlStreamReader>
#include <QFile>
#include <iostream>
void ReadXml()
{
//用来计数
int teacherCount = 0;
int ageCount = 0;
int sanlouCount = 0;
int schoolCount = 0;
//读取文件
QString fileName = "D:/JBXML/teachers.xml";
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text))
{
return ;
}
//QXmlStreamReader操作任何QIODevice.
QXmlStreamReader xml(&file);
//解析XML,直到结束
while (!xml.atEnd() && !xml.hasError())
{
//读取下一个element.
QXmlStreamReader::TokenType token = xml.readNext();
/*以下内容用于分析读取的内容,可以将每一个读取到的标签名字打印出来*//*
if (token == QXmlStreamReader::Invalid)
{
//如果有读取错误,将被打印出来
std::cout << xml.errorString().toStdString();
}
std::cout << xml.tokenString().toStdString() << "\t";
std::cout << xml.name().toString().toStdString() << std::endl;*/
/*显示这个分析过程,你会看到很清晰的读取过程*/
//如果获取的仅为StartDocument,则进行下一个
if (token == QXmlStreamReader::StartDocument)
{
continue;
}
//如果获取了StartElement,则尝试读取
if (token == QXmlStreamReader::StartElement)
{
//如果为person,则对其进行解析
if (xml.name() == "teacher")
{
teacherCount++;
}
if (xml.name() == "age")
{
ageCount++;
}
if (xml.name() == "floor3")
{
sanlouCount++;
}
if (xml.name() == "school")
{
schoolCount++;
}
}
}
if (xml.hasError())
{
//QMessageBox::information(NULL, QString("parseXML"), xml.errorString());
}
file.close();
std::cout << teacherCount << " teacher" << std::endl;
std::cout << ageCount << " ages" << std::endl;
std::cout << sanlouCount << " 3rdFloors" << std::endl;
std::cout << schoolCount << " schools" << std::endl;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ReadXml();
return a.exec();
}
可以得到XML文件中,各个标签tag, 即尖括号内的信息,然后进行判断。
如果是“teacher”,会计数,然后可以看到,总共有6个老师。
运行结果如下图所示:
如果把中间注释的代码打开,可以看到每一个读取到的标签,并将读取过程打印出来。运行结果如下图所示: