<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                #(59):使用流處理 XML 本章開始我們將了解到如何使用 Qt 處理 XML 格式的文檔。 XML(eXtensible Markup Language)是一種通用的文本格式,被廣泛運用于數據交換和數據存儲(雖然近年來 JSON 盛行,大有取代 XML 的趨勢,但是對于一些已有系統和架構,比如 WebService,由于歷史原因,仍舊會繼續使用 XML)。XML 由 World Wide Web Consortium(W3C)發布,作為 SHML(Standard Generalized Markup Language)的一種輕量級方言。XML 語法類似于 HTML,與后者的主要區別在于 XML 的標簽不是固定的,而是可擴展的;其語法也比 HTML 更為嚴格。遵循 XML 規范的 HTML 則被稱為 XHTML(不過這一點有待商榷,感興趣的話可以詳見[這里](http://www.devbean.net/2011/04/dive_into_html5_2_3/))。 我們說過,XML 類似一種元語言,基于 XML 可以定義出很多新語言,比如 SVG(Scalable Vector Graphics)和 MathML(Mathematical Markup Language)。SVG 是一種用于矢量繪圖的描述性語言,Qt 專門提供了 QtSVG 對其進行解釋;MathML 則是用于描述數學公式的語言,Qt Solutions 里面有一個 QtMmlWidget 模塊專門對其進行解釋。 另外一面,針對 XML 的通用處理,Qt4 提供了 QtXml 模塊;針對 XML 文檔的 Schema 驗證以及 XPath、XQuery 和 XSLT,Qt4 和 Qt5 則提供了 QtXmlPatterns 模塊。Qt 提供了三種讀取 XML 文檔的方法: * `QXmlStreamReader`:一種快速的基于流的方式訪問良格式 XML 文檔,特別適合于實現一次解析器(所謂“一次解析器”,可以理解成我們只需讀取文檔一次,然后像一個遍歷器從頭到尾一次性處理 XML 文檔,期間不會有反復的情況,也就是不會讀完第一個標簽,然后讀第二個,讀完第二個又返回去讀第一個,這是不允許的); * DOM(Document Object Model):將整個 XML 文檔讀入內存,構建成一個樹結構,允許程序在樹結構上向前向后移動導航,這是與另外兩種方式最大的區別,也就是允許實現多次解析器(對應于前面所說的一次解析器)。DOM 方式帶來的問題是需要一次性將整個 XML 文檔讀入內存,因此會占用很大內存; * SAX(Simple API for XML):提供大量虛函數,以事件的形式處理 XML 文檔。這種解析辦法主要是由于歷史原因提出的,為了解決 DOM 的內存占用提出的(在現代計算機上,這個一般已經不是問題了)。 在 Qt4 中,這三種方式都位于 QtXml 模塊中。Qt5 則將`QXmlStreamReader`/`QXmlStreamWrite`r 移動到 QtCore 中,QtXml 則標記為“不再維護”,這已經充分表明了 Qt 的官方意向。 至于生成 XML 文檔,Qt 同樣提供了三種方式: * `QXmlStreamWriter`,與`QXmlStreamReader`相對應; * DOM 方式,首先在內存中生成 DOM 樹,然后將 DOM 樹寫入文件。不過,除非我們程序的數據結構中本來就維護著一個 DOM 樹,否則,臨時生成樹再寫入肯定比較麻煩; * 純手工生成 XML 文檔,顯然,這是最復雜的一種方式。 使用`QXmlStreamReader`是 Qt 中最快最方便的讀取 XML 的方法。因為`QXmlStreamReader`使用了遞增式的解析器,適合于在整個 XML 文檔中查找給定的標簽、讀入無法放入內存的大文件以及處理 XML 的自定義數據。 每次`QXmlStreamReader`的`readNext()`函數調用,解析器都會讀取下一個元素,按照下表中展示的類型進行處理。我們通過表中所列的有關函數即可獲得相應的數據值: | 類型 | 示例 | 有關函數 | | -- || -- || -- | | `StartDocument` | – | `documentVersion()`,`documentEncoding()`,`isStandaloneDocument()` | | `EndDocument` | – | | | `StartElement` | | `namespaceUri()`,`name()`,`attributes()`,`namespaceDeclarations()` | | `EndElement` | | `namespaceUri()`,`name()` | | `Characters` | AT&amp;T | `text()`,`isWhitespace()`,`isCDATA()` | | `Comment` | | `text()` | | `DTD` | | `text()`,`notationDeclarations()`,`entityDeclarations()`,`dtdName()`,`dtdPublicId()`,`dtdSystemId()` | | `EntityReference` | &trade; | `name()`,`text()` | | `ProcessingInstruction` | | `processingInstructionTarget()`,`processingInstructionData()` | | `Invalid` | >&<! | `error()`,?`errorString()` | 考慮如下 XML 片段: ~~~ <doc> <quote>Einmal ist keinmal</quote> </doc> ~~~ 一次解析過后,我們通過`readNext()`的遍歷可以獲得如下信息: ~~~ StartDocument StartElement (name() == "doc") StartElement (name() == "quote") Characters (text() == "Einmal ist keinmal") EndElement (name() == "quote") EndElement (name() == "doc") EndDocument ~~~ 通過`readNext()`函數的循環調用,我們可以使用`isStartElement()`、`isCharacters()`這樣的函數檢查當前讀取的類型,當然也可以直接使用`state()`函數。 下面我們看一個完整的例子。在這個例子中,我們讀取一個 XML 文檔,然后使用一個`QTreeWidget`顯示出來。我們的 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> ~~~ 首先來看頭文件: ~~~ 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`顯然就是我們的主窗口,其構造函數也沒有什么好說的: ~~~ 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() { } ~~~ 接下來看幾個處理 XML 文檔的函數,這正是我們關注的要點: ~~~ 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()`函數用于打開給定文件。我們使用`QFile`打開文件,將其設置為`QXmlStreamReader`的設備。也就是說,此時`QXmlStreamReader`就可以從這個設備(`QFile`)中讀取內容進行分析了。接下來便是一個 while 循環,只要沒讀到文件末尾,就要一直循環處理。首先判斷是不是`StartElement`,如果是的話,再去處理 bookindex 標簽。注意,因為我們的根標簽就是 bookindex,如果讀到的不是 bookindex,說明標簽不對,就要發起一個錯誤(`raiseError()`)。如果不是`StartElement`(第一次進入循環的時候,由于沒有事先調用`readNext()`,所以會進入這個分支),則調用`readNext()`。為什么這里要用 while 循環,XML 文檔不是只有一個根標簽嗎?直接調用一次`readNext()`函數不就好了?這是因為,XML 文檔在根標簽之前還有別的內容,比如聲明,比如 DTD,我們不能確定第一個`readNext()`之后就是根標簽。正如我們提供的這個 XML 文檔,首先是?聲明,其次才是根標簽。如果你說,第二個不就是根標簽嗎?但是 XML 文檔還允許嵌入 DTD,還可以寫注釋,這就不確定數目了,所以為了通用起見,我們必須用 while 循環判斷。處理完之后就可以關閉文件,如果有錯誤則顯示錯誤。 接下來看`readBookindexElement()`函數: ~~~ 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(); } } } ~~~ 注意第一行我們加了一個斷言。意思是,如果在進入函數的時候,reader 不是`StartElement`狀態,或者說標簽不是 bookindex,就認為出錯。然后繼續調用`readNext()`,獲取下面的數據。后面還是 while 循環。如果是`EndElement`,退出,如果又是`StartElement`,說明是 entry 標簽(注意我們的 XML 結構,bookindex 的子元素就是 entry),那么開始處理 entry,否則跳過。 那么下面來看`readEntryElement()`函數: ~~~ 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(); } } } ~~~ 這個函數接受一個`QTreeWidgetItem`指針,作為根節點。這個節點被當做這個 entry 標簽在`QTreeWidget`中的根節點。我們設置其名字是 entry 的 term 屬性的值。然后繼續讀取下一個數據。同樣使用 while 循環,如果是`EndElement`就繼續讀取;如果是`StartElement`,則按需調用`readEntryElement()`或者`readPageElement()`。由于 entry 標簽是可以嵌套的,所以這里有一個遞歸調用。如果既不是 entry 也不是 page,則跳過位置標簽。 然后是`readPageElement()`函數: ~~~ 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 是葉子節點,沒有子節點,所以不需要使用 while 循環讀取。我們只是遍歷了 entry 下所有的 page 標簽,將其拼接成合適的字符串。 最后`skipUnknownElement()`函數: ~~~ void MainWindow::skipUnknownElement() { reader.readNext(); while (!reader.atEnd()) { if (reader.isEndElement()) { reader.readNext(); break; } if (reader.isStartElement()) { skipUnknownElement(); } else { reader.readNext(); } } } ~~~ 我們沒辦法確定到底要跳過多少位置標簽,所以還是得用 while 循環讀取,注意位置標簽中所有子標簽都是未知的,因此只要是`StartElement`,都直接跳過。 好了,這是我們的全部程序。只要在`main()`函數中調用一下即可: ~~~ MainWindow w; w.readFile("books.xml"); w.show(); ~~~ 然后就能看到運行結果: [![](https://box.kancloud.cn/2015-12-29_5682326f5dd72.png)](http://files.devbean.net/images/2013/07/xml-reader-demo.png) 值得一提的是,雖然我們的代碼比較復雜,但是思路很清晰,一層一層地處理,這正是遞歸下降算法的有一個示例。我們曾在前面講解[布爾表達式的樹模型](http://www.devbean.net/2013/05/qt-study-road-2-bool-tree-model/)章節使用過這個思想。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看