사용자 도구

사이트 도구


kb:yamlparserforcsharp

Yaml Parser for C#

C++용 YAML 파서를 C# 용으로 바꿔봤다. 자세한 내용은 C++ 버전을 참고.

고칠 점 투성이일 듯. 파싱할 때 그냥 문자열이 아니라, 정규식 사용하면 좀 더 깔끔할까?

소스

YamlTree.cs
////////////////////////////////////////////////////////////////////////////////
// File        : YamlTree.cs
// Author      : 김성민 (http://serious-code.net)
// Desc        : 간단 YAML 파서
// Created     : 2006-07-24
// Last Update : 2006-07-24
////////////////////////////////////////////////////////////////////////////////
 
namespace Yaml
{
    /// <summary>
    /// 간단한 YAML 파서. XML 쪽의 DOM 파서를 생각하면 된다. 즉 문서를 통채로 
    /// 로드해서 메모리 상에 트리 형태로 구성하는 파서이다.
    ///
    /// XML 파일이 너무 지저분한 경향이 있어, 대안을 찾다가 YAML이라는 것을 
    /// 알게 되었다. 문법이 깔끔하기는 한데, C/C++ 파서가 현재 존재하지 않아서 
    /// 대충 쓰는 기능들만 모아서 한번 만들어봤다. 현재 지원하는 기능을 보자면 
    /// 다음과 같다.
    ///
    ///   - 기본적인 scalar, sequence, mapping 지원
    ///   - Block scalar 및 folded scalar 지원
    ///   - Anchor & alias 지원
    ///   - 라인 주석 지원
    ///
    /// 자식 노드는 맵 형태 또는 배열 형태로 액세스할 수 있다. 둘을 혼용할 수는 
    /// 없다. 기본적으로는 키 문자열을 이용해 맵 형태로 자식 노드들을 두게 된다. 
    /// 이는 같은 이름(key)의 자식 노드가 2개 이상 있는 경우, 그 중에 하나만 
    /// 선택됨을 의미한다. Ruby에 있는 YAML 파서 같은 경우에는 제일 마지막에 
    /// 들어온 자식 노드를 선택한다. 하지만 여기서는 제일 첫번째로 들어온 자식 
    /// 노드를 선택하고, 그 다음 들어오는 노드는 삭제해 버리는 방식을 택했다. 
    /// 배열 형태로 자식을 두기 위해서는 '-' 연산자를 이용해야 한다. 자세한 것은 
    /// 역시 YAML 문법을 참고. 
    /// </summary>
    /// <remarks>Verbatim 처리가 필요하다.</remarks>
    public class YamlTree
    {
        // 스칼라의 종류
        public enum ScalarType 
        { 
            NORMAL, BLOCK, FOLDED 
        };
 
        // 차일드 노드 저장 방식
        public enum ChildType 
        { 
            MAPPED, SEQNENTIAL, UNKNOWN 
        };
 
        // typedefs
        public class Mapping : 
            System.Collections.Generic.Dictionary<string, YamlTree> {};
        public class Sequence : 
            System.Collections.Generic.List<YamlTree> {};
        public class Scalars : 
            System.Collections.Generic.List<string> {};
 
        // instance variables
        protected string m_Key = "";
        protected string m_Value = "";
        protected string m_Comment = "";
        protected ScalarType m_ScalarType = ScalarType.NORMAL;
        protected Scalars m_Scalars = new Scalars();
        protected YamlTree m_Alias = null;
        protected ChildType m_ChildType = ChildType.UNKNOWN;
        protected Mapping m_MappedChilds = new Mapping();
        protected Sequence m_SequentialChilds = new Sequence();
        protected int m_MaxChildKeyLength = 0;
 
        // properties
        public string Key { get { return m_Key; } }
        public string Value { get { return m_Value; } set { m_Value = value; } }
        public YamlTree Alias { get { return m_Alias; } set { m_Alias = value; } }
        public Mapping ChildAsMapping { get { return m_MappedChilds; } }
        public Sequence ChildAsSequence { get { return m_SequentialChilds; } }
        public int ChildCount { get { return m_SequentialChilds.Count; } }
 
        // constructor
        public YamlTree() 
        { 
        }
 
