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文件

实例1
这是一个基于控制台的应用程序,解析XML格式的文件
xml文件如下,命名为labels.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>

main.cpp

#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();
}

xmlreader.h

#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

xmlreader.cpp

#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()输出当前节点的名字,这里输出labelsreadlabelsElement();      //读取labels节点的内容} else {    //raiseError()函数用来自定义输出错误日志的内容,这里输出Not a labels filereader.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();}}
}


实例2
XML文件的意思是:学校(school)三楼(floor3)的老师信息,还有一个学生的信息。
结构比较常见,但是数据很粗糙。大家主要看结构。
XML文件的名字 teachers.xml
XML文件的内容,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个老师。

直接运行结果如下:

如果把中间注释的代码打开,可以看到每一个读取到的标签,并将读取过程打印出来。

运行结果如下:

示例3

考虑如下 XML 片段:

<doc><quote>Einmal ist keinmal</quote>
</doc>

一次解析过后,我们通过readNext()的遍历可以获得如下信息:

StartDocument
StartElement (name() == "doc")
StartElement (name() == "quote")
Characters (text() == "Einmal ist keinmal")
EndElement (name() == "quote")
EndElement (name() == "doc")
EndDocument

通过readNext()函数的循环调用,我们可以使用isStartElement()、isCharacters()这样的函数检查当前读取的类型,当然也可以直接使用state()函数。

<?xml version="1.0" encoding="utf-8"?>
<bookindex> <!--根标签--><entry term="叶节点0"><page>10</page><page>34-35</page><page>307-308</page></entry><entry term="叶节点1"><entry term="叶节点1.1"><page>115</page><page>244</page></entry><entry term="叶节点1.2"><page>9</page></entry></entry><entry term="叶节点2"><entry term="叶节点2.1"><page>115</page><page>244</page></entry><entry term="叶节点2.2"><page>9</page></entry></entry>
</bookindex>

首先来看头文件:

class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = 0);~MainWindow();bool readFile(const QString &fileName);
private:void readBookindexElement();void readEntryElement(QTreeWidgetItem *parent);void readPageElement(QTreeWidgetItem *parent);void skipUnknownElement();QTreeWidget *treeWidget;QXmlStreamReader reader;private:Ui::MainWindow *ui;
};

MainWindow显然就是我们的主窗口,即构造函数

 setWindowTitle(tr("XML Reader"));treeWidget = new QTreeWidget(this);QStringList headers;headers << "Items" << "Pages";treeWidget->setHeaderLabels(headers);setCentralWidget(treeWidget);

