Qt4_使用QXmlStreamReader读取XML

使用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
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
QXmlStreamReader是Qt中的一个XML解析器,可以用来解析XML文件。它是基于事件驱动的解析器,逐行读取XML文件并发出相应的事件,应用程序可以根据这些事件来处理XML数据。在使用QXmlStreamReader时,我们需要先创建一个QXmlStreamReader对象,然后使用该对象的readNext()函数读取XML文件的下一行,再根据返回的事件类型来处理相应的数据。 例如,以下代码片段展示了如何使用QXmlStreamReader解析一个简单的XML文件: ```cpp QFile file("example.xml"); if (!file.open(QFile::ReadOnly | QFile::Text)) { qDebug() << "Failed to open file"; return; } QXmlStreamReader xml(&file); while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { qDebug() << "Element: " << xml.name(); } else if (xml.isEndElement()) { qDebug() << "End element: " << xml.name(); } else if (xml.isCharacters() && !xml.isWhitespace()) { qDebug() << "Text: " << xml.text(); } } if (xml.hasError()) { qDebug() << "XML error: " << xml.errorString(); } file.close(); ``` 在上面的代码中,我们首先打开一个XML文件,并创建一个QXmlStreamReader对象来读取文件。然后,在while循环中,我们使用readNext()函数逐行读取XML文件的下一行,并根据事件类型来处理数据。如果当前行是一个元素的开始标签,我们会输出它的名称;如果是一个元素的结束标签,我们也会输出它的名称;如果是元素的文本内容,我们会输出文本内容。最后,我们检查是否有任何XML错误,并关闭文件。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值