使用TinyXml庫值得注意的幾個地方

這兩天仔細看了下TinyXml的源代碼, 完美地搞清楚了一些網友和我自己的很多疑問. 鑑於TinyXml的實用性, 而且現在不少人在使用, 就決定在此做點有意義的事情 ---- 列出使用TinyXml庫值得注意的幾個地方.

     關於TinyXml庫的介紹網上有很多資料, 大家可以試着搜下, 這裏我就不多說了,TinyXml很小巧, 但它提供了非常豐富的接口, 特別適用於存取程序的數據, 如果你使用它, 相信你會感覺到它的靈活的.

    TinyXml下載地址: http://download.csdn.net/detail/hoyt00/3904805

                                http://sourceforge.net/projects/tinyxml/

 

     以下幾點值得注意哦:

 

1.new出指針而不見delete.

     如果你看了TinyXml的文檔或者一些網友寫的例子程序, 你會發現, 其中new出了很多對象而不見一個delete, 這個問題我當時也感覺特別難以接受, 網上也有各種各樣的說法, 有說"nwe出這麼多, 內存不泄漏纔怪", 有說"TinyXml的指針有自銷燬功能"等等, 這些說法都是錯誤的, 只要你正確的使用TinyXml, 無論你new多少(當然在內存的有效使用範圍之內)都不會有內存泄漏, 而且TinyXml的指針根本沒有自銷燬功能, 可以想像一下, 在C++中一個沒有經過任何類的包裝的指向堆內存的指針變量怎麼可能在它自身的生命結束時管得了它所指向的對象? 那它到底是怎麼回事?
     我們都知道(這裏我假設你已經看過TinyXml文檔), TinyXml是利用DOM(文檔對象模型)來呈現XML文檔的, 在它眼中XML文檔就是一棵樹, TiXmlDocument對象就是這棵樹的根結點, 在一個完整的文檔中, 除了它, 其餘結點必須都是它的後代, 所以TinyXml用了一個很巧妙的方法來析構每一個結點所對應的對象 ---- 每個結點的析構任務都委託給了它的父親, 這樣只要保證父親被正確析構,或者調用了父親的Clear函數, 它的所有後代都會被正確的析構,所以對整個文檔來說只要TiXmlDocument對象被正確析構,那就萬無一失了, 這棵樹會先從葉子銷燬, 一直到樹根. 像這種數據結構, 爲了保證樹的完整性而使用堆內存, 和由它自己管理內存也是理所當然的, 由此便可得到以下幾個結論:
     (1)TiXmlDocument對象最好在棧上創建, 如果在堆上創建了, 那你必須得自己銷燬它, 千萬不要像別的對象一樣new出了就不管了.
     (2)除了TiXmlDocument對象, 樹中的別的結點對象, 必須是堆上創建的, 千萬不要把棧上對象的地址鏈接(LinkEndChild)到樹中, 因爲棧上對象是不能用delete銷燬的, 當然TinyXml也有對棧上對象插入的方法, 以下會說到.
     (3) 除了文檔結點, new出的所有結點對象必須被鏈接到一個父親結點上, 纔可以不用管對象的delete, 而且必須不能管, 不然有可能整棵樹會被破壞而得不到正確的遍歷和析構, 如果new出的對象從來沒鏈接到某棵樹上, 而且將來也不打算鏈接, 無論有沒有別的結點鏈接到它身上, 你都必須手動delete它.
     (4)不要嘗試鏈接已經被別的結點鏈接過的指針, 很顯然, 這會造成無法估量的麻煩, 不用多說, 大家都懂的.

 

2.Insert vs. Link.

     當 然不是所有人都喜歡new, 可能他們更喜歡像std::vector那樣插入元素的副本而不是交給它一個指針, TinyXml也提供了同樣的方式插入結點 ---- Insert函數(InsertEndChild,InsertBeforeChild, InsertAfterChild), 因爲這些函數插入的是結點的副本(包括所有子結點) ,所以可以傳給它棧上或堆上的對象, 但是要注意, 如果傳入的是堆上對象, 那還必須手動delete它, 而不像LinkEndChild那樣鏈接了就不能管了, 因爲樹析構的時候析構的是它的副本, 而不是它. 我更傾向於使用Link + 堆上對象,而不Insert + 棧上對象, 因爲這樣可以省去創建副本的運行成本, 除非要插入到具體的位置, 而不是End,當然也可以修改源代碼使Link函數也有這種功能.

 

