using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using Pf2e.Scrapper.SchemaBuilder.Types; namespace Pf2e.Scrapper.SchemaBuilder; /// /// Элемент схемы данных. /// /// /// Пример использования: /// /// SchemaNode schema = new (); // Создание схемы данных /// data = JObject.Parse(reader.ReadToEnd()); // Загрузка данных /// schema.AddValue(data); // Добавление данных /// /// public class SchemaNode { /// /// Отображаемое имя элемента. /// public readonly string Name = ""; /// /// Путь к элементу. /// public string Path = ""; /// /// Родительский элемент. /// public readonly SchemaNode? Parent; /// /// Указание, является ли элемент корневым. /// public readonly bool Root; /// /// Корневой элемент схемы данных. /// public SchemaNode RootNode => Root ? this : Parent!.RootNode; /// /// Типы данных, использующиеся в элементе. /// public readonly List Types = []; /// /// Указание, может ли этот элемент отсутсвовать в структуре данных. /// public bool IsNullable; /// /// Объект для определения типа поля в схеме данных. /// public TypeResolver Resolver = new(); /// /// Количество значений, добавленных в тип. /// public int Values { get; protected set; } /// /// Указание, является ли тип элемента простым. /// public bool IsSimple => Types.Count == 1; /// /// Конструктор корневого элемента. /// public SchemaNode() { Root = true; } /// /// Конструктор элемента схемы данных. /// /// Родительский элемент. /// Имя элемента. public SchemaNode(SchemaNode parent, string name) { Parent = parent; Name = name; Path = parent.Path + "." + name; Resolver = parent.Resolver; } /// /// Добавление нового возможного значения. /// /// Добавляемое значение public void AddValue(JToken? value) { if (value is null || Resolver.TokenType(value) == JTokenType.Null) { IsNullable = true; return; } Values++; foreach (AbstractSchemaType type in Types.Where(type => type.IsCompatibleValue(value))) { type.AddValue(value); return; } AbstractSchemaType newType = Resolver.Resolve(this, value); if (newType.IsCompatibleValue(value)) { Types.Add(newType); newType.AddValue(value); } else { AbstractSchemaType fallbackType = Resolver.Resolve(this, value, true); Types.Add(fallbackType); fallbackType.AddValue(value); } } /// /// Получение строкового представоения типа элемента. /// /// Строковое представоение типа элемента public string GetTypeName() { if (IsSimple) { return Types[0].GetTypeName(); } List typeNames = [.. Types.Select(t => t.GetTypeName())]; return string.Join(" | ", typeNames); } /// /// Полное строковое представление элемента. /// /// Строковое представление элемента. public override string ToString() { Dictionary result = new() { ["type"] = GetTypeName(), ["path"] = Path, ["is_null"] = IsNullable ? "true" : "false", ["values"] = Values.ToString(), }; if (IsSimple) { return FormatYaml(result, Types[0].GetYamlData()); } if (Types.Count == 0) { return FormatYaml(result, new() { ["is_empty"] = "true" }); } List types = []; foreach (AbstractSchemaType type in Types) { types.Add(Indent(type.ToString())); } return FormatYaml(result, new() { ["types"] = "- " + string.Join("\r\n- ", types) }); } /// /// Подготовка элемента к красивому представлению. /// public void Beautify() { foreach (AbstractSchemaType type in Types) { type.Beautify(); } } /// /// Отступ для строкового представления. /// public const string Tab = " "; /// /// Предел количество отображаемых элементов. /// public const int ElementLimit = 20; /// /// Увеличение отступа строки. /// /// Изменяемая строка. /// Строка с добавленными отступами. public static string Indent(string text) { return text.Replace("\r\n", "\r\n" + Tab); } /// /// Форматирование данных в YAML. /// /// Словарь данных. /// Строка форматированных данных public static string FormatYaml(params List> input) { string result = ""; foreach (KeyValuePair kvp in MergeDictionaries(input)) { string val = kvp.Value.Contains("\r\n") ? $"\r\n{Tab}{Indent(kvp.Value)}" : kvp.Value; result += $"{kvp.Key}: {val}\r\n"; } return result.Trim(); } public static Dictionary MergeDictionaries( params List> input ) { Dictionary result = []; foreach (KeyValuePair kvp in input.SelectMany(item => item)) { result[kvp.Key] = kvp.Value; } return result; } }