使用QXmlStreamReader和QXmlStreamWriter讀寫XMl文件

QXmlStreamReader類通過簡單的流式API爲我們提供了一種快速的讀取xml文件的方式。他比Qt自己使用的SAX解析方式還要快。

所謂的流式讀取即將一個xml文檔讀取成一系列標記的流,類似於SAX。而QXmlStreamReader類和SAX的主要區別就是解析這些標記的方式。使用SAX解析時,應用程序必須提供一些處理器(回調函數)來處理來自解析器的一系列的所謂的xml事件,不同的xml標記會觸發不同的事件,從而進行相應的處理。而使用QXmlStreamReader,應用程序自己可以驅動整個循環,從解析器中一個接一個的拉取出xml標記。這個動作可以通過readNext()來完成,該函數會讀取出下一個完整的標記,然後其tokenType()。然後,我們就可以使用一系列方便的函數,如isStartElement(),text()等來確定或得到具體所讀取的內容。這種拉模式(pull)的解析方法的好處就在於,我們可以將對一個xml文檔的解析分開到多個函數中來完成,對不同的標記使用一個單獨的函數來處理。

QXmlStreamReader類的典型使用方法如下:

    QXmlStreamReader xml;
    ...
    while (!xml.atEnd()) {
          xml.readNext();
          ... // do processing
    }
    if (xml.hasError()) {
          ... // do error handling
    }
如果在解析的過程中出現了錯誤,atEnd()和hasError()會返回true,error()會返回所出現的具體錯誤類型。errorString(),lineNumber(),columnNumber()和characterOffset()函數可以用來得到錯誤的具體信息,一般我們使用這幾個函數來構建一個錯誤字符串來提示用戶具體的錯誤信息。同時,爲了簡化應用程序代碼,QXmlStreamReader還提供了一個raiseError()的機制,可以讓我們在必要時觸發一個自定義的錯誤信息。

QXmlStreamReader是一個增量式的解析器。它可以處理文檔不能被一下處理完的情況,比如該xml文件來自於多個文件或來自於網絡。當QXmlStreamReader解析完所有的數據但該xml文檔是不完整的,這時它會返回一個PrematureEndOfDocumentError類型的錯誤。然後,當有更多的數據到來時,它會從這個錯誤中恢復,然後繼續調用readNext()來解析新的數據。

還有,QXmlStreamReader是不太消耗內存的,因爲它不會在內存中存儲整個xml文檔樹,僅僅存儲當前它所解析的標記。此外,QXmlStreamReader使用QStringRef來解析所有的字符串數據而不是真實的QString對象,這可以避免不必要的小字符串內存分配代價。QStringRef是對QString或其子串的一個簡單包裝,並提供了一些類似於QString類的API,但它不會進行內存的分配,並在底層使用了引用計數來共享數據。我們可以在需要時,調用QStringRef的toString()來得到一個真實的QString對象。

說完了QXmlStreamReader,我們再來看看QXmlStreamWriter類。該類是和QXmlStreamReader配合使用的一個類,以流的方式向設備上寫出xml文檔。它提供了一系列的方便的API供我們使用,如writeStartDocument(),writeEndDocument()用來開始和結束一個xml文檔;writeStartElement()和writeEndElement()用來開始和結束一個標記;writeAttribute()或writeAttributes()用來爲標記添加屬性;writeCharacters()用來添加標記間的具體內容。

另外,QXmlStreamWriter會自動地通過在元素間插入換行符和縮進空格來格式化xml數據,以使xml文檔更便於人類閱讀,也有助於某些源代碼管理工具的顯示。

默認情況下,QXmlStreamWriter是以utf-8來編碼xml文檔的,我們也可以使用setCodec()來設置不同的編碼。

類似於QXmlStreamReader,如果在寫入的過程中出現了錯誤,hasError()會返回真,並且後續的寫入會被忽略。

下面,我們通過一個實例,來演示一下這兩個的類的使用方法。

在這個例子中,我們使用QXmlStreamReader還讀取一個xml文件,再使用QXmlStreamWriter將它寫到控制檯上。

其中,xml文件內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<xbel version="1.0">
    <folder folded="yes">
        <title>Literate Programming</title>
        <bookmark href="http://www.vivtek.com/litprog.html">
            <title>Synopsis of Literate Programming</title>
        </bookmark>
    </folder>
</xbel>


先來看程序代碼:

#include <QCoreApplication>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QFile>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //設置輸入文件
    QFile inputfile("test.xml");
    if(!inputfile.open(QIODevice::ReadOnly))
    {
        qDebug() << "Open input file failed";
        return 0;
    }
    QXmlStreamReader reader(&inputfile);

    //設置輸出文件
    QFile outputfile;
    if(!outputfile.open(stdout, QIODevice::WriteOnly))
    {
        qDebug() << "Open output file failed";
        return 0;
    }
    QXmlStreamWriter writer(&outputfile);
    writer.setAutoFormatting(true);

    //開始解析
    while (!reader.atEnd())
    {
        QXmlStreamReader::TokenType token = reader.readNext();
        switch (token)
        {
        case QXmlStreamReader::StartDocument:
            writer.writeStartDocument();
            break;
        case QXmlStreamReader::EndDocument:
            writer.writeEndDocument();
            break;
        case QXmlStreamReader::StartElement:
            if(reader.name() == "xbel")
            {
                writer.writeStartElement(reader.name().toString());
                writer.writeAttribute("version", reader.attributes().value("version").toString());
            }
            else if(reader.name() == "folder")
            {
                writer.writeStartElement(reader.name().toString());
                writer.writeAttribute("folded", reader.attributes().value("folded").toString());
            }
            else if(reader.name() == "title")
            {
                writer.writeStartElement(reader.name().toString());
            }
            else if(reader.name() == "bookmark")
            {
                writer.writeStartElement(reader.name().toString());
                writer.writeAttribute("href", reader.attributes().value("href").toString());
            }
            break;
        case QXmlStreamReader::EndElement:
            writer.writeEndElement();
            break;
        case QXmlStreamReader::Characters:
            writer.writeCharacters(reader.text().toString());
            break;
        default:
            break;
        }
    }

    //是否是正常結束
    if (reader.error())
    {
        qDebug() << "Error: " << reader.errorString() << "in file test.xml at line " << reader.lineNumber() << ", column " << reader.columnNumber();
        return 0;
    }

    //關閉文件
    inputfile.close();
    outputfile.flush();
    outputfile.close();

    return a.exec();
}
程序邏輯也非常的簡單。我們先使用QFile打開要解析的xml文件,然後使用該文件初始化一個QXmlStreamReader;再打開我們的輸出文件,在這裏就是控制檯,即stdout,然後使用其來初始化一個QXmlStreamWriter對象,並設置該對象的自動格式化;最後,使用一個while循環,解析該xml文件直到結束或出錯。而具體的解析過程就是,先使用readNext()來讀取一個標記,然後判斷該標記的具體類型,根據不同的類型做不同的處理,此處即爲向控制檯寫出同樣的內容。

運行結果如下:

這兩個類的基本使用方法就是這樣。當然,至於xml文檔中,其他標記的處理,比如實體,實體引用,CDATA,命名空間,這兩個類中也都提供了相應的API,大家可以在需要時自行參考幫助文件即可。

當然,Qt中對xml的支持,處了這兩個類之外,還有其他的xml操作方式,我們會在以後再來學習其他的xml解析方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章