3.元素的屬性不是普通結點.

     與文本對象(TiXmlText)不同, 屬性對象(TiXmlAttribute)在文檔樹中不是以普通結點形式存在的, 也不從TiXmlNode類派生, 而是被元素結點的數據成員 ---- 一個TiXmlAttributeSet對象所管理, 這個TiXmlAttributeSet對象持有一個環狀的TiXmlAttribute對象鏈表(具體可以參看源代碼), 所以TiXmlElement對象通過調用Child系列函數是無法得到屬性的, 相反這件事是通過Attribute系列函數完成的. 訪問器(從TiXmlVisitor派生的用戶實現的類對象, 一種附加的回調機制, 具體參看源代碼)沒有針對TiXmlAttribute對象的接口, 而必須在對TiXmlElement對象的實現中處理屬性, 因此TiXmlAttribute類也沒有Accept虛函數.

 

4.儘量不要在xml中使用中文.

     TinyXml只認識UTF-8和ISO 8859-1編碼, 而不知GB2312爲何物, 但事實上你以GB2312在文檔中寫入中文, 之後可以正確讀取, 而且文檔在記事本中打開也能顯示正確的中文, 其實這是種巧合, 並不是TinyXml支持GB2312了.
     這個問題需要解釋下, 我已經仔細分析過了, TinyXml的函數有char*類型參數, 而沒有wchar_t*類型參數, 所以直接在程序中向文檔寫入中文必然是GB2312方式(這裏是以VC編譯器爲例的), 這時char*只是指向一塊內存塊, 跟void*一樣, 這塊內存只有用GB2312才能正確解釋爲中文, 因爲TinyXml是被設計跨平臺的, 所以不要指望它會調用WideCharToMultiByte和MultiByteToWideChar來幫你做轉換, 而以GB2312寫入的中文在讀取時這些中文字符的碼值是不變的, 也就是你在準備寫入文檔時是碼值是多少, 讀取到程序裏的值就是多少, 而把這個值當作GB2312編碼時就是原來寫文檔時的中文字符了, 當把寫入的文檔在記事本打開時, 由於沒有utf-8標記字節0xEF 0xBB 0xBF(TinyXml默認不寫入這三個字節, 稍後再說怎麼讓它寫入), 所以記事本把xml文件當作GB2312編碼打開, 就陰差陽錯地把原本錯誤的字節以正確的中文字符顯示了, 但終究這篇xml文檔是utf-8編碼的, 那些中文字符本應顯示爲亂碼的, 就這樣錯誤的寫入, 錯誤的讀取, 記事本錯誤的判斷, 都加到一起就離奇地沒有錯誤了.
     但錯誤還是錯誤, 有一個辦法, 就是在文檔頭部加上utf-8標記字節0xEF 0xBB 0xBF, 這樣記事本就能正確判斷文檔編碼, 正確地以utf-8打開, 正確地把中文顯示爲亂碼.
     基於這幾種錯誤疊加的現象, 如果你生成的xml文檔只用在你自己特定的程序中而不用在其它軟件(比如有時要用別的文本處理軟件打開查看內容), 那麼在文檔中存取中文是完全沒有問題的, 但在別的地方以utf-8打開時中文就是亂碼.
     保證所有地方都正確的方法是在寫入時和讀取時用WideCharToMultiByte和MultiByteToWideChar把GB2312編碼的中文字串轉換爲UTF-8編碼的中文字串, 如此, 所有軟件都能正確的讀取UTF-8編碼的中文字符(爲了讓記事本正確的判斷爲UTF-8, 可以加上utf-8標記字節, 雖然它不是標準, 但普遍使用).當然還是那句話 ---- 儘量不使用中文和其它非英文字符, 除非迫不得已. 

 

另外我在看源代碼時, 有幾個地方也發表下自己的想法:

 1.關於explicit關鍵字

     tinystr.h第85行  TIXML_EXPLICIT TiXmlString ( const char * str, size_typelen): rep_(0)

     (TIXML_EXPLICIT宏的內容是explicit)explicit關鍵字的作用是使單參數的構造函數不用作隱式轉換, 但這個構造函數有兩個參數, 很顯然, 它根本就沒有發生隱式轉換的可能, 也完全沒有必要使用explicit關鍵字.