        // constructor
        public YamlTree(string key, object value) 
        { 
            m_Key = key; 
            m_Value = value.ToString(); 
        }
 
        // destructor
        ~YamlTree()
        {
            m_MappedChilds.Clear();
            m_SequentialChilds.Clear();
        }
 
        // get node value
        public string GetValueAsString() { return m_Value; }
        public int GetValueAsInt() { return int.Parse(m_Value); }
        public float GetValueAsFloat() { return float.Parse(m_Value); }
        public double GetValueAsDouble() { return double.Parse(m_Value); }
        public bool GetValueAsBoolean() { return bool.Parse(m_Value); }
 
        // set node value
        public void SetValue(string value) { m_Value = value; }
        public void SetValue(int value) { m_Value = value.ToString(); }
        public void SetValue(float value) { m_Value = value.ToString(); }
        public void SetValue(double value) { m_Value = value.ToString(); }
        public void SetValue(bool value) { m_Value = value.ToString(); }
 
        // comment
        public string GetComment() 
        { 
            return m_Alias == null ? m_Comment : m_Alias.GetComment(); 
        }
        public void SetComment(string comment) 
        { 
            if (m_Alias == null) { m_Comment = comment; } 
            else { m_Alias.SetComment(comment); } 
        }
 
        // scalar
        public ScalarType GetScalarType() 
        { 
            return m_Alias == null ? m_ScalarType : m_Alias.GetScalarType();  
        }
        public void SetScalarType(ScalarType type) 
        { 
            if (m_Alias == null) { m_ScalarType = type; } 
            else { m_Alias.SetScalarType(type); } 
        }
        public void AddScalar(string text) 
        { 
            if (m_Alias == null) { m_Scalars.Add(text); }
            else { m_Alias.AddScalar(text); } 
        }
        public Scalars GetScalars() 
        { 
            return m_Alias == null ? m_Scalars : m_Alias.GetScalars(); 
        }
 
        // 자식 노드를 추가한다.
        public YamlTree AddChild(YamlTree child)
        {
            if (m_Alias != null)
                throw new System.Exception("cannot add child to aliased node");
 
            if (m_Value.Length > 0 && m_Value[0] != '&' && m_Value[0] != '*')
                throw new System.Exception("cannot add child to scalar node");
 
            if (child.Key == "")
            {
                if (m_ChildType == ChildType.UNKNOWN)
                    m_ChildType = ChildType.SEQNENTIAL;
 
                if (m_ChildType != ChildType.SEQNENTIAL)
                    throw new System.Exception("cannot add sequencial node");
 
                string buf = string.Format("{0,5:N}", m_SequentialChilds.Count);
 
                m_SequentialChilds.Add(child);
                m_MappedChilds.Add(buf.ToString(), child);
 
                return child;
            }
            else
            {
                if (m_ChildType == ChildType.UNKNOWN)
                    m_ChildType = ChildType.MAPPED;
 
                if (m_ChildType != ChildType.MAPPED)
                    throw new System.Exception("cannot add mapped node");
 
                if (!m_MappedChilds.ContainsKey(child.Key.ToLower()))
                {
                    m_MaxChildKeyLength = 
                        System.Math.Max(m_MaxChildKeyLength, child.Key.Length);
                    m_MappedChilds.Add(child.Key.ToLower(), child);
                    m_SequentialChilds.Add(child);
                    return child;
                }
                else
                {
                    throw new System.Exception(
                        string.Format("cannot add duplicated mapping - {0}", child.Key)
                        );
                }
            }
        }
 
        // 자식 노드의 존재 여부를 체크한다.
        public bool HasChild(string key)
        {
            if (m_Alias != null) return m_Alias.HasChild(key);
 
            if (m_ChildType == ChildType.MAPPED)
            {
                if (m_MappedChilds.ContainsKey(key.ToLower())) return true;
            }
            else if (m_ChildType == ChildType.SEQNENTIAL)
            {
                foreach (YamlTree child in m_SequentialChilds)
                    if (child.Key == key) return true;
            }
 
            return false;
        }
 
