前言:
之前趁着這段時間比較空閑,也因為聽聞tinyxml大名,因此就閱讀了tinyxml的源碼.在上一篇博文中<tinyxml源碼解析(上)>中對tinyxml整體架構以及其中兩個基類TiXmlBase,TiXmlNode進行了注釋解析.不過文章寫得有點啰嗦,太過詳細.因此這篇文章將提取其中的部分設計而不是對於每個函數進行注釋.
本文將主要介紹tinyxml對於attribute的處理,各個元素類的簡單介紹以及tinyxml的整體處理流程.
正文:
還是先回顧下之前看的tinyxml的結構圖吧.

從圖上我們可以看到,TiXmlAttribute並不是繼承與TiXmlNode中,雖然其是xml的元素之一,在實現上很多也是element的葉節點,不過tinyxml並沒有這么做.可以先看下TiXmlAttribute的聲明.
View Code
1 class TiXmlAttribute : public TiXmlBase 2 { 3 friend class TiXmlAttributeSet;//TiXMlAttrubteSet之后我們將會看到 4 public: 5 //....構造函數 6 7 //返回屬性的值 8 const char* Name() const { return name.c_str(); } 9 const char* Value() const { return value.c_str(); } 10 //.... 11 //提供了比IntValue()更好的錯誤檢查.后頭還是調用了sscanf進行處理,而IntValue則是采用了atoi 12 //QueryXXXValue系列都是采用了sscanf進行處理. 13 int QueryIntValue( int* _value ) const; 14 int QueryDoubleValue( double* _value ) const; 15 16 //設置值,對於有類型轉化的,采用了sprintf進行處理 17 void SetName( const char* _name ) { name = _name; } 18 void SetValue( const char* _value ) { value = _value; } 19 20 void SetIntValue( int _value ); 21 void SetDoubleValue( double _value ); 22 23 //提供了string的版本 24 ///... 25 26 ///簡單的比較函數 27 ///.... 28 ///Parse將在下面統一講解 29 virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); 30 31 32 private: 33 TiXmlAttribute( const TiXmlAttribute& ); // not implemented. 34 void operator=( const TiXmlAttribute& base ); // not allowed. 35 36 TiXmlDocument* document; // A pointer back to a document, for error reporting. 37 TIXML_STRING name; 38 TIXML_STRING value; 39 TiXmlAttribute* prev; 40 TiXmlAttribute* next; 41 };
TiXmlAttribute提供了少部分功能的轉化函數.不過特殊的是其Next函數以及Previous函數的實現比較特別.
View Code
1 const TiXmlAttribute* TiXmlAttribute::Next() const 2 { 3 //這里判斷是否到尾部的方式是判斷next的成員的值 4 if ( next->value.empty() && next->name.empty() ) 5 return 0; 6 return next; 7 } 8 9 const TiXmlAttribute* TiXmlAttribute::Previous() const 10 { 11 // 這里也是 12 if ( prev->value.empty() && prev->name.empty() ) 13 return 0; 14 return prev; 15 }
原因在於,tinyxml使用了循環雙向鏈表進行處理.看下面的TiXmlAttributeSet就可以看得出了.作者給出的理由是:他喜歡循環鏈表.另外,說明了相對於典型雙向鏈表的獨立性(這個我也沒搞明白是怎么回事).
TiXmlAttributeSet是一個工具類,其負責管理Element中的attribute.提供了添加,刪除,查找等功能,使得代碼更為簡潔.
1 //折疊起來總是有BUG.打不開.所以就只要不折疊了... 2 class TiXmlAttributeSet 3 { 4 public: 5 //構造和析構函數,構造函數就是把sentinel的頭尾連在一起,而析構則是兩句assert,判斷是否所有元素被移除. 6 7 void Add( TiXmlAttribute* attribute ); 8 void Remove( TiXmlAttribute* attribute ); 9 10 //有元素返回,沒元素返回null 11 const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } 12 TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } 13 const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } 14 TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } 15 16 //查找 17 TiXmlAttribute* Find( const char* _name ) const; 18 TiXmlAttribute* FindOrCreate( const char* _name ); 19 ///stl版本 20 ///... 21 22 private: 23 //因為TiXmlAttribute禁止復制,因此AttributeSet也禁止 24 TiXmlAttributeSet( const TiXmlAttributeSet& ); 25 void operator=( const TiXmlAttributeSet& ); 26 27 TiXmlAttribute sentinel;//循環鏈表的頭.其目的在於做為頭和尾的區分. 28 };
可以看看循環鏈表的處理.
View Code
1 void TiXmlAttributeSet::Add( TiXmlAttribute* addMe ) 2 { 3 #ifdef TIXML_USE_STL 4 assert( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set. 5 #else 6 assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set. 7 #endif 8 9 addMe->next = &sentinel; 10 addMe->prev = sentinel.prev; 11 12 sentinel.prev->next = addMe; 13 sentinel.prev = addMe; 14 } 15 16 void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) 17 { 18 TiXmlAttribute* node; 19 20 for( node = sentinel.next; node != &sentinel; node = node->next ) 21 { 22 if ( node == removeMe ) 23 { 24 node->prev->next = node->next; 25 node->next->prev = node->prev; 26 node->next = 0; 27 node->prev = 0; 28 return; 29 } 30 } 31 assert( 0 ); // we tried to remove a non-linked attribute.
在查看了attribute的處理后,我們就可以看看element的處理了.
View Code
1 class TiXmlElement : public TiXmlNode 2 { 3 public: 4 5 //構造,復制,析構函數 6 ///... 7 8 ///一系列的Attribute函數僅僅就是調用attributeset中的find,查找到后調用attribute的query函數 9 ///其他都類似 10 const char* Attribute( const char* name ) const 11 { 12 const TiXmlAttribute* node = attributeSet.Find( name ); 13 if ( node ) 14 return node->Value(); 15 return 0; 16 } 17 18 //set函數也不例外,就是find然后set 19 void TiXmlElement::SetAttribute( const char * name, int val ) 20 { 21 TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); 22 if ( attrib ) { 23 attrib->SetIntValue( val ); 24 } 25 } 26 27 //可想而知,remove肯定也是一樣的,調用attributeset的函數進行處理 28 void RemoveAttribute( const char * name ); 29 30 //獲取其中第一個文字節點 31 const char* GetText() const; 32 33 /// 復制當前節點,重寫了TiXmlNode的純虛函數 34 virtual TiXmlNode* Clone() const; 35 //打印函數, 36 virtual void Print( FILE* cfile, int depth ) const; 37 38 //parse函數之后統一說 39 virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); 40 41 //這部分設計的很精巧.在父類TiXmlNode中,所有的ToXXXX函數返回皆為null.而在子類中相應的重寫函數,使之轉化成為可能.而轉化成其他類的函數仍然不可以. 42 virtual const TiXmlElement* ToElement() const { return this; } 43 virtual TiXmlElement* ToElement() { return this; } 44 45 virtual bool Accept( TiXmlVisitor* visitor ) const; 46 47 protected: 48 //復制函數,提供給復制構造函數 49 void CopyTo( TiXmlElement* target ) const; 50 void ClearThis(); 51 //獲取element中的值.在parse中被調用. 52 const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); 53 54 private: 55 TiXmlAttributeSet attributeSet;//管理attribute列表 56 };
剩下的幾個元素,如declaration,unknown,text,comment都和element差別不大.我們直接來看TiXmlDocument:
View Code
1 class TiXmlDocument : public TiXmlNode 2 { 3 public: 4 //構造函數 5 TiXmlDocument() 6 { 7 tabsize = 4;//設定輸出tab格式 8 useMicrosoftBOM = false;//設定是否采用ms的utf8-bom格式 9 ClearError(); 10 } 11 12 //復制構造函數 13 TiXmlDocument( const TiXmlDocument& copy ); 14 TiXmlDocument& operator=( const TiXmlDocument& copy ) 15 { 16 Clear(); 17 copy.CopyTo( this );//采用了private的copyTo函數 18 return *this; 19 } 20 21 virtual ~TiXmlDocument() {}//析構沒什么好做的 22 23 //保存和讀取文本函數, 24 bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); 25 bool SaveFile( FILE* ) const; 26 27 //分析,下面將主要分析這個函數 28 virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); 29 30 //帶格式輸出 31 virtual void Print( FILE* cfile, int depth = 0 ) const; 32 void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); 33 34 //同TiXmlElement 35 virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. 36 virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. 37 38 /** Walk the XML tree visiting this node and all of its children. 39 */ 40 virtual bool Accept( TiXmlVisitor* content ) const; 41 42 protected : 43 // 重寫的Clone函數 44 virtual TiXmlNode* Clone() const; 45 #ifdef TIXML_USE_STL 46 virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); 47 #endif 48 49 private: 50 //復制的內部函數 51 void CopyTo( TiXmlDocument* target ) const; 52 53 bool error; 54 int errorId; 55 TIXML_STRING errorDesc; 56 int tabsize; 57 TiXmlCursor errorLocation;//定位錯誤,包涵了行數和列數 58 bool useMicrosoftBOM; // 使用utf-bom 59 };
大部分只是簡單的設置,我們就着重挑幾個感興趣的看看.
首先是文件操作,LoadFile以及SaveFile
View Code
1 //載入數據 2 bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) 3 { 4 if ( !file ) 5 { 6 SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); 7 return false; 8 } 9 10 // 初始化數據 11 Clear(); 12 location.Clear(); 13 14 // 采用fseek的方式來獲取文件的大小。並且預分配數據大小,加速大文件處理 15 long length = 0; 16 fseek( file, 0, SEEK_END ); 17 length = ftell( file ); 18 fseek( file, 0, SEEK_SET ); 19 20 // 比較奇怪的情況。 21 if ( length <= 0 ) 22 { 23 SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); 24 return false; 25 } 26 27 //先new需要讀取的數據的大小. 28 char* buf = new char[ length+1 ]; 29 buf[0] = 0; 30 31 //然后一次性fread進來. 32 if ( fread( buf, length, 1, file ) != 1 ) { 33 delete [] buf; 34 SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); 35 return false; 36 } 37 38 // 統一回車符,采用p,q兩個指針進行處理. 39 //將\r\n,\n,\r統一轉化為\n. 40 const char* p = buf; // the read head 41 char* q = buf; // the write head 42 const char CR = 0x0d; 43 const char LF = 0x0a; 44 45 buf[length] = 0; 46 while( *p ) { 47 assert( p < (buf+length) ); 48 assert( q <= (buf+length) ); 49 assert( q <= p ); 50 51 //是\r 52 if ( *p == CR ) { 53 *q++ = LF;//轉化為\n,並且判斷下一個字符,,如果是\n,則跳過 54 p++; 55 if ( *p == LF ) { 56 p++; 57 } 58 } 59 else { 60 *q++ = *p++; 61 } 62 } 63 assert( q <= (buf+length) ); 64 *q = 0; 65 66 Parse( buf, 0, encoding );//進行解析,生成xml節點樹 67 68 delete [] buf;//刪除緩存的數據 69 return !Error(); 70 } 71 //save相對簡單的多 72 bool TiXmlDocument::SaveFile( FILE* fp ) const 73 { 74 //如果是ms的格式,則寫一個UTF8的頭 75 if ( useMicrosoftBOM ) 76 { 77 const unsigned char TIXML_UTF_LEAD_0 = 0xefU; 78 const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; 79 const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; 80 81 fputc( TIXML_UTF_LEAD_0, fp ); 82 fputc( TIXML_UTF_LEAD_1, fp ); 83 fputc( TIXML_UTF_LEAD_2, fp ); 84 } 85 //然后調用Print進行格式化輸出 86 Print( fp, 0 ); 87 return (ferror(fp) == 0); 88 }
接下來是我們用於輸出的Print函數:
View Code
1 void TiXmlDocument::Print( FILE* cfile, int depth ) const 2 { 3 assert( cfile ); 4 for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) 5 { 6 //使用虛函數繼承的方式,調用相應的處理函數 7 node->Print( cfile, depth ); 8 fprintf( cfile, "\n" ); 9 } 10 }
這里用到了多態的方式,來實現相應的函數調用.
而生成所有xml樹的方法Parse也是想同的想法.
這里就只列舉了TiXmlDocument和TiXmlElement的方法.其他的都是類似的處理方式.
View Code
1 //在TiXmlDocument::Parse中調用,用於判斷接下來數據p中是什么類型的節點,並且生成該類型的節點返回 2 TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) 3 { 4 TiXmlNode* returnNode = 0; 5 6 p = SkipWhiteSpace( p, encoding ); 7 //跳過空格后應該是'<',否則要么出錯要么結束,返回null 8 if( !p || !*p || *p != '<' ) 9 { 10 return 0; 11 } 12 13 p = SkipWhiteSpace( p, encoding ); 14 15 if ( !p || !*p ) 16 { 17 return 0; 18 } 19 20 21 22 const char* xmlHeader = { "<?xml" }; 23 const char* commentHeader = { "<!--" }; 24 const char* dtdHeader = { "<!" }; 25 const char* cdataHeader = { "<