Qt使用流處理XML文件的示例代碼
本章開(kāi)始我們將了解到如何使用 Qt 處理 XML 格式的文檔。
XML(eXtensible Markup Language)是一種通用的文本格式,被廣泛運(yùn)用于數(shù)據(jù)交換和數(shù)據(jù)存儲(chǔ)(雖然近年來(lái) JSON 盛行,大有取代 XML 的趨勢(shì),但是對(duì)于一些已有系統(tǒng)和架構(gòu),比如 WebService,由于歷史原因,仍舊會(huì)繼續(xù)使用 XML)。XML 由 World Wide Web Consortium(W3C)發(fā)布,作為 SHML(Standard Generalized Markup Language)的一種輕量級(jí)方言。XML 語(yǔ)法類似于 HTML,與后者的主要區(qū)別在于 XML 的標(biāo)簽不是固定的,而是可擴(kuò)展的;其語(yǔ)法也比 HTML 更為嚴(yán)格。遵循 XML 規(guī)范的 HTML 則被稱為 XHTML(不過(guò)這一點(diǎn)有待商榷,感興趣的話可以詳見(jiàn)這里)。
我們說(shuō)過(guò),XML 類似一種元語(yǔ)言,基于 XML 可以定義出很多新語(yǔ)言,比如 SVG(Scalable Vector Graphics)和 MathML(Mathematical Markup Language)。SVG 是一種用于矢量繪圖的描述性語(yǔ)言,Qt 專門提供了 QtSVG 對(duì)其進(jìn)行解釋;MathML 則是用于描述數(shù)學(xué)公式的語(yǔ)言,Qt Solutions 里面有一個(gè) QtMmlWidget 模塊專門對(duì)其進(jìn)行解釋。
另外一面,針對(duì) XML 的通用處理,Qt4 提供了 QtXml 模塊;針對(duì) XML 文檔的 Schema 驗(yàn)證以及 XPath、XQuery 和 XSLT,Qt4 和 Qt5 則提供了 QtXmlPatterns 模塊。Qt 提供了三種讀取 XML 文檔的方法:
- QXmlStreamReader:一種快速的基于流的方式訪問(wèn)良格式 XML 文檔,特別適合于實(shí)現(xiàn)一次解析器(所謂 “一次解析器”,可以理解成我們只需讀取文檔一次,然后像一個(gè)遍歷器從頭到尾一次性處理 XML 文檔,期間不會(huì)有反復(fù)的情況,也就是不會(huì)讀完第一個(gè)標(biāo)簽,然后讀第二個(gè),讀完第二個(gè)又返回去讀第一個(gè),這是不允許的);
- DOM(Document Object Model):將整個(gè) XML 文檔讀入內(nèi)存,構(gòu)建成一個(gè)樹(shù)結(jié)構(gòu),允許程序在樹(shù)結(jié)構(gòu)上向前向后移動(dòng)導(dǎo)航,這是與另外兩種方式最大的區(qū)別,也就是允許實(shí)現(xiàn)多次解析器(對(duì)應(yīng)于前面所說(shuō)的一次解析器)。DOM 方式帶來(lái)的問(wèn)題是需要一次性將整個(gè) XML 文檔讀入內(nèi)存,因此會(huì)占用很大內(nèi)存;
- SAX(Simple API for XML):提供大量虛函數(shù),以事件的形式處理 XML 文檔。這種解析辦法主要是由于歷史原因提出的,為了解決 DOM 的內(nèi)存占用提出的(在現(xiàn)代計(jì)算機(jī)上,這個(gè)一般已經(jīng)不是問(wèn)題了)。
在 Qt4 中,這三種方式都位于 QtXml 模塊中。Qt5 則將 QXmlStreamReader/QXmlStreamWriter 移動(dòng)到 QtCore 中,QtXml 則標(biāo)記為 “不再維護(hù)”,這已經(jīng)充分表明了 Qt 的官方意向。
至于生成 XML 文檔,Qt 同樣提供了三種方式:
- QXmlStreamWriter,與 QXmlStreamReader 相對(duì)應(yīng);
- DOM 方式,首先在內(nèi)存中生成 DOM 樹(shù),然后將 DOM 樹(shù)寫(xiě)入文件。不過(guò),除非我們程序的數(shù)據(jù)結(jié)構(gòu)中本來(lái)就維護(hù)著一個(gè) DOM 樹(shù),否則,臨時(shí)生成樹(shù)再寫(xiě)入肯定比較麻煩;
- 純手工生成 XML 文檔,顯然,這是最復(fù)雜的一種方式。
使用 QXmlStreamReader 是 Qt 中最快最方便的讀取 XML 的方法。因?yàn)?QXmlStreamReader 使用了遞增式的解析器,適合于在整個(gè) XML 文檔中查找給定的標(biāo)簽、讀入無(wú)法放入內(nèi)存的大文件以及處理 XML 的自定義數(shù)據(jù)。
每次 QXmlStreamReader 的 readNext() 函數(shù)調(diào)用,解析器都會(huì)讀取下一個(gè)元素,按照下表中展示的類型進(jìn)行處理。我們通過(guò)表中所列的有關(guān)函數(shù)即可獲得相應(yīng)的數(shù)據(jù)值:
| 類型 | 示例 | 有關(guān)函數(shù) |
| StartDocument | – | documentVersion(),documentEncoding(),isStandaloneDocument() |
| EndDocument | – | |
| StartElement | <item> | namespaceUri(),name(),attributes(),namespaceDeclarations() |
| EndElement | </item> | namespaceUri(),name() |
| Characters | AT&T | text(),isWhitespace(),isCDATA() |
| Comment | <!– fix –> | text() |
| DTD | <!DOCTYPE …> | text(),notationDeclarations(),entityDeclarations(),dtdName(),dtdPublicId(),dtdSystemId() |
| EntityReference | ™ | name(),text() |
| ProcessingInstruction | <?alert?> | processingInstructionTarget(),processingInstructionData() |
| Invalid | >&<! | error(), errorString() |
考慮如下 XML 片段:
<doc>
<quote>Einmal ist keinmal</quote>
</doc>
一次解析過(guò)后,我們通過(guò) readNext() 的遍歷可以獲得如下信息:
StartDocument StartElement (name() == "doc") StartElement (name() == "quote") Characters (text() == "Einmal ist keinmal") EndElement (name() == "quote") EndElement (name() == "doc") EndDocument
通過(guò) readNext() 函數(shù)的循環(huán)調(diào)用,我們可以使用 isStartElement()、isCharacters() 這樣的函數(shù)檢查當(dāng)前讀取的類型,當(dāng)然也可以直接使用 state() 函數(shù)。
下面我們看一個(gè)完整的例子。在這個(gè)例子中,我們讀取一個(gè) XML 文檔,然后使用一個(gè) QTreeWidget 顯示出來(lái)。我們的 XML 文檔如下:
<bookindex>
<entry term="sidebearings">
<page>10</page>
<page>34-35</page>
<page>307-308</page>
</entry>
<entry term="subtraction">
<entry term="of pictures">
<page>115</page>
<page>244</page>
</entry>
<entry term="of vectors">
<page>9</page>
</entry>
</entry>
</bookindex>首先來(lái)看頭文件:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
bool readFile(const QString &fileName);
private:
void readBookindexElement();
void readEntryElement(QTreeWidgetItem *parent);
void readPageElement(QTreeWidgetItem *parent);
void skipUnknownElement();
QTreeWidget *treeWidget;
QXmlStreamReader reader;
};MainWindow 顯然就是我們的主窗口,其構(gòu)造函數(shù)也沒(méi)有什么好說(shuō)的:
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()
{
}接下來(lái)看幾個(gè)處理 XML 文檔的函數(shù),這正是我們關(guān)注的要點(diǎn):
bool MainWindow::readFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::critical(this, tr("Error"),
tr("Cannot read file %1").arg(fileName));
return false;
}
reader.setDevice(&file);
while (!reader.atEnd()) {
if (reader.isStartElement()) {
if (reader.name() == "bookindex") {
readBookindexElement();
} else {
reader.raiseError(tr("Not a valid book file"));
}
} else {
reader.readNext();
}
}
file.close();
if (reader.hasError()) {
QMessageBox::critical(this, tr("Error"),
tr("Failed to parse file %1").arg(fileName));
return false;
} else if (file.error() != QFile::NoError) {
QMessageBox::critical(this, tr("Error"),
tr("Cannot read file %1").arg(fileName));
return false;
}
return true;
}readFile() 函數(shù)用于打開(kāi)給定文件。我們使用 QFile 打開(kāi)文件,將其設(shè)置為 QXmlStreamReader 的設(shè)備。也就是說(shuō),此時(shí) QXmlStreamReader 就可以從這個(gè)設(shè)備(QFile)中讀取內(nèi)容進(jìn)行分析了。接下來(lái)便是一個(gè) while 循環(huán),只要沒(méi)讀到文件末尾,就要一直循環(huán)處理。首先判斷是不是 StartElement,如果是的話,再去處理 bookindex 標(biāo)簽。注意,因?yàn)槲覀兊母鶚?biāo)簽就是 bookindex,如果讀到的不是 bookindex,說(shuō)明標(biāo)簽不對(duì),就要發(fā)起一個(gè)錯(cuò)誤(raiseError())。
如果不是 StartElement(第一次進(jìn)入循環(huán)的時(shí)候,由于沒(méi)有事先調(diào)用 readNext(),所以會(huì)進(jìn)入這個(gè)分支),則調(diào)用 readNext()。為什么這里要用 while 循環(huán),XML 文檔不是只有一個(gè)根標(biāo)簽嗎?直接調(diào)用一次 readNext() 函數(shù)不就好了?這是因?yàn)?,XML 文檔在根標(biāo)簽之前還有別的內(nèi)容,比如聲明,比如 DTD,我們不能確定第一個(gè) readNext() 之后就是根標(biāo)簽。正如我們提供的這個(gè) XML 文檔,首先是 聲明,其次才是根標(biāo)簽。如果你說(shuō),第二個(gè)不就是根標(biāo)簽嗎?但是 XML 文檔還允許嵌入 DTD,還可以寫(xiě)注釋,這就不確定數(shù)目了,所以為了通用起見(jiàn),我們必須用 while 循環(huán)判斷。處理完之后就可以關(guān)閉文件,如果有錯(cuò)誤則顯示錯(cuò)誤。
接下來(lái)看 readBookindexElement() 函數(shù):
void MainWindow::readBookindexElement()
{
Q_ASSERT(reader.isStartElement() && reader.name() == "bookindex");
reader.readNext();
while (!reader.atEnd()) {
if (reader.isEndElement()) {
reader.readNext();
break;
}
if (reader.isStartElement()) {
if (reader.name() == "entry") {
readEntryElement(treeWidget->invisibleRootItem());
} else {
skipUnknownElement();
}
} else {
reader.readNext();
}
}
}注意第一行我們加了一個(gè)斷言。意思是,如果在進(jìn)入函數(shù)的時(shí)候,reader 不是 StartElement 狀態(tài),或者說(shuō)標(biāo)簽不是 bookindex,就認(rèn)為出錯(cuò)。然后繼續(xù)調(diào)用 readNext(),獲取下面的數(shù)據(jù)。后面還是 while 循環(huán)。如果是 EndElement,退出,如果又是 StartElement,說(shuō)明是 entry 標(biāo)簽(注意我們的 XML 結(jié)構(gòu),bookindex 的子元素就是 entry),那么開(kāi)始處理 entry,否則跳過(guò)。
那么下面來(lái)看 readEntryElement() 函數(shù):
void MainWindow::readEntryElement(QTreeWidgetItem *parent)
{
QTreeWidgetItem *item = new QTreeWidgetItem(parent);
item->setText(0, reader.attributes().value("term").toString());
reader.readNext();
while (!reader.atEnd()) {
if (reader.isEndElement()) {
reader.readNext();
break;
}
if (reader.isStartElement()) {
if (reader.name() == "entry") {
readEntryElement(item);
} else if (reader.name() == "page") {
readPageElement(item);
} else {
skipUnknownElement();
}
} else {
reader.readNext();
}
}
}這個(gè)函數(shù)接受一個(gè) QTreeWidgetItem 指針,作為根節(jié)點(diǎn)。這個(gè)節(jié)點(diǎn)被當(dāng)做這個(gè) entry 標(biāo)簽在 QTreeWidget 中的根節(jié)點(diǎn)。我們?cè)O(shè)置其名字是 entry 的 term 屬性的值。然后繼續(xù)讀取下一個(gè)數(shù)據(jù)。同樣使用 while 循環(huán),如果是 EndElement 就繼續(xù)讀?。蝗绻?StartElement,則按需調(diào)用 readEntryElement() 或者 readPageElement()。由于 entry 標(biāo)簽是可以嵌套的,所以這里有一個(gè)遞歸調(diào)用。如果既不是 entry 也不是 page,則跳過(guò)位置標(biāo)簽。
然后是 readPageElement() 函數(shù):
void MainWindow::readPageElement(QTreeWidgetItem *parent)
{
QString page = reader.readElementText();
if (reader.isEndElement()) {
reader.readNext();
}
QString allPages = parent->text(1);
if (!allPages.isEmpty()) {
allPages += ", ";
}
allPages += page;
parent->setText(1, allPages);
}
由于 page 是葉子節(jié)點(diǎn),沒(méi)有子節(jié)點(diǎn),所以不需要使用 while 循環(huán)讀取。我們只是遍歷了 entry 下所有的 page 標(biāo)簽,將其拼接成合適的字符串。
最后 skipUnknownElement() 函數(shù):
void MainWindow::skipUnknownElement()
{
reader.readNext();
while (!reader.atEnd()) {
if (reader.isEndElement()) {
reader.readNext();
break;
}
if (reader.isStartElement()) {
skipUnknownElement();
} else {
reader.readNext();
}
}
}我們沒(méi)辦法確定到底要跳過(guò)多少位置標(biāo)簽,所以還是得用 while 循環(huán)讀取,注意位置標(biāo)簽中所有子標(biāo)簽都是未知的,因此只要是 StartElement,都直接跳過(guò)。
好了,這是我們的全部程序。只要在 main() 函數(shù)中調(diào)用一下即可:
MainWindow w;
w.readFile("books.xml");
w.show();
然后就能看到運(yùn)行結(jié)果:

