1. 程式人生 > >Qt淺談之二十八解析XML檔案

Qt淺談之二十八解析XML檔案

一、簡介

        QtXml模組提供了一個讀寫XML檔案的流,解析方法包含DOM和SAX。DOM(Document ObjectModel):將XML檔案表示成一棵樹,便於隨機訪問其中的節點,但消耗記憶體相對多一些。SAX(Simple APIfor XML):一種事件驅動的XML API,接近於底層,速度較快,但不便於隨機訪問任意節點。    
       使用XML模組,在.pro檔案中新增QT += xml,並加如相應的標頭檔案#include <QDomDocument>或者#include <QXmlStreamReader>。
       分析的xml檔案:

<?xml version="1.0" encoding="UTF-8"?>
<COMMAND>
    <OBJECT>USER</OBJECT>
    <ACTION>LOGIN</ACTION>
    <DATA>
        <USER NAME="root" PASSWORD="123456"/>
    </DATA>
</COMMAND>

二、詳解

1、QXmlStreamReader

(1)streamparsexml.h

#ifndef STREAMPARSEXML_H
#define STREAMPARSEXML_H
#include <QXmlStreamWriter>
#include <QXmlStreamReader>
#include <QFile>
#include <QMessageBox>

class StreamParseXml
{
public:
    StreamParseXml();
    ~StreamParseXml();
    int writeXml();
    int readXml();

private:
    void parseUserInformation();
    QString getValue(const QString &name);
    QString getAttribute(const QString &name);

private:
    QString fileName;
    QXmlStreamReader *reader;
};

#endif // STREAMPARSEXML_H
(2)streamparsexml.cpp
#include <QDebug>
#include "streamparsexml.h"

StreamParseXml::StreamParseXml()
{
    fileName = "streamparse.xml";
}

StreamParseXml::~StreamParseXml()
{

}

int StreamParseXml::writeXml()
{
    QFile file(fileName);
    if(file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QXmlStreamWriter writer(&file);
        writer.setAutoFormatting(true);
        writer.writeStartDocument();
        writer.writeStartElement("COMMAND");
        writer.writeTextElement("OBJECT", "USER");
        writer.writeTextElement("ACTION", "LOGIN");
        writer.writeStartElement("DATA");
        writer.writeStartElement("USER");
        writer.writeAttribute("NAME", "root");
        writer.writeAttribute("PASSWORD", "123456");
        writer.writeEndElement();
        writer.writeEndElement();
        writer.writeEndElement();
        file.close();
    }
    return 0;
}

int StreamParseXml::readXml()
{
    if(fileName.isEmpty()) return -2;
    QFile *file = new QFile(fileName);
    if(!file->open(QFile::ReadOnly | QFile::Text)) {
        QMessageBox::information(NULL, QString("title"), QString("open error!"));
        return -1;
    }
    reader = new QXmlStreamReader(file);
     while(!reader->atEnd() && !reader->hasError()) {
         QXmlStreamReader::TokenType token = reader->readNext();
         if(token == QXmlStreamReader::StartDocument) {
             continue;
         }
          if (reader->isStartElement() && reader->name() == "OBJECT") {
              QString elementText = reader->readElementText();
              if (elementText == "USER") {
                  parseUserInformation();
                  break;
              }
          }
     }
    if (reader->hasError()) {
        qDebug() << reader->errorString();
        //QMessageBox::information(NULL, QString("parseXML"), reader->errorString());
    }
    reader->clear();
    delete reader;
    reader = NULL;
    return 0;
}

void StreamParseXml::parseUserInformation()
{
    QString elementString = getValue("ACTION");
    if (elementString == "LOGIN") {
        while(!reader->atEnd()) {
            reader->readNext();
            if (reader->name() == "USER") {
                QXmlStreamAttributes attributes = reader->attributes();
                if(attributes.hasAttribute("NAME")) {
                    qDebug() << "USER=" << attributes.value("NAME").toString();
                }
                if(attributes.hasAttribute("PASSWORD")) {
                    qDebug() << "PASSWORD=" << attributes.value("PASSWORD").toString();
                }
            }
        }
    }
}
QString StreamParseXml::getValue(const QString &name)
{
    while(!reader->atEnd()) {
        reader->readNext();
        if (reader->isStartElement() && reader->name() == name) {
            return reader->readElementText();
        }
    }
    return "";
}
(3)執行

