Qt 学习之路 2(61):使用 SAX 处理 XML

 豆子  2013年8月13日  Qt 学习之路 2  没有评论

前面两章我们介绍了使用流和 DOM 的方式处理 XML 的相关内容,本章将介绍处理 XML 的最后一种方式:SAX。SAX 是一种读取 XML 文档的标准 API,同 DOM 类似,并不以语言为区别。Qt 的 SAX 类基于 SAX2 的 Java 实现,不过具有一些必要的名称上的转换。相比 DOM,SAX 的实现更底层因而处理起来通常更快。但是,我们前面介绍的QXmlStreamReader类更偏向 Qt 风格的 API,并且比 SAX 处理器更快,所以,现在我们之所以使用 SAX API,更主要的是为了把 SAX API 引入 Qt。在我们通常的项目中,并不需要真的使用 SAX。

Qt 提供了QXmlSimpleReader类,提供基于 SAX 的 XML 处理。同前面所说的 DOM 方式类似,这个类也不会对 XML 文档进行有效性验证。QXmlSimpleReader可以识别良格式的 XML 文档,支持 XML 命名空间。当这个处理器读取 XML 文档时,每当到达一个特定位置,都会调用一个用于处理解析事件的处理类。注意,这里所说的“事件”,不同于 Qt 提供的鼠标键盘事件,这仅是处理器在到达预定位置时发出的一种通知。例如,当处理器遇到一个标签的开始时,会发出“新开始一个标签”这个通知,也就是一个事件。我们可以从下面的例子中来理解这一点:

 
 
 
 
 

XHTML

 
1
2
3
<doc>
    <quote>Gnothi seauton</quote>
</doc>

当读取这个 XML 文档时,处理器会依次发出下面的事件:

 
 
1
2
3
4
5
6
7
startDocument()
startElement("doc")
startElement("quote")
characters("Gnothi seauton")
endElement("quote")
endElement("doc")
endDocument()

每出现一个事件,都会有一个回调,这个回调函数就是在称为 Handler 的处理类中定义的。上面给出的事件都是在QXmlContentHandler接口中定义的。为简单起见,我们省略了一些函数。QXmlContentHandler仅仅是众多处理接口中的一个,我们还有QXmlEntityResolver,QXmlDTDHandler,QXmlErrorHandler,QXmlDeclHandler以及QXmlLexicalHandler等。这些接口都是纯虚类,分别定义了不同类型的处理事件。对于大多数应用程序,QXmlContentHandler和QXmlErrorHandler是最常用的两个。

为简化处理,Qt 提供了一个QXmlDefaultHandler。这个类实现了以上所有的接口,每个函数都提供了一个空白实现。也就是说,当我们需要实现一个处理器时,只需要继承这个类,覆盖我们所关心的几个函数即可,无需将所有接口定义的函数都实现一遍。这种设计在 Qt 中并不常见,但是如果你熟悉 Java,就会感觉非常亲切。Java 中很多接口都是如此设计的。

使用 SAX API 与QXmlStreamReader或者 DOM API 之间最大的区别是,使用 SAX API 要求我们必须自己记录当前解析的状态。在另外两种实现中,这并不是必须的,我们可以使用递归轻松地处理,但是 SAX API 则不允许(回忆下,SAX 仅允许一遍读取文档,递归意味着你可以先深入到底部再回来)。

下面我们使用 SAX 的方式重新解析前面两章所出现的示例程序。

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MainWindow : public QMainWindow, public QXmlDefaultHandler
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();
 
    bool readFile(const QString &fileName);
 
protected:
    bool startElement(const QString &namespaceURI,
                      const QString &localName,
                      const QString &qName,
                      const QXmlAttributes &attributes);
    bool endElement(const QString &namespaceURI,
                    const QString &localName,
                    const QString &qName);
    bool characters(const QString &str);
    bool fatalError(const QXmlParseException &exception);
private:
    QTreeWidget *treeWidget;
    QTreeWidgetItem *currentItem;
    QString currentText;
};

注意,我们的MainWindow不仅继承了QMainWindow,还继承了QXmlDefaultHandler。也就是说,主窗口自己就是 XML 的解析器。我们重写了startElement(),endElement(),characters(),fatalError()几个函数,其余函数不关心,所以使用了父类的默认实现。成员变量相比前面的例子也多出两个,为了记录当前解析的状态。