以上就是Qt使用流處理XML文件的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Qt處理XML的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)實(shí)時(shí)鐘表
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)實(shí)時(shí)鐘表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
帶頭結(jié)點(diǎn)的鏈表的基本操作(超詳細(xì))
鏈表是一種動(dòng)態(tài)分配空間的存儲(chǔ)結(jié)構(gòu),能更有效地利用存儲(chǔ)空間,通過(guò)對(duì)單鏈表基本操作的代碼實(shí)現(xiàn),我深刻領(lǐng)悟到以“指針”指示元素的后繼,在插入或刪除元素時(shí)不需要移動(dòng)元素2023-07-07
DEV C++自動(dòng)補(bǔ)全文件頭的設(shè)置操作教程
Dev-C++ 是一款輕量級(jí)的集成開(kāi)發(fā)環(huán)境 (IDE),主要用于 C 和 C++ 的程序編寫(xiě),它提供了基本的功能來(lái)幫助開(kāi)發(fā)者更高效地工作,其中包括文件頭的自動(dòng)補(bǔ)全功能,本文就給大家介紹了DEV C++自動(dòng)補(bǔ)全文件頭的設(shè)置操作教程,需要的朋友可以參考下2025-04-04
C++ 數(shù)據(jù)結(jié)構(gòu)完全二叉樹(shù)的判斷
這篇文章主要介紹了C++ 數(shù)據(jù)結(jié)構(gòu)完全二叉樹(shù)的判斷的相關(guān)資料,需要的朋友可以參考下2017-06-06
C語(yǔ)言中編寫(xiě)可變參數(shù)函數(shù)
這篇文章主要介紹了C語(yǔ)言中編寫(xiě)可變參數(shù)函數(shù)的相關(guān)資料,需要的朋友可以參考下2017-07-07

