사용자 도구

사이트 도구


kb:yamlparserforcsharp

차이

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

차이 보기로 링크

kb:yamlparserforcsharp [2014/11/07 16:07] (현재)
줄 1: 줄 1:
 +====== Yaml Parser for C# ======
 +C++용 YAML 파서를 C# 용으로 바꿔봤다. 자세한 내용은 [[YamlParserForCpp|C++ 버전]]을 참고.
 +
 +고칠 점 투성이일 듯. 파싱할 때 그냥 문자열이 아니라, 정규식 사용하면 좀 더 깔끔할까?​
 +
 +====== 소스 ======
 +<file 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;
 +        }
 +    };
 +}
 +</​file>​
 +
 +----
 +  * see also [[YAML]], [[CSharp|C#​]]
  
kb/yamlparserforcsharp.txt · 마지막으로 수정됨: 2014/11/07 16:07 (바깥 편집)