XML(eXtensible Markup Language)是一种通用的文本格式,被广泛运用于数据交换和数据存储(虽然近年来 JSON 盛行,大有取代 XML 的趋势,但是对于一些已有系统和架构,比如 WebService,由于历史原因,仍旧会继续使用 XML)。
Qt 提供了三种读取 XML 文档的方法:
l QXmlStreamReader:一种快速的基于流的方式访问良格式 XML 文档,特别适合于实现一次解析器(所谓“一次解析器”,可以理解成我们只需读取文档一次,然后像一个遍历器从头到尾一次性处理 XML 文档,期间不会有反复的情况,也就是不会读完第一个标签,然后读第二个,读完第二个又返回去读第一个,这是不允许的);
l DOM(Document Object Model):将整个 XML 文档读入内存,构建成一个树结构,允许程序在树结构上向前向后移动导航,这是与另外两种方式最大的区别,也就是允许实现多次解析器(对应于前面所说的一次解析器)。DOM 方式带来的问题是需要一次性将整个 XML 文档读入内存,因此会占用很大内存;
l SAX(Simple API for XML):提供大量虚函数,以事件的形式处理 XML 文档。这种解析办法主要是由于历史原因提出的,为了解决 DOM 的内存占用提出的(在现代计算机上,这个一般已经不是问题了)。
生成 XML 文档,Qt 同样提供了三种方式:
l QXmlStreamWriter,与QXmlStreamReader相对应;
l DOM 方式,首先在内存中生成 DOM 树,然后将 DOM 树写入文件。不过,除非我们程序的数据结构中本来就维护着一个 DOM 树,否则,临时生成树再写入肯定比较麻烦;
l 纯手工生成 XML 文档,显然,这是最复杂的一种方式。
使用QXmlStreamReader是 Qt 中最快最方便的读取 XML 的方法。
每次QXmlStreamReader的readNext()函数调用,解析器都会读取下一个元素,按照下表中展示的类型进行处理。我们通过表中所列的有关函数即可获得相应的数据值:
类型
|
示例
|
有关函数
|
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 片段:
-
<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()这样的函数检查当前读取的类型,
下面我们看一个完整的例子。
我们的 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); //将其设置为QXmlStreamReader的设备
-
-
/**
-
* 这里要用 while 循环
-
* XML 文档在根标签之前还有别的内容,比如声明,比如 DTD
-
* 所以为了通用起见,我们必须用 while 循环判断。
-
*/
-
while (!reader.atEnd()) {
-
if (reader.isStartElement()) { //首先判断是不是StartElement
-
if (reader.name() == "bookindex") { //根标签名 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;
-
}
接下来看readBookindexElement()函数:
-
void MainWindow::readBookindexElement()
-
{
-
/*如果在进入函数的时候,reader 不是StartElement状态,或者说标签不是 bookindex,就认为出错。*/
-
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();
-
}
-
}
-
}
那么下面来看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();
-
}
-
}
-
}
然后是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);
-
}
最后skipUnknownElement()函数:
-
void MainWindow::skipUnknownElement()
-
{
-
reader.readNext();
-
while (!reader.atEnd()) {
-
if (reader.isEndElement()) {
-
reader.readNext();
-
break;
-
}
-
-
if (reader.isStartElement()) {
-
skipUnknownElement();
-
} else {
-
reader.readNext();
-
}
-
}
-
}
返回目录:Qt学习整理
阅读(4183) | 评论(0) | 转发(3) |