本帖最后由 baizy77 于 2019-7-2 20:35 编辑
版权声明--------------------------------------------------------------------------------------------------------------------- 作者: 女儿叫老白 转载请注明出处! --------------------------------------------------------------------------------------------------------------------- 引言 ---------------------------------------------------------------------------------------------------------------------- 在进行项目或者产品研发时,我们的软件模块为了适应不同的应用场景,经常需要进行灵活配置,而配置文件可能会以如下形式存在: l unix风格的配置文件 l xml格式 l ini格式 今天开始,我们为大家介绍配置文件的设计与访问。 正文 ---------------------------------------------------------------------------------------------------------------------- unix风格的配置文件不是本套教程的重点,在此仅做一下简单介绍,我们看一个示例: - #
- # The network configuration file. This file is currently only used in
- # conjunction with the TI-RPC code in the libtirpc library.
- #
- # Entries consist of:
- #
- # <network_id> <semantics> <flags> <protofamily> <protoname> \
- # <device> <nametoaddr_libs>
-
- # The <device> and <nametoaddr_libs> fields are always empty in this
- # implementation.
- #
- udp tpi_clts v inet udp - -
- tcp tpi_cots_ord v inet tcp - -
- udp6 tpi_clts v inet6 udp - -
- tcp6 tpi_cots_ord v inet6 tcp - -
- rawip tpi_raw - inet - - -
- local tpi_cots_ord - loopback - - -
- unix tpi_cots_ord - loopback - - -
复制代码 上面的示例是/etc/netconfig文件的内容。可以看出它既不是xml格式也不是ini格式的配置。这种配置比较灵活,因此不同的配置文件也没有互通性。也就是说每次访问某个这种风格的配置文件可能都需要重新编程。 下面进入本节主题,xml格式的配置文件。 XML(Extensible Markup Language),中文名称为:可扩展标记语言。1998年2月,W3C正式批准了可扩展标记语言的标准定义。可扩展标记语言可以对文档和数据进行结构化处理,以便在不同的应用之间交换数据。我们来看一个XML的例子: - <?xml version="1.0" encoding="UTF-8"?>
- <bookshelf>
- <book category="student">
- <title>How to Learn Qt5</title>
- <author>女儿叫老白</author>
- <year>2018</year>
- <price>?</price>
- </book>
- <book category="teacher">
- <title>C++跨平台开发干货系列教程教学指南</title>
- <author>女儿叫老白</author>
- <year>2019</year>
- <price>?</price>
- </book>
- </bookshelf>
复制代码 我们看到,第一行是 XML 声明。它定义 XML 的版本(1.0)和所使用的编码(UTF-8 : 万国码, 可显示各种语言)。关于编码,大家可以看 这篇文章[注1]。 从第二行开始就是XML的内容了,实际上,XML是一种带有层级的描述,可以嵌套。比如<bookshelf>包含了两个<book>元素。XML的元素可以带有属性和值,比如<book>元素的: 一个元素可以带有多个属性和值的键值对,比如下面的XML文件片段: - <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
复制代码 OK,现在我们来为大家介绍XML文件的存取。 我们先看一下XML文件的保存,比如我们希望保存如下的XML文件: <?xml version="1.0" encoding="GB2312"?>
<doc>
<courses institution="星点课堂" teacher="女儿叫老白" count="7">
<lesson id="1" fee="免费" url="https://study.163.com/instructor/1143174933.htm">C++老鸟日记</lesson>
<lesson id="2" fee="免费" url="https://study.163.com/instructor/1143174933.htm">C++跨平台开发中的编译错误</lesson>
<lesson id="3" url="https://study.163.com/instructor/1143174933.htm">Qt入门与提高-GUI产品开发</lesson>
<lesson id="4" url="sorry, not ready">C++跨平台服务模块开发</lesson>
</courses>
</doc>
我们先来总体看一下这个XML文件。它的根为doc元素,doc下面有一个子元素courses,courses元素下面有4个lesson子元素。 我们分为XML文件创建和读取两个场景来介绍。 场景1. 首先我们从创建XML文件开始: - QString strPath = QCoreApplication::applicationDirPath();
- strPath = ns_train::getPath(strPath);
- if (!strPath.endsWith("/")) {
- strPath += "/";
- }
-
- QString strFileName = strPath + "test04_13.xml"; // 程序运行目录下的xml文件
- QFile file(strFileName);
- if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) { // QFile::Truncate,需要清空原来的内容
- return ;
- }
复制代码 首先,为了方便我们选择将文件保存到运行程序所在目录。这里用到了QCoreApplication::applicationDirPath()。所以在此之前,我们需要在main()函数中构造一个QApplication对象。得到目录的路径之后,为了以防万一,我们为路径添加了最后的"/"。然后组织文件名。打开文件时用了:只写、文本、清空这3个选项。 然后,我们利用QTextStream来写文件,并设置了字符编码: - QTextStream out(&file);
- out.setCodec("GB2312");
复制代码 创建XML文档对象QDocument: - QDomDocument document;
- QDomProcessingInstruction instruction; //添加处理指令
- // 请注意此处的编码必须和您的源代码文件的编码保持一致。
- instruction = document.createProcessingInstruction("xml",
- "version="1.0" encoding="GB2312"");
- document.appendChild(instruction);
复制代码 上述代码中的QDomProcessingInstruction用来写入XML的第一行:版本号和编码。我们这里设置为1.0版,编码为"GB2312"。 最后一句document.appendChild(instruction)用来将instruction添加到document对象。 然后,我们开始创建XML的第一个根元素"doc"并将它作为子节点添加到document对象: - // doc
- QDomElement rootDoc = document.createElement("doc");
- document.appendChild(rootDoc);
复制代码 请注意,创建元素接口为QDocument::createElement()。 然后,我们创建"courses"元素并将它作为子节点添加到rootDoc(也就是"doc"元素)。 - // courses
- QDomElement eleCourses = document.createElement("courses");
- rootDoc.appendChild(eleCourses);
复制代码 然后,我们调用setAttribute()接口为元素添加属性: - strName = "institution";
- strValue = QString::fromLocal8Bit("星点课堂");
- eleCourses.setAttribute(strName, strValue);
复制代码 为了显示课程名称,我们使用QDomDocument::createTextNode()创建了QDomText对象,并将其作为子节点添加到eleLesson 用同样的方法,我们设置了“teacher”、“count”等属性。 - QDomText dtText = document.createTextNode(QString::fromLocal8Bit("C++老鸟日记"));
- eleLesson.appendChild(dtText);<span style="background-color: rgb(255, 255, 255);"> 请注意,我们用到汉字时调用了QString::fromLocal8Bit()。</span>
复制代码 等所有属性设置都设置完毕,我们再将eleLesson对象添加到其父节点。 - eleCourses.appendChild(eleLesson);
复制代码 请注意,如果在设置eleLesson对象的属性之前将它添加到eleCourses,那么将添加一个没有属性的子节点给eleCourses,因为appendChild()接口传入的是拷贝而不是引用。 我们用同样的方法将其他几门课程添加到eleCourses。为了防止eleLesson有缓存,我们每次都重新生成新的节点。 最后,我们将XML内容写入文件并关闭。 - document.save(out, 4, QDomNode::EncodingFromTextStream); // 4:缩进值
- file.close();
复制代码 上述代码中,我们将XML的缩进设置为4。 场景2. 读取XML文件: 读取XML时,我们使用了只读和文本两个设置。 - QFile file(strFileName);
- if (!file.open(QFile::ReadOnly | QFile::Text)) {
- return;
- }
复制代码通过QDomDocument::setContent()接口可以读取整个XML到内存: - QDomDocument document;
- QString error;
- int row = 0, column = 0;
- if (!document.setContent(&file, false, &error, &row, &column)) {
- return;
- }
复制代码第二个参数false表示不用解析命名空间,如果您的XML中需要使用命名空间的话,将该值设置为true。 error用来存放打开文件过程中产生的错误信息。row和column用来记录出错时的行列数。 然后,我们获取第一个根元素,并判断是否是我们所期望的节点元素: - QDomElement rootDoc = document.firstChildElement();
- if (rootDoc.nodeName() != "doc") {
- return;
- }
复制代码 然后,我们获取到doc元素的第一个子元素"courses",并判断它是否是一个元素(拥有属性的节点才是元素): - QDomElement eleCourses = rootDoc.firstChildElement();
- while (eleCourses.isElement()) {
复制代码 因为我们写XML时就知道"courses"节点是有属性的,所以这里我们可以对它进行这样的判断。 然后,我们判断其标签名是否是期望的"courses",如果不是那么我们通过调用eleCourse. nextSiblingElement()直接跳到其下一个兄弟(同等级的下一个节点): - strName = eleCourses.tagName();
- if (strName != "courses") {
- eleCourses = eleCourses.nextSiblingElement();
- continue;
- }
复制代码 然后,通过QDomElement::attribute()接口读取元素的属性值: - strName = "institution";
- strValue = eleCourses.attribute(strName);
复制代码 用相同的方法获取到"teacher"、"count"等属性值。 然后,遍历其子节点: - QDomElement eleLesson = eleCourses.firstChildElement();
- while (eleLesson.isElement()) {
- strName = eleLesson.tagName();
- if (strName == "lesson") {
- ……
- }
- eleLesson = eleLesson.nextSiblingElement();
- }
-
复制代码 上述代码中,eleCourses.firstChildElement()得到"courses"的第一个子元素,如果eleLesson有效则进入while循环。 while循环内部首先判断eleLesson的标签是否是期望值,如果不是则跳转到下一个元素。 如果是“lesson”标签,则读取其属性值: - QDomNamedNodeMap attrs = eleLesson.attributes();
- int nC = attrs.count();
- for (int i = 0; i < nC; ++i) {
- QDomAttr attrEle = attrs.item(i).toAttr();
- if (!attrEle.isNull()) {
- strName = attrEle.name();
- strValue = attrEle.value();
- }
- }
-
复制代码 上述代码中,eleLesson.attributes()得到一个QDomNamedNodeMap对象,该对象保存了属性、值的键值对映射。我们对其遍历,获取每一个属性名称、属性值。每一个属性对应一个QDomAttr。 然后,我们访问eleLesson的文本子节点,eleLesson.firstChild()可以获取到第一个子节点: - if (!eleLesson.firstChild().isNull()) {
- dtText = eleLesson.firstChild().toCharacterData();
- if (!dtText.isNull()) {
- strValue = dtText.data();
- }
- }
复制代码 因为我们保存XML文件时,"lesson"元素的第一个子节点就是文本,所以我们对应这样编码是可以的。toCharacterData()将其转化为字符对象,然后调用data()就可以获取到内容了。大家试一下吧。
结语 ---------------------------------------------------------------------------------------------------------------------- 我们通过XML文件的保存介绍了XML文件的格式以及Qt的相关类。其实XML还有其他一些知识我们没有展开,比如css、CDATA、命名空间等内容。我们先带大家热热身,等大家可以熟练操作XML了,这些内容也就很容易理解和掌握了。使用DOM方式操作XML文件的优点是编码简单,缺点是占用内存非常多,而且也比流方式操作XML要慢,那么,您希望了解使用流方式读取XML相关的内容吗?欢迎关注 《QT入门与提高-GUI产品开发》。选课后加课程的QQ群可以与更多学员交流,日常工作问题说不定可以在群中得到快速解答。
注解 ----------------------------------------------------------------------------------------------------------------------
|