Chinaunix首页 | 论坛 | 博客
  • 博客访问: 299988
  • 博文数量: 101
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 150
  • 用 户 组: 普通用户
  • 注册时间: 2013-12-07 07:42
文章分类

全部博文(101)

文章存档

2016年(12)

2015年(48)

2014年(41)

我的朋友

分类: 嵌入式

2015-06-13 10:43:05

原文地址:Qt使用流处理 XML 作者:luozhiyong131

XMLeXtensible Markup Language)是一种通用的文本格式,被广泛运用于数据交换和数据存储(虽然近年来 JSON 盛行,大有取代 XML 的趋势,但是对于一些已有系统和架构,比如 WebService,由于历史原因,仍旧会继续使用 XML)。

Qt 提供了三种读取 XML 文档的方法:

QXmlStreamReader:一种快速的基于流的方式访问良格式 XML 文档,特别适合于实现一次解析器(所谓“一次解析器”,可以理解成我们只需读取文档一次,然后像一个遍历器从头到尾一次性处理 XML 文档,期间不会有反复的情况,也就是不会读完第一个标签,然后读第二个,读完第二个又返回去读第一个,这是不允许的);

DOMDocument Object Model):将整个 XML 文档读入内存,构建成一个树结构,允许程序在树结构上向前向后移动导航,这是与另外两种方式最大的区别,也就是允许实现多次解析器(对应于前面所说的一次解析器)。DOM 方式带来的问题是需要一次性将整个 XML 文档读入内存,因此会占用很大内存;

SAXSimple API for XML):提供大量虚函数,以事件的形式处理 XML 文档。这种解析办法主要是由于历史原因提出的,为了解决 DOM 的内存占用提出的在现代计算机上,这个一般已经不是问题了)。

生成 XML 文档,Qt 同样提供了三种方式:

QXmlStreamWriter,与QXmlStreamReader相对应;

DOM 方式,首先在内存中生成 DOM 树,然后将 DOM 树写入文件。不过,除非我们程序的数据结构中本来就维护着一个 DOM 树,否则,临时生成树再写入肯定比较麻烦

纯手工生成 XML 文档,显然,这是最复杂的一种方式。

使用QXmlStreamReader是 Qt 中最快最方便的读取 XML 的方法。

每次QXmlStreamReaderreadNext()函数调用,解析器都会读取下一个元素,按照下表中展示的类型进行处理。我们通过表中所列的有关函数即可获得相应的数据值:

类型

示例

有关函数

StartDocument

-

documentVersion(),documentEncoding(),isStandaloneDocument()

EndDocument

-

StartElement

namespaceUri(),name(),attributes(),namespaceDeclarations()

EndElement

namespaceUri(),name()

Characters

AT&T

text(),isWhitespace(),isCDATA()

Comment

text()

DTD

text(),notationDeclarations(),entityDeclarations(),dtdName(),dtdPublicId(),dtdSystemId()

EntityReference

?

name(),text()

ProcessingInstruction

processingInstructionTarget(),processingInstructionData()

Invalid

>&

error(), errorString()

考虑如下 XML 片段:

点击(此处)折叠或打开

  1. <doc>
  2.     <quote>Einmal ist keinmal</quote>
  3. </doc>

一次解析过后,我们通过readNext()的遍历可以获得如下信息:

点击(此处)折叠或打开

  1. StartDocument
  2. StartElement (name() == "doc")
  3. StartElement (name() == "quote")
  4. Characters (text() == "Einmal ist keinmal")
  5. EndElement (name() == "quote")
  6. EndElement (name() == "doc")
  7. EndDocument

通过readNext()函数的循环调用,我们可以使用isStartElement()isCharacters()这样的函数检查当前读取的类型,


下面我们看一个完整的例子。


我们的 XML 文档如下:

点击(此处)折叠或打开

  1. <bookindex>
  2.     <entry term="sidebearings">
  3.         <page>10</page>
  4.         <page>34-35</page>
  5.         <page>307-308</page>
  6.     </entry>
  7.     <entry term="subtraction">
  8.         <entry term="of pictures">
  9.             <page>115</page>
  10.             <page>244</page>
  11.         </entry>
  12.         <entry term="of vectors">
  13.             <page>9</page>
  14.         </entry>
  15.     </entry>
  16. </bookindex>
首先来看头文件:

点击(此处)折叠或打开

  1. class MainWindow : public QMainWindow
  2. {
  3.     Q_OBJECT
  4. public:
  5.     explicit MainWindow(QWidget *parent = 0);
  6.     ~MainWindow();
  7.  
  8.     bool readFile(const QString &fileName);
  9. private:
  10.     void readBookindexElement();
  11.     void readEntryElement(QTreeWidgetItem *parent);
  12.     void readPageElement(QTreeWidgetItem *parent);
  13.     void skipUnknownElement();
  14.  
  15.   QTreeWidget *treeWidget;
  16.     QXmlStreamReader reader;
  17. };

MainWindow显然就是我们的主窗口,其构造函数也没有什么好说的:

