#(61):使用 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 提供的鼠標鍵盤事件,這僅是處理器在到達預定位置時發出的一種通知。例如,當處理器遇到一個標簽的開始時,會發出“新開始一個標簽”這個通知,也就是一個事件。我們可以從下面的例子中來理解這一點:
~~~
<doc>
<quote>Gnothi seauton</quote>
</doc>
~~~
當讀取這個 XML 文檔時,處理器會依次發出下面的事件:
~~~
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 的方式重新解析前面兩章所出現的示例程序。
~~~
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 文檔的處理,現在我們不關心命名空間。函數開始,如果是 標簽,我們創建一個新的`QTreeWidgetItem`。如果這個標簽是嵌套在另外的 標簽中的,currentItem 被定義為當前標簽的子標簽,否則則是根標簽。我們使用`setText()`函數設置第一列的值,同前面的章節類似。如果是 標簽,我們將 currentText 清空,準備接下來的處理。最后,我們返回 true,告訴 SAX 繼續處理文件。如果有任何錯誤,則可以返回 false 告訴 SAX 停止處理。此時,我們需要覆蓋`QXmlDefaultHandler`的`errorString()`函數來返回一個恰當的錯誤信息。
~~~
bool MainWindow::characters(const QString &str)
{
currentText += str;
return true;
}
~~~
注意下我們的 XML 文檔。`characters()`僅在 標簽中出現。因此我們在`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()`類似,這個函數的第三個參數也是標簽的名字。我們檢查如果是 ,則將 currentItem 指向其父節點。這保證了 currentItem 恢復到處理 標簽之前所指向的節點。如果是 ,我們需要把新讀到的 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 文檔修改為不合法的形式。
我們程序的運行結果同前面還是一樣的,這里也不再贅述了。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)