342 lines
10 KiB
C#
342 lines
10 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using Pf2e.Database.Model.Base.Source;
|
||
|
||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||
|
||
/// <summary>
|
||
/// Класс коллекции книг, содержащих правила.
|
||
/// Реализует шаблон <c>Singleton</c>.
|
||
/// </summary>
|
||
public class RuleTocCollection
|
||
{
|
||
/// <summary>
|
||
/// Список книг, содержащих правила.
|
||
/// </summary>
|
||
public List<RuleTocBook> Books { get; } = [];
|
||
|
||
/// <summary>
|
||
/// Поиск книги по числовому идентификатору.
|
||
/// </summary>
|
||
public RuleTocBook this[int id] =>
|
||
Books.FirstOrDefault(i => i.SourceId == id) ?? throw new($"Book with id {id} not found");
|
||
|
||
/// <summary>
|
||
/// Поиск книги по CLR-представлению источника.
|
||
/// </summary>
|
||
public RuleTocBook this[Source source]
|
||
{
|
||
get
|
||
{
|
||
RuleTocBook? result = Books.FirstOrDefault(i => i.SourceId == source.Id);
|
||
|
||
if (result is not null)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
result = new(source);
|
||
Books.Add(result);
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Поиск книги по имени.
|
||
/// </summary>
|
||
public RuleTocBook? FindName(string name)
|
||
{
|
||
foreach (RuleTocBook book in Books)
|
||
{
|
||
if (book.IsNameExists(name))
|
||
{
|
||
return book;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Поиск правила по идентификатору.
|
||
/// </summary>
|
||
public RuleTocItem? FindEntityId(int id)
|
||
{
|
||
foreach (RuleTocBook book in Books)
|
||
{
|
||
RuleTocItem? result = book.GetByEntityId(id);
|
||
|
||
if (result is not null)
|
||
{
|
||
return result;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возвращает строковое представление коллекции в файле YAML.
|
||
/// </summary>
|
||
public override string ToString()
|
||
{
|
||
return string.Join("\n", Books.Select(book => book.ToString()).ToList());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Сохраняет коллекцию в файле YAML.
|
||
/// </summary>
|
||
public void SaveFile()
|
||
{
|
||
string fileName =
|
||
$"rules-toc.{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()}.debug.yaml";
|
||
|
||
using FileStream fileStream = File.Create(fileName);
|
||
using StreamWriter writer = new(fileStream);
|
||
|
||
writer.Write(ToString());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Экземпляр класса <see cref="RuleTocCollection"/>.
|
||
/// </summary>
|
||
public static RuleTocCollection Instance { get; } = new();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Класс представляет собой книгу, содержащую правила.
|
||
/// </summary>
|
||
public class RuleTocBook(Source source)
|
||
{
|
||
/// <summary>
|
||
/// Идентификатор источника.
|
||
/// </summary>
|
||
public int SourceId { get; } = source.Id;
|
||
|
||
/// <summary>
|
||
/// Названия источника.
|
||
/// </summary>
|
||
public string SourceName { get; } = source.Name;
|
||
|
||
/// <summary>
|
||
/// Список дочерних правил.
|
||
/// </summary>
|
||
/// <summary>
|
||
/// Список дочерних элементов.
|
||
/// </summary>
|
||
public List<RuleTocItem> Children { get; } = [];
|
||
|
||
/// <summary>
|
||
/// Находит элемент по хлебным крошкам.
|
||
/// </summary>
|
||
/// <param name="breadcrumbs">Массив хлебных крошек, указывающих на путь в дереве.</param>
|
||
/// <returns><see cref="RuleTocItem"/> или <c>null</c> если элемент не найден.</returns>
|
||
public RuleTocItem Get(string[] breadcrumbs)
|
||
{
|
||
RuleTocItem? node = null;
|
||
|
||
while (true)
|
||
{
|
||
List<RuleTocItem> children = node?.Children ?? Children;
|
||
|
||
if (breadcrumbs.Length == 0)
|
||
{
|
||
return node!;
|
||
}
|
||
|
||
string root = breadcrumbs[0];
|
||
RuleTocItem? item = children.FirstOrDefault(i => i.Name == root);
|
||
|
||
if (item is null)
|
||
{
|
||
item = new(root, node);
|
||
children.Add(item);
|
||
}
|
||
|
||
breadcrumbs = breadcrumbs[1..];
|
||
node = item;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, существует ли имя в текущей книге или дочерних элементах.
|
||
/// </summary>
|
||
/// <param name="name">Имя для проверки.</param>
|
||
/// <param name="node">Начальный элемент для поиска.</param>
|
||
/// <returns>true если имя.</returns>
|
||
public bool IsNameExists(string name, RuleTocItem? node = null)
|
||
{
|
||
foreach (RuleTocItem item in node?.Children ?? Children)
|
||
{
|
||
if (item.Name == name || IsNameExists(name, item))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Находит элемент по идентификатору сущности.
|
||
/// </summary>
|
||
/// <param name="id">Идентификатор сущности.</param>
|
||
/// <param name="node">Начальный узел для поиска.</param>
|
||
/// <returns><see cref="RuleTocItem"/> или <c>null</c> если элемент не найден.</returns>
|
||
public RuleTocItem? GetByEntityId(int id, RuleTocItem? node = null)
|
||
{
|
||
if (node?.RuleId == id)
|
||
{
|
||
return node;
|
||
}
|
||
|
||
foreach (RuleTocItem item in node?.Children ?? Children)
|
||
{
|
||
RuleTocItem? result = GetByEntityId(id, item);
|
||
|
||
if (result is not null)
|
||
{
|
||
return result;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возвращает строковое представление книги в файле YAML.
|
||
/// </summary>
|
||
/// <returns>Строковое представление книги.</returns>
|
||
public override string ToString()
|
||
{
|
||
return $"\"{SourceName}\":\n"
|
||
+ string.Join(
|
||
"\n",
|
||
Children.Select(child => RuleTocItem.Indent(child.ToString())).ToList()
|
||
);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Класс представляет собой элемент дерева правил.
|
||
/// </summary>
|
||
public class RuleTocItem(string name, RuleTocItem? parent)
|
||
{
|
||
public string? Id { get; set; } = null;
|
||
|
||
/// <summary>
|
||
/// Имя элемента.
|
||
/// </summary>
|
||
public string Name { get; } = name;
|
||
|
||
/// <summary>
|
||
/// Идентификатор правила.
|
||
/// </summary>
|
||
public int? RuleId { get; set; } = null;
|
||
|
||
/// <summary>
|
||
/// Указывает, что элемент обрабатывается.
|
||
/// </summary>
|
||
public bool IsHandled { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// Родительский элемент дерева.
|
||
/// </summary>
|
||
public RuleTocItem? Parent { get; } = parent;
|
||
|
||
/// <summary>
|
||
/// Порядок дочерних элементов.
|
||
/// </summary>
|
||
public List<string>? ChildrenOrder { get; set; } = null;
|
||
|
||
/// <summary>
|
||
/// Коллекция дочерних элементов.
|
||
/// </summary>
|
||
public List<RuleTocItem> Children { get; } = [];
|
||
|
||
/// <summary>
|
||
/// Предыдущий идентификатор.
|
||
/// </summary>
|
||
public string? PreviousId { get; set; }
|
||
|
||
/// <summary>
|
||
/// Следующий идентификатор.
|
||
/// </summary>
|
||
public string? NextId { get; set; }
|
||
|
||
/// <summary>
|
||
/// Идентификатор первого дочернего элемента.
|
||
/// </summary>
|
||
public string? FirstChildId { get; set; }
|
||
|
||
/// <summary>
|
||
/// Идентификатор последнего дочернего элемента.
|
||
/// </summary>
|
||
public string? LastChildId { get; set; }
|
||
|
||
/// <summary>
|
||
/// Возвращает строковое представление элемента содержания в файле YAML.
|
||
/// </summary>
|
||
/// <returns>Строковое представление элемента содержания.</returns>
|
||
public override string ToString()
|
||
{
|
||
List<string> rows =
|
||
[
|
||
$"{Name}:",
|
||
$"{Tab}aonprd: {Id ?? "null"}",
|
||
$"{Tab}entity: {RuleId?.ToString() ?? "null"}",
|
||
];
|
||
|
||
if (Children.Count == 0)
|
||
{
|
||
// rows.Add($"{TAB}children: None");
|
||
return string.Join("\n", rows);
|
||
}
|
||
|
||
rows.Add($"{Tab}children:");
|
||
|
||
foreach (RuleTocItem child in Children)
|
||
{
|
||
rows.Add(child.Indent(2));
|
||
}
|
||
|
||
return string.Join("\n", rows);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Увеличение отступа строкового представления элемента содержания в файле YAML.
|
||
/// </summary>
|
||
/// <param name="amount">Количество отступов.</param>
|
||
/// <returns>Строковое представление элемента содержания с отступом.</returns>
|
||
/// <seealso cref="Tab"/>
|
||
public string Indent(int amount = 1)
|
||
{
|
||
string result = ToString();
|
||
for (int i = 0; i < amount; i++)
|
||
{
|
||
result = Indent(result);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Увеличение отступа строки.
|
||
/// </summary>
|
||
/// <param name="str">Входная строка.</param>
|
||
/// <returns>Строка с отступом.</returns>
|
||
/// <seealso cref="Tab"/>
|
||
public static string Indent(string str)
|
||
{
|
||
return Tab + str.Replace("\n", $"\n{Tab}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Строка отступа строки.
|
||
/// </summary>
|
||
/// <seealso cref="Indent(int)"/>
|
||
/// <seealso cref="Indent(string)"/>
|
||
public const string Tab = " ";
|
||
}
|