233 lines
6.7 KiB
C#
233 lines
6.7 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Newtonsoft.Json.Linq;
|
|
using Pf2eDataScrapper.SchemaBuilder.Types;
|
|
|
|
namespace Pf2eDataScrapper.SchemaBuilder;
|
|
|
|
/// <summary>
|
|
/// Элемент схемы данных.
|
|
/// </summary>
|
|
internal 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;
|
|
}
|
|
}
|