Qt之XML(SAX)

简介: 简述SAX(Simple API for XML)是用于 XML 解析器的基于事件的标准接口。XML 类的设计遵循 SAX2 Java interface,名称适合 Qt 的命名约定。对于任何使用 SAX2 的人来说,使用 Qt XML 类应该非常容易。SAX 不同于 DOM 解析,它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数

简述

SAX(Simple API for XML)是用于 XML 解析器的基于事件的标准接口。XML 类的设计遵循 SAX2 Java interface,名称适合 Qt 的命名约定。对于任何使用 SAX2 的人来说,使用 Qt XML 类应该非常容易。

SAX 不同于 DOM 解析,它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。

Qt XML 模块提供了一个抽象类 QXmlReader,它定义了潜在的 SAX2 读取器的接口,它有一个简单的 XML 读取器的实现 - QXmlSimpleReader(目前只有这一个,在将来的版本中,可能有更多的具有不同属性的读取器。例如:验证解析器),通过子类化,很容易适应。

QXmlSimpleReader

QXmlSimpleReader 类提供了一个简单的 XML 解析器的实现。

这种 XML reader(读取器)适用于广泛的应用,它能够解析格式良好的 XML,并可以将元素的命名空间报告给 ContentHandler。然而,它不解析任何外部实体。由于历史原因,不执行 XML 1.0 规范中描述的属性值规范化和结束处理。

此类的最简单的使用模式是创建一个读取器实例,定义一个输入源,指定读取器使用的 handler(处理程序),并解析数据。

例如,我们可以使用 QFile 来提供输入。在这里,创建一个读取器,并定义一个输入源供读取器使用:

QXmlSimpleReader xmlReader;
QXmlInputSource *source = new QXmlInputSource(file);

在读取器遇到某些类型的内容,或者输入发生错误时,handler 允许我们执行操作。必须告诉读取器哪种类型的事件使用哪个 handler 。对于许多常见的应用程序,可以通过对 QXmlDefaultHandler 进行子类化来创建自定义处理程序,并使用它来处理错误和内容事件:

Handler *handler = new Handler;
xmlReader.setContentHandler(handler);
xmlReader.setErrorHandler(handler);

如果既不设置 ContentHandler,又不设置 ErrorHandler,解析器将回退到其默认行为,并且什么也不做。

处理输入的最方便的方法是在单次传递中读取它,通过使用带有一个参数(指定输入源)的 parse() 函数:

bool ok = xmlReader.parse(source);

if (!ok)
    std::cout << "Parsing failed." << std::endl;

如果不能一次性解析整个输入(例如:XML 比较巨大,或正在通过网络连接传递),数据可以被分片发送至到解析器。这个通过告诉 parse() 逐步工作来实现,并对 parseContinue() 函数进行后续调用,直到所有数据都被处理完成。

执行增量解析的常见方法是连接网络的 readyRead() 信号至一个从哦啊函数,并在那里处理传入的数据,可以参考:QNetworkAccessManager。

可以使用 setFeature() 和 setProperty() 来调整解析行为方面。

xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);

QXmlSimpleReader 是不可重入的,如果要在线程代码中使用类,需要使用锁机制(例如:QMutex)来锁定使用 QXmlSimpleReader 的代码。

使用

为了便于演示,使用上节生成的格式化 XML(Blogs.xml):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--纯正开源之美,有趣、好玩、靠谱。。。-->
<?xml-stylesheet type="text/css" href="style.css"?>
<Blogs Version="1.0">
 <Blog>
  <作者>一去丶二三里</作者>
  <主页>http://blog.csdn.net/liang19890820</主页>
  <个人说明>青春不老,奋斗不止!</个人说明>
 </Blog>
 <Blog>
  <作者>奇趣科技</作者>
  <主页>https://www.qt.io</主页>
  <个人说明>Qt 是由奇趣科技开发的跨平台 C++ 图形用户界面应用程序开发框架!</个人说明>
 </Blog>
</Blogs>

详细说明见: Qt之生成XML(QXmlStreamWriter)

效果如下所示:

这里写图片描述

下面,一起来看下源码:

XbelHandler.h

#ifndef XBELHANDLER_H
#define XBELHANDLER_H

#include <QIcon>
#include <QXmlDefaultHandler>

QT_BEGIN_NAMESPACE
class QTreeWidget;
class QTreeWidgetItem;
QT_END_NAMESPACE

class XbelHandler : public QXmlDefaultHandler
{
public:
    XbelHandler(QTreeWidget *treeWidget);

    // 启动 XML 解析
    bool readFile(const QString &fileName);