        // 해당하는 자식 노드를 반환한다.
        public YamlTree GetChild(string key, bool strict)
        {
            if (m_Alias != null) return m_Alias.GetChild(key, strict);
 
            if (m_ChildType == ChildType.MAPPED)
            {
                if (m_MappedChilds.ContainsKey(key.ToLower()))
                    return m_MappedChilds[key.ToLower()];
 
                if (strict)
                    throw new System.Exception(
                        string.Format("cannot find specified child node {0} at {1}", key, m_Key)
                        );
 
                return null;
            }
            else if (m_ChildType == ChildType.SEQNENTIAL)
            {
                foreach (YamlTree child in m_SequentialChilds)
                    if (child.Key == key) return child;
 
                if (strict)
                    throw new System.Exception(
                        string.Format("cannot find specified child node {0} at {1}", key, m_Key)
                        );
 
                return null;
            }
 
            if (strict)
                throw new System.Exception(string.Format("{0} node has invalid child", m_Key));
 
            return null;
        }
 
        // 해당하는 자식 노드를 반환한다.
        public YamlTree GetChild(int i, bool strict)
        {
            if (m_Alias != null)
                return m_Alias.GetChild(i, strict);
 
            if (i < m_SequentialChilds.Count)
                return m_SequentialChilds[i];
 
            if (strict)
                throw new System.Exception(
                    string.Format("out of range in node {0} with {1}", m_Key, i)
                    );
 
            return null;
        }
 
        // 자식 노드의 값을 설정한다.
        public YamlTree AddAttr(string key, string value) { return AddChild(new YamlTree(key, value)); }
        public YamlTree AddAttr(string key, int value) { return AddChild(new YamlTree(key, value)); }
        public YamlTree AddAttr(string key, float value) { return AddChild(new YamlTree(key, value)); }
        public YamlTree AddAttr(string key, double value) { return AddChild(new YamlTree(key, value)); }
        public YamlTree AddAttr(string key, bool value) { return AddChild(new YamlTree(key, value)); }
 
        // 자식 노드의 값을 반환한다.
        public string AttrAsString(string key) 
        { 
            YamlTree child = GetChild(key, true); 
            return child != null ? child.GetValueAsString() : ""; 
        }
        public int AttrAsInt(string key) 
        { 
            YamlTree child = GetChild(key, true); 
            return child != null ? child.GetValueAsInt() : 0; 
        }
        public float AttrAsFloat(string key) 
        { 
            YamlTree child = GetChild(key, true); 
            return child != null ? child.GetValueAsFloat() : 0.0f; 
        }
        public double AttrAsDouble(string key) 
        { 
            YamlTree child = GetChild(key, true); 
            return child != null ? child.GetValueAsFloat() : 0.0; 
        }
        public bool AttrAsBool(string key) 
        { 
            YamlTree child = GetChild(key, true); 
            return child != null ? child.GetValueAsBoolean() : false; 
        }
 
        // 자식 노드의 값을 안전하게(-_-) 반환한다.
        public string AttrAsStringSafe(string key, string nullValue) 
        { 
            YamlTree child = GetChild(key, false); 
            return child != null ? child.GetValueAsString() : nullValue; 
        }
        public int AttrAsIntSafe(string key, int nullValue) 
        { 
            YamlTree child = GetChild(key, false); 
            return child != null ? child.GetValueAsInt() : nullValue; 
        }
        public float AttrAsFloatSafe(string key, float nullValue) 
        { 
            YamlTree child = GetChild(key, false); 
            return child != null ? child.GetValueAsFloat() : nullValue; 
        }
        public double AttrAsDoubleSafe(string key, double nullValue) 
        { 
            YamlTree child = GetChild(key, false); 
            return child != null ? child.GetValueAsFloat() : nullValue; 
        }
        public bool AttrAsBoolSafe(string key, bool nullValue) 
        { 
            YamlTree child = GetChild(key, false); 
            return child != null ? child.GetValueAsBoolean() : nullValue; 
        }
 
        // 문자열화한다.
        public override string ToString()
        {
            System.Text.StringBuilder msg = new System.Text.StringBuilder();
 
            if (m_Comment.Length > 0)
                msg.AppendFormat("{0}\n", m_Comment);
 
            bool mapped = m_ChildType == ChildType.MAPPED;
            foreach (YamlTree child in m_SequentialChilds)
            {
                string result = child.ToString(0, mapped, "", m_MaxChildKeyLength);
                msg.AppendFormat("{0}", result);
            }
 
            return msg.ToString();
        }
 
