TinyXML Tutorial 中文指南

注:  本文是 TinyXML 2.5.3 版本 Document 中的《TinyXML Tutorial》的翻譯文檔,由本人 Dennis.Gao 翻譯,版權歸原作者所有,轉載本文檔請註明出處。原文出自 TinyXML 源碼包doc目錄。在線文檔:http://www.grinninglizard.com/tinyxmldocs/tutorial0.html
Author : Dennis.Gao
Date   : 2008.01.01

這是什麼?

本指南就如何有效的使用 TinyXML 提供一些竅門和建議。
這裏也會包括一些 C++ 的竅門,像如何在字符串和整數之間進行轉換。這和 TinyXML 本身並沒有任何關係,但是它會對你的工程有所幫助,所以我把它寫了進來。
如果你不知道 C++ 的基本概念,那麼本指南對你沒有任何用處。同樣,如果你不知道 DOM 是什麼,先在別的地方學習一下吧。

開始之前

一些 XML 數據集/文件將會被用到:
example1.xml:

<?xml version="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();

下面是一個更實際的用法。它會加載文件並在標準輸出中顯示文件內容:

// 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 十分有用。

通過編程建立 XML 文檔

下面的函數可以建立 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"); // this func defined later in the tutorial

你會發現它和 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]

屬性

很簡單就可以給一個已經存在的節點設置屬性:

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

你也可以使用 TiXmlAttribute 對象來做這件事。
下面這段代碼給出了一種(並非僅此一種)如何得到一個元素的所有屬性,然後打印出名字和字符串值的方法,並且,如果這些值可以轉換成整形或浮點型,那麼把他們也打印出來:

// 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 用其他的 API 所謂的“優美”格式來寫 XML ,它修改元素文字的空白,然後用嵌套層次的方式顯示這棵樹。
我沒有注意到在寫文件的時候是否有辦法關掉縮排,但是這肯定很容易。
[ Lee:在 STL 模式下很容易,只要使用 cout << myDoc 就可以了。非 STL 模式通常是“優美”格式的。加入一個“開關”會是一個不錯的特性,並且已經這麼做了。]

XML 與 C++ 對象之間的轉換

介紹

這個示例假定你正要加載你的應用設置並把它們保存在 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

編碼爲 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 文件,然後使用上面所說的遞歸遍歷的方式將 XML 結構輸出到標準輸出中。

// 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::DOCUMENT:
        printf( "Document" );
        break;

    case TiXmlNode::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::COMMENT:
        printf( "Comment: [%s]", pParent->Value());
        break;

    case TiXmlNode::UNKNOWN:
        printf( "Unknown" );
        break;

    case TiXmlNode::TEXT:
        pText = pParent->ToText();
        printf( "Text: [%s]", pText->Value() );
        break;

    case TiXmlNode::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]

作者以及變動 
  • 由 Ellers 於2005年4月~6月編寫
  • 由 Lee Thomason 於2005年9月整理入 doc 文檔中
  • 2005年10月由 Ellers 進行更新

因翻譯倉促,水平有限,文中如有疏漏或錯誤,請不吝賜教。聯繫作者:
Email / MSN: [email protected]
blog: http://LoveRene.cublog.cn/
發佈了13 篇原創文章 · 獲贊 8 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章