    // 解析开始元素
    bool startElement(const QString &namespaceURI, const QString &localName,
                      const QString &qName, const QXmlAttributes &attributes) Q_DECL_OVERRIDE;
    // 解析结束元素
    bool endElement(const QString &namespaceURI, const QString &localName,
                    const QString &qName) Q_DECL_OVERRIDE;
    // 解析字符数据
    bool characters(const QString &str) Q_DECL_OVERRIDE;
    // 解析过程中的错误处理
    bool fatalError(const QXmlParseException &exception) Q_DECL_OVERRIDE;
    QString errorString() const Q_DECL_OVERRIDE;

private:
    // 创建树节点
    QTreeWidgetItem *createChildItem(const QString &tagName);

private:
    QTreeWidget *treeWidget;
    QTreeWidgetItem *item;
    QString currentText;
    QString errorStr;

    QIcon folderIcon;
    QIcon otherIcon;
};

#endif

XbelHandler.cpp

#include <QtWidgets>
#include "XbelHandler.h"

XbelHandler::XbelHandler(QTreeWidget *treeWidget)
    : QXmlDefaultHandler(),
      treeWidget(treeWidget)
{
    item = 0;

    QStyle *style = treeWidget->style();

    folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirClosedIcon),
                         QIcon::Normal, QIcon::Off);
    folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirOpenIcon),
                         QIcon::Normal, QIcon::On);
    otherIcon.addPixmap(style->standardPixmap(QStyle::SP_FileIcon));
}

构造函数主要用于初始化变量。

来看 readFile() 函数:

// 启动 XML 解析
bool XbelHandler::readFile(const QString &fileName)
{
    if (fileName.isEmpty())
        return false;

    treeWidget->clear();
    item = 0;

    QFile file(fileName);
    QXmlInputSource inputSource(&file);

    QXmlSimpleReader reader;
    reader.setContentHandler(this);
    reader.setErrorHandler(this);

    return reader.parse(inputSource);
}

这个函数中,首先将成员变量清空,然后读取 XML 文档,用于加载新数据。注意:我们使用了QXmlSimpleReader,将 ContentHandler 和 ErrorHandler 设置为自身。因为我们仅重写了 ContentHandler 和 ErrorHandler 的函数。如果还需要另外的处理,还需要继续设置其它的 handler。设置完成之后,调用 QXmlSimpleReader 提供的 parse() 是函数,开始进行 XML 的解析。

// 解析开始元素
bool XbelHandler::startElement(const QString & /* namespaceURI */,
                               const QString & /* localName */,
                               const QString &qName,
                               const QXmlAttributes &attributes)
{
    if (qName == "Blogs") {
        QString version = attributes.value("Version");
        if (!version.isEmpty() && version != "1.0") {
            errorStr = QObject::tr("The file is not an XBEL version 1.0 file.");
            return false;
        }
        item = createChildItem(qName);
        item->setFlags(item->flags() | Qt::ItemIsEditable);
        item->setIcon(0, folderIcon);
        item->setText(0, qName);
        treeWidget->setItemExpanded(item, true);
    } else if (qName == "Blog") {
        item = createChildItem(qName);
        item->setFlags(item->flags() | Qt::ItemIsEditable);
        item->setIcon(0, folderIcon);
        item->setText(0, qName);
        treeWidget->setItemExpanded(item, true);
    } else if (qName == QString::fromLocal8Bit("作者")
               || qName == QString::fromLocal8Bit("主页")
               || qName == QString::fromLocal8Bit("个人说明")) {
        item = createChildItem(qName);
        item->setFlags(item->flags() | Qt::ItemIsEditable);
        item->setIcon(0, otherIcon);
        item->setText(0, qName);
    }

    currentText.clear();
    return true;
}

在读取到一个新的开始标签时调用 startElement()。该函数有四个参数,这里主要关心第三和第四个参数:第三个参数是标签的名字(正式的名字是“限定名”qualified name,因此形参是 qName);第四个参数是属性列表。前两个参数主要用于带有命名空间的 XML 文档的处理,现在我们不关心命名空间。

函数开始,如果是 <Blogs> 标签,先判断标签中的属性值是否为“1.0”,如果不是,返回 false, 告诉 SAX 停止处理;否则创建一个新的 QTreeWidgetItem,设置图标、文本。然后将 currentText 清空,准备接下来的处理。最后,返回 true,告诉 SAX 继续处理文件。

// 解析字符数据
bool XbelHandler::characters(const QString &str)
{
    currentText += str;
    return true;
}

函数 characters() 将标签的内容保存至成员变量 currentText 中,保存到变量的 currentText 的值由 endElement() 设置为节点的文本。

**注意:**XML 文档中 characters() 在 <作者><主页><个人说明>标签中出现。

// 解析结束元素
bool XbelHandler::endElement(const QString & /* namespaceURI */,
                             const QString & /* localName */,
                             const QString &qName)
{
    if (qName == "Blog") {
        if (item) {
            item = item->parent();
        }
    } else if (qName == QString::fromLocal8Bit("作者")
            || qName == QString::fromLocal8Bit("主页")
            || qName == QString::fromLocal8Bit("个人说明")) {
        if (item) {
            item->setText(1, currentText);
            item = item->parent();
        }
    }
    return true;
}

