【Qt笔记】使用 SAX 处理 XML

前面两章咱们介绍了使用流和 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 提供的鼠标键盘事件,这仅是处理器在到达预约位置时发出的一种通知。例如,当处理器遇到一个标签的开始时,会发出“新开始一个标签”这个通知,也就是一个事件。咱们能够从下面的例子中来理解这一点:this

<doc>
    <quote>Gnothi seauton</quote>
</doc>

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

startDocument()
startElement("doc")
startElement("quote")
characters("Gnothi seauton")
endElement("quote")
endElement("doc")
endDocument()

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

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

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

下面咱们使用 SAX 的方式从新解析前面两章所出现的示例程序。继承

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的构造函数和析构函数同前面没有变化:接口

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() 函数:事件

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,将ContentHandlerErrorHandler设置为自身。由于咱们仅重写了ContentHandlerErrorHandler的函数。若是咱们还须要另外的处理,还须要继续设置其它的 handler。parse()函数是QXmlSimpleReader提供的函数,开始进行 XML 解析。

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 中止处理。此时,咱们须要覆盖QXmlDefaultHandlererrorString()函数来返回一个恰当的错误信息。

bool MainWindow::characters(const QString &str)
{
    currentText += str;
    return true;
}

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

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 追加到第二列。

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 文档修改成不合法的形式。

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

相关文章
相关标签/搜索