TinyXML教程

TinyXML教程

這是什麼?

這個教程對如何有效地使用TinyXML有幾個技巧與建議。

我也會盡量包含一些C++的小技巧,如如何將string類型轉換爲整形和反過來轉換。這並不是只針對TinyXML自身,只是因爲它或許對你的項目有用,所以我才放在本文的一些地方。

如果你並不瞭解基本的C++概念,這篇教程對你並沒有用。同樣地,如果你不知道DOM是什麼,先到別的地方瞭解一下。

在開始之前

一些將要被用到的XML數據組或文件。

example1.xml:

<?xmlversion="1.0" ?>

<Hello>World</Hello>

example2.xml:

<?xml version="1.0" ?>
<poetry>
        <verse>
                 Alas
                   Great World
                          Alas (again)
        </verse>
</poetry>

example3.xml:

<?xml version="1.0" ?>
<shapes>
        <circle name="int-based" x="20" y="30" r="50" />
        <point name="float-based" x="3.5" y="52.1" />
</shapes>

example4.xml

<?xml version="1.0" ?>
<MyApp>
    <!-- Settings for MyApp -->
    <Messages>
        <Welcome>Welcome to MyApp</Welcome>
        <Farewell>Thank you for using MyApp</Farewell>
    </Messages>
    <Windows>
        <Window name="MainFrame" x="5" y="15" w="400" h="250" />
    </Windows>
    <Connection ip="192.168.0.1" timeout="123.456000" />
</MyApp>

開始

從文件中載入XML

從文件導入到TinyXML DOM的最簡單方法就是:

TiXmlDocument doc( "demo.xml" );
doc.LoadFile();

下面展示了一個更實用的方法。它會載入文件並且將文件內容顯示到STDOUT。

// load the named file and dump its structure to STDOUT
void dump_to_stdout(const char* pFilename)
{
        TiXmlDocument doc(pFilename);
        bool loadOkay = doc.LoadFile();
        if (loadOkay)
        {
                 printf("\n%s:\n", pFilename);
                 dump_to_stdout( &doc ); // defined later in the tutorial
        }
        else
        {
                 printf("Failed to load file \"%s\"\n", pFilename);
        }
}

最簡單的證實這個函數的方法是使用像下面一樣的main函數:

int main(void)
{
        dump_to_stdout("example1.xml");
        return 0;
}

回顧Example 1中的XML:

<?xml version="1.0" ?>
<Hello>World</Hello>

使用這個XML運行上面的程序將在控制檯/DOS窗口中輸出如下的結果:

DOCUMENT
+ DECLARATION
+ ELEMENT Hello
  + TEXT[World]

“dump_to_stdout”函數會在本教程的下面重新定義,如果你想理解遞歸遍歷一個DOM,這將非常有用。

程序化創建文檔

這是如何程序化構建Example 1:

void build_simple_doc( )
{
        // Make xml: <?xml ..><Hello>World</Hello>
        TiXmlDocument doc;
        TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "", "" );
        TiXmlElement * element = new TiXmlElement( "Hello" );
        TiXmlText * text = new TiXmlText( "World" );
        element->LinkEndChild( text );
        doc.LinkEndChild( decl );
        doc.LinkEndChild( element );
        doc.SaveFile( "madeByHand.xml" );
}

這個文件可以通過如下方法載入並且顯示在控制檯:

dump_to_stdout("madeByHand.xml"); // 這個函數將會在本教程後續定義

你會發現它與Example 1相同:

madeByHand.xml:
Document
+ Declaration
+ Element [Hello]
  + Text: [World]

這個代碼生成完全相同的XML DOM,但是它卻展示了創建與連接節點的不同順序:

void write_simple_doc2( )
{
        // same as write_simple_doc1 but add each node
        // as early as possible into the tree.
 
        TiXmlDocument doc;
        TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "", "" );
        doc.LinkEndChild( decl );
        
        TiXmlElement * element = new TiXmlElement( "Hello" );
        doc.LinkEndChild( element );
        
        TiXmlText * text = new TiXmlText( "World" );
        element->LinkEndChild( text );
        
        doc.SaveFile( "madeByHand2.xml" );
}

這些都產生相同的XML:

<?xml version="1.0" ?>
<Hello>World</Hello>

或者相同的結構:

DOCUMENT
+ DECLARATION
+ ELEMENT Hello
  + TEXT[World]

屬性(Attributes)

給一個存在的節點設置屬性是簡單的:

window = new TiXmlElement( "Demo" );  
window->SetAttribute("name", "Circle");
window->SetAttribute("x", 5);
window->SetAttribute("y", 15);
window->SetDoubleAttribute("radius", 3.14159);

