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

咱們程序的運行結果同前面仍是同樣的,這裏也再也不贅述了。

相關文章
相關標籤/搜索