        // 문자열화한다.
        private string ToString(int indent, bool mapped, string header, int maxKeyLength)
        {
            System.Text.StringBuilder msg = new System.Text.StringBuilder();
 
            string spaces = "";
            for (int i=0; i<indent; ++i)
                spaces = spaces + "  ";
 
            if (m_Comment.Length > 0)
            {
                string comment = m_Comment;
                comment = comment.Replace("\n", "\n" + spaces);
 
                string trimmed = comment.Trim();
                if (trimmed.Length > 0 && trimmed[trimmed.Length-1] == '\n')
                    msg.AppendFormat("{0}{1}", spaces, comment);
                else
                    msg.AppendFormat("{0}{1}\n", spaces, comment);
            }
 
            if (mapped)
            {
                msg.AppendFormat("{0}{1}{2}:", spaces, header, m_Key);
 
                string value = m_Value;
                switch (m_ScalarType)
                {
                case ScalarType.NORMAL:
                    msg.AppendFormat(" {0}\n", value);
                    break;
                case ScalarType.BLOCK:
                    msg.AppendFormat(" |\n");
                    foreach (string text in m_Scalars)
                        msg.AppendFormat("{0}    {1}\n", spaces, text);
                    break;
                case ScalarType.FOLDED:
                    msg.AppendFormat(" >\n");
                    foreach (string text in m_Scalars)
                        msg.AppendFormat("{0}    {1}\n", spaces, text);
                    break;
                default:
                    msg.AppendFormat("{0}{1}: {2}\n", spaces, m_Key, value);
                    break;
                }
 
                if (m_ChildType == ChildType.MAPPED)
                {
                    foreach (YamlTree child in m_SequentialChilds)
                    {
                        if (header.Length == 0)
                            msg.Append(child.ToString(indent + 2, true, "", m_MaxChildKeyLength));
                        else
                            msg.Append(child.ToString(indent + 3, true, "", m_MaxChildKeyLength));
                    }
                }
                else if (m_ChildType == ChildType.SEQNENTIAL)
                {
                    foreach (YamlTree child in m_SequentialChilds)
                    {
                        msg.Append(child.ToString(indent + 1, false, "", m_MaxChildKeyLength));
                    }
                }
            }
            else
            {
                bool first = true;
                bool childMapped = m_ChildType == ChildType.MAPPED;
 
                foreach (YamlTree child in m_SequentialChilds)
                {
                    if (first)
                    {
                        msg.Append(child.ToString(indent, childMapped, "- ", m_MaxChildKeyLength));
                        first = false;
                    }
                    else
                    {
                        msg.Append(child.ToString(indent + 1, childMapped, "", m_MaxChildKeyLength));
                    }
                }
            }
 
            return msg.ToString();
        }
 
        // 모든 노드 삭제
        public void Clear()
        {
            if (m_SequentialChilds.Count != m_MappedChilds.Count)
                throw new System.Exception(
                    string.Format("child count mismatch at node {0}", m_Key)
                    );
 
            m_Key = "";
            m_Value = "";
            m_Comment = "";
            m_ScalarType = ScalarType.NORMAL;
            m_Scalars.Clear();
            m_Alias = null;
            m_ChildType = ChildType.UNKNOWN;
            m_MappedChilds.Clear();
            m_SequentialChilds.Clear();
            m_MaxChildKeyLength = 0;
        }
    }
 
    // 파싱 중에 내부적으로 사용하기 위한 구조체
    internal struct STATE
    {
        public YamlTree node;
        public int indent;
        public STATE(YamlTree n, int i) { node = n; indent = i; }
    };
 
    // 위에서 선언한 구조체를 이용한 스택
    internal class StateStack : System.Collections.Generic.Stack<STATE> { };
 
