using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace Superlazy.Loader { public static class YamlLoader { public static SLEntity LoadYaml(string yamlString) { var root = SLEntity.Empty; LoadYaml(root, yamlString); return root; } public static void LoadYaml(SLEntity root, string yamlString) { var currentEntity = root; var stack = new Stack>(); using var reader = new StringReader(yamlString); string line; while ((line = reader.ReadLine()) != null) { var lineBegin = 0; while (lineBegin < line.Length && line[lineBegin] == ' ') lineBegin++; if (line.Length <= lineBegin) continue; var separatorIndex = line.IndexOf(':'); string key; var isArray = false; if (separatorIndex == -1) { if (line[lineBegin] == '#') { continue; } else if (line[lineBegin] == '-') { isArray = true;// 배열 전용 처리 key = null; separatorIndex = lineBegin; } else { continue; } } else { var commentIndex = line.IndexOf('#'); if (commentIndex >= 0 && commentIndex <= separatorIndex) // 키에는 주석을 넣을수 없기 때문에 구분자 이전은 주석 { continue; // 전체 주석 } else { key = line.Substring(lineBegin, separatorIndex - lineBegin); } } var valueBegin = separatorIndex + 2; var valueEnd = line.Length; if (valueBegin + 1 < valueEnd) { var commentIndex = line.IndexOf('#', valueBegin, valueEnd - valueBegin); // TODO: 문자열 내의 #은 인식 못하도록 현재가 문자열 내부인지 체크가 필요 if (commentIndex >= 0 && (commentIndex == 0 || line[commentIndex - 1] == ' ')) // 빈칸이 하나 있어야 주석임 { valueEnd = commentIndex - 1; } while (valueEnd > valueBegin && line[valueBegin] == ' ') { valueBegin++; } while (valueEnd >= valueBegin && line[valueEnd - 1] == ' ') { valueEnd--; } } while (stack.Count > 0 && lineBegin <= stack.Peek().Item2) { currentEntity.EndModified(); currentEntity = stack.Pop().Item1; } if (isArray) { key = (currentEntity.Count() + 1).ToString(); } if (valueBegin >= valueEnd) { stack.Push(new Tuple(currentEntity, lineBegin)); currentEntity = currentEntity[key]; } else { var forceString = false; if (line[valueBegin] == '"' && line[valueEnd - 1] == '"' || line[valueBegin] == '\'' && line[valueEnd - 1] == '\'') { valueBegin += 1; valueEnd -= 1; forceString = true; } var value = line.Substring(valueBegin, valueEnd - valueBegin); if (forceString) { currentEntity[key] = value; } else if (int.TryParse(value, out var intValue)) { currentEntity[key] = intValue; } else if (double.TryParse(value, out var doubleValue)) { currentEntity[key] = doubleValue; } else { currentEntity[key] = value; } } } while (stack.Count > 0) { currentEntity.EndModified(); currentEntity = stack.Pop().Item1; } } public static string SaveToYaml(SLEntity entity, int indentation = 0, bool useArray = false) { var yamlString = new StringBuilder(); var index = 1; IEnumerable childs = entity; if (useArray && entity.All(x => int.TryParse(x.ID, out _))) { childs = entity.OrderBy(x => int.Parse(x.ID)); } else // 전부 int로 파싱 못하면, 배열로 처리하지 않음 { useArray = false; } foreach (var child in childs) { var key = child.ID; var value = child; if (useArray && index > 0 && key == index.ToString()) { key = "-"; ++index; yamlString.Append(' ', indentation).Append(key); } else { index = -1; yamlString.Append(' ', indentation).Append(key).Append(':'); } if (value.IsValue) { var valueString = value.ToString(); var needsQuotes = value.IsNumeric == false && (int.TryParse(valueString, out _) || double.TryParse(valueString, out _)) // 숫자인데 강제로 문자화 한거거나 || valueString.Contains('#') || valueString.StartsWith('[') || valueString.StartsWith('{') // 특수문자가 포함되어 있거나, 배열이나 객체로 시작하는 경우 {Value} || valueString.Contains(':') // "남은 시간: {Value}" 꼴 || (value.IsNumeric == false && valueString.StartsWith('-')); // "- 어쩌고" 꼴, 배열인것으로 이해 yamlString.Append(' '); if (needsQuotes) { yamlString.Append('"').Append(valueString.Replace("\"", "\\\"")).Append('"'); } else { yamlString.Append(valueString); } yamlString.Append(Environment.NewLine); } else { yamlString.Append(Environment.NewLine).Append(SaveToYaml(value, indentation + 2, useArray)); } } return yamlString.ToString(); } } }