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(
" %s: ", pFilename);
        dump_to_stdout( 
&doc ); // 稍后在指南中定义
    }

    
else
    
{
        printf(
"Failed to load file "%s” ", 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(
" ");
    
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( 
" " );
        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 ", 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) "
        settings.m_name.c_str(), w.name.c_str(), w.x, w.y, w.w, w.h);
        printf(
"%s: %s ", 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(
" ");
    
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( 
" " );
        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( 
" " );
    
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(
" %s: ", pFilename);
        dump_to_stdout( 
&doc ); 
    }

    
else
    
{
        printf(
"Failed to load file "%s” ", 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 inyxml >  Debug inyxml_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月做了更新

你可能感兴趣的:(TinyXML中文指南)