Files

241 lines
7.0 KiB
C#

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