TinyXML加載保存C++對象

TinyXML是一個非常小巧簡單的XML解析庫,採用DOM方式來解析XML文件。不足的是它本身不支持DTD和XSL,但普通簡單的XML使用需求還是可以滿足了。

TinyXML由2個頭文件四個CPP文件構成。繼承結構如下:

要操作XML首先需要加載XML,很簡單:

TiXmlDocument doc( "demo.xml" );
doc.LoadFile();

一個更加真實的用例如下所示,加載一個XML文件然後顯示內容到標準輸出上。

// 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
	}
	else
	{
		printf("Failed to load file \"%s\"\n", pFilename);
	}
}

寫個簡單的Main函數

int main(void)
{
	dump_to_stdout("example1.xml");
	return 0;
}


讀下面的文件

<?xml version="1.0" ?>
<Hello>World</Hello>

輸出如下

DECLARATION
+ ELEMENT Hello
  + TEXT[World]

dump_to_stdout遞歸遍歷輸出所有的XML內容,詳細實現在本文的末尾。

TinyXML同樣可以簡單的編程生成一個上面的XML:

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

與其等價的寫法:

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

給定一個節點,設置它的屬性也很簡單:

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

可以用下面的函數獲得元素的所有屬性:

// 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;
}

我們經常通過配置文件來保存程序的一些配置信息。下面給一個實例來用XML加載和保存C++對象。

#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;
	}
};

類AppSettings提供了保存和加載配置信息的函數save和load。如下代碼表示利用缺省的配置保存和加載:

int main(void)
{
	AppSettings settings;
	
	settings.save("appsettings2.xml");
	settings.load("appsettings2.xml");
	return 0;
}

同樣的我們可以運行時修改配置:

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

有很多方法可以保存對象的屬性信息,也就是對象的狀態信息。下面的實例展示如何將對象的狀態編碼進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);  
}

下面的示例展示如何從XML文件中獲得對象的狀態信息:

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如下:

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

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

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

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

	case TiXmlNode::TINYXML_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);
	}
}


如果你想在MFC中使用TinyXML,會出現這樣的編譯錯誤 fatal error C1010: unexpected end of file while looking for precompiled header。因爲預編譯頭文件通過編譯stdafx.cpp生成,可以在4個實現CPP文件中引入頭#include   "stdafx.h"。記得放在最前面。這樣就可以編譯通過了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章