12. 数据交换和存储¶
上一章讲到了Qt中文本的读写,涉及到部分的数据的存储, 这一章我们更加深入的来讲讲qt中的数据加载、交换和存储。
Qt提供对XML和JSON的支持
XML和JSON
可扩展标记语言,标准通用标记语言的子集,简称XML。是一种用于标记电子文件使其具有结构性的标记语言, XML被设计为传输和存储数据,其焦点是数据的内容,XML的本质为带标签的纯文本。Qt提供API来读取和解析XML流,以及写入这些流。
JSON是一种对源自Javascript的对象数据进行编码的格式,现在已广泛用作Internet上的数据交换格式。 Qt中的JSON支持提供了易于使用的C++ API来解析,修改和保存JSON数据。 它还支持以二进制格式保存此数据,该格式可以直接“mmap”访问,并且访问速度非常快。
XML和JSON主要用于数据交换,而数据主要存储在数据仓库,也就是我们通常所说的数据库。
数据库
数据库的概念实际包括两层意思:数据库既是用来存储数据的仓库,也是一个按数据结构来存储和管理数据的计算机软件系统。
数据库是一个实体,它是能够合理保管数据的“仓库”,用户在该“仓库”中存放要管理的事务数据,“数据”和“库”两个概念结合成为数据库。
数据库是数据管理的新方法和技术,它能更合适的组织数据、更方便的维护数据、更严密的控制数据和更有效的利用数据。
常用的数据库软件:Oracle、MySQL(MongoDB)、Microsoft SQL Server、SQlite。
虽说这些数据库软件各不相同,但是这些数据库都是使用SQL语句来进行数据的增删改查的。 Qt使用数据库插件的形式来支持不同的数据库软件,封装了统一的操作接口来对数据仓库中的数据进行操作。
12.1. XML¶
下面就是一段实际的xml数据:
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 | <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xbel>
<xbel version="1.0">
<folder folded="no">
<title>Qt Resources</title>
<bookmark href="http://qt.io/">
<title>Qt home page</title>
</bookmark>
<bookmark href="https://www.qt.io/partners/">
<title>Qt Partners</title>
</bookmark>
<bookmark href="https://www.qt.io/qt-training/">
<title>Training</title>
</bookmark>
<folder folded="yes">
<title>Community Resources</title>
<bookmark href="http://www.qtcentre.org/content/">
<title>Qt Centre</title>
</bookmark>
<bookmark href="http://www.qtforum.org/">
<title>QtForum.org</title>
</bookmark>
</folder>
</folder>
</xbel>
|
第一行是 XML 声明。它定义 XML 的版本 (1.0) 和所使用的编码 (UTF-8)。
第二行标明了文档格式 xbel,XML 的一种。
第三行标明了 xbel 版本,也是文档的根元素,xbel就是元素的标签,标签和关闭标签必须成对出现也就是有 <xbel> 就必须有 </xbel> 。
后面以此就是xbel根元素的子元素。
XML 文档是一种树结构,它从“根元素”开始逐渐扩展到“子元素”,所有元素标签和关闭标签都必须成对出现; 标签对大小写敏感,标签可以拥有属性值,比如 version=”1.0”,属性值要加引号。
我们大概来了解一下XML的语法规则,参考 https://www.w3school.com.cn/xml/index.asp
Qt提供QXmlStreamReader和QXmlStreamWriter类来完成XML的基本读取解析和写入。
12.1.1. QXmlStreamReader¶
QXmlStreamReader提供了一个简单的流API来解析格式正确的XML。 这是先将完整的XML数据加载到DOM树中,数据来源可以是QIODevice也可以是QByteArray。
流读取器的基本概念是将XML文档作为令牌流(stream of tokens)报告,类似于SAX。 QXmlStreamReader和SAX之间的主要区别在于如何报告这些XML令牌。 使用SAX,应用程序必须提供处理程序(回调函数),以便在解析器方便时从解析器接收所谓的XML事件。 使用QXmlStreamReader,应用程序代码本身会驱动循环并根据需要从读取器中逐个提取令牌,通过调用readNext()来完成, 在该API中,读取器从输入流中读取数据,直到完成下一个标记为止,此时它返回tokenType()。
Qt提供了一组方便的函数(包括isStartElement()和text())来检查令牌,以获取有关已读取内容的信息。 这些API最大优点是可以使用它构建递归下降解析器,这意味着您可以轻松地将XML解析代码拆分为不同的方法或类。 这也使得在解析XML时很容易跟踪应用程序自身的状态。
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解析器1.0,它不包括外部解析实体。 只要没有错误发生,就可以确保应用程序代码确保流读取器提供的数据满足W3C的格式正确的XML标准。 例如,可以确定所有标签确实都正确嵌套和关闭,对内部实体的引用已被正确的替换文本替换, 并且属性已根据DTD的内部子集进行了规范化或添加。
12.1.2. QXmlStreamWriter¶
QXmlStreamWriter是对应QXmlStreamReader用来写入XML数据的类。 QXmlStreamWriter提供如下接口:
writeStartDocument() 开始写入XML数据,写入以XML版本号和编码信息;
writeEndDocument() 结束XML数据写入并关闭所有打开的标签;
writeStartElement() 开始写入标签;
writeAttribute() 和 writeTextElement() 写入标签内容;
writeEndElement() 结束标签写入。
1 2 3 4 5 6 7 8 9 10 | QXmlStreamWriter stream(&output);
stream.setAutoFormatting((true);
stream.writeStartDocument();
......
stream.writeStartElement("bookmark");
stream.writeAttribute("href", "http://qt-project.org/");
stream.writeTextElement("title", "Qt Project");
stream.writeEndElement();// bookmark
......
stream.writeEndDocument();
|
12.2. JSON¶
我们先通过接口测试软件Postman,来访问天气接口(网上找的),看看究竟JSON格式数据长什么样子。
最终返回数据如下,根据下面的数据,我们就大概知道了天气的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | {"data":
{"yesterday":
{"date":"30日星期五","high":"高温 31℃","fx":"南风","low":"低温 20℃","fl":"<![CDATA[2级]]>","type":"阴"},
"city":"东莞","forecast":[
{"date":"1日星期六","high":"高温 32℃","fengli":"<![CDATA[2级]]>","low":"低温 22℃","fengxiang":"南风","type":"多云"},
{"date":"2日星期天","high":"高温 31℃","fengli":"<![CDATA[2级]]>","low":"低温 24℃","fengxiang":"南风","type":"多云"},
{"date":"3日星期一","high":"高温 29℃","fengli":"<![CDATA[3级]]>","low":"低温 24℃","fengxiang":"东南风","type":"雷阵雨"},
{"date":"4日星期二","high":"高温 29℃","fengli":"<![CDATA[3级]]>","low":"低温 24℃","fengxiang":"南风","type":"多云"},
{"date":"5日星期三","high":"高温 30℃","fengli":"<![CDATA[2级]]>","low":"低温 24℃","fengxiang":"北风","type":"雷阵雨"}
],
"ganmao":"感冒易发期,外出请适当调整衣物,注意补充水分。","wendu":"31"
},
"status":1000,
"desc":"OK"
}
|
json相较XML更加轻量级,读写速度更快, JSON 语法是 JavaScript 对象表示法语法的子集,其语法规则如下:
数据在名称/值对中
数据由逗号分隔
花括号保存对象
方括号保存数组
Qt提供QJsonDocument类对JSON文档进行读取和写入。 首先QJsonDocument :: fromJson()将JSON文档从其基于文本的表示形式转换为QJsonDocument(toJson()将其转换回文本), 解析器非常快速高效,并将JSON转换为Qt使用的二进制表示形式 然后通过isArray()和isObject()查询文档是否包含数组或对象,并通过array()或object()检索文档中包含的数组或对象,然后对其进行读取或操作。
在示例程序中用了另外一个简单JSON类来处理JSON数据。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | #ifndef JSON_H
#define JSON_H
#include <QVariant>
#include <QString>
namespace QtJson {
typedef QVariantMap JsonObject;
typedef QVariantList JsonArray;
QVariant clone(const QVariant &data);
void insert(QVariant &v, const QString &key, const QVariant &value);
void append(QVariant &v, const QVariant &value);
QVariant parse(const QString &json);
QVariant parse(const QString &json, bool &success);
QByteArray serialize(const QVariant &data);
QByteArray serialize(const QVariant &data, bool &success);
QString serializeStr(const QVariant &data);
QString serializeStr(const QVariant &data, bool &success);
void setDateTimeFormat(const QString& format);
void setDateFormat(const QString& format);
QString getDateTimeFormat();
QString getDateFormat();
class Object : public QVariant {
template<typename T>
Object& insertKey(Object* ptr, const QString& key) {
T* p = (T*)ptr->data();
if (!p->contains(key)) p->insert(key, QVariant());
return *reinterpret_cast<Object*>(&p->operator[](key));
}
template<typename T>
void removeKey(Object *ptr, const QString& key) {
T* p = (T*)ptr->data();
p->remove(key);
}
public:
Object() : QVariant() {}
Object(const Object& ref) : QVariant(ref) {}
Object& operator=(const QVariant& rhs) {
setValue(rhs);
return *this;
}
Object& operator[](const QString& key) {
if (type() == QVariant::Map)
return insertKey<QVariantMap>(this, key);
else if (type() == QVariant::Hash)
return insertKey<QVariantHash>(this, key);
setValue(QVariantMap());
return insertKey<QVariantMap>(this, key);
}
const Object& operator[](const QString& key) const {
return const_cast<Object*>(this)->operator[](key);
}
void remove(const QString& key) {
if (type() == QVariant::Map)
removeKey<QVariantMap>(this, key);
else if (type() == QVariant::Hash)
removeKey<QVariantHash>(this, key);
}
};
}
#endif //JSON_H
|
应用示例通过异步接收天气API返回的JSON数据,拿到JSON数据之后调用SltWeatherReply进行解析。 在该函数中,将对应的JSON对象、数组分别转换我们需要的类型,显示在UI上。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | void WeatherWidget::SltWeatherReply(const QByteArray &jsonData)
{
bool bOk = false;
QtJson::JsonObject result = QtJson::parse(jsonData, bOk).toMap();
if (bOk) {
// 当前天气接口 http://wthrcdn.etouch.cn/weather_mini?city=%1
if(result.value("desc").toString()!="OK")
{
qDebug() << "weather data error";
return ;
}
QStringList strTodayData;
QtJson::JsonObject jsonCity = result.value("data").toMap();
QtJson::JsonObject jsonyesterday = jsonCity.value("yesterday").toMap();
QString CityName = jsonCity.value("city").toString();
QString Info = jsonCity.value("ganmao").toString();
double Curtem = jsonCity.value("wendu").toDouble();
strTodayData << CityName;
strTodayData << jsonCity.value("wendu").toString();
QtJson::JsonArray jsonForecast = jsonCity.value("forecast").toList();
for (int i = 0; i < jsonForecast.size(); i++) {
QtJson::JsonObject jsonObj = jsonForecast.at(i).toMap();
QStringList strForeacast;
strForeacast << jsonObj.value("date").toString();
strForeacast << jsonObj.value("type").toString();
strForeacast << getTemperature(jsonObj.value("high").toString(),jsonObj.value("low").toString());
// 添加当天的天气
if (0 == i) {
strTodayData << QDateTime::currentDateTime().toString("dddd");
strTodayData << jsonObj.value("type").toString();
strTodayData << getTemperature(jsonObj.value("high").toString(),jsonObj.value("low").toString());
m_weatherView->setWeatherData(strTodayData);
}
m_weatherItems.insert(i, new QtListWidgetItem(i, strForeacast));
}
m_labelDate->setText(QTime::currentTime().toString("hh:mm") + tr("更新"));
m_weatherReport->SetItems(m_weatherItems);
}
else {
qDebug() << "get weather failed";
}
}
|
上面提到的天气数据就是存储在数据库中的, 我们访问天气API,实际上就是向服务器程序发起请求,服务器程序接收到请求,再去访问数据库得到数据, 然后再将数据包装成JSON格式,返回给我们。
实际上,我们的程序也可以直接访问数据库,通过SQL语句直接操纵数据库的数据。 当然市面上存在各种各样的数据库,Qt不可能每一种数据库都去写封装一套操作库,所以Qt就以数据库插件的形式来拓展对数据库的管理。
通过插件呢我们就可以直接读写数据库了。数据库的知识还是蛮多的我们就单独写了一章, Qt使用SQLite 。
12.3. 例程说明¶
野火提供的Qt Demo已经开源,仓库地址在:
文档所涉及的示例代码也提供下载,仓库地址在:
本章例程在 embed_qt_develop_tutorial_code/Data 下面 XML 和 Weather
12.3.1. 编程思路¶
本章包含多个例程,分别演示XML、JSON格式数据的读写。
XML例程为Qt官方例程,工程下面有5个子程序。
dombookmarks 演示了QDomDocument读写XBEL文件;
streambookmarks 演示了QXmlStreamReader、QXmlStreamWriter读写XBEL文件;
htmlinfo 演示了解析html文件,输出标签’title’、’a’、’p’的内容;
rsslisting 演示了从网络获取rss.xml文件并解析;
xmlstreamlint 演示了输出xml流。
Weather为野火demo获取天气示例程序。
程序通过QNetworkAccessManager访问某天气接口,获取天气数据。 天气数据为JSON格式,调用JSON类解析数据最终显示在UI上。
12.3.2. 代码讲解¶
12.3.3. 编译构建¶
Ubuntu 选择 Desktop Qt 5.11.3 GCC 64bit 套件,编译运行
LubanCat 选择 ebf_lubancat,只编译
提示
当两种构建套件进行切换时,请重新构建项目或清除之前项目。针对我们的工程还需要手动重新构建QtUI和Skin。
12.3.4. 运行结果¶
12.3.4.2. LubanCat实验¶
通过SCP或者NFS将编译好的程序拷贝到LubanCat上
NFS环境搭建 参考这里
SCP命令如下:
# scp传输文件
# scp 文件名 服务器上的某个用户@服务器ip:/文件保存路径
scp filename server_user_name@192.168.0.205:server_file_path
# 从服务器拉取文件
# scp 服务器上的某个用户@服务器ip:/服务器文件存放路径 拉取文件保存路径
scp server_user_name@192.168.0.229:server_file_path local_path
编译好的程序在 embed_qt_develop_tutorial_code/app_bin/data 目录中,通过scp命令将编译好的程序拉到LubanCat。
scp root@192.168.0.174:/home/embed_qt_develop_tutorial_code/app_bin/data/Weather /usr/local/qt-app/
在LubanCat运行程序,使用run_myapp.sh配置好环境,并执行 Weather 。
sudo /usr/local/qt-app/run_myapp.sh /usr/local/qt-app/Weather