Qt之XML(SAX)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 简述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";
}

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

更多参考

目录
相关文章
|
4月前
|
XML JavaScript Java
【JAVA XML 探秘】DOM、SAX、StAX:揭秘 Java 中 XML 解析技术的终极指南!
【8月更文挑战第25天】本文详细探讨了Java中三种主流的XML解析技术:DOM、SAX与StAX。DOM将XML文档转换为树状结构,便于全方位访问和修改;SAX采取事件驱动模式,适用于大型文件的顺序处理;StAX则兼具DOM和SAX的优点,支持流式处理和随机访问。文中提供了每种技术的示例代码,帮助读者理解如何在实际项目中应用这些解析方法。
218 1
|
7月前
|
XML JavaScript Java
Java一分钟之-XML解析:DOM, SAX, StAX
Java中的XML解析包括DOM、SAX和StAX三种方法。DOM将XML加载成内存中的树形结构,适合小文件和需要随意访问的情况,但消耗资源大。SAX是事件驱动的,逐行读取,内存效率高,适用于大型文件,但编程复杂。StAX同样是事件驱动,但允许程序员控制解析流程,低内存占用且更灵活。每种方法都有其特定的易错点和避免策略,选择哪种取决于实际需求。
149 0
|
XML 数据格式
|
7月前
|
XML JavaScript API
【python】SAX和DOM处理XML文件
【python】SAX和DOM处理XML文件
59 0
|
XML 存储 JavaScript
SAX解析XML
SAX解析XML
85 0
|
XML JavaScript API
Qt通过Doc模式读取XML并设计一个增删改查方便的操作类
Qt通过Doc模式读取XML并设计一个增删改查方便的操作类
196 0
|
XML JavaScript 数据格式
QT Dom方式操作XML
本文提供了读者已知的XML文件语法结构,对xml基础知识不作介绍,仅介绍了QT操作xml的方法之一。
289 0
|
XML 数据格式
QT5.2 QDomDocument操作xml
QT5.2 QDomDocument操作xml
203 0
QT5.2 QDomDocument操作xml
|
XML 数据格式 开发者
使用 Schema 的 sax 方式操作 xml(二)| 学习笔记
快速学习使用 Schema 的 sax 方式操作 xml。
使用 Schema 的 sax 方式操作 xml(二)| 学习笔记
|
XML JavaScript 安全
Java解析XML(DOM解析和SAX解析)
Java解析XML(DOM解析和SAX解析)
796 0
Java解析XML(DOM解析和SAX解析)