    /// <summary>
    /// 형식화된 파일 입출력을 위한 스트림 객체. 라인 단위로 파일을 읽어들이는 
    /// 기능 외에 마지막으로 읽어들인 문자열을 이용해 다음 라인을 어떻게 
    /// 처리하느냐를 판단하는 기능도 담당한다.
    /// </summary>
    internal class YamlStream
    {
        // 상수들
        private const string YAML_WHITESPACES = " \t\r\n";
        private const string YAML_LINEFEEDS = "\r\n";
        private const string YAML_INDENT = " \t";
        private const char YAML_COLON = ':';
        private const char YAML_SHARP = '#';
        private const char YAML_MINUS = '-';
        private const char YAML_AMPERSAND = '&';
        private const char YAML_ASTERISK = '*';
        private const string YAML_PIPE = "|";
        private const string YAML_RIGHT_BRACKET = ">";
        private const string YAML_DOCUMENT_HEADER = "---";
        private const string YAML_DOCUMENT_TERMINATOR = "...";
 
        // 파싱 결과
        public enum ParseResult { NORMAL, EMPTY };
 
        // 현재 파싱 모드
        public enum ParseMode { NORMAL, BLOCK, FOLDED };
 
        // 현재 인덴트 값을 기준으로 스택에서 정확한 부모를 찾아, 주어진 노드를 
        // 자식 노드로 편입시킨다.
        public static YamlTree AdoptChild(StateStack stack, int indent, YamlTree child)
        {
            while (stack.Count > 0)
            {
                STATE e = stack.Peek();
                if (e.indent == indent)
                    return e.node.AddChild(child);
                else
                    stack.Pop();
            }
 
            return null;
        }
 
        // C++ STL string::find_first_not_of
        public static int IndexNotOf(string text, string delimiters, int startIndex)
        {
            int index = startIndex;
            while (index < text.Length)
            {
                if (delimiters.IndexOf(text[index]) == -1)
                    return index;
 
                index++;
            }
 
            return -1;
        }
 
        // C++ STL string::find_last_not_of
        public static int LastIndexNotOf(string text, string delimiters, int startIndex)
        {
            int index = startIndex;
            int foundIdx = -1;
            while (index < text.Length)
            {
                if (delimiters.IndexOf(text[index]) == -1)
                    foundIdx = index;
 
                index++;
            }
 
            return foundIdx;
        }
 
        // instance variables
        System.IO.StreamReader m_File = null;
        YamlDocument m_Document = null;
        int m_Line = 0;
        int m_Indent = 0;
        string m_Text = "";
        string m_Comment = "";
        ParseMode m_Mode = ParseMode.NORMAL;
 
        // properties
        public ParseMode Mode { get { return m_Mode; } }
        public int Line { get { return m_Line; } }
        public int Indent { get { return m_Indent; } }
        public string Text { get { return m_Text; } }
 
        // costructor
        public YamlStream(string fileName, YamlDocument document)
        {
            m_File = new System.IO.StreamReader(fileName, document.Encoding);
            m_Document = document;
        }
 
        // 한 라인을 읽어들여 파싱한다.
        public ParseResult ParseNextLine(bool processComment)
        {
            ++m_Line;
 
            m_Text = m_File.ReadLine();
            if (m_Text.Length == 0)
                return ParseResult.EMPTY; // empty line
 
            int pos = IndexNotOf(m_Text, YAML_INDENT, 0);
            if (pos < 0)
            {
                m_Comment += "\n";
                return ParseResult.EMPTY; // only whitespace
            }
 
            if (processComment && m_Text[pos] == YAML_SHARP)
            {
                m_Comment += m_Text.Substring(pos) + "\n";
                return ParseResult.EMPTY; // comment line
            }
 
            m_Indent = pos;
 
            return ParseResult.NORMAL;
        }
 
