使用QXmlStreamReader读取XML

使用 QXmlStreamReader,是在 Qt 中读取 XML 文档的最快且最简单的方式。因为解析器的工作能力是逐渐递增的,所以它尤其适用于诸如查找 XML 文档中一个给定的标记符出现的次数、读取内存容纳不了的特大文件、组装定制的数据结构以反映 XML 文档的内容等。

QXmlStreamReader 解析器根据图 16.1 中所列出的记号(token) 工作。每次只要调用 readNext()函数,下一个记号就会被读取并变成当前的记号。当前记号的属性取决于记号的类型,可以使用表格中列出的 getter 函数读取当前记号。

考虑如下的 XML 文档:

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

如果解析这个文档,则 readNext()每调用一次都将生成一个新记号,若使用 getter 函数还会获得额外的信息:

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

每次调用 readNext() 后,都可以使用 isStartElement() 、isCharacters()及类似的函数或者仅仅用state()来测试当前记号的类型。

下面将查看一个实例,它告诉我们如何使用QXmlStreamReader 解析一个专门的 XML 文件格式并在QTreeWidget 中显示其内容。所解析的是那种具有书刊索引目录且包含索引条目和子条目的文挡格式。图 16.2 是在 QTreeWidget 中显示的书刊索引文件。

<?xml version="1.0"?>
<bookindex><entry term="sidebearings"><page>10</page><page>34-35</page><page>307-308</page></entry><entry term="subtraction"><entry term="of.pictures"><page>115</page><page>244</page></entry><entry term="of vectors"><page>9</page></entry></entry>
</bookindex>

首先查看从应用程序的 main() 函数中提取出的代码,以从中了解 XML 阅读器在上下文中是如何使用的。然后,我们将查看阅读器的实现代码。

int main(int argc, char *argv[])
{QApplication app(argc, argv);QStringList args = QApplication::arguments();...QTreeWidget treeWidget;...XmlStreamReader reader(&treeWidget);for(int i=1; i<args.count(); ++i)reader.readFile(args[i]);return app.exec();
}

在图 16.2 中显示的应用程序首先创建一个 QTreeWidget。之后,这个应用程序创建一个XmlStreamReader,并将树形窗口部件值传递给该 XmIStreamReader 并要求它解析在命令行中所指定的每一个文件。

class XmlStreamReader
{public:XmlStreamReader(QTreeWidget *tree);bool readFile(const QString &fileName);private:void readBookindexElement();void readEntryElement(QTreeWidgetItem *parent);void readPageElement(QTreeWidgetItem *parent);void skipUnknownElement();QTreeWidget *treeWidget;QXmlStreamReader reader;
};

XmlStreamReader 类提供了两个公共函数:构造函数和 parseFile()函数。这个类使用 QXmlStremReader 实例解析XML文件,并组装 QTreeWidget 窗口以反映其读入的 XML 数据。通过使用向下递归的方法来实现这一解析过程。
● readBookindexElement() 解析一个含有0个或0个以上 <entry> 元素的 <bookindex>...</bookinex>元素。
● readEntryElement() 解析一个含有0个或0个以上<page>元素的 <entry>...</entry> 元素,以及嵌套任意层次的含有0个或0个以上 <entry> 元素。
● readPageElement() 解析一个<page>...</page>元素。
● skipUnknownElement() 跳过不能识别的元素。
现在看看XmlStreamReader 类的实现,由构造函数开始。

XmlStreamReader::XmlStreamReader(QTreeWidget *tree)
{treeWidget = tree;
}

构造函数只用来建立阅读器将使用的那个 QTreeWidget 。所有的操作都将在 readFile()函数中完成(由 main() 函数调用),我们将分三个部分来查看它们。

bool XmlStreamReader::readFile(const QString &fileName)
{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;}reader.setDevice(&file);

readFile() 函数首先会尝试打开文件。如果失败,则会输出一条出错信息并返回false值;如果成功,则它将被设置为 QXmlStreamReader的输入设备。

    reader.readNext();while (!reader.atEnd()) {if (reader.isStartElement()) {if (reader.name() == "bookindex") {readBookindexElement();} else {reader.raiseError(QObject::tr("Not a bookindex file"));}} else {reader.readNext();}}