点击(此处)折叠或打开

  1. MainWindow::MainWindow(QWidget *parent) :
  2.     QMainWindow(parent)
  3. {
  4.     setWindowTitle(tr("XML Reader"));

  5.     treeWidget = new QTreeWidget(this);
  6.     QStringList headers;
  7.     headers << "Items" << "Pages";
  8.     treeWidget->setHeaderLabels(headers);
  9.     setCentralWidget(treeWidget);
  10. }

  11. MainWindow::~MainWindow()
  12. {
  13. }

接下来看几个处理 XML 文档的函数,这正是我们关注的要点:

点击(此处)折叠或打开

  1. bool MainWindow::readFile(const QString &fileName)
  2. {
  3.    QFile file(fileName);
  4.    if (!file.open(QFile::ReadOnly | QFile::Text)) {
  5.        QMessageBox::critical(this, tr("Error"),
  6.                              tr("Cannot read file %1").arg(fileName));
  7.        return false;
  8.    }
  9.    reader.setDevice(&file); //将其设置为QXmlStreamReader的设备

  10.    /**
  11.     * 这里要用 while 循环
  12.     * XML 文档在根标签之前还有别的内容,比如声明,比如 DTD
  13.     * 所以为了通用起见,我们必须用 while 循环判断。
  14.     */
  15.    while (!reader.atEnd()) {
  16.        if (reader.isStartElement()) { //首先判断是不是StartElement
  17.            if (reader.name() == "bookindex") { //根标签名 bookindex
  18.                readBookindexElement();
  19.            } else {
  20.                reader.raiseError(tr("Not a valid book file")); //发起一个错误
  21.            }
  22.        } else {
  23.            reader.readNext();
  24.        }
  25.    }
  26.    file.close(); /* 关闭文件 */

  27.    /* 如果有错误则显示错误*/
  28.    if (reader.hasError()) {
  29.        QMessageBox::critical(this, tr("Error"),
  30.                              tr("Failed to parse file %1").arg(fileName));
  31.        return false;
  32.    } else if (file.error() != QFile::NoError) {
  33.        QMessageBox::critical(this, tr("Error"),
  34.                              tr("Cannot read file %1").arg(fileName));
  35.        return false;
  36.    }
  37.    return true;
  38. }

接下来看readBookindexElement()函数:

点击(此处)折叠或打开

  1. void MainWindow::readBookindexElement()
  2. {
  3.    /*如果在进入函数的时候,reader 不是StartElement状态,或者说标签不是 bookindex,就认为出错。*/
  4.    Q_ASSERT(reader.isStartElement() && reader.name() == "bookindex");
  5.    reader.readNext();
  6.    while (!reader.atEnd()) {
  7.        if (reader.isEndElement()) {
  8.            reader.readNext();
  9.            break;
  10.        }

  11.        if (reader.isStartElement()) {
  12.            if (reader.name() == "entry") { //子元素
  13.                readEntryElement(treeWidget->invisibleRootItem());
  14.            } else {
  15.                skipUnknownElement();
  16.            }
  17.        } else {
  18.            reader.readNext();
  19.        }
  20.    }
  21. }

那么下面来看readEntryElement()函数:

点击(此处)折叠或打开

  1. void MainWindow::readEntryElement(QTreeWidgetItem *parent)
  2. {
  3.     QTreeWidgetItem *item = new QTreeWidgetItem(parent);
  4.     item->setText(0, reader.attributes().value("term").toString());

  5.     reader.readNext();
  6.     while (!reader.atEnd()) {
  7.         if (reader.isEndElement()) {
  8.             reader.readNext();
  9.             break;
  10.         }

  11.         if (reader.isStartElement()) {
  12.             if (reader.name() == "entry") {
  13.                 readEntryElement(item);
  14.             } else if (reader.name() == "page") {
  15.                 readPageElement(item);
  16.             } else {
  17.                 skipUnknownElement();
  18.             }
  19.         } else {
  20.             reader.readNext();
  21.         }
  22.     }
  23. }

然后是readPageElement()函数:

点击(此处)折叠或打开

  1. void MainWindow::readPageElement(QTreeWidgetItem *parent)
  2. {
  3.     QString page = reader.readElementText();
  4.     if (reader.isEndElement()) {
  5.         reader.readNext();
  6.     }

  7.     QString allPages = parent->text(1);
  8.     if (!allPages.isEmpty()) {
  9.         allPages += ", ";
  10.     }
  11.     allPages += page;
  12.     parent->setText(1, allPages);
  13. }

最后skipUnknownElement()函数:

点击(此处)折叠或打开

  1. void MainWindow::skipUnknownElement()
  2. {
  3.     reader.readNext();
  4.     while (!reader.atEnd()) {
  5.         if (reader.isEndElement()) {
  6.             reader.readNext();
  7.             break;
  8.         }

  9.         if (reader.isStartElement()) {
  10.             skipUnknownElement();
  11.         } else {
  12.             reader.readNext();
  13.         }
  14.     }
  15. }

 
返回目录:Qt学习整理 

阅读(581) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~