前面兩章咱們介紹了使用流和 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
僅僅是衆多處理接口中的一個,咱們還有QXmlEntityResolver
,QXmlDTDHandler
,QXmlErrorHandler
,QXmlDeclHandler
以及QXmlLexicalHandler
等。這些接口都是純虛類,分別定義了不一樣類型的處理事件。對於大多數應用程序,QXmlContentHandler
和QXmlErrorHandler
是最經常使用的兩個。設計
爲簡化處理,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
,將ContentHandler
和ErrorHandler
設置爲自身。由於咱們僅重寫了ContentHandler
和ErrorHandler
的函數。若是咱們還須要另外的處理,還須要繼續設置其它的 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 中止處理。此時,咱們須要覆蓋QXmlDefaultHandler
的errorString()
函數來返回一個恰當的錯誤信息。
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 文檔修改成不合法的形式。
咱們程序的運行結果同前面仍是同樣的,這裏也再也不贅述了。