2.關於成員函數const和非const

     TinyXml的類庫中有許多類的函數有const和非const版本, 爲什麼非const版本的函數不把函數體重新寫一遍而是用const_cast把const函數的const性質轉換掉再調用它呢? 其實函數體也就返回數據成員的那一兩句而已.


3.關於默認實參

     有些類的重載成員函數們, 一個函數的參數表和另一個函數的參數表的前面部分完全一致, 爲什麼不使用默認實參而是參數較少的函數版本調用參數較多的函數版本呢?

 

4.關於Test項目中的亂碼

     這也是很多人都疑惑的一個問題 ---- TinyXml庫自帶Test項目竟然編譯不過, 這個問題的原因很簡單, 打開xmltest.cpp文件, 定位到第1141行, 你會發現, 該行和下面幾行有亂碼, 這又是怎麼回事?

     其實這幾行是作者爲測試ISO 8859-1編碼(一種單字節編碼,編碼範圍使用了8位的所有取值, 也就是0到0xFF)文檔而寫, 意圖把ISO 8859-1字符串寫入文檔對象, 再從文檔對象讀取, 只有用ISO 8859-1編碼才能正確解釋.

     但 是編譯器以GB2312打開cpp文件時遇到這幾個字符就發生了奇怪的事情 ---- 源碼變亂碼了. 本來亂碼沒什麼的, 大不了我輸出到xml文件也亂碼就是了,但問題是那幾個字符不僅使它本身亂, 而且它後面的一個asc2字符也有50%的機率跟着遭殃, 因爲我們都知道用GB2312解碼字符串時如果第一個字節大於0x7F, 一般情況下會把它和接下來的一個字節當作一個整體字符, 當然這個字符可能顯示, 也可能不顯示, 無論怎樣都不是正確的,所以你會看到那些字串中有用於xml元素的"<"符號而沒有">"符號, 有的字串甚至都沒反引號, 這時不要簡單的加上反引號, 雖然語法正確了, 能編譯, 但xml語法還沒糾正, 調試時會導致文檔對象裏的斷言失敗而退出. 另外我還查看了這幾個亂碼字符的二進制值:

第1141行

"<?"

二進制值

22 3C E4 3E 22 0A

GB2312

"<(\xE4\x3E)"

ISO 8859-1

"<ä>"

 

第1142行

"C鰊t鋘t咪鰉鬧?

二進制值

22 43 F6 6E 74 E4 6E 74 DF E4 F6 FC C4 D6 DC 22 0A

GB2312

"C(\xF6\x6E)t(\xE4\x6E)t(\xDF\xE4)(\xF6\xFC)(\xC4\xD6)(\xDC\x22)

ISO 8859-1                                               

"CöntäntßäöüÄÖÜ"

 

第1143行

"</?";

二進制值

22 3C 2F E4 3E 22 3B 0A

GB2312

"</(\xE4\x3E)";

ISO 8859-1      

"</ä>";

其中二進制值省略了行首的空白字符, GB2312的括號中兩個字節表示被當作的一個整體,ISO 8859-1表示原本用ISO 8859-1編碼時顯示的字串. 可以看出以ISO 8859-1編碼的文本是完全符合C++語法和xml語法的, 大家在生成項目時將這塊代碼註釋掉就能通過編譯和調試了.

     還有剛纔提到TinyXml保存文檔時默認不寫入utf-8標記字節BOM, 如果想讓它保存時寫入這三個字節, 可以修改TiXmlDocument類添加一個成員:

[cpp]viewplaincopy

  1. public:  
  2.     void SetMicrosoftBOM(bool _useBOM) {useMicrosoftBOM = _useBOM;}  

當然也可以修改別的成員函數, 比如給SaveFile函數添加一個bool參數_useBOM等等, 看個人愛好了.


     關於TinyXml的使用就先寫到這了, 如果有不對的地方, 希望大家指出, 如果有不明白的地方也可以聯繫我!



XML也是有這幾個對象組成了,一般來說我們經常使用的類如下: 

l TiXmlDocument:文檔類,它代表了整個xml文件。

 l TiXmlDeclaration:聲明類,它表示文件的聲明部分,如上圖所示。

 l TiXmlComment:註釋類,它表示文件的註釋部分,如上圖所示。

 l TiXmlElement:元素類,它是文件的主要部分,並且支持嵌套結構,一般使用這種結構來分類的存儲信息,它可以包含屬性類和文本類,如上圖所示。 

