사용자 도구

사이트 도구


kb:yamlparserforcpp

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

kb:yamlparserforcpp [2014/11/07 16:07] (현재)
줄 1: 줄 1:
 +====== Yaml Parser for C++ ======
 +지저분한 [[XML]] 설정 파일들을 쓰다가 답답한 마음에 결국 파서를 만들었다. [[YAML]] 스펙이 워낙 방대하나보니,​ 완벽한 C/C++ 파서를 만들다가는 몇달이 걸릴지 몰라서, 그냥 쓸만하다고 판단되는 기능만 골라서 만들었다. ​
 +
 +  * **구현한 기능 및 특징**
 +    * 기본적인 scalar, sequence, mapping 지원 (모든 스칼라는 문자열로 처리한다.)
 +    * Block scalar 및 folded scalar 지원
 +    * Anchor & alias 지원
 +    * 주석 읽어들이기/​쓰기 지원
 +    * D3DXVECTOR2,​ D3DXVECTOR3,​ D3DXVECTOR4,​ D3DXQUATERNION,​ D3DXCOLOR 지원. 일반적으로 생각하자면 들어가야할 기능은 아니다만,​ 일단은 "​게임"​ 프로그램에 쓰는 거다 보니...
 +  * **필요하다고 생각하는 기능 (구현 예정?)**
 +    * Inline flow mapping
 +    * Escape character 또는 verbatim 처리
 +    * Document start/end marker
 +    * Binary scalar
 +    * 유니코드/​인코딩 지원
 +
 +기본적인 구현 방식은 [[XML]] 쪽의 DOM 파서를 생각하면 된다. 즉 문서를 통채로 로드해서 메모리 상에 트리 형태로 구성하는 파서이다.
 +
 +====== 소스 ======
 +<file cpp YamlTree.h>​
 +#!cpp
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \file YamlTree.h
 +/// \author excel96
 +/// \date 2006.4.21
 +////////////////////////////////////////////////////////////////////////////////​
 +
 +#ifndef __YAMLTREE_H__
 +#define __YAMLTREE_H__
 +
 +#include <​iosfwd>​
 +#include <map>
 +#include <​string>​
 +#include <​vector>​
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \class cYamlTree
 +/// \brief 간단한 YAML 파서. XML 쪽의 DOM 파서를 생각하면 된다. 즉 문서를 ​
 +/// 통채로 로드해서 메모리 상에 트리 형태로 구성하는 파서이다.
 +///
 +/// XML 파일이 너무 지저분한 경향이 있어, 대안을 찾다가 YAML이라는 것을 알게 ​
 +/// 되었다. 문법이 깔끔하기는 한데, C/C++ 파서가 현재 존재하지 않아서 대충 ​
 +/// 쓰는 기능들만 모아서 한번 만들어봤다. 현재 지원하는 기능을 보자면 다음과
 +/// 같다.
 +///
 +///   - 기본적인 scalar, sequence, mapping 지원
 +///   - Block scalar 및 folded scalar 지원
 +///   - Anchor & alias 지원
 +///   - 라인 주석 지원
 +///
 +/// 자식 노드는 맵 형태 또는 배열 형태로 액세스할 수 있다. 둘을 혼용할 수는 ​
 +/// 없다. 기본적으로는 키 문자열을 이용해 맵 형태로 자식 노드들을 두게 된다. ​
 +/// 이는 같은 이름(key)의 자식 노드가 2개 이상 있는 경우, 그 중에 하나만 ​
 +/// 선택됨을 의미한다. Ruby에 있는 YAML 파서 같은 경우에는 제일 마지막에 들어온 ​
 +/// 자식 노드를 선택한다. 하지만 여기서는 제일 첫번째로 들어온 자식 노드를 ​
 +/// 선택하고,​ 그 다음 들어오는 노드는 삭제해 버리는 방식을 택했다. 배열 형태로 ​
 +/// 자식을 두기 위해서는 '​-'​ 연산자를 이용해야 한다. 자세한 것은 역시 YAML 
 +/// 문법을 참고. ​
 +///
 +/// \todo Verbatim 처리가 필요하다.
 +////////////////////////////////////////////////////////////////////////////////​
 +
 +class cYamlTree
 +{
 +public:
 +    // 스칼라의 종류
 +    enum ScalarType
 +    {
 +        NORMAL_SCALAR, ​ ///< 일반적인 스칼라
 +        BLOCK_SCALAR, ​  ///<​ key : | 형태의 값
 +        FOLDED_SCALAR ​  ///<​ key : > 형태의 값
 +    };
 +
 +    // 자식 노드 횡단을 위한 타입 선언
 +    typedef std::​map<​std::​string,​ cYamlTree*>::​iterator iterator;
 +    typedef std::​map<​std::​string,​ cYamlTree*>::​const_iterator const_iterator;​
 +    typedef std::​vector<​std::​string>​ SCALARS;
 +
 +    // 에러 통지 함수 타입 선언
 +    typedef void (*PFN_ERROR_HANDLER)(const char* errormsg);
 +
 +
 +protected:
 +    enum ChildType
 +    {
 +        CHILD_MAPPED, ​    ///<​ 자식 노드의 컬렉션으로 맵을 사용한다.
 +        CHILD_SEQNENTIAL,​ ///< 자식 노드의 컬렉션으로 배열을 사용한다.
 +        CHILD_UNKNOWN ​    ///<​ 내부적으로 사용하는 값.
 +    };
 +
 +    typedef std::​vector<​cYamlTree*>​ SEQUENCE;
 +    typedef std::​map<​std::​string,​ cYamlTree*>​ MAPPING;
 +
 +    std::​string ​ m_Key; ​               ///< 키
 +    std::​string ​ m_Value; ​             ///< 값
 +    std::​string ​ m_Comment; ​           ///< 코멘트
 +    ScalarType ​  ​m_ScalarType; ​        ///<​ 값의 종류
 +    SCALARS ​     m_Scalars; ​           ///< 여러 행에 걸친 스칼라 값
 +    cYamlTree* ​  ​m_Alias; ​             ///< 앵커 알리아스
 +    ChildType ​   m_ChildType; ​         ///< 자식 타입 (map or hash)
 +    MAPPING ​     m_MappedChilds; ​      ///<​ 자식 노드들
 +    SEQUENCE ​    ​m_SequentialChilds; ​  ///<​ 자식 노드들
 +    size_t ​      ​m_MaxChildKeyLength; ​ ///< 자식 노드의 이름 길이 최대값
 +
 +    static PFN_ERROR_HANDLER s_ParseErrorHandler; ​ ///< 파싱 에러 핸들러
 +    static PFN_ERROR_HANDLER s_AccessErrorHandler;​ ///< 일반 에러 핸들러
 +
 +
 +public:
 +    /// \name 생성자
 +    /// \{ 
 +    cYamlTree();​
 +    cYamlTree(const std::​string&​ key, const std::​string&​ value);
 +    cYamlTree(const std::​string&​ key, const char* value);
 +    cYamlTree(const std::​string&​ key, int value);
 +    cYamlTree(const std::​string&​ key, float value);
 +    cYamlTree(const std::​string&​ key, double value);
 +    cYamlTree(const std::​string&​ key, bool value);
 +
 +#ifdef D3DX_VERSION
 +    cYamlTree(const std::​string&​ key, const D3DXVECTOR2&​ value);
 +    cYamlTree(const std::​string&​ key, const D3DXVECTOR3&​ value);
 +    cYamlTree(const std::​string&​ key, const D3DXVECTOR4&​ value);
 +    cYamlTree(const std::​string&​ key, const D3DXQUATERNION&​ value);
 +    cYamlTree(const std::​string&​ key, const D3DXCOLOR&​ value);
 +#endif
 +    /// \} 
 +
 +    /// \brief 소멸자
 +    virtual ~cYamlTree();​
 +
 +
 +public:
 +    /// \name 키 반환/​설정 함수
 +    /// \{ 
 +    const std::​string&​ GetKey() const { return m_Key; }
 +    void SetKey(const std::​string&​ key) { m_Key = key; }
 +    void SetKey(const char* key) { m_Key = key; }
 +    /// \} 
 +
 +    /// \name 값 반환/​설정 함수
 +    /// \{ 
 +    const std::​string&​ GetValue() const;
 +    const char* GetValueAsString() const;
 +    int GetValueAsInt() const;
 +    float GetValueAsFloat() const;
 +    double GetValueAsDouble() const;
 +    bool GetValueAsBool() const;
 +
 +    void SetValue(const std::​string&​ value);
 +    void SetValue(const char* value);
 +    void SetValue(int value);
 +    void SetValue(float value);
 +    void SetValue(double value);
 +    void SetValue(bool value);
 +
 +#ifdef D3DX_VERSION
 +    D3DXVECTOR2 GetValueAsVector2() const;
 +    D3DXVECTOR3 GetValueAsVector3() const;
 +    D3DXVECTOR4 GetValueAsVector4() const;
 +    D3DXQUATERNION GetValueAsQuaternion() const;
 +    D3DXCOLOR GetValueAsColor() const;
 +
 +    void SetValue(const D3DXVECTOR2&​ value);
 +    void SetValue(const D3DXVECTOR3&​ value);
 +    void SetValue(const D3DXVECTOR4&​ value);
 +    void SetValue(const D3DXQUATERNION&​ value);
 +    void SetValue(const D3DXCOLOR&​ value);
 +#endif
 +    /// \} 
 +
 +    /// \name 주석 문자열 반환/​설정 함수
 +    /// \{ 
 +    const std::​string&​ GetComment() const;
 +    void SetComment(const std::​string&​ comment);
 +    /// \} 
 +
 +    /// \name 값의 종류 반환/​설정 함수
 +    /// \{ 
 +    ScalarType GetScalarType() const;
 +    void SetScalarType(ScalarType type);
 +    /// \} 
 +
 +    /// \name 스칼라 값 반환/​설정 함수
 +    /// \{ 
 +    void AddScalar(const std::​string&​ value);
 +    const SCALARS&​ GetScalars() const;
 +    /// \} 
 +
 +    /// \name 앵커 알리아스 반환 및 설정
 +    /// \{ 
 +    cYamlTree* GetAlias() const;
 +    void SetAlias(cYamlTree* alias);
 +    /// \} 
 +
 +
 +public:
 +    /// \brief 자식 노드의 종류를 반환한다.
 +    ChildType GetChildType() const { return m_ChildType;​ }
 +
 +    /// \brief 자식 노드를 추가한다. 추가한 자식 노드는 내부에서 delete로
 +    /// 삭제하므로 반드시 new를 통해 힙에다 생성해야 한다.
 +    cYamlTree* AddChild(cYamlTree* child);
 +
 +    /// \brief 해당하는 자식 노드를 가지고 있는지의 여부를 반환한다.
 +    bool HasChild(const std::​string&​ key) const;
 +
 +    /// \name 배열(sequence) 형태로 자식 노드를 액세스하기 위한 함수들
 +    /// \{ 
 +    cYamlTree* GetChild(size_t i, bool strict=true) const;
 +    cYamlTree&​ operator[](size_t i);
 +    const cYamlTree&​ operator[](size_t i) const;
 +    size_t GetChildCount() const;
 +    /// \} 
 +
 +    /// \name 맵 형태로 자식 노드를 액세스하기 위한 함수들
 +    /// \{ 
 +    cYamlTree* GetChild(const std::​string&​ key, bool strict=true) const;
 +    cYamlTree&​ operator[](const std::​string&​ key);
 +    const cYamlTree&​ operator[](const std::​string&​ key) const;
 +    iterator BeginChild() { return m_MappedChilds.begin();​ }
 +    iterator EndChild() { return m_MappedChilds.end();​ }
 +    const_iterator BeginChild() const { return m_MappedChilds.begin();​ }
 +    const_iterator EndChild() const { return m_MappedChilds.end();​ }
 +    /// \} 
 +
 +
 +public:
 +    /// \name 자식 노드 값 반환 함수
 +    /// \{ 
 +    const char* AttrAsString(const std::​string&​ key) const;
 +    int AttrAsInt(const std::​string&​ key) const;
 +    float AttrAsFloat(const std::​string&​ key) const;
 +    double AttrAsDouble(const std::​string&​ key) const;
 +    bool AttrAsBool(const std::​string&​ key) const;
 +
 +    const char* AttrAsStringSafe(const std::​string&​ key, const char* nullValue=""​) const;
 +    int AttrAsIntSafe(const std::​string&​ key, int nullValue=-1) const;
 +    float AttrAsFloatSafe(const std::​string&​ key, float nullValue=-1.0f) const;
 +    double AttrAsDoubleSafe(const std::​string&​ key, double nullValue=-1.0) const;
 +    bool AttrAsBoolSafe(const std::​string&​ key, bool nullValue=false) const;
 +
 +#ifdef D3DX_VERSION
 +    D3DXVECTOR2 AttrAsVector2(const std::​string&​ key) const;
 +    D3DXVECTOR3 AttrAsVector3(const std::​string&​ key) const;
 +    D3DXVECTOR4 AttrAsVector4(const std::​string&​ key) const;
 +    D3DXQUATERNION AttrAsQuaternion(const std::​string&​ key) const;
 +    D3DXCOLOR AttrAsColor(const std::​string&​ key) const;
 +
 +    D3DXVECTOR2 AttrAsVector2Safe(const std::​string&​ key, const D3DXVECTOR2&​ nullValue) const;
 +    D3DXVECTOR3 AttrAsVector3Safe(const std::​string&​ key, const D3DXVECTOR3&​ nullValue) const;
 +    D3DXVECTOR4 AttrAsVector4Safe(const std::​string&​ key, const D3DXVECTOR4&​ nullValue) const;
 +    D3DXQUATERNION AttrAsQuaternionSafe(const std::​string&​ key, const D3DXQUATERNION&​ nullValue) const;
 +    D3DXCOLOR AttrAsColorSafe(const std::​string&​ key, const D3DXCOLOR&​ nullValue) const;
 +#endif
 +    /// \} 
 +
 +    /// \name 자식 노드 값 추가 함수
 +    /// \{ 
 +    cYamlTree* AddAttr(const std::​string&​ key, const std::​string&​ value);
 +    cYamlTree* AddAttr(const std::​string&​ key, const char* value);
 +    cYamlTree* AddAttr(const std::​string&​ key, int value);
 +    cYamlTree* AddAttr(const std::​string&​ key, float value);
 +    cYamlTree* AddAttr(const std::​string&​ key, double value);
 +    cYamlTree* AddAttr(const std::​string&​ key, bool value);
 +
 +#ifdef D3DX_VERSION
 +    cYamlTree* AddAttr(const std::​string&​ key, const D3DXVECTOR2&​ value);
 +    cYamlTree* AddAttr(const std::​string&​ key, const D3DXVECTOR3&​ value);
 +    cYamlTree* AddAttr(const std::​string&​ key, const D3DXVECTOR4&​ value);
 +    cYamlTree* AddAttr(const std::​string&​ key, const D3DXQUATERNION&​ value);
 +    cYamlTree* AddAttr(const std::​string&​ key, const D3DXCOLOR&​ value);
 +#endif
 +    /// \} 
 +
 +
 +public:
 +    /// \brief 자신 및 모든 자식 노드들을 초기화한다.
 +    void Clear();
 +
 +    /// \brief 내부 데이터를 문자열 형태로 반환한다.
 +    std::string ToString(bool beautify=false,​ bool includeSelf=false) const;
 +
 +
 +public:
 +    /// \name 에러 통지 함수 설정/​반환
 +    /// \{ 
 +    static PFN_ERROR_HANDLER GetParseErrorHandler() { return s_ParseErrorHandler;​ }
 +    static PFN_ERROR_HANDLER GetAccessErrorHandler() { return s_AccessErrorHandler;​ }
 +    static void SetParseErrorHandler(PFN_ERROR_HANDLER handler) { s_ParseErrorHandler = handler; }
 +    static void SetAccessErrorHandler(PFN_ERROR_HANDLER handler) { s_AccessErrorHandler = handler; }
 +    /// \} 
 +
 +
 +protected:
 +    /// \brief 내부 데이터를 문자열 형태로 반환한다.
 +    std::string ToString(size_t indent, bool mapped, const std::​string&​ header, ​
 +        bool beautify, size_t maxKeyLength) const;
 +
 +
 +private:
 +    /// 복사 생성 금지
 +    cYamlTree(const cYamlTree&​) {}
 +
 +    /// 대입 연산 금지
 +    cYamlTree&​ operator = (cYamlTree&​) { return *this; }
 +};
 +
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \class cYamlDocument
 +/// \brief cYamlTree 최상위 노드. ​
 +///
 +/// 실제로 파일 입출력을 다룰 때에는 이 클래스를 이용해야 한다. 이와 같은 ​
 +/// 클래스가 필요한 이유는 ANCHOR 정보가 문서 전체를 통해 전역으로 존재하기 ​
 +/// 때문이다. Load/Save 함수도 어차피 이와 같은 클래스를 따로 두고, 이 안에
 +/// 넣는 것이 깔끔하기는 하다.
 +///
 +/// Anchor & alias 같은 경우, 파일을 읽어들일 때, 즉 이미 존재하는 ​
 +/// anchor & alias 정보를 읽어들이는 것은 별 문제가 없다. 문제는 그 반대, ​
 +/// 즉 메모리 상에서 트리 구조를 구성한 후, 파일에다 쓸 때는 인터페이스를 ​
 +/// 어떻게 제공해야 할지를 잘 모르겠다.
 +////////////////////////////////////////////////////////////////////////////////​
 +
 +class cYamlDocument : public cYamlTree
 +{
 +private:
 +    typedef std::​map<​std::​string,​ cYamlTree*>​ ANCHORS;
 +
 +    ANCHORS ​    ​m_Anchors; ​  ///<​ 앵커 목록
 +    std::string m_LastError;​ ///< 마지막으로 발생한 에러 문자열
 +
 +
 +public:
 +    /// \brief 생성자
 +    cYamlDocument();​
 +
 +    /// \brief 소멸자
 +    virtual ~cYamlDocument();​
 +
 +
 +public:
 +    /// \brief 앵커를 추가한다.
 +    void AddAnchor(const std::​string&​ name, cYamlTree* tree);
 +
 +    /// \brief 해당하는 이름의 앵커를 반환한다.
 +    cYamlTree* GetAnchor(const std::​string&​ name) const;
 +
 +    /// \brief 모든 앵커를 삭제한다.
 +    void ClearAnchors();​
 +
 +    /// \brief 파일에서 데이터를 읽어들여 데이터를 구성한다.
 +    bool Load(const char* fileName);
 +
 +    /// \brief 내부 데이터를 파일에다 저장한다.
 +    bool Save(const char* fileName, bool beautify=false) const;
 +
 +    /// \brief 마지막으로 발생한 에러 문자열을 반환한다.
 +    const std::​string&​ GetLastError() const { return m_LastError;​ }
 +};
 +
 +#endif
 +</​file>​
 +<file cpp YamlTree.cpp>​
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \file YamlTree.cpp
 +/// \author excel96
 +/// \date 2006.4.21
 +////////////////////////////////////////////////////////////////////////////////​
 +
 +#include "​PCH.h"​
 +#include "​YamlTree.h"​
 +
 +namespace ​
 +{
 +    const std::string YAML_WHITESPACES ​        = " \t\r\n";​
 +    const std::string YAML_LINEFEEDS ​          = "​\r\n";​
 +    const std::string YAML_INDENT ​             = " \t";
 +    const char        YAML_COLON ​              = ':';​
 +    const char        YAML_SHARP ​              = '#';​
 +    const char        YAML_MINUS ​              = '​-';​
 +    const char        YAML_AMPERSAND ​          = '&';​
 +    const char        YAML_ASTERISK ​           = '​*';​
 +    const std::string YAML_PIPE ​               = "​|";​
 +    const std::string YAML_RIGHT_BRACKET ​      = ">";​
 +    //const std::string YAML_DOCUMENT_HEADER ​    = "​---";​
 +    //const std::string YAML_DOCUMENT_TERMINATOR = "​...";​
 +
 +    /// 한 라인에 대한 파싱 결과
 +    enum ParseResult
 +    {
 +        RESULT_NORMAL,​
 +        RESULT_EMPTY,​
 +    };
 +
 +    /// 여러 라인에 걸친 데이터를 처리하기 위한 상수
 +    enum ParseMode
 +    {
 +        MODE_NORMAL,​
 +        MODE_BLOCK_SCALAR,​
 +        MODE_FOLDED_SCALAR,​
 +    };
 +
 +    /// \struct STATE 파싱을 위한 상태 구조체
 +    /// \brief YAML 파일 포맷을 보면 알겠지만,​ 파싱을 위해서 스택이 필요하다.
 +    /// 스택에다 가장 최근의 부모를 저장해 두어야, 현재 라인의 인덴트 값과
 +    /// 비교해 부모 자식 관계를 파악할 수 있다.
 +    struct STATE
 +    {
 +        cYamlTree* node;
 +        int        indent;
 +        STATE(cYamlTree* n, int i) : node(n), indent(i) {}
 +    };
 +
 +    typedef std::​vector<​STATE>​ STATE_STACK;​
 +
 +    /// 현재 인덴트 값을 기준으로 스택에서 정확한 부모를 찾아, 주어진 노드를 ​
 +    /// 자식 노드로 편입시킨다.
 +    inline cYamlTree* AdoptChild(STATE_STACK&​ stateStack, int indent, cYamlTree* child)
 +    {
 +        while (!stateStack.empty())
 +        {
 +            STATE& e = stateStack.back();​
 +            if (e.indent == indent)
 +                return e.node->​AddChild(child);​
 +            else
 +                stateStack.pop_back();​
 +        }
 +
 +        return NULL;
 +    }
 +
 +    /// 문자열 좌우의 공백을 제거해서 반환한다.
 +    inline std::string Trim(std::​string str, const std::​string&​ whitespaces = YAML_WHITESPACES)
 +    {
 +        size_t begin = str.find_first_not_of(whitespaces);​
 +        size_t end = str.find_last_not_of(whitespaces);​
 +
 +        if (begin == std::​string::​npos)
 +        {
 +            if (end == std::​string::​npos) return "";​
 +            else begin = 0;
 +        }
 +        else if (end == std::​string::​npos)
 +        {
 +            end = str.size();
 +        }
 +
 +        return str.substr(begin , end - begin + 1);
 +    }
 +
 +    /// \brief 주어진 문장에 있는 알파벳을 모두 소문자로 바꾼다.
 +    inline std::string Lower(std::​string original)
 +    {
 +        std::​transform(original.begin(),​ original.end(),​ original.begin(),​ tolower);
 +        return original;
 +    }
 +
 +    /// \brief 주어진 문자열 내에 존재하는 문자열을 다른 문자열로 치환한다.
 +    inline size_t Replace(std::​string&​ text, 
 +        const std::​string&​ findToken, const std::​string&​ replaceToken)
 +    {
 +        size_t find_token_length = findToken.size();​
 +        size_t replace_token_length = replaceToken.size();​
 +        size_t replaced = 0;
 +
 +        size_t i = 0;
 +        while ((i = text.find(findToken,​ i)) != std::​string::​npos)
 +        {
 +            text.replace(i,​ find_token_length,​ replaceToken);​
 +            i += replace_token_length;​
 +            ++replaced;
 +        }
 +
 +        return replaced;
 +    }
 +
 +    /// \class cYamlStream
 +    /// \brief 형식화된 파일 입출력을 위한 스트림 객체. 라인 단위로 파일을
 +    /// 읽어들이는 기능 외에 마지막으로 읽어들인 문자열을 이용해 다음 라인을
 +    /// 어떻게 처리하느냐를 판단하는 기능도 담당한다.
 +    class cYamlStream
 +    {
 +    private:
 +        std::​ifstream ​ m_File; ​    ///<​ 스트림 객체
 +        cYamlDocument* m_Document; ///< 도큐먼트 객체
 +        int            m_Line; ​    ///<​ 현재 라인 카운트
 +        int            m_Indent; ​  ///<​ 현재 라인을 파싱해서 얻어낸 인덴트 값
 +        std::​string ​   m_Text; ​    ///<​ 현재 라인 문자열
 +        std::​string ​   m_Comment; ​ ///< 현재 처리 중인 코멘트
 +        ParseMode ​     m_Mode; ​    ///<​ 임시 변수
 +
 +
 +    public:
 +        /// 생성자
 +        cYamlStream(const char* fileName, cYamlDocument* document) ​
 +            : m_File(fileName,​ std::​ios::​in),​ m_Document(document),​ m_Line(0), m_Indent(0)
 +        {
 +        }
 +
 +        /// 소멸자
 +        ~cYamlStream() {}
 +
 +        /// 파일에서 한 라인을 읽어들이고,​ 인덴트 값을 내부 변수에다 기록해둔다.
 +        inline ParseResult ParseNextLine(bool processComment)
 +        {
 +            ++m_Line;
 +
 +            char buf[2048+1] = {0, };
 +            m_File.getline(buf,​ 2048);
 +
 +            m_Text = buf;
 +            if (m_Text.empty()) ​
 +                return RESULT_EMPTY;​ // empty line
 +
 +            size_t pos = m_Text.find_first_not_of(YAML_INDENT);​
 +            if (pos == std::​string::​npos) ​
 +            {
 +                m_Comment += "​\n";​
 +                return RESULT_EMPTY;​ // only whitespace
 +            }
 +            ​
 +            if (processComment && m_Text[pos] == YAML_SHARP)
 +            {
 +                m_Comment += m_Text.substr(pos) + "​\n";​
 +                return RESULT_EMPTY;​ // comment line
 +            }
 +
 +            m_Indent = static_cast<​int>​(pos);​
 +
 +            return RESULT_NORMAL;​
 +        }
 +
 +        /// 현재 라인의 key:value 문자열을 이용해 트리 객체를 생성해서 반환한다.
 +        inline cYamlTree* CreateTree() ​
 +        {
 +#pragma warning(push)
 +#pragma warning(disable:​6211)
 +
 +            std::string text(m_Text);​
 +            cYamlTree* result = NULL;
 +
 +            // ':'​ 문자를 찾는다.
 +            size_t pos = text.find_first_of(YAML_COLON);​
 +            if (pos == std::​string::​npos)
 +                throw std::​string("​cannot find colon character"​);​
 +
 +            // 파싱 성공!
 +            std::string key = Trim(text.substr(0,​ pos));
 +            std::string value = Trim(text.substr(pos+1));​
 +
 +            // 키 문자열이 정상적인지 검사한다.
 +            if (key.empty())
 +                throw std::​string("​key text is empty"​);​
 +
 +            if (key[0] == YAML_MINUS)
 +            {
 +                m_Indent += static_cast<​int>​(key.find_first_not_of(YAML_INDENT,​ 1));
 +                key = Trim(key.substr(1));​
 +                result = new cYamlTree("",​ ""​);​
 +                result->​AddChild(new cYamlTree(key,​ value));
 +            }
 +            else
 +            {
 +                result = new cYamlTree(key,​ value);
 +            }
 +
 +            if (!m_Comment.empty())
 +            {
 +                // 이때까지 모은 주석을 설정한다. 단 마지막의 '​\n'​ 하나는 뗀다.
 +                result->​SetComment(m_Comment.substr(0,​ m_Comment.size() - 1));
 +                m_Comment = "";​
 +            }
 +
 +            if (value.size() > 1)
 +            {
 +                if (value[0] == YAML_AMPERSAND)
 +                {
 +                    m_Document->​AddAnchor(Trim(value.substr(1)),​ result);
 +                }
 +                else if (value[0] == YAML_ASTERISK)
 +                {
 +                    std::string trimmed(value.substr(1));​
 +                    cYamlTree* anchor = m_Document->​GetAnchor(trimmed);​
 +                    if (anchor == NULL)
 +                    {
 +                        delete result;
 +                        throw (std::​string("​no such anchor ") + trimmed);
 +                    }
 +
 +                    result->​SetAlias(anchor);​
 +                }
 +            }
 +
 +            // 다음 라인을 어떻게 처리할 지 결정해 둔다.
 +            if (value == YAML_PIPE)
 +            {
 +                m_Mode = MODE_BLOCK_SCALAR;​
 +                result->​SetValue(""​);​
 +            }
 +            else if (value == YAML_RIGHT_BRACKET)
 +            {
 +                m_Mode = MODE_FOLDED_SCALAR;​
 +                result->​SetValue(""​);​
 +            }
 +            else
 +            {
 +                m_Mode = MODE_NORMAL;​
 +            }
 +
 +            return result;
 +
 +#pragma warning(pop)
 +        }
 +
 +        /// 다음 라인을 어떤 식으로 처리해야 하는지를 반환한다.
 +        inline ParseMode GetNextMode() const { return m_Mode; }
 +
 +        /// 스트림의 상태가 정상적인지의 여부를 반환한다.
 +        inline bool IsGood() const { return m_File.good();​ }
 +
 +        /// 현재 처리 중인 라인의 번호를 반환한다.
 +        inline int GetCurrentLine() const { return m_Line; }
 +
 +        /// 현재 라인의 인덴트 값을 반환한다.
 +        inline int GetIndent() const { return m_Indent; }
 +
 +        /// 현재 라인 문자열을 반환한다.
 +        inline const std::​string&​ GetText() const { return m_Text; }
 +
 +        /// 현재 라인이 빈 라인 또는 코멘트 라인인지의 여부를 반환한다.
 +        inline bool IsEmptyLine() const 
 +        { 
 +            std::string trimmed(Trim(m_Text));​
 +            return trimmed.empty() || trimmed[0] == YAML_SHARP; ​
 +        }
 +    };
 +}
 +
 +cYamlTree::​PFN_ERROR_HANDLER cYamlTree::​s_ParseErrorHandler = NULL;
 +cYamlTree::​PFN_ERROR_HANDLER cYamlTree::​s_AccessErrorHandler = NULL;
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree()
 +: m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL),​ m_ChildType(CHILD_UNKNOWN), ​
 +m_MaxChildKeyLength(0)
 +{
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, const std::​string&​ value)
 +: m_Key(key), m_Value(value),​ m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL),​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, const char* value)
 +: m_Key(key), m_Value(value),​ m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL),​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, int value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL), ​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, float value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL), ​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, double value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL), ​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, bool value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL),​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 소멸자
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​~cYamlTree()
 +{
 +    Clear();
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 std::string 반환한다.
 +/// \return const std::​string&​ 값 문자열
 +////////////////////////////////////////////////////////////////////////////////​
 +const std::​string&​ cYamlTree::​GetValue() const 
 +{
 +    return m_Alias == NULL ? m_Value : m_Alias->​GetValue();​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 char* 형태로 반환한다.
 +/// \return const char* 값 문자열
 +////////////////////////////////////////////////////////////////////////////////​
 +const char* cYamlTree::​GetValueAsString() const 
 +
 +    return m_Alias == NULL ? m_Value.c_str() : m_Alias->​GetValueAsString();​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 int 형태로 변환해서 반환한다.
 +/// \return int 변환한 값
 +////////////////////////////////////////////////////////////////////////////////​
 +int cYamlTree::​GetValueAsInt() const 
 +
 +    return m_Alias == NULL ? atoi(GetValueAsString()) : m_Alias->​GetValueAsInt();​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 float 형태로 변환해서 반환한다.
 +/// \return float 변환한 값
 +////////////////////////////////////////////////////////////////////////////////​
 +float cYamlTree::​GetValueAsFloat() const 
 +
 +    return m_Alias == NULL ? 
 +        static_cast<​float>​(atof(GetValueAsString())) : m_Alias->​GetValueAsFloat(); ​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 double 형태로 변환해서 반환한다.
 +/// \return double 변환한 값
 +////////////////////////////////////////////////////////////////////////////////​
 +double cYamlTree::​GetValueAsDouble() const 
 +
 +    return m_Alias == NULL ? atof(GetValueAsString()) : m_Alias->​GetValueAsDouble();​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 bool 형태로 변환해서 반환한다.
 +/// \return bool 변환한 값. 노드의 값이 대소문자 구별 없이 "​true"​일 경우에만
 +/// true가 되고, 그 외에는 모두 false로 취급한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +bool cYamlTree::​GetValueAsBool() const 
 +
 +#if _MSC_VER >= 1400
 +    return m_Alias == NULL ? 
 +        _stricmp(m_Value.c_str(),​ "​true"​) == 0 : m_Alias->​GetValueAsBool(); ​
 +#else
 +    return m_Alias == NULL ? 
 +        stricmp(m_Value.c_str(),​ "​true"​) == 0 : m_Alias->​GetValueAsBool(); ​
 +#endif
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 값 문자열을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(const std::​string&​ value) ​
 +{
 +    if (m_Alias == NULL)
 +        m_Value = value;
 +    else 
 +        m_Alias->​SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 값 문자열을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(const char* value) ​
 +{
 +    if (m_Alias == NULL)
 +        m_Value = value;
 +    else 
 +        m_Alias->​SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief int 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(int value) ​
 +{
 +    char buf[256] = {0,};
 +#if _MSC_VER >= 1400
 +    _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "​%d",​ value);
 +#else
 +    sprintf(buf,​ "​%d",​ value);
 +#endif
 +    buf[sizeof(buf)-1] = 0;
 +    SetValue(buf);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief float 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(float value) ​
 +{
 +    char buf[256] = {0,};
 +#if _MSC_VER >= 1400
 +    _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "​%f",​ value);
 +#else
 +    sprintf(buf,​ "​%f",​ value);
 +#endif
 +    buf[sizeof(buf)-1] = 0;
 +    SetValue(buf);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief double 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(double value) ​
 +{
 +    char buf[256] = {0,};
 +#if _MSC_VER >= 1400
 +    _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "​%f",​ value);
 +#else
 +    sprintf(buf,​ "​%f",​ value);
 +#endif
 +    buf[sizeof(buf)-1] = 0;
 +    SetValue(buf);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief bool 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(bool value) ​
 +{
 +    SetValue(value ? "​true"​ : "​false"​); ​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 주석 문자열을 반환한다.
 +/// \return const std::​string&​
 +////////////////////////////////////////////////////////////////////////////////​
 +const std::​string&​ cYamlTree::​GetComment() const
 +{
 +    return m_Alias == NULL ? m_Comment : m_Alias->​GetComment();​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 주석 문자열을 설정한다.
 +/// \param comment 주석 문자열
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetComment(const std::​string&​ comment)
 +{
 +    if (m_Alias == NULL)
 +        m_Comment = comment;
 +    else
 +        m_Alias->​SetComment(comment);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 값의 종류를 반환한다.
 +/// \return cYamlTree::​ScalarType 값의 종류
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​ScalarType cYamlTree::​GetScalarType() const 
 +
 +    return m_Alias == NULL ? m_ScalarType : m_Alias->​GetScalarType(); ​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 값의 종류를 설정한다.
 +/// \param type 값의 종류
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetScalarType(cYamlTree::​ScalarType type) 
 +
 +    if (m_Alias == NULL)
 +        m_ScalarType = type;
 +    else 
 +        m_Alias->​SetScalarType(type);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 스칼라 값을 추가한다.
 +/// \param value 스칼라 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​AddScalar(const std::​string&​ value) ​
 +{
 +    if (m_Alias == NULL)
 +        m_Scalars.push_back(value);​
 +    else 
 +        m_Alias->​AddScalar(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 스칼라 값의 컬렉션을 반환한다.
 +/// \return const cYamlTree::​SCALARS&​ 스칼라 값의 컬렉션
 +////////////////////////////////////////////////////////////////////////////////​
 +const cYamlTree::​SCALARS&​ cYamlTree::​GetScalars() const 
 +
 +    return m_Alias == NULL ? m_Scalars : m_Alias->​GetScalars();​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 앵커 알리아스를 반환한다.
 +/// \return cYamlTree* 알리아스
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​GetAlias() const 
 +
 +    return m_Alias; ​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 앵커 알리아스를 설정한다.
 +/// \param alias 알리아스
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetAlias(cYamlTree* alias) ​
 +
 +    if (alias != this) m_Alias = alias; ​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다. 추가한 자식 노드는 내부에서 delete로
 +/// 삭제하므로 반드시 new를 통해 힙에다 생성해야 한다.
 +/// \param child 추가할 자식 노드
 +/// \return cYamlTree* 인수로 주어진 child 를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddChild(cYamlTree* child) ​
 +{
 +    if (m_Alias)
 +    {
 +        throw std::​string("​cannot add child to aliased node"​);​
 +    }
 +
 +    // 자식 노드가 있는 경우 스칼라 값을 가질 수 없다.
 +    if (!m_Value.empty() && ​
 +        m_Value[0] != YAML_AMPERSAND && ​
 +        m_Value[0] != YAML_ASTERISK)
 +    {
 +        throw std::​string("​cannot add child to scalar node"​);​
 +    }
 +
 +    // 자식 노드의 key 문자열이 빈 문자열이라면,​
 +    // 이는 배열을 이용해야 된다는 말이다.
 +    if (child->​GetKey().empty())
 +    {
 +        // 최초의 자식 노드라면 ChildType이 UNKNOWN일 것이다.
 +        if (m_ChildType == CHILD_UNKNOWN)
 +            m_ChildType = CHILD_SEQNENTIAL;​
 +
 +        // 최초 이후부터는 같은 타입의 자식 노드만이 들어와야한다.
 +        if (m_ChildType != CHILD_SEQNENTIAL)
 +            throw std::​string("​cannot add sequencial node"​);​
 +
 +        char buf[1024] = {0, };
 +#if _MSC_VER >= 1400
 +        _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "​%05d",​ m_SequentialChilds.size());​
 +#else
 +        sprintf(buf,​ "​%05d",​ m_SequentialChilds.size());​
 +#endif
 +        buf[sizeof(buf)-1] = 0;
 +
 +        m_SequentialChilds.push_back(child);​
 +        m_MappedChilds.insert(MAPPING::​value_type(std::​string(buf),​ child));
 +        return child;
 +    }
 +    // 자식 노드의 key 문자열이 빈 문자열이 아니라면,​
 +    // 맵을 이용해서 자식 노드를 관리해야 한다.
 +    else
 +    {
 +        // 최초의 자식 노드라면 ChildType이 UNKNOWN일 것이다.
 +        if (m_ChildType == CHILD_UNKNOWN) ​
 +            m_ChildType = CHILD_MAPPED;​
 +
 +        // 최초 이후부터는 같은 타입의 자식 노드만이 들어와야한다.
 +        if (m_ChildType != CHILD_MAPPED)
 +            throw std::​string("​cannot add mapped node"​);​
 +
 +        MAPPING::​const_iterator itr(m_MappedChilds.find(Lower(child->​GetKey())));​
 +        if (itr == m_MappedChilds.end())
 +        {
 +#ifdef NOMINMAX
 +            m_MaxChildKeyLength = std::​max(m_MaxChildKeyLength,​ child->​GetKey().size());​
 +#else
 +            m_MaxChildKeyLength = __max(m_MaxChildKeyLength,​ child->​GetKey().size());​
 +#endif
 +
 +            m_MappedChilds.insert(MAPPING::​value_type(Lower(child->​GetKey()),​ child));
 +            m_SequentialChilds.push_back(child);​
 +            return child;
 +        }
 +        else
 +        {
 +            char buf[1024] = {0, };
 +#if _MSC_VER >= 1400
 +            _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, ​
 +                "​cannot add duplicated mapping - %s", child->​GetKey().c_str());​
 +#else
 +            sprintf(buf, ​
 +                "​cannot add duplicated mapping - %s", child->​GetKey().c_str());​
 +#endif
 +            buf[sizeof(buf)-1] = 0;
 +            throw std::​string(buf);​
 +        }
 +    }
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 자식 노드를 가지고 있는지의 여부를 반환한다.
 +/// \param key 키
 +/// \return cYamlTree* 해당하는 키를 가진 자식 노드가 있다면 true를 반환하고,​
 +/// 없다면 false를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +bool cYamlTree::​HasChild(const std::​string&​ key) const
 +{
 +    if (m_Alias) return m_Alias->​HasChild(key);​
 +
 +    if (m_ChildType == CHILD_MAPPED)
 +    {
 +        if (m_MappedChilds.find(Lower(key)) != m_MappedChilds.end()) return true;
 +    }
 +    else if (m_ChildType == CHILD_SEQNENTIAL)
 +    {
 +        size_t size = m_SequentialChilds.size();​
 +        for (size_t i=0; i<size; ++i)
 +        {
 +            const cYamlTree* const child = m_SequentialChilds[i];​
 +            if (child->​GetKey() == key) return true;
 +        }
 +    }
 +
 +    return false;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 인덱스의 자식 노드를 반환한다.
 +/// \param i 인덱스
 +/// \param strict 해당하는 자식 노드가 존재하지 않을 경우, 어서트를 발생시킬 ​
 +/// 것인가의 여부
 +/// \return cYamlTree* 자식 노드
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​GetChild(size_t i, bool strict) const 
 +
 +    if (m_Alias) ​
 +        return m_Alias->​GetChild(i,​ strict);
 +
 +    if (i < m_SequentialChilds.size()) ​
 +        return m_SequentialChilds[i]; ​
 +
 +    if (strict)
 +    {
 +        char buf[1024] = {0, };
 +#if _MSC_VER >= 1400
 +        _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, ​
 +            "out of range in node %s with %d", m_Key.c_str(),​ i);
 +#else
 +        sprintf(buf, ​
 +            "out of range in node %s with %d", m_Key.c_str(),​ i);
 +#endif
 +        buf[sizeof(buf)-1] = 0;
 +        throw std::​string(buf);​
 +    }
 +
 +    return NULL;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 인덱스의 자식 노드를 반환한다.
 +/// \param i 인덱스
 +/// \return const cYamlTree&​ 해당하는 인덱스의 자식 노드. 범위를 넘어간 경우
 +/// 어디를 가르키게 될지는 모른다.
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree&​ cYamlTree::​operator[](size_t i) 
 +{
 +    return *GetChild(i,​ true);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 인덱스의 자식 노드를 반환한다.
 +/// \param i 인덱스
 +/// \return const cYamlTree&​ 해당하는 인덱스의 자식 노드. 범위를 넘어간 경우
 +/// 어디를 가르키게 될지는 모른다.
 +////////////////////////////////////////////////////////////////////////////////​
 +const cYamlTree&​ cYamlTree::​operator[](size_t i) const
 +{
 +    return *GetChild(i,​ true);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드의 갯수를 반환한다.
 +/// \return size_t 자식 노드의 갯수
 +////////////////////////////////////////////////////////////////////////////////​
 +size_t cYamlTree::​GetChildCount() const 
 +{
 +    if (m_Alias) return m_Alias->​GetChildCount();​
 +    if (m_ChildType == CHILD_MAPPED) return m_MappedChilds.size();​
 +    if (m_ChildType == CHILD_SEQNENTIAL) return m_SequentialChilds.size();​
 +    return 0; 
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 반환한다.
 +/// \param key 키
 +/// \param strict 해당하는 자식 노드가 존재하지 않을 경우, 어서트를 발생시킬 ​
 +/// 것인가의 여부
 +/// \return cYamlTree* 해당하는 키를 가진 자식 노드가 있다면 그 노드의 포인터를
 +/// 반환하고,​ 없다면 NULL을 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​GetChild(const std::​string&​ key, bool strict) const
 +{
 +    if (m_Alias) return m_Alias->​GetChild(key,​ strict);
 +
 +    if (m_ChildType == CHILD_MAPPED)
 +    {
 +        MAPPING::​const_iterator itr(m_MappedChilds.find(Lower(key)));​
 +        if (itr != m_MappedChilds.end()) return itr->​second;​
 +
 +        if (strict)
 +        {
 +            char buf[1024] = {0, };
 +#if _MSC_VER >= 1400
 +            _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, ​
 +                "​cannot find specified child node %s at %s", ​
 +                key.c_str(),​ m_Key.c_str());​
 +#else
 +            sprintf(buf, ​
 +                "​cannot find specified child node %s at %s", ​
 +                key.c_str(),​ m_Key.c_str());​
 +#endif
 +            buf[sizeof(buf)-1] = 0;
 +
 +            if (s_AccessErrorHandler)
 +                s_AccessErrorHandler(buf);​
 +            else
 +                throw std::​string(buf);​
 +        }
 +
 +        return NULL;
 +    }
 +    else if (m_ChildType == CHILD_SEQNENTIAL)
 +    {
 +        size_t size = m_SequentialChilds.size();​
 +        for (size_t i=0; i<size; ++i)
 +        {
 +            cYamlTree* child = m_SequentialChilds[i];​
 +            if (child->​GetKey() == key) return child;
 +        }
 +
 +        if (strict)
 +        {
 +            char buf[1024] = {0, };
 +#if _MSC_VER >= 1400
 +            _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, ​
 +                "​cannot find specified child node %s at %s", ​
 +                key.c_str(),​ m_Key.c_str());​
 +#else
 +            sprintf(buf, ​
 +                "​cannot find specified child node %s at %s", ​
 +                key.c_str(),​ m_Key.c_str());​
 +#endif
 +            buf[sizeof(buf)-1] = 0;
 +
 +            if (s_AccessErrorHandler)
 +                s_AccessErrorHandler(buf);​
 +            else
 +                throw std::​string(buf);​
 +        }
 +
 +        return NULL;
 +    }
 +
 +    if (strict)
 +    {
 +        char buf[1024] = {0, };
 +#if _MSC_VER >= 1400
 +        _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, ​
 +            "​cannot find specified child node %s at %s", ​
 +            key.c_str(),​ m_Key.c_str());​
 +#else
 +        sprintf(buf,​
 +            "​cannot find specified child node %s at %s", ​
 +            key.c_str(),​ m_Key.c_str());​
 +#endif
 +        buf[sizeof(buf)-1] = 0;
 +
 +        if (s_AccessErrorHandler)
 +            s_AccessErrorHandler(buf);​
 +        else
 +            throw std::​string(buf);​
 +    }
 +
 +    return NULL;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드 중에 첫번째 것을 반환한다.
 +/// \param key 키
 +/// \return const cYamlTree&​ 해당하는 키를 가진 자식 노드. 해당하는 자식 노드가
 +/// 없을 경우에는 어떻게 될 지 모른다.
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree&​ cYamlTree::​operator[](const std::​string&​ key)
 +{
 +    return *GetChild(key,​ true);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드 중에 첫번째 것을 반환한다.
 +/// \param key 키
 +/// \return const cYamlTree&​ 해당하는 키를 가진 자식 노드. 해당하는 자식 노드가
 +/// 없을 경우에는 어떻게 될 지 모른다.
 +////////////////////////////////////////////////////////////////////////////////​
 +const cYamlTree&​ cYamlTree::​operator[](const std::​string&​ key) const
 +{
 +    return *GetChild(key,​ true);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// const char* 형태로 반환한다.
 +/// \param key 키
 +/// \return const char* 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 빈 문자열을 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +const char* cYamlTree::​AttrAsString(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? child->​GetValueAsString() : "";​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// int 형태로 반환한다.
 +/// \param key 키
 +/// \return int 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 -1을 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +int cYamlTree::​AttrAsInt(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? child->​GetValueAsInt() : -1;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// float 형태로 반환한다.
 +/// \param key 키
 +/// \return float 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 -1.0f를 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +float cYamlTree::​AttrAsFloat(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? child->​GetValueAsFloat() : -1.0f;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 double ​
 +/// 형태로 반환한다.
 +/// \param key 키
 +/// \return double 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 -1.0을 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +double cYamlTree::​AttrAsDouble(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? child->​GetValueAsDouble() : -1.0;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// bool 형태로 반환한다.
 +/// \param key 키
 +/// \return bool 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 false를 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +bool cYamlTree::​AttrAsBool(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? child->​GetValueAsBool() : false;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// const char* 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return const char* 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +const char* cYamlTree::​AttrAsStringSafe(const std::​string&​ key, const char* nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsString() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// int 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return int 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +int cYamlTree::​AttrAsIntSafe(const std::​string&​ key, int nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsInt() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// float 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return float 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +float cYamlTree::​AttrAsFloatSafe(const std::​string&​ key, float nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsFloat() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// double 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return double 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +double cYamlTree::​AttrAsDoubleSafe(const std::​string&​ key, double nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsDouble() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// bool 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return bool 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +bool cYamlTree::​AttrAsBoolSafe(const std::​string&​ key, bool nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsBool() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, const std::​string&​ value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, const char* value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, int value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, float value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, double value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, bool value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자신 및 모든 자식 노드들을 초기화한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​Clear()
 +{
 +    if (m_SequentialChilds.size() != m_MappedChilds.size())
 +    {
 +        char buf[1024] = {0, };
 +#if _MSC_VER >= 1400
 +        _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, ​
 +            "child count mismatch at node %s", m_Key.c_str());​
 +#else
 +        sprintf(buf, ​
 +            "child count mismatch at node %s", m_Key.c_str());​
 +#endif
 +        buf[sizeof(buf)-1] = 0;
 +
 +        if (s_AccessErrorHandler)
 +            s_AccessErrorHandler(buf);​
 +        else
 +            throw std::​string(buf);​
 +    }
 +
 +    m_Key = "";​
 +    m_Value = "";​
 +    m_Comment = "";​
 +    m_ScalarType = NORMAL_SCALAR;​
 +    m_Scalars.clear();​
 +    m_Alias = NULL;
 +    m_ChildType = CHILD_UNKNOWN;​
 +    m_MaxChildKeyLength = 0;
 +
 +    size_t size = m_SequentialChilds.size();​
 +    for (size_t i=0; i<size; ++i)
 +        delete m_SequentialChilds[i];​
 +
 +    m_SequentialChilds.clear();​
 +    m_MappedChilds.clear();​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 내부 데이터를 문자열 형태로 반환한다.
 +/// \param beautify 출력 파일의 인덴트를 예쁘게 정렬할 것인가의 여부
 +/// \param includeSelf 자기 자신도 출력에 포함시킬 것인가의 여부
 +/// \return std::string 문자열 형태로 변환한 데이터
 +////////////////////////////////////////////////////////////////////////////////​
 +std::string cYamlTree::​ToString(bool beautify, bool includeSelf) const
 +{
 +    std::​stringstream msg;
 +
 +    // 현재 노드의 깊이에 맞춰 주석을 기록해준다. 유의할 점은 공백 문자로만 ​
 +    // 이루어진 주석은 앞에다 '#'​ 문자를 출력하지 않는다는 점이다. 그냥 빈 줄을 ​
 +    // 출력하기 위해서이다.
 +    if (!m_Comment.empty())
 +        msg << m_Comment << std::endl;
 +
 +    if (includeSelf)
 +        msg << m_Key << ":"​ << m_Value << std::endl;
 +
 +    size_t size = m_SequentialChilds.size();​
 +    bool mapped = m_ChildType == CHILD_MAPPED;​
 +    for (size_t i=0; i<size; ++i)
 +    {
 +        const cYamlTree* const child = m_SequentialChilds[i];​
 +        msg << child->​ToString(0,​ mapped, "",​ beautify, m_MaxChildKeyLength);​
 +    }
 +
 +    return msg.str();
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 내부 데이터를 문자열 형태로 반환한다.
 +/// \param indent 들여쓰기할 칸
 +/// \param mapped 부모 노드의 child type
 +/// \param header 키 문자열 출력시 앞에다 붙일 헤더
 +/// \param beautify 출력 파일의 인덴트를 예쁘게 정렬할 것인가의 여부
 +/// \param maxKeyLength 인덴트를 예쁘게 하는데 사용할 값
 +/// \return std::string 문자열 형태로 변환한 데이터
 +////////////////////////////////////////////////////////////////////////////////​
 +std::string cYamlTree::​ToString(size_t indent, bool mapped, ​
 +    const std::​string&​ header, bool beautify, size_t maxKeyLength) const
 +{
 +    std::​stringstream msg;
 +
 +    std::string spaces;
 +    for (size_t i=0; i<​indent;​ ++i)
 +        spaces = spaces + " ​ ";
 +
 +    // 현재 노드의 깊이에 맞춰 주석을 기록해준다. 유의할 점은 공백 문자로만 ​
 +    // 이루어진 주석은 앞에다 '#'​ 문자를 출력하지 않는다는 점이다. 그냥 빈 줄을 ​
 +    // 출력하기 위해서이다.
 +    if (!m_Comment.empty())
 +    {
 +        std::string comment = m_Comment;
 +        Replace(comment,​ "​\n",​ std::​string("​\n"​ + spaces));
 +
 +        std::string trimmed = Trim(comment,​ " ");
 +        if (!trimmed.empty() && trimmed[trimmed.size()-1] == '​\n'​)
 +            msg << spaces << comment;
 +        else
 +            msg << spaces << comment << std::endl;
 +    }
 +
 +    if (mapped)
 +    {
 +        msg << spaces << header << m_Key << ":";​
 +
 +        if (beautify && m_Key.size() < maxKeyLength && m_SequentialChilds.empty())
 +            msg << std::​string(maxKeyLength - m_Key.size(),​ ' ');
 +
 +        std::string value(m_Value);​
 +        switch (m_ScalarType)
 +        {
 +        case NORMAL_SCALAR:​
 +            msg << " " << value << std::endl;
 +            break;
 +        case BLOCK_SCALAR:​
 +            msg << " |" << std::endl;
 +            for (SCALARS::​const_iterator itr(m_Scalars.begin());​
 +                itr != m_Scalars.end();​ ++itr)
 +            {
 +                msg << spaces << " ​   " << *itr << std::endl;
 +            }
 +            break;
 +        case FOLDED_SCALAR:​
 +            msg << " >" << std::endl;
 +            for (SCALARS::​const_iterator itr(m_Scalars.begin());​
 +                itr != m_Scalars.end();​ ++itr)
 +            {
 +                msg << spaces << " ​   " << *itr << std::endl;
 +            }
 +            break;
 +        default:
 +            msg << spaces << m_Key << ": " << value << std::endl;
 +            break;
 +        }
 +
 +        if (m_ChildType == CHILD_MAPPED)
 +        {
 +            size_t size = m_SequentialChilds.size();​
 +            for (size_t i=0; i<size; ++i)
 +            {
 +                const cYamlTree* const child = m_SequentialChilds[i];​
 +
 +                if (header.empty())
 +                    msg << child->​ToString(indent + 2, true, "",​ beautify, m_MaxChildKeyLength);​
 +                else
 +                    msg << child->​ToString(indent + 3, true, "",​ beautify, m_MaxChildKeyLength);​
 +            }
 +        }
 +        else if (m_ChildType == CHILD_SEQNENTIAL)
 +        {
 +            size_t size = m_SequentialChilds.size();​
 +            for (size_t i=0; i<size; ++i)
 +            {
 +                const cYamlTree* const child = m_SequentialChilds[i];​
 +                if (header.empty())
 +                    msg << child->​ToString(indent + 1, true, "",​ beautify, m_MaxChildKeyLength);​
 +                else
 +                    msg << child->​ToString(indent + 2, true, "",​ beautify, m_MaxChildKeyLength);​
 +            }
 +        }
 +    }
 +    else
 +    {
 +        bool first = true;
 +        bool childMapped = m_ChildType == CHILD_MAPPED;​
 +
 +        size_t size = m_SequentialChilds.size();​
 +        for (size_t i=0; i<size; ++i)
 +        {
 +            const cYamlTree* const child = m_SequentialChilds[i];​
 +            if (first)
 +            {
 +                msg << child->​ToString(indent,​ childMapped,​ "- ", beautify, m_MaxChildKeyLength);​
 +                first = false;
 +            }
 +            else
 +            {
 +                msg << child->​ToString(indent + 1, childMapped,​ "",​ beautify, m_MaxChildKeyLength);​
 +            }
 +        }
 +    }
 +
 +    return msg.str();
 +}
 +
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlDocument::​cYamlDocument()
 +: cYamlTree()
 +{
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 소멸자
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlDocument::​~cYamlDocument()
 +{
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 앵커를 추가한다.
 +/// \param name 앵커 이름
 +/// \param tree 앵커의 대상이 되는 트리
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlDocument::​AddAnchor(const std::​string&​ name, cYamlTree* tree)
 +{
 +    if (!name.empty())
 +        m_Anchors[name] = tree;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 이름의 앵커를 반환한다.
 +/// \param name 앵커 이름
 +/// \return cYamlTree* 해당하는 트리가 존재하는 경우 그 트리를 반환하고,​
 +/// 존재하지 않는 경우에는 NULL을 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlDocument::​GetAnchor(const std::​string&​ name) const
 +{
 +    ANCHORS::​const_iterator itr(m_Anchors.find(name));​
 +    return itr != m_Anchors.end() ? itr->​second : NULL;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 모든 앵커를 삭제한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlDocument::​ClearAnchors()
 +{
 +    m_Anchors.clear();​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief YAML 파일에서 데이터를 읽어들여 데이터를 구성한다.
 +/// \param fileName 파일 이름
 +/// \return bool 파일을 무사히 읽어들인 경우 true, 뭔가 에러가 생긴 경우에는
 +/// false를 반환한다. 에러가 생긴 경우에는 std::​cerr을 이용해 에러 메시지가 ​
 +/// 출력된다.
 +////////////////////////////////////////////////////////////////////////////////​
 +bool cYamlDocument::​Load(const char* fileName)
 +{
 +    Clear();
 +    ClearAnchors();​
 +
 +    cYamlStream stream(fileName,​ this);
 +    if (!stream.IsGood())
 +    {
 +        char buf[1024] = {0,};
 +#if _MSC_VER >= 1400
 +        _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "​cannot open %s", fileName);
 +#else
 +        sprintf(buf,​ "​cannot open %s", fileName);
 +#endif
 +        buf[sizeof(buf)-1] = 0;
 +        m_LastError = buf;
 +        return false;
 +    }
 +
 +    std::​vector<​STATE>​ stateStack;
 +    stateStack.push_back(STATE(this,​ -1));
 +
 +    bool result = true;
 +    ParseMode mode = MODE_NORMAL;​
 +    cYamlTree* lastChild = NULL;
 +
 +    try
 +    {
 +        while (stream.IsGood())
 +        {
 +            // 스택이 비었다는 말은, AdoptChild 함수에서 ​
 +            // 올바른 parent를 찾다가 결국 못 찾았다는 이야기다. ​
 +            // 그러므로 이는 인덴트 에러다.
 +            if (stateStack.empty())
 +                throw std::​string("​indent error"​);​
 +
 +            if (stream.ParseNextLine(mode == MODE_NORMAL) != RESULT_NORMAL) ​
 +                continue; // 주석이라면 패스~
 +
 +            int indent = stream.GetIndent();​
 +            STATE& top = stateStack.back();​
 +
 +            // YAML은 무조건 컬럼 0부터 시작할 필요가 없기 때문에, ​
 +            // 루트 노드가 컬럼 0에 존재한다고 가정하면 곤란하다. ​
 +            // 제일 처음에 노드가 나오는 컬럼을 루트 노드의 컬럼으로 잡는다.
 +            if (top.indent == -1) { top.indent = indent; }
 +
 +            //​------------------------------------------------------------------
 +            // 한 라인씩 처리 중
 +            //​------------------------------------------------------------------
 +            if (mode == MODE_NORMAL)
 +            {
 +                if (top.indent == indent) // 탭이 이전 노드와 같다.
 +                {
 +                    //​cYamlTree* child = stream.CreateTree();​
 +                    //lastChild = top.node->​AddChild(child);​
 +
 +                    /// 부모 노드가 alias된 노드라면 곤란하다.
 +                    if (top.node->​GetAlias())
 +                    {
 +                        std::string msg("​cannot add child node to aliased node ");
 +                        throw (msg + top.node->​GetKey());​
 +                    }
 +
 +                    cYamlTree* child = stream.CreateTree();​
 +                    if (child->​GetChildCount() == 0 || child->​GetAlias())
 +                    {
 +                        lastChild = top.node->​AddChild(child);​
 +                    }
 +                    else
 +                    {
 +                        top.node->​AddChild(child);​
 +                        stateStack.push_back(STATE(child,​ stream.GetIndent()));​
 +                        lastChild = child->​GetChild(0);​
 +                    }
 +                }
 +                else if (top.indent < indent) // 탭이 늘어났다.
 +                {
 +                    if (lastChild == NULL)
 +                        throw std::​string("​indent error"​);​
 +
 +                    /// 부모 노드가 alias된 노드라면 곤란하다.
 +                    if (lastChild->​GetAlias())
 +                    {
 +                        std::string msg("​cannot add child node to aliased node ");
 +                        throw (msg + lastChild->​GetKey());​
 +                    }
 +
 +                    stateStack.push_back(STATE(lastChild,​ indent));
 +                    cYamlTree* child = stream.CreateTree();​
 +                    if (child->​GetChildCount() == 0 || child->​GetAlias())
 +                    {
 +                        lastChild = lastChild->​AddChild(child);​
 +                    }
 +                    else
 +                    {
 +                        stateStack.push_back(STATE(child,​ stream.GetIndent()));​
 +                        lastChild->​AddChild(child);​
 +                        lastChild = child->​GetChild(0);​
 +                    }
 +                }
 +                else if (indent < top.indent) // 탭이 줄어들었다.
 +                {
 +                    cYamlTree* child = stream.CreateTree();​
 +                    if (child->​GetChildCount() == 0 || child->​GetAlias())
 +                    {
 +                        lastChild = AdoptChild(stateStack,​ indent, child);
 +                    }
 +                    else
 +                    {
 +                        AdoptChild(stateStack,​ indent, child);
 +                        stateStack.push_back(STATE(child,​ stream.GetIndent()));​
 +                        lastChild = child->​GetChild(0);​
 +                    }
 +                }
 +
 +                mode = stream.GetNextMode();​
 +            }
 +            //​------------------------------------------------------------------
 +            // 여러 행에 걸친 값 처리 중
 +            //​------------------------------------------------------------------
 +            else 
 +            {
 +                if (top.indent == indent) // 탭이 이전 노드와 같다.
 +                {
 +                    if (stream.IsEmptyLine()) continue;
 +                    cYamlTree* child = stream.CreateTree();​
 +                    lastChild = top.node->​AddChild(child);​
 +                    mode = stream.GetNextMode();​
 +                }
 +                else if (top.indent < indent) // 탭이 늘어났다.
 +                {
 +                    std::string trimmed(Trim(stream.GetText()));​
 +                    if (mode == MODE_BLOCK_SCALAR)
 +                    {
 +                        lastChild->​SetValue(lastChild->​GetValue() + "​\n"​ + trimmed);
 +                        lastChild->​SetScalarType(cYamlTree::​BLOCK_SCALAR);​
 +                        lastChild->​AddScalar(trimmed);​
 +                    }
 +                    else if (mode == MODE_FOLDED_SCALAR)
 +                    {
 +                        lastChild->​SetValue(lastChild->​GetValue() + 
 +                            " " + trimmed);
 +                        lastChild->​SetScalarType(cYamlTree::​FOLDED_SCALAR);​
 +                        lastChild->​AddScalar(trimmed);​
 +                    }
 +                }
 +                else if (indent < top.indent) // 탭이 줄어들었다.
 +                {
 +                    // 탭이 줄어들었다고 해도, 커멘트 라인이라면 패스
 +                    if (stream.IsEmptyLine()) continue;
 +
 +                    lastChild = AdoptChild(stateStack,​ indent, stream.CreateTree());​
 +                    mode = stream.GetNextMode();​
 +                }
 +            }
 +        }
 +    }
 +    catch (std::​string&​ e)
 +    {
 +        char buf[1024] = {0, };
 +
 +#if _MSC_VER >= 1400
 +        _snprintf_s(buf,​ sizeof(buf),​ _TRUNCATE, ​
 +            "​%s:​%d:​ %s", fileName, stream.GetCurrentLine(),​ e.c_str());
 +#else
 +        sprintf(buf, ​
 +            "​%s:​%d:​ %s", fileName, stream.GetCurrentLine(),​ e.c_str());
 +#endif
 +        buf[sizeof(buf)-1] = 0;
 +        m_LastError = buf;
 +
 +        if (s_ParseErrorHandler)
 +            s_ParseErrorHandler(m_LastError.c_str());​
 +
 +        result = false;
 +    }
 +
 +    return result;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 내부 데이터를 파일에다 저장한다.
 +/// \param fileName 파일 이름
 +/// \param beautify 출력 파일의 인덴트를 예쁘게 정렬할 것인가의 여부
 +/// \return bool 저장에 성공한 경우 true, 파일을 쓰기용으로 열지 못했다면 false
 +////////////////////////////////////////////////////////////////////////////////​
 +bool cYamlDocument::​Save(const char* fileName, bool beautify) const
 +{
 +    std::​ofstream file(fileName,​ std::​ios::​out | std::​ios::​trunc);​
 +    if (!file) return false;
 +    file << ToString(beautify,​ false) << std::endl;
 +    return true;
 +}
 +
 +
 +
 +#ifdef D3DX_VERSION
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, const D3DXVECTOR2&​ value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL), ​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, const D3DXVECTOR3&​ value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL), ​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, const D3DXVECTOR4&​ value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL), ​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, const D3DXQUATERNION&​ value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL), ​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 생성자
 +/// \param key 키
 +/// \param value 값
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree::​cYamlTree(const std::​string&​ key, const D3DXCOLOR&​ value)
 +: m_Key(key), m_ScalarType(NORMAL_SCALAR),​ m_Alias(NULL), ​
 +m_ChildType(CHILD_UNKNOWN),​ m_MaxChildKeyLength(0)
 +{
 +    SetValue(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 D3DXVECTOR2 형태로 반환한다.
 +/// \return D3DXVECTOR2 값
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR2 cYamlTree::​GetValueAsVector2() const
 +{
 +    char text[256] = {0, };
 +#if _MSC_VER >= 1400
 +    strcpy_s(text,​ sizeof(text),​ m_Value.c_str());​
 +#else
 +    strcpy(text,​ m_Value.c_str());​
 +#endif
 +
 +    float value[2] = {0.0f, };
 +    int count = 0;
 +    char* token = NULL;
 +
 +#if _MSC_VER >= 1400
 +    char* context = NULL;
 +    token = strtok_s(text,​ ", \t", &​context);​
 +#else
 +    token = strtok(text,​ ", \t");
 +#endif
 +    while (token && count < 2)
 +    {
 +        value[count++] = static_cast<​float>​(atof(token));​
 +#if _MSC_VER >= 1400
 +        token = strtok_s(NULL,​ ", \t", &​context);​
 +#else
 +        token = strtok(text,​ ", \t");
 +#endif
 +    }
 +
 +    return D3DXVECTOR2(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 D3DXVECTOR3 형태로 반환한다.
 +/// \return D3DXVECTOR3 값
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR3 cYamlTree::​GetValueAsVector3() const
 +{
 +    char text[256] = {0, };
 +#if _MSC_VER >= 1400
 +    strcpy_s(text,​ sizeof(text),​ m_Value.c_str());​
 +#else
 +    strcpy(text,​ m_Value.c_str());​
 +#endif
 +
 +    float value[3] = {0.0f, };
 +    int count = 0;
 +    char* token = NULL;
 +
 +#if _MSC_VER >= 1400
 +    char* context = NULL;
 +    token = strtok_s(text,​ ", \t", &​context);​
 +#else
 +    token = strtok(text,​ ", \t");
 +#endif
 +    while (token && count < 3)
 +    {
 +        value[count++] = static_cast<​float>​(atof(token));​
 +#if _MSC_VER >= 1400
 +        token = strtok_s(NULL,​ ", \t", &​context);​
 +#else
 +        token = strtok(text,​ ", \t");
 +#endif
 +    }
 +
 +    return D3DXVECTOR3(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 D3DXVECTOR4 형태로 반환한다.
 +/// \return D3DXVECTOR4 값
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR4 cYamlTree::​GetValueAsVector4() const
 +{
 +    char text[256] = {0, };
 +#if _MSC_VER >= 1400
 +    strcpy_s(text,​ sizeof(text),​ m_Value.c_str());​
 +#else
 +    strcpy(text,​ m_Value.c_str());​
 +#endif
 +
 +    float value[4] = {0.0f, };
 +    int count = 0;
 +    char* token = NULL;
 +
 +#if _MSC_VER >= 1400
 +    char* context = NULL;
 +    token = strtok_s(text,​ ", \t", &​context);​
 +#else
 +    token = strtok(text,​ ", \t");
 +#endif
 +    while (token && count < 4)
 +    {
 +        value[count++] = static_cast<​float>​(atof(token));​
 +#if _MSC_VER >= 1400
 +        token = strtok_s(NULL,​ ", \t", &​context);​
 +#else
 +        token = strtok(text,​ ", \t");
 +#endif
 +    }
 +
 +    return D3DXVECTOR4(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 D3DXQUATERNION 형태로 반환한다.
 +/// \return D3DXQUATERNION 값
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXQUATERNION cYamlTree::​GetValueAsQuaternion() const
 +{
 +    char text[256] = {0, };
 +#if _MSC_VER >= 1400
 +    strcpy_s(text,​ sizeof(text),​ m_Value.c_str());​
 +#else
 +    strcpy(text,​ m_Value.c_str());​
 +#endif
 +
 +    float value[4] = {0.0f, };
 +    int count = 0;
 +    char* token = NULL;
 +
 +#if _MSC_VER >= 1400
 +    char* context = NULL;
 +    token = strtok_s(text,​ ", \t", &​context);​
 +#else
 +    token = strtok(text,​ ", \t");
 +#endif
 +    while (token && count < 4)
 +    {
 +        value[count++] = static_cast<​float>​(atof(token));​
 +#if _MSC_VER >= 1400
 +        token = strtok_s(NULL,​ ", \t", &​context);​
 +#else
 +        token = strtok(text,​ ", \t");
 +#endif
 +    }
 +
 +    return D3DXQUATERNION(value);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 노드의 값을 D3DXCOLOR 형태로 반환한다.
 +/// \return D3DXCOLOR 값
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXCOLOR cYamlTree::​GetValueAsColor() const
 +{
 +    char text[256] = {0, };
 +#if _MSC_VER >= 1400
 +    strcpy_s(text,​ sizeof(text),​ m_Value.c_str());​
 +#else
 +    strcpy(text,​ m_Value.c_str());​
 +#endif
 +
 +    unsigned char value[4] = {0, };
 +    int count = 0;
 +    char* token = NULL;
 +
 +#if _MSC_VER >= 1400
 +    char* context = NULL;
 +    token = strtok_s(text,​ ", \t", &​context);​
 +#else
 +    token = strtok(text,​ ", \t");
 +#endif
 +    while (token && count < 4)
 +    {
 +        value[count++] = (unsigned char)atoi(token);​
 +#if _MSC_VER >= 1400
 +        token = strtok_s(NULL,​ ", \t", &​context);​
 +#else
 +        token = strtok(text,​ ", \t");
 +#endif
 +    }
 +
 +    return D3DXCOLOR(D3DCOLOR_RGBA(value[0],​ value[1], value[2], value[3]));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief D3DXVECTOR2 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(const D3DXVECTOR2&​ value)
 +{
 +    char buf[256] = {0, };
 +#if _MSC_VER >= 1400
 +    _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "%f, %f", value.x, value.y);
 +#else
 +    sprintf(buf,​ "%f, %f", value.x, value.y);
 +#endif
 +    buf[sizeof(buf)-1] = 0;
 +    SetValue(buf);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief D3DXVECTOR3 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(const D3DXVECTOR3&​ value)
 +{
 +    char buf[256] = {0, };
 +#if _MSC_VER >= 1400
 +    _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "%f, %f, %f", ​
 +        value.x, value.y, value.z);
 +#else
 +    sprintf(buf,​ "%f, %f, %f", ​
 +        value.x, value.y, value.z);
 +#endif
 +    buf[sizeof(buf)-1] = 0;
 +    SetValue(buf);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief D3DXVECTOR4 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(const D3DXVECTOR4&​ value)
 +{
 +    char buf[256] = {0, };
 +#if _MSC_VER >= 1400
 +    _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "%f, %f, %f, %f", ​
 +        value.x, value.y, value.z, value.w);
 +#else
 +    sprintf(buf,​ "%f, %f, %f, %f", ​
 +        value.x, value.y, value.z, value.w);
 +#endif
 +    buf[sizeof(buf)-1] = 0;
 +    SetValue(buf);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief D3DXQUATERNION 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(const D3DXQUATERNION&​ value)
 +{
 +    char buf[256] = {0, };
 +#if _MSC_VER >= 1400
 +    _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "%f, %f, %f, %f", ​
 +        value.x, value.y, value.z, value.w);
 +#else
 +    sprintf(buf,​ "%f, %f, %f, %f", ​
 +        value.x, value.y, value.z, value.w);
 +#endif
 +    buf[sizeof(buf)-1] = 0;
 +    SetValue(buf);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief D3DXCOLOR 형태로 값을 설정한다.
 +/// \param value 설정할 값
 +////////////////////////////////////////////////////////////////////////////////​
 +void cYamlTree::​SetValue(const D3DXCOLOR&​ value)
 +{
 +    char buf[256] = {0, };
 +#if _MSC_VER >= 1400
 +    _snprintf_s(buf,​ sizeof(buf)-1,​ _TRUNCATE, "%d, %d, %d, %d", ​
 +        (int)(value.r * 255), (int)(value.g * 255), (int)(value.b * 255), (int)(value.a * 255));
 +#else
 +    sprintf(buf,​ "%d, %d, %d, %d", ​
 +        (int)(value.r * 255), (int)(value.g * 255), (int)(value.b * 255), (int)(value.a * 255));
 +#endif
 +    buf[sizeof(buf)-1] = 0;
 +    SetValue(buf);​
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXVECTOR2 형태로 반환한다.
 +/// \param key 키
 +/// \return D3DXVECTOR2 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 0.0f 배열을 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR2 cYamlTree::​AttrAsVector2(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? 
 +        child->​GetValueAsVector2() : D3DXVECTOR2(0.0f,​ 0.0f);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXVECTOR3 형태로 반환한다.
 +/// \param key 키
 +/// \return D3DXVECTOR3 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 0.0f 배열을 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR3 cYamlTree::​AttrAsVector3(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? 
 +        child->​GetValueAsVector3() : D3DXVECTOR3(0.0f,​ 0.0f, 0.0f);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXVECTOR4 형태로 반환한다.
 +/// \param key 키
 +/// \return D3DXVECTOR4 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 0.0f 배열을 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR4 cYamlTree::​AttrAsVector4(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? 
 +        child->​GetValueAsVector4() : D3DXVECTOR4(0.0f,​ 0.0f, 0.0f, 0.0f);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을
 +/// D3DXQUATERNION 형태로 반환한다.
 +/// \param key 키
 +/// \return D3DXQUATERNION 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 0.0f 배열을 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXQUATERNION cYamlTree::​AttrAsQuaternion(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? 
 +        child->​GetValueAsQuaternion() : D3DXQUATERNION(0.0f,​ 0.0f, 0.0f, 0.0f);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXCOLOR 형태로 반환한다.
 +/// \param key 키
 +/// \return D3DXCOLOR 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 0.0f 배열을 반환한다.
 +/// \note 해당하는 자식 노드가 없는 경우, 디버그 버전에서는 어서트를 발생시킨다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXCOLOR cYamlTree::​AttrAsColor(const std::​string&​ key) const
 +{
 +    cYamlTree* child = GetChild(key,​ true);
 +    return child ? 
 +        child->​GetValueAsColor() : D3DXCOLOR(0.0f,​ 0.0f, 0.0f, 0.0f);
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXVECTOR2 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return D3DXVECTOR2 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR2 cYamlTree::​AttrAsVector2Safe(const std::​string&​ key, const D3DXVECTOR2&​ nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsVector2() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXVECTOR3 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return D3DXVECTOR3 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR3 cYamlTree::​AttrAsVector3Safe(const std::​string&​ key, const D3DXVECTOR3&​ nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsVector3() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXVECTOR4 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return D3DXVECTOR4 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXVECTOR4 cYamlTree::​AttrAsVector4Safe(const std::​string&​ key, const D3DXVECTOR4&​ nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsVector4() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXQUATERNION 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return D3DXQUATERNION 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXQUATERNION cYamlTree::​AttrAsQuaternionSafe(const std::​string&​ key, const D3DXQUATERNION&​ nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsQuaternion() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 해당하는 키를 가진 자식 노드를 찾아, 그 노드의 텍스트 값을 ​
 +/// D3DXCOLOR 형태로 반환한다.
 +/// \param key 키
 +/// \param nullValue 해당하는 자식 노드가 존재하지 않을 경우에 반환할 값
 +/// \return D3DXCOLOR 해당하는 키를 가진 자식 노드가 있다면 그 노드의 값을 ​
 +/// 반환하고,​ 없다면 주어진 nullValue를 반환한다.
 +////////////////////////////////////////////////////////////////////////////////​
 +D3DXCOLOR cYamlTree::​AttrAsColorSafe(const std::​string&​ key, const D3DXCOLOR&​ nullValue) const
 +{
 +    cYamlTree* child = GetChild(key,​ false);
 +    return child ? child->​GetValueAsColor() : nullValue;
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, const D3DXVECTOR2&​ value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, const D3DXVECTOR3&​ value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, const D3DXVECTOR4&​ value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, const D3DXQUATERNION&​ value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +////////////////////////////////////////////////////////////////////////////////​
 +/// \brief 자식 노드를 추가한다.
 +/// \param key 키
 +/// \param value 값
 +/// \return cYamlTree* 추가한 자식 노드의 포인터
 +////////////////////////////////////////////////////////////////////////////////​
 +cYamlTree* cYamlTree::​AddAttr(const std::​string&​ key, const D3DXCOLOR&​ value)
 +{
 +    return AddChild(new cYamlTree(key,​ value));
 +}
 +
 +#endif
 +</​file>​
 +
 +====== 샘플 ======
 +아래와 같은 [[YAML]] 파일이 있다면.
 +<code yaml>
 +invoice: 34843 
 +date   : 2001-01-23 ​
 +bill-to: &​id001 ​
 +    given  : Chris 
 +    family : Dumars ​
 +    address: ​
 +        lines: | 
 +            458 Walkman Dr. 
 +            Suite #292 
 +        city    : Royal Oak 
 +        state   : MI 
 +        postal ​ : 48046 
 +ship-to: *id001 ​
 +product: ​
 +    - sku         : BL394D4 ​
 +      quantity ​   : 4 
 +      description : Basketball ​
 +      price       : 450.00 ​
 +    - sku         : BL4438H ​
 +      quantity ​   : 1 
 +      description : Super Hoop 
 +      price       : 2392.00 ​
 +</​code>​
 +
 +다음과 같은 방식으로 읽어들일 수 있다.
 +
 +<code cpp>
 +cYamlDocument document;
 +if (!document.Load("​test.yaml"​))
 +{
 +    std::cerr << document.GetLastError() << std::endl;
 +    return -1;
 +}
 +
 +std::cout << "​Postal:​ " ​
 +    << document.GetChild("​bill-to"​)->​GetChild("​address"​)->​GetChild("​postal"​)->​GetValueAsString() ​
 +    << std::endl;
 +
 +std::cout << "First Product Price" ​
 +    << document.GetChild("​product"​)->​GetChild(0)->​GetChild("​Price"​)->​GetValueAsFloat() ​
 +    << std::endl;
 +</​code>​
 +
 +====== 주의 사항 ======
 +한글로 된 이름을 가진 파일이 제대로 열리지 않는 경우가 있는데, 이는 파서 자체의 문제가 아니라, 파서 내부에서 사용하는 ifstream, ofstream 클래스의 문제다. 파일 이름을 받아서 유니 코드로 변환 후에 비로서 파일 열기를 시도하는데,​ 이때 로케일을 제대로 지정해 두지 않으면 콩가루 변환이 일어난다. 프로그램 초기화하는 부분에서 로케일을 제대로 설정해 두자. (VisualCpp 8.0 에서만 발생하는 듯?)
 +<code cpp>
 +// 로케일 지정
 +std::​locale::​global(std::​locale("​kor"​));​
 +</​code>​
 +
 +----
 +  * see also [[YAML]]
  
kb/yamlparserforcpp.txt · 마지막으로 수정됨: 2014/11/07 16:07 (바깥 편집)