在遇到结束标签时调用 endElement()。和 startElement() 类似,这个函数的第三个参数也是标签的名字。当检查如果是 </Blog>,则将 item 指向其父节点,这保证了 item 恢复到处理 <Blog> 标签之前所指向的节点。如果是 </作者></主页></个人说明>,需要把新读到的 currentText 追加到第二列,同时将 item 指向其父节点。

// 解析过程中的错误处理
bool XbelHandler::fatalError(const QXmlParseException &exception)
{
    QMessageBox::information(treeWidget->window(), QObject::tr("SAX Parser"),
                             QObject::tr("Parse error at line %1, column %2:\n"
                                         "%3")
                             .arg(exception.lineNumber())
                             .arg(exception.columnNumber())
                             .arg(exception.message()));
    return false;
}

当遇到处理失败的时候,SAX 会回调 fatalError() 函数。这里仅仅向用户显示出来哪里遇到了错误。如果想看这个函数的运行,可以将 XML 文档修改为不合法的形式。

// 返回错误字符串
QString XbelHandler::errorString() const
{
    return errorStr;
}

当 handler 的任何一个函数返回 false 时,errorString() 会得到一个错误的字符串。

// 创建树节点
QTreeWidgetItem *XbelHandler::createChildItem(const QString &tagName)
{
    QTreeWidgetItem *childItem;
    if (item) {
        childItem = new QTreeWidgetItem(item);
    } else {
        childItem = new QTreeWidgetItem(treeWidget);
    }
    childItem->setData(0, Qt::UserRole, tagName);
    return childItem;
}

在解析过程中,为了构建一颗树形控件,调用 createChildItem() 根据指定的标签名,来添加节点。

构建完 handler 后,就可以直接使用了。

// 加载 XML
void MainWindow::open()
{
    QString fileName =
            QFileDialog::getOpenFileName(this, tr("Open File"),
                                         QDir::currentPath(),
                                         tr("XBEL Files (*.xbel *.xml)"));
    if (fileName.isEmpty())
        return;

    XbelHandler handler(m_pTreeWidget);
    if (handler.readFile(fileName))
        qDebug() << "File loaded";
}

打开文件选择对话框,选择文件,解析!

更多参考

目录
相关文章
|
8月前
|
XML 数据格式
|
10月前
|
XML JavaScript API
Qt通过Doc模式读取XML并设计一个增删改查方便的操作类
Qt通过Doc模式读取XML并设计一个增删改查方便的操作类
110 0
|
11月前
|
XML JavaScript 数据格式
QT Dom方式操作XML
本文提供了读者已知的XML文件语法结构,对xml基础知识不作介绍,仅介绍了QT操作xml的方法之一。
223 0
|
XML 数据格式
QT5.2 QDomDocument操作xml
QT5.2 QDomDocument操作xml
156 0
QT5.2 QDomDocument操作xml
|
XML 数据格式
Qt 利用XML文档,写一个程序集合 四
接上一个 启动外部程序 https://blog.csdn.net/z609932088/article/details/80775086
70 0
|
XML 数据格式
Qt 利用XML文档,写一个程序集合 三
接上一篇 https://blog.csdn.net/z609932088/article/details/80774950
85 0
|
XML 数据格式
Qt 利用XML文档,写一个程序集合 二
接上一篇文章 https://blog.csdn.net/z609932088/article/details/80774663 XML文件的读写
176 0
Qt 利用XML文档,写一个程序集合 二
|
XML 数据格式
Qt 利用XML文档,写一个程序集合 一
接到领导需求安排,说公司未来的硬件设备会越来越多,与每个设备对应的设备检测和设置程序也会增多。导致软甲太多,不好掌控。所以希望做一个完整的软件,但是呢,每个子程序还得独立,应为每个用户购买的设备不是一样的。
97 0
Qt 利用XML文档,写一个程序集合 一
|
XML 编解码 JSON
Qt-QML-C++交互实现文件IO系统-后继-读取XML文件和创建XML文件
在前面两篇中,大致完成了一个文件IO的读和写操作。前面两篇文章链接
277 0
Qt-QML-C++交互实现文件IO系统-后继-读取XML文件和创建XML文件
|
XML 存储 JavaScript
QT开发(四十一)——XML文件解析基础
  一、XML文档简介   XML(Extensible Markup Language,可扩展标记语言),是一种通用的文本格式,被广泛运用于数据交换和数据存储,而不是显示数据。XML的标签没有被预定义,用户需要在使用时自行进行定义。XML是W3C(万维网联盟)的推荐标准。相对于数据库表格的二维表示,XML使用的树形结构更能表现出数据的包含关系,作为一种文本文件格式,XML简单明了的特性使得它在信息存储和描述领域非常流行。
489 0

相关课程

更多

推荐镜像

更多