2、QDomDocument

        QDomDocument類代表整個的XML檔案。概念上講:它是文件樹的根節點,並提供了文件資料的基本訪問方法。由於元素、文字節點、註釋、指令執行等等不可能脫離一個文件的上下文,所以文件類也包含了需要用來建立這些物件的工廠方法。被建立的節點物件有一個ownerDocument()函式,它將物件與物件常見的文件上下文環境關聯起來。DOM類中最常使用的是QDomNode、QDomDocument、QDomElement和QDomText。         解析後的XML檔案在內部是通過一個物件樹來表示的,物件樹可以使用各種QDom類進行訪問。所有的QDom類只引用內部樹上的物件。一旦最後一個DOM樹的QDom物件和QDocument本身被刪除掉時,DOM樹上的所有內部物件會被刪除掉。元素、文字節點等的建立是通過使用類提供的各種工廠方法完成的。使用QDom類的預設建構函式只會生成空的物件,這些空的物件不能操作,也不能寫入到文件中。         QDomDocument::setContent()完成XML文件的設定,他從QFile物件中讀取XML資料並檢測XML文件的編碼。setContent()有幾種過載形式,可以分別從QByteArray、QString、QIODevice、QXmlInputSource中讀取XML資料。
(1)domdocument.h
#ifndef DOMDOCUMENT_H
#define DOMDOCUMENT_H
#include <QDomDocument>
#include <QFile>
#include <QTextStream>

class DomDocument
{
public:
    DomDocument();
    ~DomDocument();
    int writeXml();
    int readXml();
    int readXml2();

private:
    QString fileName;
};

#endif // DOMDOCUMENT_H
(2)domdocument.cpp
#include <QDebug>
#include "domdocument.h"

DomDocument::DomDocument()
{
    fileName = "domparse.xml";
}

DomDocument::~DomDocument()
{

}

int DomDocument::writeXml()
{
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
        return -2;
    QTextStream out(&file);
    QDomDocument doc;
    QDomText text;
    QDomElement element;
    QDomAttr attr;
    QDomProcessingInstruction instruction;
    instruction = doc.createProcessingInstruction( "xml", "version = \'1.0\' encoding=\'UTF-8\'" );
    doc.appendChild( instruction );

    QDomElement root = doc.createElement( "COMMAND" );
    doc.appendChild(root);
    element = doc.createElement( "OBJECT" );
    text = doc.createTextNode( "USER" );
    element.appendChild(text);
    root.appendChild(element);

    element = doc.createElement( "ACTION" );
    text = doc.createTextNode( "LOGIN" );
    element.appendChild(text);
    root.appendChild(element);

    element = doc.createElement( "DATA" );
    root.appendChild(element);

    QDomElement userElement = doc.createElement( "USERINFO" );
    attr = doc.createAttribute( "NAME" );
    attr.setValue("root");
    userElement.setAttributeNode(attr);
    attr = doc.createAttribute( "PASSWORD" );
    attr.setValue("123456");
    userElement.setAttributeNode(attr);
    element.appendChild(userElement);

    doc.save(out, 4);       //each line space of file is 4
    return 0;
}

int DomDocument::readXml()
{
    QDomDocument doc;
    QFile file(fileName);
    QString error = "";
    int row = 0, column = 0;
    if (!file.open(QIODevice::ReadOnly)) return -2;

    if(!doc.setContent(&file, false, &error, &row, &column)){
        qDebug() << "parse file failed:" << row << "---" << column <<":" <<error;
        file.close();
        return -1;
    }

    file.close();
    QDomElement root = doc.documentElement();
    QDomNode node = root.firstChild();
    while(!node.isNull()) {
       QDomElement element = node.toElement(); // try to convert the node to an element.
       if(!element.isNull()) {
          qDebug()<<element.tagName() << ":" << element.text();
          QDomNode nodeson = element.firstChild();
          while(!nodeson.isNull()) {
              QDomElement elementson = nodeson.toElement();
              if(!elementson.isNull()) {
                  qDebug()<< "---" <<elementson.tagName();
                  if (elementson.hasAttribute("NAME")) {
                      qDebug()<< "---" << "NAME=" << elementson.attributeNode("NAME").value();
                  }
                  if (elementson.hasAttribute("PASSWORD")) {
                      qDebug()<< "---" << "PASSWORD=" << elementson.attributeNode("PASSWORD").value();
                  }
              }
              nodeson = nodeson.nextSibling();
          }
       }
       node = node.nextSibling();
    }
    return 0;
}
int DomDocument::readXml2()
{
    QDomDocument doc;
    QFile file(fileName);
    QString error = "";
    int row = 0, column = 0;
    if (!file.open(QIODevice::ReadOnly)) return -2;

    if(!doc.setContent(&file, false, &error, &row, &column)){
        qDebug() << "parse file failed:" << row << "---" << column <<":" <<error;
        file.close();
        return -1;
    }

    file.close();
    QDomElement root = doc.documentElement();
    QDomNode node = root.firstChildElement();
    while(!node.isNull()) {
       QDomElement element = node.toElement(); // try to convert the node to an element.
       if(!element.isNull()) {
           if (element.tagName() == "DATA") {
               qDebug()<< "---" <<element.tagName();
                QDomNodeList list = element.childNodes();
                for(int index = 0; index < list.count(); index++) {
                    QDomNode list_node = list.item(index);
                    QDomElement list_element = list_node.toElement();
                    if (list_element.hasAttribute("NAME")) {
                        qDebug()<< "---" << "NAME =" << list_element.attributeNode("NAME").value();
                    }
                    if (list_element.hasAttribute("PASSWORD")) {
                        qDebug()<< "---" << "PASSWORD =" << list_element.attributeNode("PASSWORD").value();
                    }
                }
           }
           else {
               qDebug()<<element.tagName() << ":" << element.text();
           }
       }
       node = node.nextSibling();
    }
    return 0;
}
(3)main.cpp
#include <QCoreApplication>
#include <QDebug>
#include "streamparsexml.h"
#include "domdocument.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
//    StreamParseXml stream;
//    stream.writeXml();
//    stream.readXml();

    DomDocument dom;
    dom.writeXml();
    dom.readXml();
    qDebug() << "***********************";
    dom.readXml2();

    return a.exec();
}