QXmlStreamReader的readNext()函数从输入流中读取下一个记号。如果成功而且还没有到达 XML文件的结尾函数将进入while循环。由于索引文件的结构,我们知道在该循环内部只有三种可能性发生:<bookindex>开始标签正好被读入;另一个开始标签正好被读入(在这种情况下,读取的文件不是一个书刊索引);读入的是其他种类的记号。

如果有正确的开始标签,就调用readBookindexElement()继续完成处理。否则,就调用QXmlStreamReader::raiseError()并给出出错信息。下一次(在while循环条件下)调用atEnd()时,它将返还true值。这就确保了解析过程可以在遇到错误时能尽快停止。通过对QFile调用error()和errorString()就可以在稍后查询这些出错信息。当在书刊索引文件中检测到有错误时,也会立即返回一个类似的出错信息。其实,使用raiseError()通常会更加方便,因为它对低级的XML解析错误和与应用程序相关的错误使用了相同的错误报告机制,而这些低级的XML解析错误会在QXmlStre-amReader运行到无效的XML时就自动出现。

    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;
}

一旦处理完成,就会关闭文件。如果存在解析器错误或者文件错误,该函数就输出一个出错信息并返回 false 值;否则,返回 true 值并报告解析成功。

void XmlStreamReader::readBookindexElement()
{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();}}
}

readBookindexElementO的作用就是读取文件的主体部分。它首先跳过当前的记号(此处只可能是<bookinde>开始标签),然后遍历读取整个输入文件。

如果读取到了关闭标签,那么它只可能是</bookindex>标答,否则QXmlStreamReader早就已经报告出错了(UnexpectedElementEror)。如果是那样的话,就跳过这个标签并跳出循环。否则,将应该有一个顶级索引<entry>开始标签。如果情况确实如此,调用readEntryElement()来处理条目数据;不然就调用skipUnknowElement()。使用skipUnknownElement()而不调用raiseError(),意味着如果要在将来扩展书刊索引格式以包含新的标答的话这个阅读器将继续有效因为它仅忽略了不能识别的标签。

readEntryElement()具有一个确认父对象条目的QTreeWidgetltem*参数。我们将QTreeWidget::invisibleRootItem()作为父对象项传递,以使新的项以其为根基。在readEntryElement()中,用一个不同的父对象项循环调用readEntryElement()。

void XmlStreamReader::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();}}
}

每当遇到一个<entry>开始标签时就会调用readEntryElement()函数。我们希望为每一个索引条目创建一个树形的窗口部件项,因此创建一个新的QTreeWidgetltem,并将其第一列的文本值设置为条目的项属性文本。

一旦条目被添加到树中就开始读取下一个记号。如果这是一个关闭标签,就跳过该标签并跳出循环。如果遇到的是开始标签,那么它可能是<entry>标签(表示一个子条目),<page>标签(该条目项的页码数)或者是一个未知的标签。如果开始标签是一个子条目,就递归调用readEntryElement()。如果该标签是<page>标签,就调用readPageElement()。

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

只要读取的是<page>标签,就调用readPageElement()函数。被传递的正是符合页码文本所属条目的树项。我们从读取<page></page>标签之间的文本开始。成功读取完以后,readElementText()函数将让解析器停留在必须跳过的</page>标签上。

这些页被存储在树形窗口部件项的第二列。我们首先提取那里已有的文本。如果文本不为空值就在其后添加一个逗号,为新页的文本做好准备。然后添加新的文本并相应地更新该列的文本。

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

最后,当遇到未知的标签时,将继续读取,直到读取到也将跳过的未知元素的关闭标签为止。这意味着我们将跳过那些具有良好形式但却无法识别的元素,并从 XML 文件中读取尽可能多的可识别的数据。

这里给出的实例可以作为类似的XML向下递归解析器的基础。然而,有时候实现这样一个解析器可能是相当棘手的,如果没有调用 readNext()或者在不恰当地方调用。一些程序员通过在代码中使用断言(assertion)来强调这个问题。例如,在readBookindexElement()的开头,我们可以加上一行代码:

