TinyXML中文指南

TinyXML 指南

 

这是什么?

这份指南有一些关于如何有效地使用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();

一个更接近于现实应用的例子如下。它加载文件并把内容显示到标准输出STDOUT上:

 

// 加载指定的文件并把它的结构输出到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 ); // 稍后在指南中定义

    }

    else

    {

        printf(“Failed to load file "%s&#8221;\n”, pFilename);

    }

}

在main中使用此函数的一个简单应用示范如下:

 

int main(void)

{

    dump_to_stdout(“example1.xml”);

    return 0;

}

回想example1的XML:

 

<?xml version="1.0" ?>

<Hello>World</Hello>

用这个XML运行程序就会在控制台/DOS窗口中显示:

 

DOCUMENT

  • DECLARATION

  • ELEMENT Hello

  + TEXT[World]

”dump_to_stdout“函数稍后会在这份指南中定义,如果你想要理解怎样递归遍历一个DOM它会很有用。

用程序建立文档对象

这是用程序建立example1的方法:

 

void build_simple_doc( )

{

    // 生成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"); // 此函数稍后会中指南中定义

你会看到跟example1一模一样:

 

madeByHand.xml:

Document

  • Declaration

  • Element [Hello]

  + Text: [World]

这段代码会产生相同的XML DOM,但它以不同的顺序来创建和链接结点:

 

void write_simple_doc2( )

{

    // 实现与 write_simple_doc1一样的功能,(译注:我想它指是build_simple_doc)

    // 但尽可能早地把结点添加到树中。

    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对象达到同样的目的。

下面的代码向我们展示了一种(并不只有一种)获取某一元素属性并打印出它们的名字和字符串值的方法,如果值能够被转化为整型数或者浮点数,也把值打印出来:

 

// 打印pElement的所有属性。

// 返回已打印的属性数量。

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 );

回想一下,比如example4:

 

<?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); // 浮点数属性

    

    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称作“pretty”格式的方式来输出XML,对此我感到惊讶。这种格式修改了元素的文本结点中的空格,以使输出来的结点树包含一个嵌套层标记。

我还没有仔细看当写到一个文件中时是否有办法关闭这种缩进——这肯定很容易做到。(译注:这两句话大概是Ellers说的

[Lee:在STL模式下这很容易做到,只需要cout << myDoc就行了。在非STL模式下就总是用“pretty”格式了,加多一个开关是一个很好的特性,这已经被要求过了。]

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;

 

// 基本的窗口抽象 - 仅仅是个示例

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;

    }

};

&nbsp;

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);

    

    // 仅用于显示它是如何工作的

    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;

    }

};

这是一个基本的mian(),它向我们展示了怎样创建一个默认的settings对象树,怎样保存并再次加载:

 

int main(void)

{

    AppSettings settings;

    

    settings.save(“appsettings2.xml”);

    settings.load(“appsettings2.xml”);

    return 0;

}

接下来的main()展示了如何创建,修改,保存和加载一个settings结构:

 

int main(void)

{

    // 区块:定制并保存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”);

    }

    

    // 区块:加载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 ); 

    

    // 区块: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 ); 

        }

    }

    

    // 区块: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);

        }

    }

    

    // 区块: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中解码出状态

就像编码一样,也有许多方法可以让你从自己的C++对象结构中解码出XML。下面的方法使用了TiXmlHandles。

 

void AppSettings::load(const char* pFilename)

{

    TiXmlDocument doc(pFilename);

    if (!doc.LoadFile()) return;

    

    TiXmlHandle hDoc(&doc);

    TiXmlElement* pElem;

    TiXmlHandle hRoot(0);

    

    // 区块:name

    {

        pElem=hDoc.FirstChildElement().Element();

        // 必须有一个合法的根结点,如果没有则温文地处理(译注:直接返回

        if (!pElem) return;

        m_name=pElem->Value();

        

        // 保存起来以备后面之用

        hRoot=TiXmlHandle(pElem);

    }

    

    // 区块:string table

    {

        m_messages.clear(); // 清空已有的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;

            }

        }

    }

    

    // 区块:windows

    {

        m_windows.clear(); // 清空链表

        

        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); // 如果失败,原值保持现状

            pWindowNode->QueryIntAttribute(“y”, &w.y);

            pWindowNode->QueryIntAttribute(“w”, &w.w);

            pWindowNode->QueryIntAttribute(“hh”, &w.h);

            

            m_windows.push_back(w);

        }

    }

    

    // 区块: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上。

 

// 指南示例程序

#include “stdafx.h”

#include “tinyxml.h”

 

// ———————————————————————-

// STDOUT输出和缩进实用函数

// ———————————————————————-

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 ];

}

 

// 与getIndent相同,但最后没有“+”

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 );

    }

}

 

// 加载指定的文件并把它的结构输出到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 ); 

    }

    else

    {

        printf(“Failed to load file "%s&#8221;\n”, pFilename);

    }

}

 

// ———————————————————————-

// main(),打印出从命令行指定的文件

// ———————————————————————-

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]

作者与修改

    <li><em>Ellers写于2005年4,5,6月</em>
    
    <li><em>Lee Thomason于2005年9月略加编辑后集成到文档系统中</em>
    
    <li><em>Ellers于2005年10月做了更新</em> </li>