TinyXML 指南

 

译注:本文是TinyXML 2.5.2版本Tutorial的中文译文,经原作者Lee Thomason 同意由hansen翻译,如有误译或者错漏,欢迎指正。
版权:版权归原作者所有,翻译文档版权归本人hansen所有,转载请注明出处。
原文:http://www.grinninglizard.com/tinyxmldocs/tutorial0.html

 

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/”/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/”/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]

作者与修改

  • Ellers写于2005年4,5,6月
  • Lee Thomason于2005年9月略加编辑后集成到文档系统中
  • Ellers于2005年10月做了更新
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值