解析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<int, NodeConfig> 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)

<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_paser> 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表格做配置的比較

比較 XML Excel表格
結構 樹狀的層次結構 MxN的二維數組
適用性
  • 信息具有層次性
  • 結構復雜
  • 有一個鍵值可以索引的關聯數組結構
  • 結構簡單
  • 配置操作比較簡單
不足之處
  • 配置起來不是那么方便,每個節點名、屬性名都必須指定
  • 添加新列的時候,不一定所有行都用到該列屬性,容易導致空間的浪費

 

2012/04/25 21:15 於上海


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM