TinyXML-2

rss
XML 문서를 처리할 필요가 있어 검색해 보니 스택오버플로우(StackOverflow)에 “Best open XML parser for C++”라는 글이 눈에 띄더군요. 글이 작성된 게 2008년이라 벌써 6년이나 지난 글이지만 많은 사람들의 의견을 들을 수 있어 참고했습니다. 여러 사람이 댓글로 이런 저런 툴을 추천해 주고 있는데, 성능이나 메모리 관리는 부실하지만 간단하게 쓰기에는 TinyXML이 괜찮다는 댓글이 있어 찾아 보았습니다.


TinyXML 홈페이지(http://www.grinninglizard.com/tinyxml/)에 들어가 봤더니, 이미 TinyXML에 이어 TinyXML-2를 개발하고 있으며 현재는 TinyXML-2에 개발 역량을 집중하고 있다고 소개하네요. TinyXML-2 홈페이지(http://www.grinninglizard.com/tinyxml2/index.html)의 소개에 따르면 TinyXML-2과 TinyXML-1은 유사한(둘이 동일하지는 않은 것 같네요) API를 바탕으로 한 DOM 기반 파서(parser)로, TinyXML-2가 TinyXML-1에 비해 작고 가볍고 빠르다고 하네요. 사실 큰 차이는 없을 것 같긴 하지만, 일단 최근의 개발 역량을 집중하고 있는 TinyXML-2를 사용해 보기로 합니다.

참고로 TinyXML 관련 문서는 http://www.grinninglizard.com/tinyxmldocs/index.html에서, TinyXML-2 관련 문서는 http://www.grinninglizard.com/tinyxml2docs/index.html에서 볼 수 있습니다. 또 TinyXML의 다운로드 페이지는 http://sourceforge.net/projects/tinyxml/, TinyXML-2의 다운로드 페이지는 https://github.com/leethomason/tinyxml2입니다.


비주얼 스튜디오(Visual Studio)에서의 사용 방법은 간단합니다. 다운로드한 파일의 압축을 풀어서 관련된 소스 파일과 헤더 파일을 프로젝트에 포함시키면 됩니다. TinyXML-1의 경우에는 tinystr.h, tinystr.cpp, tinyxml.h, tinyxml.cpp, tinyxmlerror.cpp, tinyxmlparser.cpp, 이렇게 파일 여섯 개를 포함시키면 된다는데, TinyXML-2에서는 tinyxml2.h, tinyxml2.cpp, 이렇게 파일 두 개만 포함시키면 됩니다.

그리고 프로젝트에 포함시킨 tinyxml2.cpp 파일 맨 앞에 stdafx.h를 포함시켜 줍니다.
#include "stdafx.h"
#include "tinyxml2.h"


시작하기
#include "stdafx.h"
#include "include/TinyXml/tinyxml2.h"

using namespace tinyxml2;

int _tmain(int argc, _TCHAR* argv[])
{
	return 0;
}
프로젝트에 TinyXML 관련 파일을 추가했으니 코드에도 함께 추가해 줍니다. 네임스페이스(namespace)도 함께 설정해 주면 편리합니다. 다만 콘솔(console) 애플리케이션이 아닌 경우 마이크로소프트의 MSXML과 중복될 가능성이 있기 때문에 이때는 네임스페이스를 설정하는 대신 명시적으로 네임스페이스를 설정하여 사용하는 게 좋을 것 같습니다.


Classes
TinyXML Class ListTinyXML-2 Class List를 살펴 보았습니다. TinyXML-1과 TinyXML-2가 각각 대응하는 클래스는 다음과 같습니다. TiXmlAttribute / XMLAttribute, TiXmlBase / (none), TiXmlComment / XMLComment, TiXmlDeclaration / XMLDeclaration, TiXmlDocument / XMLDocument, TiXmlElement / XMLElement, TiXmlHandle / XMLHandle, (none) / XMLConstHandle, TiXmlNode / XMLNode, TiXmlPrinter / XMLPrinter, TiXmlText / XMLText, TiXmlUnknown / XMLUnknown, TiXmlVisitor / XMLVisitor, 이렇게 열 두 개씩 있으며 TinyXML-1에만 존재하는 클래스는 TiXmlBase, TinyXML-2에만 존재하는 클래스는 XMLConstHandle입니다. TinyXML-1에서는 TiXmlBase가 모든 클래스의 최상위 클래스이지만 TinyXML-2에서는 그 역할을 XMLNode가 대신하고 있습니다.


간단한 문서 작성하기
TinyXML Tutorial을 기반으로 XML 문서를 작성하는 간단한 예제를 구현해 보겠습니다. 완성될 XML 문서는 다음과 같습니다. 예제에서, 첫 번째 줄은 XML 파일의 선언문(declaration)이라고 합니다.
<?xml version="1.0" ?>
<Hello>World</Hello>

이를 TinyXML-2 기반으로 작성하는 예를 들어 보겠습니다. 처음에는 declaration과 element, text를 각각 만든 후 이를 차례로 연결하여 문서를 구성하는 것을 볼 수 있습니다.
void createSimpleDoc(const char* filename)
{
	XMLDocument doc;
	XMLDeclaration* decl = doc.NewDeclaration();	// <?xml version="1.0" encoding="UTF-8"?>
	XMLElement* element = doc.NewElement("Hello");
	XMLText* text = doc.NewText("World");
	element->LinkEndChild(text);
	doc.LinkEndChild(decl);
	doc.LinkEndChild(element);
	doc.SaveFile(filename);	// writing document to a file
}

완성된 파일은 다음과 같습니다.
<?xml version="1.0" encoding="UTF-8"?>
<Hello>World</Hello>


조금 더 복잡한 문서 작성하기
<?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>

이번 예제는 앞선 예제에 비해 다양한 기능이 추가되었습니다. 우선 주석(comment)을 처리하는 부분이 생겼고, 각 element에 하위 element가 붙거나 element에 attribute를 추가하는 등의 일이 그것입니다.
void createMoreComplicatedDoc(const char* filename)
{
	XMLDocument doc;
	XMLDeclaration* decl = doc.NewDeclaration();
	doc.LinkEndChild(decl);

	XMLElement* root = doc.NewElement("MyApp");
	doc.LinkEndChild(root);

	XMLComment* comment = doc.NewComment("Settings for MyApp");
	root->LinkEndChild(comment);

	XMLElement* messages = doc.NewElement("Messages");
	root->LinkEndChild(messages);

	XMLElement* welcome = doc.NewElement("Welcome");
	welcome->LinkEndChild( doc.NewText("Welcome to MyApp") );
	messages->LinkEndChild(welcome);

	XMLElement* farewell = doc.NewElement("Farewell");
	farewell->LinkEndChild( doc.NewText("Thank you for using MyApp") );
	messages->LinkEndChild(farewell);

	XMLElement* windows = doc.NewElement("Windows");
	root->LinkEndChild(windows);

	XMLElement* window = doc.NewElement("Window");
	windows->LinkEndChild(window);
	window->SetAttribute("name", "MainFrame");	// attributes
	window->SetAttribute("x", 5);				// attributes
	window->SetAttribute("y", 15);				// attributes
	window->SetAttribute("w", 400);				// attributes
	window->SetAttribute("h", 250);				// attributes

	XMLElement* connection = doc.NewElement("Connection");
	root->LinkEndChild(connection);
	connection->SetAttribute("ip", "192.168.0.1");
	connection->SetAttribute("timeout", 123.456);

	doc.SaveFile(filename);	// writing document to a file
}


문서 읽기
이번에는 앞서 저장한 XML 문서를 불러들이도록 해 보겠습니다. 우선 XML 문서를 불러들이고, 정상적으로 문서를 불러들이면 root에서부터 순차적으로 읽어들이도록 하겠습니다.
void dumpToStdout(const char* filename)
{
	XMLDocument doc;
	if (XML_NO_ERROR == doc.LoadFile(filename)) {
		printf("\n<Document> %s:\n", filename);
		dumpToStdout(&doc);
	} else {
		printf("Failed to open: %s\n", filename);
	}
}

각 노드를 읽어들이는 부분은 다음과 같습니다. 재귀함수를 이용하여 순차적으로 호출합니다. 각 XMLNode를 이용하여 차례로 읽어들이고 있는데, 각 노드는 declaration, element, comment, 혹은 document나 unknown으로 분류할 수 있습니다. 각 노드가 어떤 타입(type)인지 알 수 있는 부분이 아래의 ToDeclaration(), ToElement(), ToComment() 메쏘드(method)입니다.
void dumpToStdout(const XMLNode* parent, unsigned int indent = 0)
{
	if (!parent) return;

	XMLNode* child;

	XMLDeclaration* decl;
	XMLElement* elem;
	XMLComment* comm;
	XMLAttribute* attr;
	XMLText* text;

	for (child = (XMLNode*)parent->FirstChild(); child != 0; child = (XMLNode*)child->NextSibling()) {
		for (int i = 0; i < indent + 1; i++) printf("    ");
		if (decl = child->ToDeclaration()) printf("<Declaration>");
		if (elem = child->ToElement()    ) printf("<Element>");
		if (comm = child->ToComment()    ) printf("<Comment>");
		printf(" %s\n", child->Value());
		if (elem) {
			attr = (XMLAttribute*)elem->FirstAttribute();
			if (attr) dumpToStdout(attr, indent + 1);
		}
		dumpToStdout(child, indent + 1);
	}
}

위 함수에서 노드 유형(type)이 XMLElement인 경우, attribute가 존재하는지 확인하고 있으면 그 이름과 값을 표시하도록 해 주었습니다.
void dumpToStdout(const XMLAttribute* firstAttr, unsigned int indent)
{
	XMLAttribute* attr;

	for (attr = (XMLAttribute*)firstAttr; attr != 0; attr = (XMLAttribute*)attr->Next()) {
		for (int i = 0; i < indent + 1; i++) printf("    ");
		printf("%s: %s\n", attr->Name(), attr->Value());
	}
}

이렇게 모두 처리한 후 화면에 표시된 결과는 다음과 같습니다.
<Document> example2.xml:
    <Declaration> xml version="1.0" encoding="UTF-8"
    <Element> MyApp
        <Comment> Settings for MyApp
        <Element> Messages
            <Element> Welcome
                 Welcome to MyApp
            <Element> Farewell
                 Thank you for using MyApp
        <Element> Windows
            <Element> Window
                name: MainFrame
                x: 5
                y: 15
                w: 400
                h: 250
        <Element> Connection
            ip: 192.168.0.1
            timeout: 123.456


더 자세한 내용은 TinyXML-2 Class List 페이지를 참고하시기 바랍니다.


Posted by EXIFEEDI

댓글을 달아 주세요