譯注:本文是TinyXML 2.5.2版本Tutorial的中文譯文,經(jīng)原作者Lee Thomason同意由hansen翻譯,如有誤譯或者錯(cuò)漏,歡迎指正。
版權(quán):版權(quán)歸原作者所有,翻譯文檔版權(quán)歸本人hansen所有,轉(zhuǎn)載請注明出處。
原文:http://www./tinyxmldocs/tutorial0.html
TinyXML 指南
這是什么?
這份指南有一些關(guān)于如何有效地使用TinyXML的技巧和建議。
我也會嘗試講一些諸如怎樣使字符串與整型數(shù)相互轉(zhuǎn)化的C++技巧。這與TinyXML本身沒什么關(guān)系,但它也許會對你的項(xiàng)目有所幫助,所以我還是把它加進(jìn)來了。
如果你不知道基本的C++概念,那么這份指南就沒什么用了。同樣的,如果你不知道什么是DOM,那先從其它地方找來看看吧。
在我們開始之前
一些將會被用到的XML數(shù)據(jù)集/文件。
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
把一個(gè)文件加載成TinyXML DOM的最簡單方法是:
TiXmlDocument doc( "demo.xml" );
doc.LoadFile();
一個(gè)更接近于現(xiàn)實(shí)應(yīng)用的例子如下。它加載文件并把內(nèi)容顯示到標(biāo)準(zhǔn)輸出STDOUT上:
// 加載指定的文件并把它的結(jié)構(gòu)輸出到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中使用此函數(shù)的一個(gè)簡單應(yīng)用示范如下:
int main(void)
{
dump_to_stdout("example1.xml");
return 0;
}
回想example1的XML:
<?xml version="1.0" ?>
<Hello>World</Hello>
用這個(gè)XML運(yùn)行程序就會在控制臺/DOS窗口中顯示:
DOCUMENT
+ DECLARATION
+ ELEMENT Hello
+ TEXT[World]
”dump_to_stdout“函數(shù)稍后會在這份指南中定義,如果你想要理解怎樣遞歸遍歷一個(gè)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"); // 此函數(shù)稍后會中指南中定義
你會看到跟example1一模一樣:
madeByHand.xml:
Document
+ Declaration
+ Element [Hello]
+ Text: [World]
這段代碼會產(chǎn)生相同的XML DOM,但它以不同的順序來創(chuàng)建和鏈接結(jié)點(diǎn):
void write_simple_doc2( )
{
// 實(shí)現(xiàn)與 write_simple_doc1一樣的功能,(譯注:我想它指是build_simple_doc)
// 但盡可能早地把結(jié)點(diǎn)添加到樹中。
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" );
}
兩個(gè)都產(chǎn)生同樣的XML,即:
<?xml version="1.0" ?>
<Hello>World</Hello>
結(jié)構(gòu)構(gòu)成都是:
DOCUMENT
+ DECLARATION
+ ELEMENT Hello
+ TEXT[World]
屬性
給定一個(gè)存在的結(jié)點(diǎn),設(shè)置它的屬性是很容易的:
window = new TiXmlElement( "Demo" );
window->SetAttribute("name", "Circle");
window->SetAttribute("x", 5);
window->SetAttribute("y", 15);
window->SetDoubleAttribute("radius", 3.14159);
你也可以用TiXmlAttribute對象達(dá)到同樣的目的。
下面的代碼向我們展示了一種(并不只有一種)獲取某一元素屬性并打印出它們的名字和字符串值的方法,如果值能夠被轉(zhuǎn)化為整型數(shù)或者浮點(diǎn)數(shù),也把值打印出來:
// 打印pElement的所有屬性。
// 返回已打印的屬性數(shù)量。
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;
}
把文檔對象寫到文件中
把一個(gè)已經(jīng)建立好的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>
以下函數(shù)建立這個(gè)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); // 浮點(diǎn)數(shù)屬性
dump_to_stdout( &doc );
doc.SaveFile( "appsettings.xml" );
}
dump_to_stdout函數(shù)將顯示如下結(jié)構(gòu):
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默認(rèn)以其它APIs稱作“pretty”格式的方式來輸出XML,對此我感到驚訝。這種格式修改了元素的文本結(jié)點(diǎn)中的空格,以使輸出來的結(jié)點(diǎn)樹包含一個(gè)嵌套層標(biāo)記。
我還沒有仔細(xì)看當(dāng)寫到一個(gè)文件中時(shí)是否有辦法關(guān)閉這種縮進(jìn)——這肯定很容易做到。(譯注:這兩句話大概是Ellers說的)
[Lee:在STL模式下這很容易做到,只需要cout << myDoc就行了。在非STL模式下就總是用“pretty”格式了,加多一個(gè)開關(guān)是一個(gè)很好的特性,這已經(jīng)被要求過了。]
XML與C++對象的相互轉(zhuǎn)化
介紹
這個(gè)例子假設(shè)你在用一個(gè)XML文件來加載和保存你的應(yīng)用程序配置,舉例來說,有點(diǎn)像example4.xml。
有許多方法可以做到這點(diǎn)。例如,看看TinyBind項(xiàng)目:http:///projects/tinybind
這一節(jié)展示了一種普通老式的方法來使用XML加載和保存一個(gè)基本的對象結(jié)構(gòu)。
建立你的對象類
從一些像這樣的基本類開始:
#include <string>
#include <map>
using namespace std;
typedef std::map<std::string,std::string> MessageMap;
// 基本的窗口抽象 - 僅僅是個(gè)示例
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);
// 僅用于顯示它是如何工作的
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;
}
};
這是一個(gè)基本的mian(),它向我們展示了怎樣創(chuàng)建一個(gè)默認(rèn)的settings對象樹,怎樣保存并再次加載:
int main(void)
{
AppSettings settings;
settings.save("appsettings2.xml");
settings.load("appsettings2.xml");
return 0;
}
接下來的main()展示了如何創(chuàng)建,修改,保存和加載一個(gè)settings結(jié)構(gòu):
int main(void)
{
// 區(qū)塊:定制并保存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");
}
// 區(qū)塊:加載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;
}
當(dāng)save()和load()完成后(請看下面),運(yùn)行這個(gè)main()就會在控制臺看到:
HitchHikerApp: Don’t Panic
HitchHikerApp: Show window ‘BookFrame’ at 15,25 (300 x 100)
HitchHikerApp: Thanks for all the fish
把C++狀態(tài)編碼成XML
有很多方法能夠做到把文檔對象保存到文件中,這就是其中一個(gè):
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 );
// 區(qū)塊: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 );
}
}
// 區(qū)塊: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);
}
}
// 區(qū)塊: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運(yùn)行會生成這個(gè)文件:
<?xml version="1.0" ?>
<HitchHikerApp>
<!– Settings for HitchHikerApp –>
<Messages>
<Farewell>Thanks for all the fish</Farewell>
<Welcome>Don'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中解碼出狀態(tài)
就像編碼一樣,也有許多方法可以讓你從自己的C++對象結(jié)構(gòu)中解碼出XML。下面的方法使用了TiXmlHandles。
void AppSettings::load(const char* pFilename)
{
TiXmlDocument doc(pFilename);
if (!doc.LoadFile()) return;
TiXmlHandle hDoc(&doc);
TiXmlElement* pElem;
TiXmlHandle hRoot(0);
// 區(qū)塊:name
{
pElem=hDoc.FirstChildElement().Element();
// 必須有一個(gè)合法的根結(jié)點(diǎn),如果沒有則溫文地處理(譯注:直接返回)
if (!pElem) return;
m_name=pElem->Value();
// 保存起來以備后面之用
hRoot=TiXmlHandle(pElem);
}
// 區(qū)塊: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;
}
}
}
// 區(qū)塊: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); // 如果失敗,原值保持現(xiàn)狀
pWindowNode->QueryIntAttribute("y", &w.y);
pWindowNode->QueryIntAttribute("w", &w.w);
pWindowNode->QueryIntAttribute("hh", &w.h);
m_windows.push_back(w);
}
}
// 區(qū)塊:connection
{
pElem=hRoot.FirstChild("Connection").Element();
if (pElem)
{
m_connection.ip=pElem->Attribute("ip");
pElem->QueryDoubleAttribute("timeout",&m_connection.timeout);
}
}
}
dump_to_stdout的完整列表
下面是一個(gè)可直接運(yùn)行的示例程序,使用上面提到過的遞歸遍歷方式,可用來加載任意的XML文件并把結(jié)構(gòu)輸出到STDOUT上。
// 指南示例程序
#include "stdafx.h"
#include "tinyxml.h"
// ———————————————————————-
// STDOUT輸出和縮進(jìn)實(shí)用函數(shù)
// ———————————————————————-
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 );
}
}
// 加載指定的文件并把它的結(jié)構(gòu)輸出到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窗口運(yùn)行它,例如:
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月略加編輯后集成到文檔系統(tǒng)中
- Ellers于2005年10月做了更新