        // 현재 라인의 텍스트를 이용해 트리 객체를 생성한다.
        public YamlTree CreateTree()
        {
            string text = m_Text;
 
            int pos = text.IndexOf(YAML_COLON);
            if (pos < 0)
                throw new System.Exception("cannot find colon character");
 
            string key = text.Substring(0, pos).Trim();
            string value = text.Substring(pos + 1).Trim();
 
            if (key.Length == 0)
                throw new System.Exception("key text is empty");
 
            YamlTree result = null;
            if (key[0] == YAML_MINUS)
            {
                m_Indent += IndexNotOf(key, YAML_INDENT, 1);
                key = key.Substring(1).Trim();
                result = new YamlTree("", "");
                result.AddChild(new YamlTree(key, value));
            }
            else
            {
                result = new YamlTree(key, value);
            }
 
            if (m_Comment.Length > 0)
            {
                result.SetComment(m_Comment.Substring(0, m_Comment.Length - 1));
                m_Comment = "";
            }
 
            if (value.Length > 1)
            {
                if (value[0] == YAML_AMPERSAND)
                {
                    m_Document.AddAnchor(value.Substring(1).Trim(), result);
                }
                else if (value[0] == YAML_ASTERISK)
                {
                    string trimmed = value.Substring(1);
                    YamlTree anchor = m_Document.GetAnchor(trimmed);
                    if (anchor == null)
                        throw new System.Exception("no such anchor " + trimmed);
                    result.Alias = anchor;
                }
            }
 
            if (value == YAML_PIPE)
            {
                m_Mode = ParseMode.BLOCK;
                result.SetValue("");
            }
            else if (value == YAML_RIGHT_BRACKET)
            {
                m_Mode = ParseMode.FOLDED;
                result.SetValue("");
            }
            else
            {
                m_Mode = ParseMode.NORMAL;
            }
 
            return result;
        }
 
        // 스트림의 상태가 정상적인가의 여부
        public bool IsGood() { return m_File.EndOfStream == false; }
 
        // 현재 라인이 빈 라인인지의 여부
        public bool IsEmptyLine() 
        { 
            string trimmed = m_Text.Trim(); 
            return trimmed.Length == 0 || trimmed[0] == YAML_SHARP; 
        }
    };
 
    /// <summary>
    /// YamlTree 최상위 노드. 
    ///
    /// 실제로 파일 입출력을 다룰 때에는 이 클래스를 이용해야 한다. 이와 같은 
    /// 클래스가 필요한 이유는 ANCHOR 정보가 문서 전체를 통해 전역으로 존재하기 
    /// 때문이다. Load/Save 함수도 어차피 이와 같은 클래스를 따로 두고, 이 안에
    /// 넣는 것이 깔끔하기는 하다.
    ///
    /// Anchor & alias 같은 경우, 파일을 읽어들일 때, 즉 이미 존재하는 
    /// anchor & alias 정보를 읽어들이는 것은 별 문제가 없다. 문제는 그 반대, 
    /// 즉 메모리 상에서 트리 구조를 구성한 후, 파일에다 쓸 때는 인터페이스를 
    /// 어떻게 제공해야 할지를 잘 모르겠다.
    /// </summary>
    public class YamlDocument : YamlTree
    {
        // typedef 
        class Anchors : 
            System.Collections.Generic.Dictionary<string,YamlTree> {};
 
        // instance variables
        System.Text.Encoding m_Encoding = null;
        Anchors m_Anchors = new Anchors();
        string m_LastError = "";
 
        // properties
        public System.Text.Encoding Encoding { get { return m_Encoding; } set { m_Encoding = value; } }
        public string LastError { get { return m_LastError; } }
 
 
        // anchors
        public void AddAnchor(string name, YamlTree tree) 
        {
            m_Anchors.Add(name.ToLower(), tree); 
        }
        public YamlTree GetAnchor(string name) 
        { 
            return m_Anchors.ContainsKey(name.ToLower()) ? m_Anchors[name.ToLower()] : null; 
        }
        public void ClearAnchors() 
        { 
            m_Anchors.Clear(); 
        }
 
        // 생성자
        public YamlDocument() 
            : base() 
        {
            m_Key = "<ROOT>";
            m_Encoding = System.Text.Encoding.Default;
        }
 
        // 생성자
        public YamlDocument(System.Text.Encoding encoding)
            : base()
        {
            m_Key = "<ROOT>";
            m_Encoding = encoding;
        }
 
