解析XML文件

动机

网游服务器端开发过程中,很多控制游戏的参数都不应该直接硬编码的。需要各种各样的配置和脚本文件,好处:
  1. 可以由策划或数值去随意修改,而不用动程序代码
  2. 配置可以动态加载,可以动态改变服务器运行中的参数,对已经发布的功能进行调整
一般,可采用:
  1. ini配置,一般用于window下的软件,游戏客户端有时会用到。比较简单,功能有限。
  2. Excel表格,数值策划特别喜欢用这个,可以做很多运算,生成数值,可以用VBA做更多的事情。
  3. xml配置,对于层次比较深、结构比较复杂的数据,应该算最佳选择了。
XML(eXtensible Markup Language)是一种标记语言,用于说明数据是什么,以及携带数据信息。主要用于:
  1. 丰富文件(Rich Documents):自定文件描述并使其更丰富
  2. 元数据(Metadata):描述其它文件
  3. 配置文件(Configuration Files):设定应用程序的参数
下面主要介绍一下对于xml文件作为服务器配置时候的解析方案。

问题

解析下面的XML文件:
<config>
     <node1 prop1="100" prop2="i am string", prop3="2012-01-02 23:00:00"/>
 
     <node2 id="1" prop1="100" prop2="string1"/>
     <node2 id="2" prop1="100" prop2="string1"/>
     <node2 id="3" prop1="100" prop2="string1"/>
     <node2 id="4" prop1="100" prop2="string1"/>
 
     <node3 prop1="100"  prop2="string1"/>
     <node3 prop1="100"  prop2="string1"/>
     <node3 prop1="100"  prop2="string1"/>
     <node3 prop1="100"  prop2="string1"/>
</config>


  • node1 - 整个xml文件里面只有一个该节点
  • node2 - 有多个并且id属性可以作为它的键值,称之为节点map
  • node3 - 有多个名为node3的节点,但没有键值,称之为节点vector

一般的解决方案

使用XMLPaser(用libxml2封装的一个解析器)来解析(TinyXML也类似,DOM方式的都大同小异):
XMLPaser xml;
if (xml.initFile("xxx.xml"))
{
     xmlNodePtr root = xml.getRootNode("config");
     if (root)
     {
          // 解析node1的prop1和prop2属性
           struct NodeConfig{
                    int prop1;
                    string prop2;
               } config;

          xmlNodePtr node1 = root->getChildNode(root, "node1");
          if (node1)
          {
              
               node1->getNodePropNum(node1, "prop1", &config.prop1, sizeof(config.prop1));
               node2->getNodePropStr(node1, "prop2", config.prop2);
          }

          // 解析node2节点map
           struct NodeConfig{
                    int prop1;
                    string prop2;
           };
          std::map nodemap;

          xmlNodePtr node2 = root->getChildNode(root, "node2");
          while (node2)
          {
               int id;
               NodeConfig config;
               
               node2->getNodePropNum(node2, "id", &id, sizeof(id));
               node2->getNodePropNum(node2, "prop1", &config.prop1, sizeof(config.prop1));
               node2->getNodePropStr(node2, "prop2", config.prop2);

               nodemap[id] = config;               

               node2 = node2->getNextNode(node2, "node2");
          }

          // 解析node3节点vector
          .....
     }
}


坏味道分析

上面的代码,有几点不足之处,列举如下:
  1. 代码重复
    • 整个解析过程大同小异,一步一步遍历加载在内存中的节点树
    • 节点或节点属性的名称、节点的层次结构不同的时候,就得写不同的代码,一般会采用复制代码的方式
  2. 使用不便
    • 往往要写一个单件管理器,在服务器启动的时候加载该配置,然后在管理器里面把需要的数据结构都定义好
    • 使用的时候,引用管理器里面的成员变量,代码既丑陋又容易出错
  3. 不安全
    • 节点名称、属性名称都是字符串,拼错了,运行时会发生逻辑错误

更好的解决方案

C++的结构与XML的对应树状结构对应起来,也就是数据绑定方案(Xml Data Binding)。自己曾经实现过一个Xml Data Binding库,名为xml_parser。具体用法如下: step1: 编写一份描述XML结构的配置文件(也是一份XML文件,xml_parser.xml)

step1:  编写一份描述XML结构的配置文件(也是一份XML文件,xml_parser.xml)
<config>
     <node1 prop1="int" prop2="string", prop3="t_Date"/>
     <node2 id="int" prop1="int" prop2="string" container_="map" key_="id"/>
     <node3 prop1="int"  prop2="string" container_="vector" />
</config>

step2: 生成binding类
xmlpg -f xml_paser.xml -o xml_parser.h

step3: 应用程序中使用
xml_config xml;
          if (xml.load("xxx.xml"))
          {
               // node1的prop1和prop2属性
               int prop1 = xml.node1.prop1();
               string prop2 = xml.node1.prop2();
               t_Date date  = xml.node1.prop3();

               // node2节点map
               for (xml_paser::Node2MapIter it = xml.node2.begin(); it != xml.node2.end(); ++ it)
               {
                    int id = it->first;
                    int prop1 = it->second.prop1();
                    string prop2 = it->second.prop2();
               }

               // node3节点vector
               for (size_t i = 0; i < xml.node3.size(); i ++)
               {
                    int prop1 = xml.node3[i].prop1();
                    string prop2 = xml.node3[i].prop2();
               }
          }


更多解决方案

方式特征开源库
DOM(Document Object Model)
  • 文档对象模型,整个文档就是一个根节点及其子节点构成
  • 树状,有子节点、父节点、兄弟节点
  • 访问效率较低
  • libxml2
  • Xerces-C++
  • TinyXML
  • SlimXML
  • RapidXML
SAX(Simple API for XML)
  • 基于事件解析XML
  • libxml2
  • Xerces-C++
Data Binding
  • C++的结构与XML的对应树状结构对应起来,使用起来比较容易
  • 安全,C++的结构为静态的,不会因为写错节点或节点属性名称拼写错误而导致逻辑错误
  • 代码简洁、清晰
  • 访问效率高,对所为节点或节点属性的访问只是函数调用,而不像DOM方式去循环遍历整个子树的节点,做一系列字符串比较操作
  • 不足之处,结构必须已知,DOM方式则不论程序里面对应的结构,先把整个节点树加载到内存中,程序根据自己的需要去读取自己想要的节点或节点属性
  • CodeSynthesis XSD

XML与Excel表格做配置的比较

比较XMLExcel表格
结构树状的层次结构MxN的二维数组
适用性
  • 信息具有层次性
  • 结构复杂
  • 有一个键值可以索引的关联数组结构
  • 结构简单
  • 配置操作比较简单
不足之处
  • 配置起来不是那么方便,每个节点名、属性名都必须指定
  • 添加新列的时候,不一定所有行都用到该列属性,容易导致空间的浪费
 2012/04/25 21:15 于上海
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值