只要你想,你就可以讓它通過TiXmlAttribute類實現。

下面的代碼展示了一個(並不是唯一)獲取節點所有屬性的方法,打印名稱和字符值,並且如果這些值能夠轉換爲整型或double類型,打印這些值:

// print all attributes of pElement.
// returns the number of attributes printed
int dump_attribs_to_stdout(TiXmlElement* pElement, unsigned int indent)
{
   if ( !pElement ) return 0;
 
   TiXmlAttribute* pAttrib=pElement->FirstAttribute();
   int i=0;
   int ival;
   double dval;
   const char* pIndent=getIndent(indent);
   printf("\n");
   while (pAttrib)
   {
printf( "%s%s: value=[%s]", pIndent, pAttrib->Name(), pAttrib->Value());
 
       if (pAttrib->QueryIntValue(&ival)==TIXML_SUCCESS)  printf( " int=%d", ival);
       if (pAttrib->QueryDoubleValue(&dval)==TIXML_SUCCESS) printf( " d=%1.1f", dval);
       printf( "\n" );
       i++;
       pAttrib=pAttrib->Next();
   }
   return i;
}

將文檔寫入文件

將一個預先構建的DOM寫入文件是個簡單的事:

doc.SaveFile( saveFilename );

回憶,例如example 4:

<?xml version="1.0" ?>
<MyApp>
    <!-- Settings for MyApp -->
    <Messages>
        <Welcome>Welcome to MyApp</Welcome>
        <Farewell>Thank you for using MyApp</Farewell>
    </Messages>
    <Windows>
        <Window name="MainFrame" x="5" y="15" w="400" h="250" />
    </Windows>
    <Connection ip="192.168.0.1" timeout="123.456000" />
</MyApp>

如下的函數創建了這個DOM並且寫入”appsettings.xml“文件:

void write_app_settings_doc( )  
        TiXmlDocument doc;  
        TiXmlElement* msg;
         TiXmlDeclaration* decl = new TiXmlDeclaration( "1.0", "", "" );  
        doc.LinkEndChild( decl );  
 
        TiXmlElement * root = new TiXmlElement( "MyApp" );  
        doc.LinkEndChild( root );  
 
        TiXmlComment * comment = new TiXmlComment();
        comment->SetValue(" Settings for MyApp " );  
        root->LinkEndChild( comment );  
 
        TiXmlElement * msgs = new TiXmlElement( "Messages" );  
        root->LinkEndChild( msgs );  
 
        msg = new TiXmlElement( "Welcome" );  
        msg->LinkEndChild( new TiXmlText( "Welcome to MyApp" ));  
        msgs->LinkEndChild( msg );  
 
        msg = new TiXmlElement( "Farewell" );  
        msg->LinkEndChild( new TiXmlText( "Thank you for using MyApp" ));  
        msgs->LinkEndChild( msg );  
 
        TiXmlElement * windows = new TiXmlElement( "Windows" );  
        root->LinkEndChild( windows );  
 
        TiXmlElement * window;
        window = new TiXmlElement( "Window" );  
        windows->LinkEndChild( window );  
        window->SetAttribute("name", "MainFrame");
        window->SetAttribute("x", 5);
        window->SetAttribute("y", 15);
        window->SetAttribute("w", 400);
        window->SetAttribute("h", 250);
 
        TiXmlElement * cxn = new TiXmlElement( "Connection" );  
        root->LinkEndChild( cxn );  
        cxn->SetAttribute("ip", "192.168.0.1");
        cxn->SetDoubleAttribute("timeout", 123.456); // floating point attrib
        
        dump_to_stdout( &doc );
        doc.SaveFile( "appsettings.xml" );  
} 

函數dump_to_stdout將顯示這個結構:

Document
+ Declaration
+ Element [MyApp]
 (No attributes)
  + Comment: [ Settings for MyApp ]
  + Element [Messages]
 (No attributes)
    + Element [Welcome]
 (No attributes)
      + Text: [Welcome to MyApp]
    + Element [Farewell]
 (No attributes)
      + Text: [Thank you for using MyApp]
  + Element [Windows]
 (No attributes)
    + Element [Window]
      + name: value=[MainFrame]
      + x: value=[5] int=5 d=5.0
      + y: value=[15] int=15 d=15.0
      + w: value=[400] int=400 d=400.0
      + h: value=[250] int=250 d=250.0
      5 attributes
  + Element [Connection]
    + ip: value=[192.168.0.1] int=192 d=192.2
    + timeout: value=[123.456000] int=123 d=123.5
    2 attributes