        // 불러오기
        public bool Load(string fileName)
        {
            Clear();
            ClearAnchors();
 
            YamlStream stream = new YamlStream(fileName, this);
            if (!stream.IsGood())
            {
                m_LastError = "cannot open " + fileName;
                return false;
            }
 
            StateStack stack = new StateStack();
            stack.Push(new STATE(this, -1));
 
            bool result = true;
            YamlStream.ParseMode mode = YamlStream.ParseMode.NORMAL;
            YamlTree lastChild = null;
 
            try
            {
                while (stream.IsGood())
                {
                    if (stack.Count == 0)
                        throw new System.Exception("indent error");
 
                    bool normal = mode == YamlStream.ParseMode.NORMAL;
                    if (stream.ParseNextLine(normal) != YamlStream.ParseResult.NORMAL) 
                        continue;
 
                    int indent = stream.Indent;
                    STATE top = stack.Peek();
 
                    if (top.indent == -1) 
                    {
                        stack.Pop();
                        stack.Push(new STATE(top.node, indent));
                        top = stack.Peek();
                    }
 
                    if (mode == YamlStream.ParseMode.NORMAL)
                    {
                        if (top.indent == indent)
                        {
                            if (top.node.Alias != null)
                                throw new System.Exception(
                                    "cannot add child node to aliased node " + top.node.Key
                                    );
 
                            YamlTree child = stream.CreateTree();
                            if (child.ChildCount == 0 || child.Alias != null)
                            {
                                lastChild = top.node.AddChild(child);
                            }
                            else
                            {
                                top.node.AddChild(child);
                                stack.Push(new STATE(child, stream.Indent));
                                lastChild = child.GetChild(0, true);
                            }
                        }
                        else if (top.indent < indent)
                        {
                            if (lastChild == null)
                                throw new System.Exception("indent error");
 
                            if (lastChild.Alias != null)
                                throw new System.Exception(
                                    "cannot add child node to aliased node " + lastChild.Key
                                    );
 
                            stack.Push(new STATE(lastChild, indent));
                            YamlTree child = stream.CreateTree();
                            if (child.ChildCount == 0 || child.Alias != null)
                            {
                                lastChild = lastChild.AddChild(child);
                            }
                            else
                            {
                                stack.Push(new STATE(child, stream.Indent));
                                lastChild.AddChild(child);
                                lastChild = child.GetChild(0, true);
                            }
                        }
                        else if (indent < top.indent)
                        {
                            YamlTree child = stream.CreateTree();
                            if (child.ChildCount == 0 || child.Alias != null)
                            {
                                lastChild = YamlStream.AdoptChild(stack, indent, child);
                            }
                            else
                            {
                                YamlStream.AdoptChild(stack, indent, child);
                                stack.Push(new STATE(child, stream.Indent));
                                lastChild = child.GetChild(0, true);
                            }
                        }
 
                        mode = stream.Mode;
                    }
                    else 
                    {
                        if (top.indent == indent)
                        {
                            if (stream.IsEmptyLine()) continue;
                            YamlTree child = stream.CreateTree();
                            lastChild = top.node.AddChild(child);
                            mode = stream.Mode;
                        }
                        else if (top.indent < indent)
                        {
                            string trimmed = stream.Text.Trim();
                            if (mode == YamlStream.ParseMode.BLOCK)
                            {
                                lastChild.SetValue(lastChild.GetValueAsString() + "\n" + trimmed);
                                lastChild.SetScalarType(YamlTree.ScalarType.BLOCK);
                                lastChild.AddScalar(trimmed);
                            }
                            else if (mode == YamlStream.ParseMode.FOLDED)
                            {
                                lastChild.SetValue(lastChild.GetValueAsString() + " " + trimmed);
                                lastChild.SetScalarType(YamlTree.ScalarType.FOLDED);
                                lastChild.AddScalar(trimmed);
                            }
                        }
                        else if (indent < top.indent)
                        {
                            if (stream.IsEmptyLine()) continue;
                            lastChild = YamlStream.AdoptChild(stack, indent, stream.CreateTree());
                            mode = stream.Mode;
                        }
                    }
                }
            }
            catch (System.Exception e)
            {
                m_LastError = string.Format("{0}:{1} {2}", fileName, stream.Line, e.ToString());
                result = false;
            }
 
            return result;
        }
 
        // 저장
        public bool Save(string fileName)
        {
            System.IO.StreamWriter file = new System.IO.StreamWriter(fileName, false, m_Encoding);
            string msg = ToString();
            file.WriteLine(msg);
            file.Close();
            return true;
        }
    };
}

kb/yamlparserforcsharp.txt · 마지막으로 수정됨: 2014/11/07 16:07 (바깥 편집)