MainWindow的构造函数和析构函数同前面没有变化:

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setWindowTitle(tr("XML Reader"));
 
    treeWidget = new QTreeWidget(this);
    QStringList headers;
    headers << "Items" << "Pages";
    treeWidget->setHeaderLabels(headers);
    setCentralWidget(treeWidget);
}
 
MainWindow::~MainWindow()
{
}

下面来看 readFile() 函数:

 
 
1
2
3
4
5
6
7
8
9
10
11
bool MainWindow::readFile(const QString &fileName)
{
    currentItem = 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。parse()函数是QXmlSimpleReader提供的函数,开始进行 XML 解析。

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool MainWindow::startElement(const QString & /*namespaceURI*/,
                              const QString & /*localName*/,
                              const QString &qName,
                              const QXmlAttributes &attributes)
{
    if (qName == "entry") {
        currentItem = new QTreeWidgetItem(currentItem ?
                currentItem : treeWidget->invisibleRootItem());
        currentItem->setText(0, attributes.value("term"));
    } else if (qName == "page") {
        currentText.clear();
    }
    return true;
}

startElement()在读取到一个新的开始标签时被调用。这个函数有四个参数,我们这里主要关心第三和第四个参数:第三个参数是标签的名字(正式的名字是“限定名”,qualified name,因此形参是 qName);第四个参数是属性列表。前两个参数主要用于带有命名空间的 XML 文档的处理,现在我们不关心命名空间。函数开始,如果是 <entry> 标签,我们创建一个新的QTreeWidgetItem。如果这个标签是嵌套在另外的 <entry> 标签中的,currentItem 被定义为当前标签的子标签,否则则是根标签。我们使用setText()函数设置第一列的值,同前面的章节类似。如果是 <page> 标签,我们将 currentText 清空,准备接下来的处理。最后,我们返回 true,告诉 SAX 继续处理文件。如果有任何错误,则可以返回 false 告诉 SAX 停止处理。此时,我们需要覆盖QXmlDefaultHandler的errorString()函数来返回一个恰当的错误信息。

 
 
1
2
3
4
5
bool MainWindow::characters(const QString &str)
{
    currentText += str;
    return true;
}

注意下我们的 XML 文档。characters()仅在 <page> 标签中出现。因此我们在characters()中直接追加 currentText。

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool MainWindow::endElement(const QString & /*namespaceURI*/,
                            const QString & /*localName*/,
                            const QString &qName)
{
    if (qName == "entry") {
        currentItem = currentItem->parent();
    } else if (qName == "page") {
        if (currentItem) {
            QString allPages = currentItem->text(1);
            if (!allPages.isEmpty())
                allPages += ", ";
            allPages += currentText;
            currentItem->setText(1, allPages);
        }
    }
    return true;
}

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

 
 
1
2
3
4
5
6
7
8
9
10
bool MainWindow::fatalError(const QXmlParseException &exception)
{
    QMessageBox::critical(this,
                          tr("SAX Error"),
                          tr("Parse error at line %1, column %2:\n %3")
                          .arg(exception.lineNumber())
                          .arg(exception.columnNumber())
                          .arg(exception.message()));
    return false;
}

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

我们程序的运行结果同前面还是一样的,这里也不再赘述了。

Qt 学习之路 2(61):使用 SAX 处理 XML的更多相关文章

  1. Qt 学习之路 2(66):访问网络(2)

    Home / Qt 学习之路 2 / Qt 学习之路 2(66):访问网络(2) Qt 学习之路 2(66):访问网络(2)  豆子  2013年10月31日  Qt 学习之路 2  27条评论 上一 ...

  2. Qt 学习之路 2(59):使用流处理 XML

    Qt 学习之路 2(59):使用流处理 XML 豆子 2013年7月25日 Qt 学习之路 2 18条评论 本章开始我们将了解到如何使用 Qt 处理 XML 格式的文档. XML(eXtensible ...

  3. Qt 学习之路 2(53):自定义拖放数据

    Qt 学习之路 2(53):自定义拖放数据 豆子  2013年5月26日  Qt 学习之路 2  13条评论上一章中,我们的例子使用系统提供的拖放对象QMimeData进行拖放数据的存储.比如使用QM ...

  4. Qt 学习之路 2(51):布尔表达式树模型

    Qt 学习之路 2(51):布尔表达式树模型 豆子 2013年5月15日 Qt 学习之路 2 17条评论 本章将会是自定义模型的最后一部分.原本打算结束这部分内容,不过实在不忍心放弃这个示例.来自于 ...

  5. Qt 学习之路 2(22):事件总结

    Qt 学习之路 2(22):事件总结 豆子 2012年10月16日 Qt 学习之路 2 47条评论 Qt 的事件是整个 Qt 框架的核心机制之一,也比较复杂.说它复杂,更多是因为它涉及到的函数众多,而 ...

  6. Qt 学习之路 2(19):事件的接受与忽略

    Home / Qt 学习之路 2 / Qt 学习之路 2(19):事件的接受与忽略 Qt 学习之路 2(19):事件的接受与忽略  豆子  2012年9月29日  Qt 学习之路 2  140条评论 ...

  7. Qt 学习之路 2(6):Qt 模块简介

    Home / Qt 学习之路 2 / Qt 学习之路 2(6):Qt 模块简介  豆子  2012年8月26日  Qt 学习之路 2  20条评论 Qt 5 与 Qt 4 最大的一个区别之一是底层架构 ...

  8. 《Qt 学习之路 2》目录

    <Qt 学习之路 2>目录 <Qt 学习之路 2>目录  豆子  2012年8月23日  Qt 学习之路 2  177条评论 <Qt 学习之路 2>目录 序 Qt ...

  9. QT学习之路--创建一个对话框

    Q_OBJECT:这是一个宏,凡是定义信号槽的类都必须声明这个宏. 函数tr()全名是QObject::tr(),被他处理过的字符串可以使用工具提取出来翻译成其他语言,也就是做国际化使用. 对于QT学 ...

随机推荐

  1. Laravel 5.3 auth中间件底层实现详解(转)

    1. 注册认证中间件, 在文件 app/Http/Kernel.php 内完成: protected $routeMiddleware = [ 'auth' => \Illuminate\Aut ...

  2. oracle数据库导入导出数据

    导出命令 exp username/password@192.168.x.xx/orcl file='D:\20170126.dmp' log='D:\20170126.log' 导入命令 imp u ...

  3. js点击按钮获取验证码倒计时

    //发送验证码倒计时 var clock = ''; var nums = 60; var btn; $("#btnGetVerCode").click(function () { ...

  4. omitTermFreqAndPositions设置,词频FQ在打分中默认为1

    compressed=true|false,是否使用gzip压缩(只有TextField和StrField可以压缩) compressThreshold=压缩阀值 multiValued=true|f ...

  5. 面试题:J2EE中web.xml配置文件详解 背1

    一.web.xml是什么 web.xml学名叫部署描述符文件,是在Servlet规范中定义的,是Web应用的配置文件,是Web应用的基础. 二.web.xml加载流程 总的来说:ServletCont ...

  6. vmware 安装不成功导致的问题解决以及右键菜单添加打开终端命令

    转自http://blog.csdn.net/puweilan/article/details/8609952 在VMware安装Ubuntu完成后,一直停留在VMware Easy Install, ...

  7. Luogu 4198 楼房重建

    BZOJ 2957 挺妙的题. 先把题目中的要求转化为斜率,一个点$(x, y)$可以看成$\frac{y}{x}$,这样子我们要求的就变成了一个区间内一定包含第一个值的最长上升序列. 然后把这个序列 ...

  8. 函数有一个特殊的属性 prototype!

    JavaScript 中只有对象,没有类. 实际上,JavaScript 才是真正应该被称为「面向对象」的语言,因为它是少有的可以不通过类,直接创建对象的语言. 函数的 prototype 属性 在 ...

  9. Session.Abandon-Session.Clear-Session.RemoveAll

    System.Web.UI.Page.Session属性和System.Web.HttpContext.Session属性 都是System.Web.SessionState.HttpSessionS ...

  10. PAT 1017 Queueing at Bank (25) (坑题)

    Suppose a bank has K windows open for service. There is a yellow line in front of the windows which ...