n TiXmlAttribute/TiXmlAttributeSet:元素屬性,它一般嵌套在元素中,用於記錄此元素的一些屬性,如上圖所示。 

n TiXmlText:文本對象,它嵌套在某個元素內部,

如上圖所示。 TinyXml使用文檔對象模型(DOM)來解析xml文件,這種模型的處理方式爲在分析時,一次性的將整個XML文檔進行分析,並在內存中形成對應的樹結構,同時,向用戶提供一系列的接口來訪問和編輯該樹結構。這種方式佔用內存大,但可以給用戶提供一個面向對象的訪問接口,對用戶更爲友好,非常方便用戶使用。下面我們依次來介紹各個類的用法。


元素屬性  

屬性一般保存在元素中,它們爲使用“=”號連接的兩個字符串,左邊的表示屬性名,等號右邊的表示屬性值,通常使用字符串、整數和浮點數等數據類型表示。例如,pi = 3.14。 你可以通過如下的函數,返回屬性值。 

+const std::string* Attribute( const std::string& name ) const;

 +const std::string* Attribute( const std::string& name, int* i ) const;

 +const std::string* Attribute( const std::string& name, double* d ) const; 

在上面3個函數中,第一個函數使用字符串保存返回的屬性值,第二個函數把屬性值轉換爲整數然後返回,第三個函數把屬性值轉換爲浮點數然後返回。不過,第二、三個函數都會以字符串的形式記錄屬性值,並作爲函數的返回值返回。 

另外,你也可以使用模板函數: 

+template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const 來返回特點的屬性值,它會根據你傳入的參數,自動選擇合適數據類型。 

另外,本類也提供瞭如下三個函數讓你設置屬性,參數的類型和返回函數類似。

 +void SetAttribute( const std::string& name, const std::string& _value ); 

+void SetAttribute( const std::string& name, int _value ); 

+void SetDoubleAttribute( const char * name, double value ); 

FirstAttribute和LastAttribute可以讓你返回第一個和最後一個屬性,它們的函數聲明如下: 

+TiXmlAttribute* FirstAttribute() 

+TiXmlAttribute* LastAttribute() 

RemoveAttribute函數可以讓你刪除指定名稱的屬性,它的函數聲明如下:

 +void RemoveAttribute( const std::string& name )


元素函數總結   

ValueStr     //返回元素名稱 

SetValue     //設置元素名稱 

Parent     //返回父節點對象

 FirstChild    //返回第一個子節點

 LastChild     //返回最後一個子節點 

IterateChildren   //返回下一個子節點

 InsertEndChild   //在最後一個子節點後插入子節點 

InsertBeforeChild   //在指定的子節點前插入子節點

 InsertAfterChild   //在指定的子節點後插入子節點 

ReplaceChild    //替換指定的子節點 

RemoveChild    //刪除指定的子節點 

Clear     //刪除所有的子節點 

PreviousSibling   //返回同級中前一個節點 

NextSibling    //返回同級中後一個節點 

NextSiblingElement   //返回同級中後一個元素 

FirstChildElement   //返回第一個子元素節點

 Attribute     //返回元素中的屬性值 

QueryValueAttribute //返回元素中的屬性值 

SetAttribute    //設置元素中的屬性值 

FirstAttribute   //返回元素中第一個屬性對象 

LastAttribute    //返回元素中最後一個屬性對象 

RemoveAttribute   //刪除元素中指定的屬性對象


屬性類   

屬性爲名稱="值"對,元素可以具有屬性值,但名稱必須唯一。 你可以通過 

+const std::string& NameTStr() const 返回屬性名稱 

也可以通過下面三個函數返回屬性值: 

+const std::string& ValueStr() const 

+int     IntValue() const; 

+double    DoubleValue() const;


 當然你也可以設置屬性值,它們的函數聲明如下: 

+void SetName( const std::string& _name )

 +void SetIntValue( int _value );  

+void SetDoubleValue( double _value ); 

+void SetValue( const std::string& _value ) 

以上函數與元素類中的相關函數類似,這裏不重複介紹了。 在元素屬性中,通常具有許多屬性,你可以通過Next函數返回下一個屬性對象的指針,也可以通過Previous函數獲得上一個屬性對象的指針。

它們的函數聲明如下:

 +TiXmlAttribute* Next() 

+TiXmlAttribute* Previous() 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章