Qt编程进阶(95):使用QXmlStreamReader读取XML
yuyutoo 2024-10-12 01:55 1 浏览 0 评论
一、XML是什么?
XML(eXtensible Markup Language,可扩展标记语言)是普遍用于数据交换和数据存储的一种多用途文本文件格式。XML首先是由万维网协会(World Wide Web Consortium,W3C)作为SGML的一个替代品来开发的。它的语法规则与HTML相似,不过XML是一种用于语言分析的语言,它并没有要求专门的标记符,属性或者条目。HTML的XML兼容版称为XHTML。
对于比较流行的SVG(可标量化矢量图形)XML格式,QtSvg模块提供了可用于载入并呈现SVG图像的类。对于使用MathML(数学标记语言)XML格式的绘制文档,可以使用Qt Solutions中的QtMmLWidget。
对于一般的XML数据处理,Qt提供了QtXml模块,这是本文的主题。
二、XML的读取方式
QtXml模块提供了三种截然不同的应用程序编程接口用来读取XML文件:
- (1)QXmlStreamReader是一个用于读取格式良好的XML文档的快速解析器。
- (2)DOM(文档对象模型)把XML文档转换为应用程序可以遍历的树形结构。
- (3)SAX(XML简单应用程序编程接口)通过虚拟函数直接向应用程序报告“解析事件”。
QXmlStreamReader类最快且最容易使用,它同时还提供了与其他Qt兼容的应用程序编程接口。它很适用于编写单通解析器。DOM的主要优点是它能以任意顺序遍历XML文档的树形表示,同时可以实现多通解析算法。有一些应用程序甚至使用DOM树作为它们的基本数据结构。SAX则因为一些历史原因而被得以沿用至今,使用QXmlStreamReader通常会有更加简单高效的编码。
使用QXmlStreamReader是在Qt中读取XML文档的最快且最简单的方式。因为解析器的工作能力是逐渐递增的,所以它尤其适用于诸如查找XML文档中一个给定的标记符号出现的次数、读取内存容纳不了的特大文件、组装定制的数据结构以反映XML文档的内容等。
QXmlStreamReader解析器根据下图中所列出的记号工作。每次只要调用readNext()函数,下一个记号就会被读取并变成当前的记号。当前记号的属性取决于记号的类型,可以使用表格中列出的getter函数读取当前记号。
考虑如下的XML文档:
<doc>
<quote>Einmal ist keinmal</quote>
</doc>
如果解析这个文档,则readNext()每调用一次都将生成一个新记号,若使用getter函数还会获得额外的信息;
StartDocument
StartElement (name() == "doc")
StartElement (name() == "quote")
Characters (text() == " Einmal ist keinmal")
EndElement (name() == "quote")
EndElement (name() == "doc")
EndDocument
每次调用readNext()后,都可以使用isStartElement(),isCharacters()及类似的函数或者仅仅用state()来测试当前记号的类型。
三、QXmlStreamReader读取XML实例
下面将查看一个实例,它告诉我们如何使用QXmlStreamReader解析一个专门的XML文件格式(图1的bookindex.xml)并在QTreeWidget中显示其内容。所解析的是那种具有书刊索引目录且包含索引条目和子条目的文档格式。下图是在QTreeWidget中显示的书刊索引文件。
1、QtXml库
在这个应用程序中使用的QXmlStreamReader类是QtXml库中的一部分。必须在.pro文件中加入如下一行命令:
QT += xml
在代码中添加头文件:
#include <QXmlStreamReader>
2、主程序代码
首先查看应用程序中XML阅读器在上下文中是如何使用的。
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
ui->treeWidget->setColumnCount(2);
QStringList header;
header<<"Terms"<<"Pages";
ui->treeWidget->setHeaderLabels(header);
XmlStreamReader reader(ui->treeWidget);
reader.readFile("bookindex.xml");
}
在图中显示的应用程序界面中创建一个QTreeWidget。之后,这个应用程序创建一个XmlStreamReader,并将树形窗口部件值传递给该XmlStreamReader,并要求它解析所指定的一个文件。
3、XmlStreamReader类的定义
然后,我们将查看阅读器的实现代码。
class XmlStreamReader
{
public:
XmlStreamReader(QTreeWidget *tree);
bool readFile(const QString &fileName);
private:
void readBookindexElement();
void readEntryElement(QTreeWidgetItem *parent);
void readPageElement(QTreeWidgetItem *parent);
void skipUnknownElement();
QTreeWidget *treeWidget;
QXmlStreamReader reader;
};
XmlStreamReader类提供了两个公共函数:构造函数和readFile()函数。这个类使用QXmlStreamReader解析XML文件,并配合QTreeWidget窗口以反映其读入的XML数据。通过使用向下递归的方法来实现这一解析过程。
- readBookindexElement()解析一个含有0或0个以上<entry>元素的<bookindex>…</bookindex>元素。
- readEntryElemen()解析一个含有0或0个以上<page>元素的<entry>…</entry>元素,以及嵌套任意层次的含有0或0个以上<entry>元素。
- readPageElement()解析一个<page> …</page>元素。
- skipUnknownElement()跳过不能识别的元素。
4、XmlStreamReader类的实现
现在看看XmlStreamReader类的实现,由构造函数开始。
XmlStreamReader::XmlStreamReader(QTreeWidget *tree)
{
treeWidget=tree;
}
构造函数只是用来建立阅读器将使用的那个QTreeWidget。所有的操作都将在readFile()函数中完成(由main()函数调用)。
bool XmlStreamReader::readFile(const QString &fileName)
{
QFile file(fileName);
if(!file.open(QFile::ReadOnly | QFile::Text)){ // (a)
std::cerr<<"Error: Cannot read file "<<qPrintable(fileName)
<<": "<<qPrintable(file.errorString())<<std::endl;
return false;
}
reader.setDevice(&file);
reader.readNext(); // (b)
while (!reader.atEnd()) {
if(reader.isStartElement()){
if(reader.name()=="bookindex"){ // (c)
readBookindexElement();
}else{
reader.raiseError(QObject::tr("Not a bookindex file"));
}
}else{
reader.readNext();
}
}
file.close();
if(reader.hasError()){ // (d)
std::cerr<<"Error: Failed to parse file"
<<qPrintable(fileName)<<": "
<<qPrintable(reader.errorString())<<std::endl;
return false;
}else if(file.error()!=QFile::NoError){
std::cerr<<"Error: Cannot read file "<<qPrintable(fileName)
<<": "<<qPrintable(file.errorString())<<std::endl;
return false;
}
return true;
}
其中:
- (a):readFile()函数首先会尝试打开文件。如果失败,则会输出一条出错信息并返回false值;如果成功,则它将被设置为QXmlStreamReader的输人设备。
- (b):QXmlStreamReader的readNext()函数从输人流中读取下一个记号。如果成功而且还没有到达XML文件的结尾,函数将进人循环。由于索引文件的结构,我们知道在该循环内部只有三种可能性发生:<bookindex>开始标签正好被读入;另一个开始标签正好被读入(在这种情况下,读取的文件不是一个书刊索引);读入的是其他种类的记号。
- (c):如果有正确的开始标签,就调用readBookindexElement()继续完成处理。否则,就调用QXmlStreamReader::raiseError()并给出出错信息。下一次(在while循环条件下)调用atEnd()时,它将返还true值。这就确保了解析过程可以在遇到错误时能尽快停止。通过对QFile调用error()和errorString(),就可以在稍后查询这些出错信息。当在书刊索引文件中检测到有错误时,也会立即返回一个类似的出错信息。其实,使用raiseError()通常会更加方便,因为它对低级的XML解析错误和与应用程序相关的错误使用了相同的错误报告机制,而这些低级的XML解析错误会在QXmlStreamReader运行到无效的XML时就自动出现。
- (d):一旦处理完成,就会关闭文件,如果存在解析器错误或者文件错误,该函数就输出一个出错信息并返回false值;否则,返回true值并报告解析成功。
void XmlStreamReader::readBookindexElement()
{
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();
}
}
}
readBookindexElement()的作用就是读取文件的主体部分。它首先跳过当前的记号(此处只可能是<bookindex>开始标签),然后遍历读取整个输人文件。
如果读取到了关闭标签,那么它只可能是</bookindex>标签,否则QXmlStreamReader早就已经报告出错。如果是那样的话,就跳过这个标签并跳出循环。否则将应该有一个顶级索引<entry>开始标签。如果情况确实如此,调用readEntryElement()来处理条目数据;不然,就调用skipUnknownElement()。使用skipUnknownElement()而不调用raiseError(),意味着如果要在将来扩展书刊索引格式以包含新的标签的话这个阅读器将继续存效,因为它仅忽略了不能识别的标签。
readEntryElement()具有一个确认父对象条目的QTreeWidgetItem *参数。我们将QTreeWidget::invisibleRootItem()作为父对象项传递,以使新的项以其为根基。在readEntryElement()中,用一个不同的父对象项循环调用readEntryElement()。
void XmlStreamReader::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();
}
}
}
每当遇到一个<entry>开始标签时,就会调用readEntryElement()函数。我们希望为每一个索引条目创建一个树形的窗口部件项,因此创建一个新的QTreeWidgetltem,并将其第一列的文本值设置为条目的项属性文本。
一旦条目被添加到树中,就开始读取下一个记号。如果这是一个关闭标签,就跳过该标签并跳出循环。如果遇到的是开始标签,那么它可能是<entry>标签(表示一个子条目),<page>标签 (该条目项的页码数),或者是一个未知的标签。如果开始标签是一个子条,就递归调用readEntryElement()。如果该标签是<page>标签,就调用readPageElement()。
void XmlStreamReader::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>标签,就调用readPageElement()函数。被传递的正是符合页码文本所属条目的树项。我们从读取<page>和</page>标签之间的文本开始。成功读取完以后,readElementText()函数将让解析器停留在必须跳过的</page>标签上。
这些页被存储在树形窗口部件项的第二列。我们首先提取那里已有的文本。如果文本不为空值,就在其后添加一个逗号,为新页的文本做好淮备。然后,添加新的文本并相应地更新该列的文本。
void XmlStreamReader::skipUnknownElement()
{
reader.readNext();
while (!reader.atEnd()) {
if (reader.isEndElement()) {
reader.readNext();
break;
}
if (reader.isStartElement()) {
skipUnknownElement();
} else {
reader.readNext();
}
}
}
最后,当遇到未知的标签时,将继续读取,直到读取到也将跳过的未知元素的关闭标签为止。这意味着我们将跳过那些具有良好形式但却无法识別的元素,并从XML文件中读取尽可能多的可识別的数据。
——————————————————
对于本文实例完整代码有需要的朋友,可关注并在评论区留言!
相关推荐
- jQuery VS AngularJS 你更钟爱哪个?
-
在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...
- Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0
-
在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...
- 如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例
-
要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...
- 编程技巧:Jquery实时验证,指定长度的「负小数」
-
为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...
- 一篇文章带你用jquery mobile设计颜色拾取器
-
【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...
- 编程技巧:Jquery实时验证,指定长度的「正小数」
-
为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...
- jquery.validate检查数组全部验证
-
问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...
- Vue进阶(幺叁肆):npm查看包版本信息
-
第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...
- layui中使用lay-verify进行条件校验
-
一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...
- jQuery是什么?如何使用? jquery是什么功能组件
-
jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...
- django框架的表单form的理解和用法-9
-
表单呈现...
- jquery对上传文件的检测判断 jquery实现文件上传
-
总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...
- Nodejs之MEAN栈开发(四)-- form验证及图片上传
-
这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...
- 大数据开发基础之JAVA jquery 大数据java实战
-
上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...
- 推荐四个开源的jQuery可视化表单设计器
-
jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)