(4)執行


3、其他XML解析庫

        常見C/C++ XML解析器有tinyxml、XERCES、squashxml、xmlite、pugxml、libxml等等,這些解析器有些是支援多語言的,有些只是單純C/C++的。
(1)Xerces XML解析器
       官方網址:http://xerces.apache.org/xerces-c/
       Xerces前身是IBM的XML4C,XML4C也是一種功能強大的XML解析器,之後交給Apache基金會管理,遂改名為Xerces,Xerces-C++讓你的程式提供讀寫XML資料更加容易,提供的共享庫通過DOM、SAX、SAX2 API等方式對XML文件進行解析、生成、操作和驗證。
       Xerces-C++忠實於XML 1.0建議和相關標準。
       Xerces-C++解析器高效能、模組化並且可擴充套件。相關開發資料也比較完善。
       除了C++版本,Xerces同時還提供Xerces Java,Xerces Perl等版本。
(2)TinyXML解析器
       官方網址:http://www.grinninglizard.com/tinyxml/
       TinyXML相比Xerces要功能簡單些,正如其名Tiny,使用方法也比較簡單,TinyXML也是一個開源的解析XML解析庫,用於C++,支援Windows和Linux。TinyXML通過DOM模型遍歷和分析XML。官方文件:
http://www.grinninglizard.com/tinyxmldocs/index.html
(3)squashXML解析器
        官方地址:http://ostatic.com/squashxml
        這個解析器在國內似乎少人使用,這個解析器也有些歷史了。squashXML基於DOM Level2,也是一個XML輕量級的解析器。天緣之所以把這個寫上是天緣比較看重這個解析器的目錄劃分及使用說明,易懂而且易上手。
(4)XMLBooster解析器
        官方網址:http://www.xmlbooster.com/
        XMLBooster開發關注點比較有特色,更加關注解析效能,聲稱:“Application integration of XML data cannot get any simpler or any faster: instead of dealing with sophisticated api (such as DOM or SAX), use a convenient data structure, generated to suit your specific purpose, in the language of your choice. ”。
        針對特殊需求使用更加方便的資料結構以提高效能。
(5)LibXML解析器
        官方地址:http://xmlsoft.org/
        LibXML本來是為Gnome專案開發(C開發),之後被廣泛使用,功能非常強大,幾乎適合於常見的所有作業系統下編譯和開發使用。libxml++(地址:http://libxmlplusplus.sourceforge.net/)是對libxml XML解析器的C++封裝版本。此外還有各種語言封裝包,參考官方連結。
(6)補充:
        除了上述XML解析庫外,還有一些XML解析器(參考:http://www.garshol.priv.no/xmltools/platform/cpp.html),比如Berkely DBXML(BDB)等,有興趣的讀者可自行Google搜尋。
儘管XML解析器有很多種,而且功能差異很大,甚至是支援跨平臺、多語言,但是對於你的應用而言,儘量選擇一種相對熟悉、功能夠用的即可,沒必要去追求龐雜的解析器,我們只需關注:功能夠用、相對穩定、適合擴充套件這三個功能即可。一旦有問題,修正和擴充套件都要更為容易。

四、總結

(1)注意:readElementText()函式在解析出開始標籤時,就可以解析元素的文字了,直到遇到結束標籤。readElementText()函式中好像有類似於readNext()這樣的函式讀取下一個元素並比較是否為endElement,所以當使用readElementText()讀取完元素文字時,該元素的閉合標籤已經被讀走了。
(2)QDomProcessingInstruction instruction;instruction = doc.createProcessingInstruction("xml","version=/"1.0/" encoding=/"UTF-8/"");用來寫入XML檔案的宣告,這對於一個XML檔案來說不可缺少。
(3)原始碼已經打包上傳到csdn上,可登入下載(http://download.csdn.net/detail/taiyang1987912/8857327)。
(4)若有建議,請留言,在此先感謝!