Qt之XML(SAX)

简述

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

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

更多参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值