找回密码
 立即注册
收起左侧

【独家连载】Qt入门与提高:KS04-13 配置文件-XML格式

2
回复
5180
查看
[复制链接]
累计签到:41 天
连续签到:1 天
来源: 原创 2018-11-19 11:45:35 显示全部楼层 |阅读模式

马上注册,查看详细内容!注册请先查看:注册须知

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本帖最后由 baizy77 于 2019-7-2 20:35 编辑

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

结语
----------------------------------------------------------------------------------------------------------------------
我们通过XML文件的保存介绍了XML文件的格式以及Qt的相关类。其实XML还有其他一些知识我们没有展开,比如css、CDATA、命名空间等内容。我们先带大家热热身,等大家可以熟练操作XML了,这些内容也就很容易理解和掌握了。使用DOM方式操作XML文件的优点是编码简单,缺点是占用内存非常多,而且也比流方式操作XML要慢,那么,您希望了解使用流方式读取XML相关的内容吗?欢迎关注《QT入门与提高-GUI产品开发》。选课后加课程的QQ群可以与更多学员交流,日常工作问题说不定可以在群中得到快速解答。

注解
----------------------------------------------------------------------------------------------------------------------


回复

使用道具 举报

累计签到:7 天
连续签到:1 天
2018-12-27 13:48:33 显示全部楼层
老白要出书了,阔以阔以!

点评

还在努力中,感谢支持,也欢迎批评指正。  详情 回复 发表于 2018-12-27 23:03
回复 支持 反对

使用道具 举报

累计签到:41 天
连续签到:1 天
2018-12-27 23:03:57 显示全部楼层
liudianwu 发表于 2018-12-27 13:48
老白要出书了,阔以阔以!

还在努力中,感谢支持,也欢迎批评指正。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了