我對TinyXML默認地按照別的APIs所謂的”整潔“方式寫XML文檔感到奇怪。它改變了包含別的節點的元素文本的空白字符,因此它寫成了包含層次嵌套的樹狀。

我並沒有看到有方法去關閉寫文件時的縮進,使界限相對簡單。

[Lee:這在STL模式時很簡單,僅僅只需使用cout<< myDoc。非STL模式總是以“整潔”模式顯示。添加一個開關是一個非常好的功能並且正在考慮中。]

XML到C++對象/C++對象到XML

介紹

這個例子結舌你已經學過了從XML文件中導入程序設置與將程序配置寫入XML文件,如類似example4.xml。

有許多實現的方法。例如,研究TinyBind項目(http://sourceforge.net/projects/tinybind)。

這個章節介紹一個使用XML讀入與寫出基本對象結構的悠久方法。

設置你的類對象

以幾個基本類開始,就像:

#include <string>
#include <map>
using namespace std;
 
typedef std::map<std::string,std::string> MessageMap;
 
// a basic window abstraction - demo purposes only
class WindowSettings
{
public:
        int x,y,w,h;
        string name;                               
 
        WindowSettings()
                 : x(0), y(0), w(100), h(100), name("Untitled")
        {
        }
 
        WindowSettings(int x, int y, int w, int h, const string& name)
        {
                 this->x=x;
                 this->y=y;
                 this->w=w;
                 this->h=h;
                 this->name=name;
        }
};
 
class ConnectionSettings
{
public:
        string ip;
        double timeout;
};
 
class AppSettings
{
public:
        string m_name;
        MessageMap m_messages;
        list<WindowSettings> m_windows;
        ConnectionSettings m_connection;
 
        AppSettings() {}
 
        void save(const char* pFilename);
        void load(const char* pFilename);
        
        // just to show how to do it
        void setDemoValues()
        {
                 m_name="MyApp";
                 m_messages.clear();
                 m_messages["Welcome"]="Welcome to "+m_name;
                 m_messages["Farewell"]="Thank you for using "+m_name;
                 m_windows.clear();
                 m_windows.push_back(WindowSettings(15,15,400,250,"Main"));
                 m_connection.ip="Unknown";
                 m_connection.timeout=123.456;
        }
};

這是一個基本的main函數,它展示瞭如何創建一個節本的對象樹,並隨後保存和載入:

int main(void)
{
        AppSettings settings;
        
        settings.save("appsettings2.xml");
        settings.load("appsettings2.xml");
        return 0;
}

下面的main函數展示創建、修改、保存和載入一個預設的結構:

int main(void)
{
        // block: customise and save settings
        {
                 AppSettings settings;
                 settings.m_name="HitchHikerApp";
                 settings.m_messages["Welcome"]="Don't Panic";
                 settings.m_messages["Farewell"]="Thanks for all the fish";
                 settings.m_windows.push_back(WindowSettings(15,25,300,250,"BookFrame"));
                 settings.m_connection.ip="192.168.0.77";
                 settings.m_connection.timeout=42.0;
 
                 settings.save("appsettings2.xml");
        }
        
        // block: load settings
        {
                 AppSettings settings;
                 settings.load("appsettings2.xml");
                 printf("%s: %s\n", settings.m_name.c_str(), 
                          settings.m_messages["Welcome"].c_str());
                 WindowSettings & w=settings.m_windows.front();
                 printf("%s: Show window '%s' at %d,%d (%d x %d)\n", 
                          settings.m_name.c_str(), w.name.c_str(), w.x, w.y, w.w, w.h);
                 printf("%s: %s\n", settings.m_name.c_str(), settings.m_messages["Farewell"].c_str());
        }
        return 0;
}

當save()與load()結束時(看下面),運行main()在控制檯上顯示:

HitchHikerApp: Don't Panic
HitchHikerApp: Show window 'BookFrame' at 15,25 (300 x 100)
HitchHikerApp: Thanks for all the fish

C++ XML編碼

實現保存到一個文件有很多種方法。這就是其中之一:

void AppSettings::save(const char* pFilename)
{
        TiXmlDocument doc;  
        TiXmlElement* msg;
        TiXmlComment * comment;
        string s;
         TiXmlDeclaration* decl = new TiXmlDeclaration( "1.0", "", "" );  
        doc.LinkEndChild( decl ); 
 
        TiXmlElement * root = new TiXmlElement(m_name.c_str());  
        doc.LinkEndChild( root );  
 
        comment = new TiXmlComment();
        s=" Settings for "+m_name+" ";
        comment->SetValue(s.c_str());  
        root->LinkEndChild( comment );  
 
        // block: messages
        {
                 MessageMap::iterator iter;
 
                 TiXmlElement * msgs = new TiXmlElement( "Messages" );  
                 root->LinkEndChild( msgs );  
 
                 for (iter=m_messages.begin(); iter != m_messages.end(); iter++)
                 {
                          const string & key=(*iter).first;
                          const string & value=(*iter).second;
                          msg = new TiXmlElement(key.c_str());  
                          msg->LinkEndChild( new TiXmlText(value.c_str()));  
                          msgs->LinkEndChild( msg );  
                 }
        }
 
        // block: windows
        {
                 TiXmlElement * windowsNode = new TiXmlElement( "Windows" );  
                 root->LinkEndChild( windowsNode );  
 
                 list<WindowSettings>::iterator iter;
 
                 for (iter=m_windows.begin(); iter != m_windows.end(); iter++)
                 {
                          const WindowSettings& w=*iter;
 
                          TiXmlElement * window;
                          window = new TiXmlElement( "Window" );  
                          windowsNode->LinkEndChild( window );  
                          window->SetAttribute("name", w.name.c_str());
                          window->SetAttribute("x", w.x);
                          window->SetAttribute("y", w.y);
                          window->SetAttribute("w", w.w);
                          window->SetAttribute("h", w.h);
                 }
        }
 
        // block: connection
        {
                 TiXmlElement * cxn = new TiXmlElement( "Connection" );  
                 root->LinkEndChild( cxn );  
                 cxn->SetAttribute("ip", m_connection.ip.c_str());
                 cxn->SetDoubleAttribute("timeout", m_connection.timeout); 
        }
 
        doc.SaveFile(pFilename);  
}

修改main函數並且運行它會產生如下的文件:

<?xml version="1.0" ?>
<HitchHikerApp>
    <!-- Settings for HitchHikerApp -->
    <Messages>
        <Farewell>Thanks for all the fish</Farewell>
        <Welcome>Don&apos;t Panic</Welcome>
    </Messages>
    <Windows>
        <Window name="BookFrame" x="15" y="25" w="300" h="250" />
    </Windows>
    <Connection ip="192.168.0.77" timeout="42.000000" />
</HitchHikerApp>

XML解碼

與對象編碼類似,有很多方法將XML解碼成C++對象結構。下面的方法使用了TiXmlHandles:

void AppSettings::load(const char* pFilename)
{
        TiXmlDocument doc(pFilename);
        if (!doc.LoadFile()) return;
 
        TiXmlHandle hDoc(&doc);
        TiXmlElement* pElem;
        TiXmlHandle hRoot(0);
 
        // block: name
        {
                 pElem=hDoc.FirstChildElement().Element();
                 // should always have a valid root but handle gracefully if it does
                 if (!pElem) return;
                 m_name=pElem->Value();
 
                 // save this for later
                 hRoot=TiXmlHandle(pElem);
        }
 
        // block: string table
        {
                 m_messages.clear(); // trash existing table
 
                 pElem=hRoot.FirstChild( "Messages" ).FirstChild().Element();
                 for( pElem; pElem; pElem=pElem->NextSiblingElement())
                 {
                          const char *pKey=pElem->Value();
                          const char *pText=pElem->GetText();
                          if (pKey && pText) 
                          {
                                   m_messages[pKey]=pText;
                          }
                 }
        }
 
        // block: windows
        {
                 m_windows.clear(); // trash existing list
 
                 TiXmlElement* pWindowNode=hRoot.FirstChild( "Windows" ).FirstChild().Element();
                 for( pWindowNode; pWindowNode; pWindowNode=pWindowNode->NextSiblingElement())
                 {
                          WindowSettings w;
                          const char *pName=pWindowNode->Attribute("name");
                          if (pName) w.name=pName;
                          
                          pWindowNode->QueryIntAttribute("x", &w.x); // If this fails, original value is left as-is
                          pWindowNode->QueryIntAttribute("y", &w.y);
                          pWindowNode->QueryIntAttribute("w", &w.w);
                          pWindowNode->QueryIntAttribute("hh", &w.h);
 
                          m_windows.push_back(w);
                 }
        }
 
        // block: connection
        {
                 pElem=hRoot.FirstChild("Connection").Element();
                 if (pElem)
                 {
                          m_connection.ip=pElem->Attribute("ip");
                          pElem->QueryDoubleAttribute("timeout",&m_connection.timeout);
                 }
        }
}

完整的dump_to_stdout

下面是一個可以複製與粘貼的程序例子,它可以載入XML屬性和通過前面提到的遞歸遍歷方式將這種結構展示到STDOUT。

// tutorial demo program
#include "stdafx.h"
#include "tinyxml.h"
 
// ----------------------------------------------------------------------
// STDOUT dump and indenting utility functions
// ----------------------------------------------------------------------
const unsigned int NUM_INDENTS_PER_SPACE=2;
 
const char * getIndent( unsigned int numIndents )
{
        static const char * pINDENT="                                      + ";
        static const unsigned int LENGTH=strlen( pINDENT );
        unsigned int n=numIndents*NUM_INDENTS_PER_SPACE;
        if ( n > LENGTH ) n = LENGTH;
 
        return &pINDENT[ LENGTH-n ];
}
 
// same as getIndent but no "+" at the end
const char * getIndentAlt( unsigned int numIndents )
{
        static const char * pINDENT="                                        ";
        static const unsigned int LENGTH=strlen( pINDENT );
        unsigned int n=numIndents*NUM_INDENTS_PER_SPACE;
        if ( n > LENGTH ) n = LENGTH;
 
        return &pINDENT[ LENGTH-n ];
}
 
int dump_attribs_to_stdout(TiXmlElement* pElement, unsigned int indent)
{
        if ( !pElement ) return 0;
 
        TiXmlAttribute* pAttrib=pElement->FirstAttribute();
        int i=0;
        int ival;
        double dval;
        const char* pIndent=getIndent(indent);
        printf("\n");
        while (pAttrib)
        {
                 printf( "%s%s: value=[%s]", pIndent, pAttrib->Name(), pAttrib->Value());
 
                 if (pAttrib->QueryIntValue(&ival)==TIXML_SUCCESS)    printf( " int=%d", ival);
                 if (pAttrib->QueryDoubleValue(&dval)==TIXML_SUCCESS) printf( " d=%1.1f", dval);
                 printf( "\n" );
                 i++;
                 pAttrib=pAttrib->Next();
        }
        return i;        
}
 
void dump_to_stdout( TiXmlNode* pParent, unsigned int indent = 0 )
{
        if ( !pParent ) return;
 
        TiXmlNode* pChild;
        TiXmlText* pText;
        int t = pParent->Type();
        printf( "%s", getIndent(indent));
        int num;
 
        switch ( t )
        {
        case TiXmlNode::TINYXML_DOCUMENT:
                 printf( "Document" );
                 break;
 
        case TiXmlNode::TINYXML_ELEMENT:
                 printf( "Element [%s]", pParent->Value() );
                 num=dump_attribs_to_stdout(pParent->ToElement(), indent+1);
                 switch(num)
                 {
                          case 0:  printf( " (No attributes)"); break;
                          case 1:  printf( "%s1 attribute", getIndentAlt(indent)); break;
                          default: printf( "%s%d attributes", getIndentAlt(indent), num); break;
                 }
                 break;
 
        case TiXmlNode::TINYXML_COMMENT:
                 printf( "Comment: [%s]", pParent->Value());
                 break;
 
        case TiXmlNode::TINYXML_UNKNOWN:
                 printf( "Unknown" );
                 break;
 
        case TiXmlNode::TINYXML_TEXT:
                 pText = pParent->ToText();
                 printf( "Text: [%s]", pText->Value() );
                 break;
 
        case TiXmlNode::TINYXML_DECLARATION:
                 printf( "Declaration" );
                 break;
        default:
                 break;
        }
        printf( "\n" );
        for ( pChild = pParent->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) 
        {
                 dump_to_stdout( pChild, indent+1 );
        }
}
 
// load the named file and dump its structure to STDOUT
void dump_to_stdout(const char* pFilename)
{
        TiXmlDocument doc(pFilename);
        bool loadOkay = doc.LoadFile();
        if (loadOkay)
        {
                 printf("\n%s:\n", pFilename);
                 dump_to_stdout( &doc ); // defined later in the tutorial
        }
        else
        {
                 printf("Failed to load file \"%s\"\n", pFilename);
        }
}
 
// ----------------------------------------------------------------------
// main() for printing files named on the command line
// ----------------------------------------------------------------------
int main(int argc, char* argv[])
{
        for (int i=1; i<argc; i++)
        {
                 dump_to_stdout(argv[i]);
        }
        return 0;
}

在命令行或者DOS窗口中運行,如下:

C:\dev\tinyxml> Debug\tinyxml_1.exe example1.xml
 
example1.xml:
Document
+ Declaration
+ Element [Hello]
 (No attributes)
  + Text: [World]

注:本文翻譯自TinyXML的源碼文檔,如果不恰當之處,請不吝賜教,謝謝!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章