Q_ASSERT(reader.isStartElement() && reader.name() == "bookindex");

也可以在readEntryElement()和 readPageElement()函数中使用类似的断言。对于 skipUnknowElement(),我们将仅声明存在一个开始元素。

QXmlStreamReader 可以从包括 QFile、QBuffer、QProcess、QTcpSocket 的任意 QIODevice 中获得输
入。一些输入数据源可能无法在解析器需要的时候提供其所需要的数据,例如由于网络等待时间所造成。但在这种情况下仍可使用 QXmlStreamReader。在 “Incremental Parsing"主题下关于 XmlStreamReader 的参考文档中,提供了关于这方面的更多信息。”

在这个应用程序中使用的 QXmIStreamReader 类是QtXml 库中的一部分。如果想要建立应用程序和 QtXml 库的关联,必须在 .pro 文件中加入如下一行命令:
QT += xml
在接下来的两节中,将看到如何使用 DOM和SAX 来编写相同的应用程序。

xmlstreamreader.h

#ifndef XMLSTREAMREADER_H
#define XMLSTREAMREADER_H#include <QXmlStreamReader>class QTreeWidget;
class QTreeWidgetItem;class XmlStreamReader
{public:XmlStreamReader(QTreeWidget *tree);bool readFile(const QString &fileName);private:void readBookindexElement();void readEntryElement(QTreeWidgetItem *parent);void readPageElement(QTreeWidgetItem *parent);void skipUnknownElement();QTreeWidget *treeWidget;QXmlStreamReader reader;
};#endif

xmlstreamreader.cpp

#include <QtGui>
#include <QtXml>
#include <iostream>#include "xmlstreamreader.h"XmlStreamReader::XmlStreamReader(QTreeWidget *tree)
{treeWidget = tree;
}bool XmlStreamReader::readFile(const QString &fileName)
{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;}reader.setDevice(&file);reader.readNext();while (!reader.atEnd()) {if (reader.isStartElement()) {if (reader.name() == "bookindex") {readBookindexElement();} else {reader.raiseError(QObject::tr("Not a bookindex file"));}} else {reader.readNext();}}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 XmlStreamReader::readBookindexElement()
{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();}}
}void XmlStreamReader::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();}}
}void XmlStreamReader::readPageElement(QTreeWidgetItem *parent)
{QString page = reader.readElementText();if (reader.isEndElement())reader.readNext();QString allPages = parent->text(1);if (!allPages.isEmpty())allPages += ", ";allPages += page;parent->setText(1, allPages);
}void XmlStreamReader::skipUnknownElement()
{reader.readNext();while (!reader.atEnd()) {if (reader.isEndElement()) {reader.readNext();break;}if (reader.isStartElement()) {skipUnknownElement();} else {reader.readNext();}}
}

main.cpp

#include <QtGui>
#include <iostream>#include "xmlstreamreader.h"int main(int argc, char *argv[])
{QApplication app(argc, argv);QStringList args = QApplication::arguments();if (args.count() < 2) {std::cerr << "Usage: xmlstreamreader file1.xml..."<< std::endl;return 1;}QStringList labels;labels << QObject::tr("Terms") << QObject::tr("Pages");QTreeWidget treeWidget;treeWidget.setHeaderLabels(labels);treeWidget.header()->setResizeMode(QHeaderView::Stretch);treeWidget.setWindowTitle(QObject::tr("XML Stream Reader"));treeWidget.show();XmlStreamReader reader(&treeWidget);for (int i = 1; i < args.count(); ++i)reader.readFile(args[i]);return app.exec();
}

xmlstreamreader.pro

TEMPLATE      = app
QT           += xml
HEADERS       = xmlstreamreader.h
SOURCES       = main.cpp \xmlstreamreader.cpp

