关于XML的文章我先前写过一篇。之后就再也没写过。原因是很简单的。虽然那篇文章是用Matlab的代码说明XML解析,但是XML的基本概念都是一致的,我也没必要再就C++或是Python等语言再写一遍在其他语言下面怎么用其他的库解析XML,都是大同小异。

可是这个世界上奇葩比较多。最近在做《网络通信原理》的project的时候,用到了Qt里面的QXmlStreamReader。有意思的是,这个东西不按常理出牌。为说明这个特性,我引用Qt 5关于QXmlStreamReader上面的一段话:

QXmlStreamReader is an incremental parser. It can handle the case where the document can’t be parsed all at once because it arrives in chunks (e.g. from multiple files, or over a network connection).

QXmlStreamReader is memory-conservative by design, since it doesn’t store the entire XML document tree in memory, but only the current token at the time it is reported.

由这段话我们可以看出,QXmlStreamReader的一个重要特点是,它是一个增量parser。QXmlStreamReader有一个特别的构造函数QXmlStreamReader::QXmlStreamReader(QIODevice * device),这个device可以是QNetworkReply也可以是QFile。相信这样的好处大家都可以看得出来。为了应付不同IODevice的特性,QXmlStreamReader也只能采取增量解析的方法。然后又有了下面的概念:token.

QXmlStreamReader不在内存中保存全部的DOM tree,现在解析的位置和所解析的对象用token说明。关于什么是token,其实我也不知道。但是QXmlStreamReader提供了一个函数:TokenType QXmlStreamReader::readNext(),有关这个函数的说明是“Reads the next token and returns its type.”

按照官方文档上面的解释,一个可行的解析模型可以是这样:

1
2
3
4
5
6
7
8
9
QXmlStreamReader xml;
...
while (!xml.atEnd()) {
xml.readNext();
... // do processing
}
if (xml.hasError()) {
... // do error handling
}

由此可见,QXmlStreamReader在解析xml的时候,以token为单位解析xml文档数据。

我在上一篇文章中讲过,xml有Element node,Element node以Text node作为child,Attribute node从属于Element node,comment node相对独立,而以上四种node都由document node生成,document node可以说是一个xml文档的代表,xml parsing的核心是element node。但是在Qt中,token与这种标准的概念似乎完全无关。它更关心我现在读到的东西是什么。在TokenType的定义中,一共给出了9种不同token的定义,而判断当前parser的tokenType是什么的函数一共有十二种。

我们可以想象,这种parser,一块一块地读取xml文档,只前进不后退,每一块代表一种既定的token,直到全部读完xml为止(也就是atEnd()为真的时候)。

下面让我展示一段我这个project中的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if(reply->error()==QNetworkReply::NoError){
ui->listWidget->clear();
articlelist.clear();
QXmlStreamReader xml(reply);
if(xml.readNextStartElement() && xml.name()=="articles"){
while(xml.readNextStartElement() && xml.name()=="article"){
Article record;
while(xml.readNextStartElement()){
if(xml.name()=="author"){
record.author = xml.readElementText();
}else if(xml.name()=="date"){
record.date = xml.readElementText();
}else if(xml.name()=="title"){
QString t = xml.readElementText();
ui->listWidget->addItem(t);
record.title = t;
}else if(xml.name()=="content"){
record.content = xml.readElementText();
}
}
articlelist.push_back(record);
}
}
...
}

xml文档格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
<articles>
<article>
<author>...</author>
<date>...</date>
<title>...</title>
<content>...</content>
</article>
<article>
...
</article>
...
</articles>

代码中reply是个API请求的回应,我的目的是吧这个回应中的每一条信息存放在articlelist中。值得注意的是14-16行那段代码,由于这个是一个增量parser,我们不能使用

1
2
ui->listWidget->addItem(xml.readElementText());
record.title = xml.readElementText();

否则record.title将为空。


留言

2014-05-30