Qt 学习之路 2(60):使用 DOM 处理 XML
Qt 学习之路 2(60):使用 DOM 处理 XML
DOM 是由 W3C 提出的一种处理 XML 文档的标准接口。Qt 实现了 DOM Level 2 级别的不验证读写 XML 文档的方法。
与上一章所说的流的方式不同,DOM 一次性读入整个 XML 文档,在内存中构造为一棵树(被称为 DOM 树)。我们能够在这棵树上进行导航,比如移动到下一节点或者返回上一节点,也可以对这棵树进行修改,或者是直接将这颗树保存为硬盘上的一个 XML 文件。考虑下面一个 XML 片段:
XHTML
|
1
2
3
4
|
<doc>
<quote>Scio me nihil scire</quote>
<translation>I know that I know nothing</translation>
</doc>
|
我们可以认为是如下一棵 DOM 树:
|
1
2
3
4
5
6
|
Document
|--Element(doc)
|--Element(quote)
| |--Text("Scio me nihil scire")
|--Element(translation)
|--Text("I know that I know nothing")
|
上面所示的 DOM 树包含了不同类型的节点。例如,Element 类型的节点有一个开始标签和对应的一个结束标签。在开始标签和结束标签之间的内容作为这个 Element 节点的子节点。在 Qt 中,所有 DOM 节点的类型名字都以 QDom 开头,因此,QDomElement就是 Element 节点,QDomText就是 Text 节点。不同类型的节点则有不同类型的子节点。例如,Element 节点允许包含其它 Element 节点,也可以是其它类型,比如 EntityReference,Text,CDATASection,ProcessingInstruction 和 Comment。按照 W3C 的规定,我们有如下的包含规则:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
[Document]
<- [Element]
<- DocumentType
<- ProcessingInstrument
<- Comment
[Attr]
<- [EntityReference]
<- Text
[DocumentFragment] | [Element] | [EntityReference] | [Entity]
<- [Element]
<- [EntityReference]
<- Text
<- CDATASection
<- ProcessingInstrument
<- Comment
|
上面表格中,带有 [] 的可以带有子节点,反之则不能。
下面我们还是以上一章所列出的 books.xml 这个文件来作示例。程序的目的还是一样的:用QTreeWidget 来显示这个文件的结构。需要注意的是,由于我们选用 DOM 方式处理 XML,无论是 Qt4 还是 Qt5 都需要在 .pro 文件中添加这么一句:
|
1
|
QT += xml
|
头文件也是类似的:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
bool readFile(const QString &fileName);
private:
void parseBookindexElement(const QDomElement &element);
void parseEntryElement(const QDomElement &element, QTreeWidgetItem *parent);
void parsePageElement(const QDomElement &element, QTreeWidgetItem *parent);
QTreeWidget *treeWidget;
};
|
MainWindow的构造函数和析构函数和上一章是一样的,没有任何区别:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle(tr("XML DOM Reader"));
treeWidget = new QTreeWidget(this);
QStringList headers;
headers << "Items" << "Pages"; treeWidget->setHeaderLabels(headers);
setCentralWidget(treeWidget);
}
MainWindow::~MainWindow()
{
}
|
readFile()函数则有了变化:
|
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
26
27
28
29
30
31
32
|
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;
}
QString errorStr;
int errorLine;
int errorColumn;
QDomDocument doc;
if (!doc.setContent(&file, false, &errorStr, &errorLine,
&errorColumn)) {
QMessageBox::critical(this, tr("Error"),
tr("Parse error at line %1, column %2: %3")
.arg(errorLine).arg(errorColumn).arg(errorStr));
return false;
}
QDomElement root = doc.documentElement();
if (root.tagName() != "bookindex") {
QMessageBox::critical(this, tr("Error"),
tr("Not a bookindex file"));
return false;
}
parseBookindexElement(root);
return true;
}
|
readFile()函数显然更长更复杂。首先需要使用QFile打开一个文件,这点没有区别。然后我们创建一个QDomDocument对象,代表整个文档。注意看我们上面介绍的结构图,Document 是 DOM 树的根节点,也就是这里的QDomDocument;使用其setContent()函数填充 DOM 树。setContent()有八个重载,我们使用了其中一个:
|
1
2
3
4
5
|
bool QDomDocument::setContent ( QIODevice * dev,
bool namespaceProcessing,
QString * errorMsg = 0,
int * errorLine = 0,
int * errorColumn = 0 )
|
不过,这几个重载形式都是调用了同一个实现:
|
1
2
3
4
5
|
bool QDomDocument::setContent ( const QByteArray & data,
bool namespaceProcessing,
QString * errorMsg = 0,
int * errorLine = 0,
int * errorColumn = 0 )
|
两个函数的参数基本类似。第二个函数有五个参数,第一个是QByteArray,也就是所读取的真实数据,由QIODevice即可获得这个数据,而QFile就是QIODevice的子类;第二个参数确定是否处理命名空间,如果设置为 true,处理器会自动设置标签的前缀之类,因为我们的 XML 文档没有命名空间,所以直接设置为 false;剩下的三个参数都是关于错误处理。后三个参数都是输出参数,我们传入一个指针,函数会设置指针的实际值,以便我们在外面获取并进行进一步处理。
当QDomDocument::setContent()函数调用完毕并且没有错误后,我们调用QDomDocument::documentElement()函数获得一个 Document 元素。如果这个 Document 元素标签是 bookindex,则继续向下处理,否则则报错。
|
1
2
3
4
5
6
7
8
9
10
11
|
void MainWindow::parseBookindexElement(const QDomElement &element)
{
QDomNode child = element.firstChild();
while (!child.isNull()) {
if (child.toElement().tagName() == "entry") {
parseEntryElement(child.toElement(),
treeWidget->invisibleRootItem());
}
child = child.nextSibling();
}
}
|
如果根标签正确,我们取第一个子标签,判断子标签不为空,也就是存在子标签,然后再判断其名字是不是 entry。如果是,说明我们正在处理 entry 标签,则调用其自己的处理函数;否则则取下一个标签(也就是nextSibling()的返回值)继续判断。注意我们使用这个 if 只选择 entry 标签进行处理,其它标签直接忽略掉。另外,firstChild()和nextSibling()两个函数的返回值都是QDomNode。这是所有节点类的基类。当我们需要对节点进行操作时,我们必须将其转换成正确的子类。这个例子中我们使用toElement()函数将QDomNode转换成QDomElement。如果转换失败,返回值将是空的QDomElement类型,其tagName()返回空字符串,if 判断失败,其实也是符合我们的要求的。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
void MainWindow::parseEntryElement(const QDomElement &element,
QTreeWidgetItem *parent)
{
QTreeWidgetItem *item = new QTreeWidgetItem(parent);
item->setText(0, element.attribute("term"));
QDomNode child = element.firstChild();
while (!child.isNull()) {
if (child.toElement().tagName() == "entry") {
parseEntryElement(child.toElement(), item);
} else if (child.toElement().tagName() == "page") {
parsePageElement(child.toElement(), item);
}
child = child.nextSibling();
}
}
|
在parseEntryElement()函数中,我们创建了一个树组件的节点,其父节点是根节点或另外一个 entry 节点。接着我们又开始遍历这个 entry 标签的子标签。如果是 entry 标签,则递归调用自身,并且把当前节点作为父节点;否则则调用parsePageElement()函数。
|
1
2
3
4
5
6
7
8
9
10
11
|
void MainWindow::parsePageElement(const QDomElement &element,
QTreeWidgetItem *parent)
{
QString page = element.text();
QString allPages = parent->text(1);
if (!allPages.isEmpty()) {
allPages += ", ";
}
allPages += page;
parent->setText(1, allPages);
}
|
parsePageElement()则比较简单,我们还是通过字符串拼接设置叶子节点的文本。这与上一章的步骤大致相同。
程序运行结果同上一章一模一样,这里不再贴出截图。
通过这个例子我们可以看到,使用 DOM 当时处理 XML 文档,除了一开始的setContent()函数,其余部分已经与原始文档没有关系了,也就是说,setContent()函数的调用之后,已经在内存中构建好了一个完整的 DOM 树,我们可以在这棵树上面进行移动,比如取相邻节点(nextSibling())。对比上一章流的方式,虽然我们早早关闭文件,但是我们始终使用的是readNext()向下移动,同时也不存在readPrevious()这样的函数。
Qt 学习之路 2(60):使用 DOM 处理 XML的更多相关文章
- Qt 学习之路 2(18):事件
Home / Qt 学习之路 2 / Qt 学习之路 2(18):事件 Qt 学习之路 2(18):事件 豆子 2012年9月27日 Qt 学习之路 2 60条评论 事件(event)是由系统 ...
- Qt 学习之路 2(66):访问网络(2)
Home / Qt 学习之路 2 / Qt 学习之路 2(66):访问网络(2) Qt 学习之路 2(66):访问网络(2) 豆子 2013年10月31日 Qt 学习之路 2 27条评论 上一 ...
- Qt 学习之路 2(62):保存 XML
Home / Qt 学习之路 2 / Qt 学习之路 2(62):保存 XML Qt 学习之路 2(62):保存 XML 豆子 2013年8月26日 Qt 学习之路 2 9条评论 前面几章我们 ...
- Qt 学习之路 2(61):使用 SAX 处理 XML
Qt 学习之路 2(61):使用 SAX 处理 XML 豆子 2013年8月13日 Qt 学习之路 2 没有评论 前面两章我们介绍了使用流和 DOM 的方式处理 XML 的相关内容,本章将介绍 ...
- Qt 学习之路 2(59):使用流处理 XML
Qt 学习之路 2(59):使用流处理 XML 豆子 2013年7月25日 Qt 学习之路 2 18条评论 本章开始我们将了解到如何使用 Qt 处理 XML 格式的文档. XML(eXtensible ...
- Qt 学习之路 2(53):自定义拖放数据
Qt 学习之路 2(53):自定义拖放数据 豆子 2013年5月26日 Qt 学习之路 2 13条评论上一章中,我们的例子使用系统提供的拖放对象QMimeData进行拖放数据的存储.比如使用QM ...
- Qt 学习之路 2(51):布尔表达式树模型
Qt 学习之路 2(51):布尔表达式树模型 豆子 2013年5月15日 Qt 学习之路 2 17条评论 本章将会是自定义模型的最后一部分.原本打算结束这部分内容,不过实在不忍心放弃这个示例.来自于 ...
- Qt 学习之路 2(28):坐标系统
Qt 学习之路 2(28):坐标系统 豆子 2012年11月25日 Qt 学习之路 2 59条评论 在经历过实际操作,以及前面一节中我们见到的那个translate()函数之后,我们可以详细了解下 Q ...
- Qt 学习之路 2(27):渐变
Qt 学习之路 2(27):渐变 豆子 2012年11月20日 Qt 学习之路 2 17条评论 渐变是绘图中很常见的一种功能,简单来说就是可以把几种颜色混合在一起,让它们能够自然地过渡,而不是一下子变 ...
随机推荐
- Android访问中央气象台的天气预报API得到天气数据
最新说明:该接口已失效! 2014-03-04 可申请它公布的API,需申请:http://smart.weather.com.cn/wzfw/smart/weatherapi.shtml 在用A ...
- Eclipse下使用Subversion(SVN工具)
本文目的 让未使用过版本控制器软件或者未使用过subversion软件的人员尽快上手. subversion的使用技巧很多,这里只总结了最小使用集,即主要的基本功能,能够用来应付日常工作. 因此不涉及 ...
- 【总结整理】word使用技巧
Tab+Enter,在编过号以后,会自动编号段落 Ctrl + D调出字体栏,配合Tab+Enter全键盘操作吧 Ctrl + L 左对齐, Ctrl + R 右对齐, Ctrl + E 居中 Ctr ...
- Docker02 基本命令、开发环境搭建、docker安装nginx、Dockerfile、路径挂载
1 基本命令 1.1 docker相关 centos6.5 安装docker环境 >sudo yum install -y http://mirrors.yun-idc.com/epel/6/i ...
- Lambda03 方法引用、类型判断、变量引用
1 方法引用 1.1 方法引用的好处 方法引用结合 Lambda 可以引用已存在的方法,省略很多编码,而且可读性更强,它可以自动装配参数与返回值. 在编写lambda表达式的时候可以通过方法引用的方式 ...
- 【项目运行异常】BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
java.lang.IllegalStateException: LifecycleProcessor not initialized - call 'refresh' before invoking ...
- Process management of windows
igfxem.exe进程是正常的进程.是intel家的核显驱动类的进程.核显即"核芯显卡",是指GPU部分它是与CPU建立在同一内核芯片上,两者完全融合的芯片."核芯显卡 ...
- Cannot resolve the collation conflict between "Chinese_PRC_CI_AS" and "SQL_L及由于排序规则不同导致查询结果为空的问题
报错:Cannot resolve the collation conflict between "Chinese_PRC_CI_AS" and "SQL_L 出错原因: ...
- 第05章-构建Spring Web应用程序
1. Spring MVC起步 1.1 跟踪Spring MVC的请求 前端控制器DispatcherServlet 请求旅程的第一站是Spring的DispatcherServlet.与大多数基于J ...
- HTML、CSS、JavaScript拾遗
1.html元素中,如果有文本存在,当元素大小不足以容纳文本时,文本会进行强制换行.比如说设置页面不出现滚动条,body的overflow为hidden时,或者scroll为no时,span在超过页面 ...