Qt4_使用QXmlStreamReader读取XML相关推荐

  1. Qt4_使用SAX读取XML

    使用SAX读取XML SAX 事实上是公共领域中一种用于读取 XML 文挡的标准应用程序编程接口.Qt 的 SAX 类是对基于 SAX2 的 Java 实现的模拟,只是在命名上有些不太符合Qt的惯例. ...

  2. Qt4_用DOM读取XML

    用DOM读取XML DOM 是一种解析由万维网协会(W3C) 所开发的 XML 文档的标准应用程序编程接口. Qt 提供一套用于读取.操作和编写 XML 文挡的非验证型二级 DOM 实现. DOM 把 ...

  3. Qt工作笔记-对QXmlStreamReader的进一步认识(读取XML)

    代码如下: #include <QApplication> #include <QDebug> #include <QFile> #include <QVec ...

  4. 使用QXmlStreamReader读取解析XML文件

    QXmlStreamReader QXmlStreamReader类通过简单的流式API为我们提供了一种快速的读取xml文件的方式.他比Qt自己使用的SAX解析方式还要快. 所谓的流式读取即将一个xm ...

  5. Qt 读取XML文档的简单示例

    目录名字 XML简介 Qt 操作XML文档一般方法 使用DOM读取XML文档 示例代码:读取XML文档 XML简介 XML(Extensible Markup Language,可扩展标记语言)是一种 ...

  6. 【Qt】DOM读取XML文档

    00. 目录 文章目录 00. 目录 01. 概述 02. 开发环境 03. XML文档示例 04. DOM读取XML文档内容 05. 预留 06. 附录 01. 概述 DOM(Document Ob ...

  7. [Qt教程] 第27篇 XML(一)使用DOM读取XML文档

    [Qt教程] 第27篇 XML(一)使用DOM读取XML文档 楼主  发表于 2013-5-21 21:14:28 | 查看: 1001| 回复: 14 使用DOM读取XML文档 版权声明 该文章原创 ...

  8. Qt: QXMLStreamReader,读XML文件实例

    主要接口: 1.TokenType QXmlStreamReader::readNext() 功能:读取下一个标记,并返回其类型. 主要的类型有: enum QXmlStreamReader::Tok ...

  9. android读取xml 字符串,Android 读取本地Xml文件,并转换成String

    问题 不是解析本地 xml 文件, 而是要将 xml 文件中的所有内容(包含格式,标签等),直接转换成 String. 与前端H5页面交互时, iOS 在请求远程 xml 文件耗时太长(有时需要4~5 ...

最新文章

  1. ASP.NET2.0 遍历文件夹下所有图片【月儿原创】
  2. CS231n:卷积神经网络
  3. 在类别无法直接使用的一些对象或方法
  4. .NET 6新特性试用 | 可写JSON DOM API
  5. 生成介于0.95-1的随机数MATLAB,matlab生成随机数函数
  6. 暴力推导 Beta 函数与 Gamma 函数关系式
  7. 2016年服装行业软件排名—许鹏
  8. 学会提问-批判性思维
  9. JavaScript系列之内置对象Object
  10. JavaScript专题(一)变量提升与预编译,一起去发现Js华丽的暗箱操作
  11. 计算机无法完成更新如何处理,Win10更新过程中碰到“无法完成更新”怎么办
  12. echarts不显示纵轴竖线?
  13. facebook头像和昵称_如何将昵称添加到您的Facebook个人资料
  14. ArcGIS 发布GP服务
  15. SpringBoot使用Netty实现远程调用
  16. 高中计算机一级基础知识,2017全国计算机一级MS Office考证常考知识点-高中课件精选.doc...
  17. 李开复自传披露离职原因
  18. Android Studio下Svn忽略文件配置的几种方法
  19. Eclipse IDE 4.5 64位下载
  20. ubuntu安装matlab,创建matlab快捷方式 ,解决快捷方式打不开

热门文章

  1. Win-MASM64汇编语言-visual studio下环境搭建
  2. Wireshark-003过滤器
  3. mysql openfire,windows下openfire的安装配置 | 学步园
  4. c# DESEncrypt 加密、解密算法
  5. Java 4.表达式
  6. 1806. [NOIP2014]无线网路发射器选址
  7. HDOJ 4734 数位DP
  8. initializer element is not a compile-time constant
  9. 使用C#读取XML节点,修改XML节点
  10. 腾讯实习生招聘笔试题目