readFile()

 QFile file(QApplication::applicationDirPath() + "/demo.xml");if (!file.open(QFile::ReadOnly | QFile::Text)){QMessageBox::critical(this, tr("Error"),tr("Cannot read file %1").arg(fileName));return false;}reader.setDevice(&file);while (!reader.atEnd()){if (reader.isStartElement()) //检测当前读取类型        {qDebug()<<"2222222222222222";if (reader.name() == "bookindex"){readBookindexElement();//递归下降算法,层层读取}else{reader.raiseError(tr("Not a valid book file"));}}else{qDebug()<<"111111111111111";reader.readNext(); //循坏调用首次移动3次,后面移动一次}}file.close();if (reader.hasError()){QMessageBox::critical(this, tr("Error"),tr("Failed to parse file %1").arg(fileName));return false;}else if (file.error() != QFile::NoError){QMessageBox::critical(this, tr("Error"),tr("Cannot read file %1").arg(fileName));return false;}return true;

readFile()函数用于打开给定文件。我们使用QFile打开文件,将其设置为QXmlStreamReader的设备。也就是说,此时QXmlStreamReader就可以从这个设备(QFile)中读取内容进行分析了。接下来便是一个 while 循环,只要没读到文件末尾,就要一直循环处理。首先判断是不是StartElement,如果是的话,再去处理 bookindex 标签。注意,因为我们的根标签就是 bookindex,如果读到的不是 bookindex,说明标签不对,就要发起一个错误(raiseError())。如果不是StartElement(第一次进入循环的时候,由于没有事先调用readNext(),所以会进入这个分支),则调用readNext()。为什么这里要用 while 循环,XML 文档不是只有一个根标签吗?直接调用一次readNext()函数不就好了?这是因为,XML 文档在根标签之前还有别的内容,比如声明,比如 DTD,我们不能确定第一个readNext()之后就是根标签。正如我们提供的这个 XML 文档,首先是 声明,其次才是根标签。如果你说,第二个不就是根标签吗?但是 XML 文档还允许嵌入 DTD,还可以写注释,这就不确定数目了,所以为了通用起见,我们必须用 while 循环判断。处理完之后就可以关闭文件,如果有错误则显示错误。

void MainWindow::readBookindexElement()
{Q_ASSERT(reader.isStartElement() && reader.name() == "bookindex");//不是则会报错reader.readNext(); // 读取下一个记号,它返回记号的类型while (!reader.atEnd()){if (reader.isEndElement()){reader.readNext();break;}if (reader.isStartElement()){if (reader.name() == "entry"){readEntryElement(treeWidget->invisibleRootItem());}else{skipUnknownElement();}}else{reader.readNext();}}
}

注意第一行我们加了一个断言。意思是,如果在进入函数的时候,reader 不是StartElement状态,或者说标签不是 bookindex,就认为出错。然后继续调用readNext(),获取下面的数据。后面还是 while 循环。如果是EndElement,退出,如果又是StartElement,说明是 entry 标签(注意我们的 XML 结构,bookindex 的子元素就是 entry),那么开始处理 entry,否则跳过。

那么下面来看readEntryElement()函数:

void MainWindow::readEntryElement(QTreeWidgetItem *parent)
{QTreeWidgetItem *item = new QTreeWidgetItem(parent);item->setText(0, reader.attributes().value("term").toString());//元素的属性reader.readNext();while (!reader.atEnd()){if (reader.isEndElement()){reader.readNext();break;}if (reader.isStartElement()){if (reader.name() == "entry"){readEntryElement(item);}else if (reader.name() == "page"){readPageElement(item);}else{skipUnknownElement();}}else{reader.readNext();}}
}

这个函数接受一个QTreeWidgetItem指针,作为根节点。这个节点被当做这个 entry 标签在QTreeWidget中的根节点。我们设置其名字是 entry 的 term 属性的值。然后继续读取下一个数据。同样使用 while 循环,如果是EndElement就继续读取;如果是StartElement,则按需调用readEntryElement()或者readPageElement()。由于 entry 标签是可以嵌套的,所以这里有一个递归调用。如果既不是 entry 也不是 page,则跳过位置标签。

然后是readPageElement()函数:

void MainWindow::readPageElement(QTreeWidgetItem *parent)
{QString page = reader.readElementText();if (reader.isEndElement()){qDebug()<<"3333333333333333";reader.readNext();}QString allPages = parent->text(1);if (!allPages.isEmpty()){allPages += ", ";}allPages += page;parent->setText(1, allPages);
}

由于 page 是叶子节点,没有子节点,所以不需要使用 while 循环读取。我们只是遍历了 entry 下所有的 page 标签,将其拼接成合适的字符串。

skipUnknownElement()函数

void MainWindow::skipUnknownElement()
{reader.readNext();while (!reader.atEnd()){if (reader.isEndElement()){reader.readNext();break;}if (reader.isStartElement()){skipUnknownElement();}else{reader.readNext();}}
}

我们没办法确定到底要跳过多少位置标签,所以还是得用 while 循环读取,注意位置标签中所有子标签都是未知的,因此只要是StartElement,都直接跳过。

使用QXmlStreamReader读取解析XML文件相关推荐

  1. 如何运用JAXB定时读取解析xml文件?

    Background系统 一.背景 在许多开发需求中都解析xml文件的需求,对于规格复杂的xml文件,方法很多主要有JDK原生dom形式,SAX形式,DOM4J ,JAXB 4种方式,但是JAXB(J ...

  2. 安卓开发Android studio学习笔记12:读取解析XML(案例演示)

    Android studio学习笔记 第一步:配置Student.XML 第二步:配置activity_main.xml 第三步:配置student.xml 第四步:配置Student用户类 第五步: ...

  3. python读取xml_python解析xml文件

    加载和读取xml文件 import xml.dom.minidom doc = xml.dom.minidom.parse(xmlfile) 获取xml文档对象(对子节点和节点node都适用) roo ...

  4. jq ajax xml,jQuery+ajax读取并解析XML文件的方法

    本文实例讲述了jQuery+ajax读取并解析XML文件的方法.分享给大家供大家参考,具体如下: ajax.xml: zhangsan 1 lisi 2 demo.html: /p> " ...

  5. java解析xml文件:创建、读取、遍历、增删查改、保存

    全栈工程师开发手册 (作者:栾鹏) java教程全解 java使用JDOM接口解析xml文件,包含创建.增删查改.保存,读取等操作. 需要引入jdom.jar,下载 xercesImpl.jar,下载 ...

  6. tinyxml 读取文本节点_c++中用TINYXML解析XML文件

    TinyXML介绍 最近做一个负载均衡的小项目,需要解析xml配置文件,用到了TinyXML,感觉使用起来很容易,给出一个使用TinyXML进行XML解析的简单例子,很多复杂的应用都可以基于本例子的方 ...

  7. python读取xml标注坐标_遍历文件 创建XML对象 方法 python解析XML文件 提取坐标计存入文件...

    XML文件??? xml即可扩展标记语言,它可以用来标记数据.定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言. 里面的标签都是可以随心所欲的按照他的命名规则来定义的,文件名为roi.xm ...

  8. SAX解析XML文件

    就目前来说,有三种方式可以解析XML文件:DOM.SAX.StAX.DOM将整个XML文件加载到内存中,并构建出节点树:应用程序可以通过遍历节点树的方式来解析XML文件中的各个节点.属性等信息:这种方 ...

  9. Android开发--详解SAX解析XML文件

    SAX技术字处理XML文件时并不是一次性把XML文件装入内存,而是一边读一边解析,因此,在解析的过程中会有几个步骤需要注意,在这里用一张图来表示解析的步骤: 在本实例中,定义了一个xml文件,其中有若 ...

最新文章

  1. ie6 span 换行IE6中float:right换行问题的替代解决方案
  2. shell编程系列25--shell操作数据库实战之备份MySQL数据,并通过FTP将其传输到远端主机...
  3. php mysql三_PHP和MySQL基础教程(三)
  4. 第 4 章  超链接和路径
  5. RHEL/CentOS/Fedora各种源
  6. 【飞秋】进一步完善 -- GEF创建助手工具条
  7. bzoj 4129 Haruna’s Breakfast 树上莫队
  8. linux cuda 安装目录,Ubuntu16.04 CUDA和GPU的最简最全安装方案及常见问题解决方法,G...
  9. 如何使用PDF虚拟打印机打印文件
  10. 8086汇编_常用指令
  11. cmos逻辑门传输延迟时间_MOS管以及简单CMOS逻辑门电路原理图解析
  12. 游戏后台开发九问--linux平台
  13. VGA、DVI、HDMI都是什么意思?
  14. 自抗扰控制器-2.非线性状态误差反馈控制律 NLSEF
  15. PC客户端中的网页----问题集合(未完待续)
  16. C++ GUI Programming with Qt4 Second Edition 之 前言
  17. 使用Servlet作为控制器
  18. dwr框架查看外放方法_硬核!教你三种方法,实现微信自定义修改地区!
  19. nas服务器硬盘,明明白白了解NAS:网络存储服务器浅析(转载)
  20. UBTC主网4月份升级啦

热门文章

  1. 计算机系统-实模式/保护模式/虚拟86模式
  2. matlab 按文件名排序,在MATLAB中对字符串进行排序,如Windows 7,对资源管理器中的文件名进行排序(尊重数字中间字符串)...
  3. python群发邮件 不进垃圾箱_实战邮箱群发2000封邮件不进垃圾箱
  4. react打包后图片丢失_宜信技术实践|指尖前端重构(React)技术调研分析
  5. java 正则表达式的包_用于Java包名称的Python正则表达式
  6. 调用方法try起来的好处_Java:一个重要的停止线程方法——interrupt
  7. 全球唯一标识符 System.Guid.NewGuid().ToString()
  8. Swift 类的使用class
  9. 吴裕雄 15-MySQL LIKE 子句
  10. ARM 指令集跳转指令