Документирование скраппера.
This commit is contained in:
@@ -6,7 +6,7 @@ namespace Pf2eDataScrapper;
|
||||
/// <summary>
|
||||
/// Класс настроек.
|
||||
/// </summary>
|
||||
internal static class EnvSettings
|
||||
public static class EnvSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Загрузка файла настроек `.env`.
|
||||
@@ -36,5 +36,8 @@ internal static class EnvSettings
|
||||
/// </summary>
|
||||
public static string OutputDir => new(OutputDirPath);
|
||||
|
||||
/// <summary>
|
||||
/// Строка подключения к базе данных.
|
||||
/// </summary>
|
||||
public static string DatabaseConnection => EnvReader.GetStringValue("DATABASE_CONNECTION");
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ internal abstract class AbstractImporter
|
||||
/// </summary>
|
||||
public abstract string Slug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Репозиторий для данных, загруженных из необходимых источников.
|
||||
/// </summary>
|
||||
public abstract AbstractImporterRepository Repository { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,33 +1,60 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel;
|
||||
using Slugify;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс предоставляет методы для работы с базой данных.
|
||||
/// </summary>
|
||||
public class DatabaseHelper
|
||||
{
|
||||
public Pf2eDbContext Context = new();
|
||||
|
||||
public readonly SlugHelper Slugger = new();
|
||||
|
||||
/// <summary>
|
||||
/// Последний найденный идентификатор сущности.
|
||||
/// Используется для генерации новых ID сущностей.
|
||||
/// </summary>
|
||||
private int _id;
|
||||
|
||||
/// <summary>
|
||||
/// Генерирует новый уникальный идентификатор сущности. и возвращает его.
|
||||
/// </summary>
|
||||
public int NextEntityId => ++_id;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис для работы с URL-Slug. Используется для преобразования названий в URL-friendly формат.
|
||||
/// </summary>
|
||||
public readonly SlugHelper Slugger = new();
|
||||
|
||||
/// <summary>
|
||||
/// Context для доступа к базе данных.
|
||||
/// </summary>
|
||||
public readonly Pf2eDbContext Context = new();
|
||||
|
||||
private DatabaseHelper()
|
||||
{
|
||||
_id = Context.Entities.Any() ? Context.Entities.Max(e => e.Id) : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерирует slug из названия.
|
||||
/// </summary>
|
||||
public string GenerateSlug(string name)
|
||||
{
|
||||
return Slugger.GenerateSlug(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает singleton-инстанцию класса <see cref="DatabaseHelper"/>.
|
||||
/// </summary>
|
||||
public static readonly DatabaseHelper Instance = new();
|
||||
|
||||
/// <summary>
|
||||
/// Контекст для доступа к базе данных.
|
||||
/// </summary>
|
||||
public static Pf2eDbContext DbContext => Instance.Context;
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет контекст и сохраняет изменения в базе данных.
|
||||
/// </summary>
|
||||
public static void ResetContext()
|
||||
{
|
||||
Instance.Context.SaveChanges();
|
||||
|
||||
@@ -4,28 +4,42 @@ using Pf2eDataScrapper.Importers.Repositories;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd;
|
||||
|
||||
/// <summary>
|
||||
/// Класс обработки данных с сайта Архивов Нефеса.
|
||||
/// </summary>
|
||||
internal class AonprdImporter : AbstractImporter
|
||||
{
|
||||
public override string Name => SourceName;
|
||||
/// <inheritdoc/>
|
||||
public override string Name => TargetName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Slug => "aonprd";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AbstractImporterRepository Repository =>
|
||||
new AonprdRepository(this, "https://elasticsearch.aonprd.com");
|
||||
|
||||
/// <summary>
|
||||
/// Инструмент для работы с данными сайта Архивов Нефеса.
|
||||
/// </summary>
|
||||
public AonprdLoader Loader { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdImporter"/>.
|
||||
/// </summary>
|
||||
public AonprdImporter()
|
||||
{
|
||||
Loader = new AonprdLoader(this);
|
||||
Loader = new(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Prepare()
|
||||
{
|
||||
Repository.Update();
|
||||
Loader.Prepare();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BuildSchemas()
|
||||
{
|
||||
AonprdSchemaBuilder builder = new(Loader);
|
||||
@@ -34,6 +48,7 @@ internal class AonprdImporter : AbstractImporter
|
||||
builder.Save(EnvSettings.OutputDir);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Import()
|
||||
{
|
||||
AonprdDataBuilder builder = new(Loader);
|
||||
@@ -43,5 +58,8 @@ internal class AonprdImporter : AbstractImporter
|
||||
builder.Build();
|
||||
}
|
||||
|
||||
public const string SourceName = "Archives of Nethys Pathfinder 2e";
|
||||
/// <summary>
|
||||
/// Название внешней ссылки.
|
||||
/// </summary>
|
||||
public const string TargetName = "Archives of Nethys Pathfinder 2e";
|
||||
}
|
||||
|
||||
@@ -3,23 +3,37 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd;
|
||||
|
||||
/// <summary>
|
||||
/// Класс загрузки данных с сайта Архивов Нефеса.
|
||||
/// </summary>
|
||||
internal class AonprdLoader(AonprdImporter importer)
|
||||
{
|
||||
public AonprdImporter Importer = importer;
|
||||
/// <summary>
|
||||
/// Референс на импортер, используемый для загрузки данных.
|
||||
/// </summary>
|
||||
public AonprdImporter Importer { get; } = importer;
|
||||
|
||||
public AonprdCompendium Compendium = new();
|
||||
/// <summary>
|
||||
/// Компендиум данных с сайта Архивов Нефеса.
|
||||
/// </summary>
|
||||
public AonprdCompendium Compendium { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Число элементов в компендарии.
|
||||
/// </summary>
|
||||
public int ItemCount => Compendium.Count();
|
||||
|
||||
/// <summary>
|
||||
/// Подготовка данных перед загрузкой.
|
||||
/// </summary>
|
||||
public void Prepare()
|
||||
{
|
||||
using StreamReader indexFile = new(Path.Combine(Importer.Dir, "index.json"));
|
||||
JObject indexJson = JObject.Parse(indexFile.ReadToEnd());
|
||||
|
||||
foreach (KeyValuePair<string, JToken?> kvp in indexJson)
|
||||
foreach ((string file, JToken? value) in indexJson)
|
||||
{
|
||||
string file = kvp.Key;
|
||||
JArray items = (JArray)kvp.Value!;
|
||||
JArray items = (JArray)value!;
|
||||
|
||||
foreach (JToken item in items)
|
||||
{
|
||||
@@ -28,16 +42,30 @@ internal class AonprdLoader(AonprdImporter importer)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает коллекцию всех доступных загружаемых элементов.
|
||||
/// </summary>
|
||||
/// <returns>Коллекция всех загружаемых элементов.</returns>
|
||||
public IEnumerable<LoaderEntry> Entries()
|
||||
{
|
||||
return Entries([.. Compendium.Types.Keys]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает коллекцию доступных загружаемых элементов.
|
||||
/// </summary>
|
||||
/// <param name="types">Тип, которые нужно включить в результат.</param>
|
||||
/// <returns>Коллекция загружаемых элементов, соответствующих предоставленному типу.</returns>
|
||||
public IEnumerable<LoaderEntry> Entries(string types)
|
||||
{
|
||||
return Entries([types]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает коллекцию доступных загружаемых элементов.
|
||||
/// </summary>
|
||||
/// <param name="types">Типы, которые нужно включить в результат.</param>
|
||||
/// <returns>Коллекция загружаемых элементов, соответствующих предоставленным типам.</returns>
|
||||
public IEnumerable<LoaderEntry> Entries(string[] types)
|
||||
{
|
||||
foreach (string file in Compendium.Files(types))
|
||||
@@ -64,8 +92,14 @@ internal class AonprdLoader(AonprdImporter importer)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс, представляющий загружаемый элемент.
|
||||
/// </summary>
|
||||
public class LoaderEntry(string id, string file, JObject itemJson) : AonprdReference(id, file)
|
||||
{
|
||||
/// <summary>
|
||||
/// Возвращает JSON-объект, содерждающий данные о элементе.
|
||||
/// </summary>
|
||||
public JObject Json { get; } = itemJson;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,25 @@ using Pf2eDataScrapper.Importers.Repositories;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd;
|
||||
|
||||
/// <summary>
|
||||
/// Основной класс для работы с репозиторием данных Aonprd.
|
||||
/// </summary>
|
||||
internal class AonprdRepository(AbstractImporter importer, string searchUrl)
|
||||
: AbstractImporterRepository(importer)
|
||||
{
|
||||
/// <summary>
|
||||
/// URL для доступа к API Aonprd.
|
||||
/// </summary>
|
||||
public readonly string SearchUrl = searchUrl;
|
||||
|
||||
/// <summary>
|
||||
/// Текущая ревизия данных. Значение обновляется при обновлении репозитория.
|
||||
/// </summary>
|
||||
public int Revision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Метод для загрузки данных из API Aonprd.
|
||||
/// </summary>
|
||||
private string Load(string url)
|
||||
{
|
||||
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
|
||||
@@ -25,6 +37,7 @@ internal class AonprdRepository(AbstractImporter importer, string searchUrl)
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update()
|
||||
{
|
||||
if (CheckRevision())
|
||||
@@ -69,9 +82,9 @@ internal class AonprdRepository(AbstractImporter importer, string searchUrl)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверка актуальности данных
|
||||
/// Проверка актуальности данных.
|
||||
/// </summary>
|
||||
/// <returns>true - актуальная ревизия, false - устаревшая/отсутсвует</returns>
|
||||
/// <returns>true - актуальная ревизия, false - устаревшая/отсутсвует.</returns>
|
||||
private bool CheckRevision()
|
||||
{
|
||||
JObject page = JObject.Parse(Load("/aon/_doc/feat-1"));
|
||||
@@ -92,6 +105,11 @@ internal class AonprdRepository(AbstractImporter importer, string searchUrl)
|
||||
return lastRevision == Revision.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запись данных в файл.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь к файлу.</param>
|
||||
/// <param name="content">Содержимое, которе требуется записать в файл.</param>
|
||||
private void WriteFile(string path, string content)
|
||||
{
|
||||
string filePath = Path.Combine(RepositoryDirPath, path);
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Класс хранения данных компендиума.
|
||||
/// </summary>
|
||||
internal class AonprdCompendium
|
||||
{
|
||||
/// <summary>
|
||||
/// Словарь данных компендиума.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, AonprdType> Types = [];
|
||||
|
||||
/// <summary>
|
||||
/// Добавить ссылку на сущность с указанием файла, в котором она хранится.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <param name="file">Имя файла.</param>
|
||||
public void AddReference(string id, string file)
|
||||
{
|
||||
AonprdReference reference = new(id, file);
|
||||
@@ -14,15 +25,29 @@ internal class AonprdCompendium
|
||||
}
|
||||
else
|
||||
{
|
||||
Types.Add(reference.Type, new AonprdType(reference));
|
||||
Types.Add(reference.Type, new(reference));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает количество элементов в компендиуме.
|
||||
/// </summary>
|
||||
/// <returns>Количество элементов.</returns>
|
||||
public int Count() => Types.Values.Sum(t => t.Count);
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает количество элементов для указанных типов данных.
|
||||
/// </summary>
|
||||
/// <param name="types">Список типов.</param>
|
||||
/// <returns>Количество элементов.</returns>
|
||||
public int Count(IEnumerable<string> types) =>
|
||||
Types.Values.Where(t => types.Contains(t.Type)).Sum(t => t.Count);
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает список файлов для выбора сущностей указанных типов данных.
|
||||
/// </summary>
|
||||
/// <param name="types">Список типов.</param>
|
||||
/// <returns>Список искомых файлов.</returns>
|
||||
public IEnumerable<string> Files(IEnumerable<string> types) =>
|
||||
Types.Values.Where(t => types.Contains(t.Type)).SelectMany(t => t.Files).Distinct();
|
||||
}
|
||||
|
||||
@@ -1,16 +1,37 @@
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на сущность с указанием файла, в котором она хранится.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <param name="file">Имя файла.</param>
|
||||
internal class AonprdReference(string id, string file)
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор сущности.
|
||||
/// </summary>
|
||||
public readonly string Id = id;
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности.
|
||||
/// </summary>
|
||||
public readonly string Type = ResolveType(id);
|
||||
|
||||
/// <summary>
|
||||
/// Имя файла, в котором хранится сущность.
|
||||
/// </summary>
|
||||
public readonly string File = file;
|
||||
|
||||
public static string ResolveType(string id)
|
||||
/// <summary>
|
||||
/// Определение типа сущности как префикса идентификатора.
|
||||
/// </summary>
|
||||
private static string ResolveType(string id)
|
||||
{
|
||||
static string? subType(string type, string cat)
|
||||
string type = string.Join('-', id.Split('-')[..^1]);
|
||||
|
||||
return SubType(type, "equipment") ?? SubType(type, "weapon") ?? type;
|
||||
|
||||
static string? SubType(string type, string cat)
|
||||
{
|
||||
return type.StartsWith($"{cat}-") && type != cat
|
||||
? type.EndsWith("-bonus")
|
||||
@@ -18,9 +39,5 @@ internal class AonprdReference(string id, string file)
|
||||
: $"{cat}-sub"
|
||||
: null;
|
||||
}
|
||||
|
||||
string type = string.Join('-', id.Split('-')[0..^1]);
|
||||
|
||||
return subType(type, "equipment") ?? subType(type, "weapon") ?? type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Класс AonprdType представляет собой уникальный тип данных, связанный с Aon (Advanced Opportunity Network).
|
||||
/// Это основной класс, который хранит информацию о различных элементах системы.
|
||||
/// </summary>
|
||||
internal class AonprdType(string type)
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор типа данных.
|
||||
/// </summary>
|
||||
public readonly string Type = type;
|
||||
|
||||
/// <summary>
|
||||
/// Список ссылок на сущности, связанных с этим типом.
|
||||
/// </summary>
|
||||
public readonly List<AonprdReference> Items = [];
|
||||
|
||||
public AonprdType(AonprdReference reference)
|
||||
@@ -12,12 +22,21 @@ internal class AonprdType(string type)
|
||||
Items.Add(reference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет новую ссылку в список.
|
||||
/// </summary>
|
||||
public void Add(AonprdReference reference)
|
||||
{
|
||||
Items.Add(reference);
|
||||
}
|
||||
|
||||
public List<string> Files => [.. Items.Select(x => x.File).Distinct()];
|
||||
/// <summary>
|
||||
/// Возвращает уникальные файлы, использованные в ссылках.
|
||||
/// </summary>
|
||||
public List<string> Files => Items.Select(x => x.File).Distinct().ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Число элементов в списке ссылок.
|
||||
/// </summary>
|
||||
public int Count => Items.Count;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,19 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Класс для создание схемы данных Архива Нефиса.
|
||||
/// </summary>
|
||||
/// <param name="loader"></param>
|
||||
internal class AonprdDataBuilder(AonprdLoader loader)
|
||||
{
|
||||
/// <summary>
|
||||
/// Класс работы с базой данных.
|
||||
/// </summary>
|
||||
public readonly Pf2eDatabase Database = Pf2eDatabase.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Загрузчик данных.
|
||||
/// Основной класс для работы с данными Архива Нефиса.
|
||||
/// </summary>
|
||||
public readonly AonprdLoader Loader = loader;
|
||||
|
||||
@@ -19,8 +26,14 @@ internal class AonprdDataBuilder(AonprdLoader loader)
|
||||
/// </summary>
|
||||
public AonprdCompendium Compendium => Loader.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция обработчиков данных.
|
||||
/// </summary>
|
||||
public readonly AonprdProcessorCollection Processors = new();
|
||||
|
||||
/// <summary>
|
||||
/// Очистить таблицы в базе данных.
|
||||
/// </summary>
|
||||
public void TruncateTables()
|
||||
{
|
||||
Console.WriteLine("Truncating all old data...");
|
||||
@@ -30,14 +43,29 @@ internal class AonprdDataBuilder(AonprdLoader loader)
|
||||
Console.WriteLine("Truncation completed!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конфигурация интерфейса вывода результатов.
|
||||
/// </summary>
|
||||
protected ConcurrentWriter Window = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Основное окно для вывода информации.
|
||||
/// </summary>
|
||||
protected Window Output = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Полоса прогресса для основного процесса обработки данных.
|
||||
/// </summary>
|
||||
protected ProgressBar ProcessProgressBar = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Полоса прогресса для под-процессов обработки данных.
|
||||
/// </summary>
|
||||
protected ProgressBar SubProcessProgressBar = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Метод для настройки интерфейса.
|
||||
/// </summary>
|
||||
protected void ConfigureWindow()
|
||||
{
|
||||
Window = new Window(0, 0, Console.WindowWidth, Console.WindowHeight).Concurrent();
|
||||
@@ -56,6 +84,9 @@ internal class AonprdDataBuilder(AonprdLoader loader)
|
||||
SubProcessProgressBar = new(bars, 1, 15);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка отдельного процессора данных.
|
||||
/// </summary>
|
||||
protected void RunProcessor(AonprdProcessorCollection.ProcessorItem item, JObject json)
|
||||
{
|
||||
AonprdProcessorInput input = new(json);
|
||||
@@ -76,6 +107,9 @@ internal class AonprdDataBuilder(AonprdLoader loader)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Полная обработка и сохранение данных.
|
||||
/// </summary>
|
||||
public void Build()
|
||||
{
|
||||
ConfigureWindow();
|
||||
|
||||
+35
@@ -1,5 +1,12 @@
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Атрибут обработчика данных Архивов Нефиса.
|
||||
/// </summary>
|
||||
/// <param name="name">Название обработчика.</param>
|
||||
/// <param name="type">Тип данных, которые обработчик обрабатывает.</param>
|
||||
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
|
||||
/// <param name="includeDefaults">Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка.</param>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class AonprdProcessorAttribute(
|
||||
string name,
|
||||
@@ -8,23 +15,51 @@ public class AonprdProcessorAttribute(
|
||||
bool includeDefaults = true
|
||||
) : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Важные общие зависимости для обработчка.
|
||||
/// </summary>
|
||||
private static readonly string[] DefaultDependencies = ["trait", "source"];
|
||||
|
||||
/// <summary>
|
||||
/// Название обработчика.
|
||||
/// </summary>
|
||||
public readonly string Name = name;
|
||||
|
||||
/// <summary>
|
||||
/// Тип данных, которые обработчик обрабатывает.
|
||||
/// </summary>
|
||||
public readonly string? Type = type;
|
||||
|
||||
/// <summary>
|
||||
/// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.
|
||||
/// </summary>
|
||||
public readonly IEnumerable<string> Dependencies =
|
||||
[
|
||||
.. includeDefaults ? DefaultDependencies : [],
|
||||
.. dependencies,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdProcessorAttribute"/> со стандартными общими зависимости для обработчка.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип данных, которые обработчик обрабатывает. Также будет использоваться в качестве названия.</param>
|
||||
public AonprdProcessorAttribute(string type)
|
||||
: this(type, type, DefaultDependencies) { }
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип данных, которые обработчик обрабатывает. Также будет использоваться в качестве названия.</param>
|
||||
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
|
||||
/// <param name="includeDefaults">Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка.</param>
|
||||
public AonprdProcessorAttribute(string type, string[] dependencies, bool includeDefaults = true)
|
||||
: this(type, type, dependencies, includeDefaults) { }
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdProcessorAttribute"/> со стандартными общими зависимости для обработчка.
|
||||
/// </summary>
|
||||
/// <param name="name">Название обработчика.</param>
|
||||
/// <param name="type">Тип данных, которые обработчик обрабатывает.</param>
|
||||
public AonprdProcessorAttribute(string name, string type)
|
||||
: this(name, type, DefaultDependencies) { }
|
||||
}
|
||||
|
||||
+44
-3
@@ -3,21 +3,47 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Класс отвечает за управление обработчиками, отмеченных атрибутом <see cref="AonprdProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
internal class AonprdProcessorCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Элемент коллекции обработчика.
|
||||
/// </summary>
|
||||
/// <param name="attribute">Атрибут, связанный с обаботчиком.</param>
|
||||
/// <param name="constructor">Конструктор обработчика.</param>
|
||||
public class ProcessorItem(AonprdProcessorAttribute attribute, ConstructorInfo constructor)
|
||||
{
|
||||
/// <summary>
|
||||
/// Атрибут, связанный с обаботчиком.
|
||||
/// </summary>
|
||||
public readonly AonprdProcessorAttribute Attribute = attribute;
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор обработчика.
|
||||
/// </summary>
|
||||
public readonly ConstructorInfo Constructor = constructor;
|
||||
|
||||
/// <summary>
|
||||
/// Приоритет выполнения обработчика.
|
||||
/// </summary>
|
||||
public int Priority = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Список всех объявленных обработчиков.
|
||||
/// </summary>
|
||||
private readonly List<ProcessorItem> _allItems;
|
||||
|
||||
/// <summary>
|
||||
/// Список используемых обработчиков.
|
||||
/// </summary>
|
||||
public List<ProcessorItem> Items { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdProcessorCollection"/>, загружающий все обработчики.
|
||||
/// </summary>
|
||||
public AonprdProcessorCollection()
|
||||
{
|
||||
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
|
||||
@@ -29,7 +55,7 @@ internal class AonprdProcessorCollection
|
||||
|
||||
if (!type.IsSubclassOf(typeof(AbstractProcessor)))
|
||||
{
|
||||
throw new Exception(
|
||||
throw new(
|
||||
$"'AonprdProcessor' attribute should only be applied to children of 'AbstractProcessor' class. '{type.FullName}' is not."
|
||||
);
|
||||
}
|
||||
@@ -56,6 +82,9 @@ internal class AonprdProcessorCollection
|
||||
_allItems = Items.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверка, что все зависимости всех обработчиков могут быть удовлетворены.
|
||||
/// </summary>
|
||||
private void CheckDependencies()
|
||||
{
|
||||
foreach (ProcessorItem item in Items)
|
||||
@@ -72,6 +101,9 @@ internal class AonprdProcessorCollection
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сортировать обработчики в соответсвии со всеми зависимостями.
|
||||
/// </summary>
|
||||
private void SortItems()
|
||||
{
|
||||
CheckDependencies();
|
||||
@@ -104,6 +136,10 @@ internal class AonprdProcessorCollection
|
||||
Items.Sort((a, b) => a.Priority - b.Priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Итерация через все используемые обработчики.
|
||||
/// </summary>
|
||||
/// <returns>Итератор.</returns>
|
||||
public IEnumerable<ProcessorItem> Iterate()
|
||||
{
|
||||
SortItems();
|
||||
@@ -114,9 +150,14 @@ internal class AonprdProcessorCollection
|
||||
}
|
||||
}
|
||||
|
||||
public void UseOnlyNames(string[] processorNames)
|
||||
/// <summary>
|
||||
/// Ограничить список используемых обработчиков по названию.
|
||||
/// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики.
|
||||
/// </summary>
|
||||
/// <param name="processorNames">Коллекция названиий требуемых обработчиков.</param>
|
||||
public void UseOnlyNames(ICollection<string> processorNames)
|
||||
{
|
||||
if (processorNames.Length == 0)
|
||||
if (processorNames.Count == 0)
|
||||
{
|
||||
Items = _allItems;
|
||||
return;
|
||||
|
||||
+42
-6
@@ -3,8 +3,13 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Класс для работы с JSON-объектом при обработки данных с Архивов Нефиса.
|
||||
/// </summary>
|
||||
/// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param>
|
||||
internal class AonprdProcessorInput(JObject input) : ProcessorInput(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override T Enum<T>(string value)
|
||||
{
|
||||
try
|
||||
@@ -17,6 +22,11 @@ internal class AonprdProcessorInput(JObject input) : ProcessorInput(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработать строку, содержащую информацию о количестве действий.
|
||||
/// </summary>
|
||||
/// <param name="input">Входная строка с данными.</param>
|
||||
/// <returns>Структура, хранящая информацию о количетсве действий.</returns>
|
||||
public ActionStruct ResolveActionString(string input)
|
||||
{
|
||||
ActionStruct result = new();
|
||||
@@ -89,13 +99,39 @@ internal class AonprdProcessorInput(JObject input) : ProcessorInput(input)
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс структуры, хранящей информацию о количетсве действий.
|
||||
/// </summary>
|
||||
public class ActionStruct
|
||||
{
|
||||
public int Actions;
|
||||
public int MaxActions;
|
||||
public bool IsPassive;
|
||||
public bool IsReaction;
|
||||
public bool IsFreeAction;
|
||||
public bool TwoRounds;
|
||||
/// <summary>
|
||||
/// Количество действий
|
||||
/// </summary>
|
||||
public int Actions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Максимальное количество действий
|
||||
/// </summary>
|
||||
public int MaxActions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что действия не требуются.
|
||||
/// </summary>
|
||||
public bool IsPassive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что действие может быть использовано как реакция.
|
||||
/// </summary>
|
||||
public bool IsReaction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что можно использовать свободное действие.
|
||||
/// </summary>
|
||||
public bool IsFreeAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что действие может быть продлено до 2 раундов.
|
||||
/// </summary>
|
||||
public bool TwoRounds { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -2,11 +2,24 @@
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс-помощник, хранящий необходимые глобальные данные.
|
||||
/// Реализует шаблон <c>Singleton</c>.
|
||||
/// </summary>
|
||||
internal class AonprdBuilderGlobals
|
||||
{
|
||||
/// <summary>
|
||||
/// Цель внешних ссылок Архивов Нефисов.
|
||||
/// </summary>
|
||||
public ExternalTarget AonprdTarget = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
|
||||
/// </summary>
|
||||
private AonprdBuilderGlobals() { }
|
||||
|
||||
/// <summary>
|
||||
/// Экземпляр класса <see cref="AonprdBuilderGlobals"/>.
|
||||
/// </summary>
|
||||
public static readonly AonprdBuilderGlobals Instance = new();
|
||||
}
|
||||
|
||||
+33
@@ -2,8 +2,14 @@
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс, помогающий разрешить строковые значения с Архивов Нефиса в перечисления.
|
||||
/// </summary>
|
||||
public static class AonprdEnumResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Коллекция классов, разрешающих строковые значения в перечисления.
|
||||
/// </summary>
|
||||
private static readonly ICollection<Solver> Solvers =
|
||||
[
|
||||
new Solver<SourceCategory>(
|
||||
@@ -62,6 +68,18 @@ public static class AonprdEnumResolver
|
||||
),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Разрешить строковое значение с Архивов Нефиса в перечисление.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип искомого перечисления.</typeparam>
|
||||
/// <param name="value">Обрабатываемое строковоее значение.</param>
|
||||
/// <returns>Значение искомого перечисления.</returns>
|
||||
/// <exception cref="EnumResolverEnumUnknownException">
|
||||
/// Исключение, если для указанного типа перечисления не указан класс, разрешающий строковые значения.
|
||||
/// </exception>
|
||||
/// <exception cref="EnumResolverValueUnknownException">
|
||||
/// Исключение, если указанное значение не получилось преобразовать искомое значение.
|
||||
/// </exception>
|
||||
public static T Resolve<T>(string value)
|
||||
where T : Enum
|
||||
{
|
||||
@@ -81,11 +99,17 @@ public static class AonprdEnumResolver
|
||||
: throw new EnumResolverValueUnknownException(type, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс, разрешающий строковые значения в перечисления.
|
||||
/// </summary>
|
||||
private abstract class Solver
|
||||
{
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный общий класс, разрешающий строковые значения в перечисления.
|
||||
/// </summary>
|
||||
private class Solver<T>(Dictionary<string, T> data) : Solver
|
||||
where T : Enum
|
||||
{
|
||||
@@ -93,9 +117,18 @@ public static class AonprdEnumResolver
|
||||
public Dictionary<string, T> Data { get; } = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, указывающее, что для указанного типа перечисления не указан класс, разрешающий строковые значения.
|
||||
/// </summary>
|
||||
/// <param name="t">Тип искомого перечисления.</param>
|
||||
public class EnumResolverEnumUnknownException(Type t)
|
||||
: Exception($"Can't resolve type '{t.FullName}': Solver not defined");
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, указывающее, что указанное значение не получилось преобразовать искомое значение.
|
||||
/// </summary>
|
||||
/// <param name="t">Тип искомого перечисления.</param>
|
||||
/// <param name="value">Искомое значение.</param>
|
||||
public class EnumResolverValueUnknownException(Type t, string value)
|
||||
: Exception($"Can't resolve type '{t.FullName}': Value '{value}' is not matched");
|
||||
}
|
||||
|
||||
+61
-5
@@ -2,10 +2,22 @@
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс-помощник, для обработки строкового ввода с Архивов Нефиса.
|
||||
/// Реализует шаблон <c>Singleton</c>.
|
||||
/// </summary>
|
||||
internal partial class StringHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="StringHelper"/>
|
||||
/// </summary>
|
||||
private StringHelper() { }
|
||||
|
||||
/// <summary>
|
||||
/// Разбиение строки по нестабильным строковым разделителям (например, <c>", "</c>, <c>" or "</c> и т.д.).
|
||||
/// </summary>
|
||||
/// <param name="value">Исходная строка.</param>
|
||||
/// <returns>Коллекция строк.</returns>
|
||||
public List<string> RowSplit(string value)
|
||||
{
|
||||
return value
|
||||
@@ -17,6 +29,11 @@ internal partial class StringHelper
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Разбор списка ссылок в исходной строке.
|
||||
/// </summary>
|
||||
/// <param name="input">Исходная строка.</param>
|
||||
/// <returns>Структура, содеражащая информацию разбиении строки по ссылкам.</returns>
|
||||
public MarkdownLinkSplit ParseMarkdownLinks(string input)
|
||||
{
|
||||
MarkdownLinkSplit result = new();
|
||||
@@ -39,31 +56,70 @@ internal partial class StringHelper
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Извлечение информации о названии источника.
|
||||
/// </summary>
|
||||
/// <param name="input">Исходная строка.</param>
|
||||
/// <returns>Название источника.</returns>
|
||||
public string ResolveSource(string input)
|
||||
{
|
||||
Match match = SourceSuffixRegex().Match(input);
|
||||
return match.Captures.Count > 0 ? match.Captures[0].Value : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Экземпляр класса <see cref="StringHelper"/>.
|
||||
/// </summary>
|
||||
public static StringHelper Instance { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Регулярное выражение для извлечения источника из строки с Markdown-ссылкой.
|
||||
/// </summary>
|
||||
/// <returns>Регулярное выражение.</returns>
|
||||
[GeneratedRegex(@"^\[.*?\]\(.*?\)\s+(.*)$")]
|
||||
public static partial Regex SourceSuffixRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Регулярное выражение для выявления Markdown-ссылок в строке.
|
||||
/// </summary>
|
||||
/// <returns>Регулярное выражение.</returns>
|
||||
[GeneratedRegex(@"(.*?)\[(.*?)\]\((.*?)\)")]
|
||||
public static partial Regex LinkRegex();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс структуры, содеражащая информацию разбиении строки по ссылкам.
|
||||
/// </summary>
|
||||
internal class MarkdownLinkSplit
|
||||
{
|
||||
public List<MarkdownLink> Links = [];
|
||||
/// <summary>
|
||||
/// Список содержащихся ссылок.
|
||||
/// </summary>
|
||||
public List<MarkdownLink> Links { get; } = [];
|
||||
|
||||
public string Suffix = "";
|
||||
/// <summary>
|
||||
/// Завершающая часть ссылки.
|
||||
/// </summary>
|
||||
public string Suffix { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Информация о ссылке в строке.
|
||||
/// </summary>
|
||||
internal class MarkdownLink
|
||||
{
|
||||
public string Prefix { get; set; } = "";
|
||||
public string Text { get; set; } = "";
|
||||
public string Url { get; set; } = "";
|
||||
/// <summary>
|
||||
/// Данные, содержащиеся до ссылки.
|
||||
/// </summary>
|
||||
public string Prefix { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Текст ссылки.
|
||||
/// </summary>
|
||||
public string Text { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// URL ссылки.
|
||||
/// </summary>
|
||||
public string Url { get; init; } = "";
|
||||
}
|
||||
|
||||
+37
-3
@@ -1,29 +1,55 @@
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик сущностей, помеченных как устаревшие.
|
||||
/// </summary>
|
||||
internal class LegacyEntityHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор устаревшей сущности.
|
||||
/// </summary>
|
||||
public string LegacyId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Название устаревшей сущности.
|
||||
/// </summary>
|
||||
public string LegacyName { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция источников устревшей сущности.
|
||||
/// </summary>
|
||||
public List<EntitySource> LegacySources { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что сведения об устаревшей сущности готовы к добавлению в обновлённые.
|
||||
/// </summary>
|
||||
public bool LegacyPrepared { get; private set; } = false;
|
||||
|
||||
public List<Entity> RemasterEntities = [];
|
||||
/// <summary>
|
||||
/// Список обновлённых сущностей, что отметели устаревшую сущность как предшественника.
|
||||
/// </summary>
|
||||
public List<Entity> RemasterEntities { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="LegacyEntityHandler"/>, если сущность сама отмечена как устаревшая.
|
||||
/// </summary>
|
||||
/// <param name="processor">Обработчик устаревшей сущности.</param>
|
||||
public LegacyEntityHandler(AbstractEntityProcessor processor)
|
||||
{
|
||||
LegacyId = processor.Input.GetRequired<string>("id");
|
||||
Prepare(processor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="LegacyEntityHandler"/>, если обновлённая сущность указывает на ещё недобавленную устаревшую сущность.
|
||||
/// </summary>
|
||||
/// <param name="legacyId">Идентификатор устаревшей сущности.</param>
|
||||
/// <param name="remasterEntity">Обновлённая сущность.</param>
|
||||
public LegacyEntityHandler(string legacyId, Entity remasterEntity)
|
||||
{
|
||||
LegacyId = legacyId;
|
||||
@@ -31,6 +57,10 @@ internal class LegacyEntityHandler
|
||||
RemasterEntities.Add(remasterEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Подготовить данные об устравшей сущности.
|
||||
/// </summary>
|
||||
/// <param name="processor">Обработчик устаревшей сущности.</param>
|
||||
public void Prepare(AbstractEntityProcessor processor)
|
||||
{
|
||||
if (LegacyPrepared)
|
||||
@@ -48,6 +78,10 @@ internal class LegacyEntityHandler
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Применить данные устаревшей сущности к обновлённой.
|
||||
/// </summary>
|
||||
/// <param name="remasterEntity">Обновлённая сущность.</param>
|
||||
public void Apply(Entity remasterEntity)
|
||||
{
|
||||
if (!remasterEntity.Name.Contains(LegacyName))
|
||||
|
||||
+96
-8
@@ -14,17 +14,37 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик сущностей, полученных из Архивов Нефиса.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="AonprdProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">Объект обработки вводимого JSON.</param>
|
||||
/// <seealso cref="AonprdProcessorAttribute"/>
|
||||
internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
: AbstractProcessor(input)
|
||||
{
|
||||
/// <summary>
|
||||
/// Корневая сущность.
|
||||
/// </summary>
|
||||
public Entity Entity { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности.
|
||||
/// </summary>
|
||||
public abstract EntityType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Основная сущность.
|
||||
/// </summary>
|
||||
public abstract IEntity Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Добавление основной сущности в контекст базы данных.
|
||||
/// </summary>
|
||||
public abstract void Add();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
if (Input.GetOptional<JArray>("remaster_id") is not null)
|
||||
@@ -42,27 +62,54 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
ProcessLegacy();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save()
|
||||
{
|
||||
Add();
|
||||
Context.SaveChanges();
|
||||
}
|
||||
|
||||
// Description generation
|
||||
#region "Генерация описания"
|
||||
|
||||
protected virtual string[] AddedDescriptionPaths() => [];
|
||||
/// <summary>
|
||||
/// Получение списка полей, которые должны быть добавлены в описание.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
// [Obsolete("Данный метод будет отключён на финальных этапах разработки.")]
|
||||
protected virtual ICollection<string> AddedDescriptionPaths() => [];
|
||||
|
||||
/// <summary>
|
||||
/// Создание строкового представления объекта по указанному пути для добавления к описанию сущности.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь к добавляемому объекту.</param>
|
||||
/// <returns>Строковое представление для добавления к описанию сущности.</returns>
|
||||
// [Obsolete("Данный метод будет отключён на финальных этапах разработки.")]
|
||||
protected string AddedDescription(string path) =>
|
||||
$"\r\n\r\n========== [{path}] ==========\r\n\r\n{Input.GetOptional<string>(path) ?? ""}";
|
||||
$"\r\n\r\n========== [{path}] ==========\r\n\r\n{Input.GetOptional<object?>(path)?.ToString() ?? ""}";
|
||||
|
||||
protected string AddedDescription(string[] paths) =>
|
||||
/// <summary>
|
||||
/// Создание строкового представления объектов по указанным путям для добавления к описанию сущности.
|
||||
/// </summary>
|
||||
/// <param name="paths">Список путей к добавляемым объектам.</param>
|
||||
/// <returns>Строковое представление для добавления к описанию сущности.</returns>
|
||||
// [Obsolete("Данный метод будет отключён на финальных этапах разработки.")]
|
||||
protected string AddedDescription(ICollection<string> paths) =>
|
||||
string.Join("", paths.Select(AddedDescription));
|
||||
|
||||
/// <summary>
|
||||
/// Сгенерировать описание сущности.
|
||||
/// </summary>
|
||||
/// <returns>Описание сущности</returns>
|
||||
protected virtual string GetDescription() =>
|
||||
Input.GetRequired<string>("markdown") + AddedDescription(AddedDescriptionPaths());
|
||||
|
||||
// Entity generation
|
||||
#endregion
|
||||
|
||||
#region "Генерация сущности"
|
||||
|
||||
/// <summary>
|
||||
/// Генерация основных полей корневой сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddEntity()
|
||||
{
|
||||
Entity.Id = Helper.NextEntityId;
|
||||
@@ -81,6 +128,9 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
Context.Entities.Add(Entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерация источников сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddSource()
|
||||
{
|
||||
foreach (EntitySource entitySource in GetSources())
|
||||
@@ -89,6 +139,9 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерация внешних ссылок сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddExternal()
|
||||
{
|
||||
EntityExternal external = new()
|
||||
@@ -102,6 +155,10 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
Context.EntityExternals.Add(external);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерация списка признаков сущности.
|
||||
/// </summary>
|
||||
/// <exception cref="Exception"></exception>
|
||||
protected virtual void AddTraits()
|
||||
{
|
||||
JToken? traitsJson = Input.Input.SelectToken("trait");
|
||||
@@ -137,6 +194,13 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Обработка сущности, отмеченной как устаревшей.
|
||||
///
|
||||
/// Обработка происходит в классе <see cref="LegacyEntityHandler"/>
|
||||
/// </summary>
|
||||
protected virtual void ProcessLegacy()
|
||||
{
|
||||
JArray? legacyIdArray = Input.GetOptional<JArray>("legacy_id");
|
||||
@@ -171,6 +235,10 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение списка источников сущности.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<EntitySource> GetSources()
|
||||
{
|
||||
string[] sourceNames = Input
|
||||
@@ -186,7 +254,7 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
);
|
||||
}
|
||||
|
||||
List<EntitySource> entitySources = new();
|
||||
List<EntitySource> entitySources = [];
|
||||
|
||||
foreach (string sourceName in sourceNames)
|
||||
{
|
||||
@@ -208,11 +276,16 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
return entitySources;
|
||||
}
|
||||
|
||||
public string GenerateExtra(params string[] args)
|
||||
/// <summary>
|
||||
/// Генерация JSON-строки из объектов, располагаюхися по путям, указанным в <paramref name="paths"/>.
|
||||
/// </summary>
|
||||
/// <param name="paths">Коллекция путей, из которых необходимо достатить объекты.</param>
|
||||
/// <returns>JSON-строка.</returns>
|
||||
public string GenerateJsonString(params ICollection<string> paths)
|
||||
{
|
||||
Dictionary<string, object?> data = [];
|
||||
|
||||
foreach (string key in args)
|
||||
foreach (string key in paths)
|
||||
{
|
||||
data.Add(key, Input.Input.SelectToken(key));
|
||||
}
|
||||
@@ -220,6 +293,12 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
return JsonConvert.SerializeObject(data, new JsonSerializerSettings());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создание контрольной суммы объекта.
|
||||
/// </summary>
|
||||
/// <param name="input">Строковое представление вычисляемого объекта.</param>
|
||||
/// <param name="isLowercase">Использовать шестнадцатеричные цифры в нижнем регистре.</param>
|
||||
/// <returns>Контрольная сумма объекта.</returns>
|
||||
public static string Md5(string input, bool isLowercase = false)
|
||||
{
|
||||
using MD5 md5 = MD5.Create();
|
||||
@@ -231,8 +310,17 @@ internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
|
||||
protected static readonly List<LegacyEntityHandler> LegacyHandlers = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, показывающее, что обработка сущности пропускается как устаревшая.
|
||||
/// </summary>
|
||||
/// <param name="name">Название сущности.</param>
|
||||
public class LegacyItemException(string name)
|
||||
: Exception($"Item '{name}' is linked with newer version, skipping.") { }
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, показывающее, что обработка сущности пропускается.
|
||||
/// </summary>
|
||||
/// <param name="name">Название сущности.</param>
|
||||
/// <param name="reason">Причина пропуска.</param>
|
||||
public class SkipItemException(string name, string reason)
|
||||
: Exception($"Item '{name}' is skipped, reason: {reason}.") { }
|
||||
|
||||
+28
@@ -3,19 +3,47 @@ using Pf2eModel;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="AonprdProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">Объект обработки вводимого JSON.</param>
|
||||
/// <seealso cref="AonprdProcessorAttribute"/>
|
||||
internal abstract class AbstractProcessor(AonprdProcessorInput input)
|
||||
{
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для работы с базой данных.
|
||||
/// </summary>
|
||||
protected readonly DatabaseHelper Helper = DatabaseHelper.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для работы со строками и форматированием.
|
||||
/// </summary>
|
||||
protected readonly StringHelper StringHelper = StringHelper.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для работы с глобальными настройками.
|
||||
/// </summary>
|
||||
protected readonly AonprdBuilderGlobals Globals = AonprdBuilderGlobals.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Контекст базы данных для работы с данными.
|
||||
/// </summary>
|
||||
public readonly Pf2eDbContext Context = DatabaseHelper.DbContext;
|
||||
|
||||
/// <summary>
|
||||
/// Объект обработки вводимого JSON.
|
||||
/// </summary>
|
||||
public readonly AonprdProcessorInput Input = input;
|
||||
|
||||
/// <summary>
|
||||
/// Основной метод обработки данных.
|
||||
/// </summary>
|
||||
public abstract void Process();
|
||||
|
||||
/// <summary>
|
||||
/// Метод для сохранения изменений в базе данных.
|
||||
/// </summary>
|
||||
public abstract void Save();
|
||||
}
|
||||
|
||||
+25
-2
@@ -6,32 +6,56 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик сущностей подклассов, полученных из Архивов Нефиса.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="AonprdProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">Объект обработки вводимого JSON.</param>
|
||||
/// <seealso cref="AonprdProcessorAttribute"/>
|
||||
internal abstract class AbstractSubclassProcessor(AonprdProcessorInput input)
|
||||
: AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Subclass;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract override Subclass Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Название подкласса.
|
||||
/// </summary>
|
||||
public abstract string? ClassName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Название группы подклассов.
|
||||
/// </summary>
|
||||
public abstract string GroupName { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Subclasses.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
Object.Group = GetGroup();
|
||||
ProcessObject();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка полей подкласса.
|
||||
/// </summary>
|
||||
public abstract void ProcessObject();
|
||||
|
||||
/// <summary>
|
||||
/// Получение CLR-представления группы подклассов, к которому относится обрабатываемый подкласс.
|
||||
/// </summary>
|
||||
/// <returns>CLR-представления группы подклассов.</returns>
|
||||
public SubclassGroup GetGroup()
|
||||
{
|
||||
SubclassGroup? group = Context.SubclassGroups.FirstOrDefault(g =>
|
||||
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
|
||||
Pf2eDbContext.LocaleSearch(g.Entity.Name, GroupName)
|
||||
);
|
||||
|
||||
@@ -42,7 +66,6 @@ internal abstract class AbstractSubclassProcessor(AonprdProcessorInput input)
|
||||
|
||||
Class? cl = ClassName is not null
|
||||
? Context.Classes.FirstOrDefault(c =>
|
||||
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
|
||||
Pf2eDbContext.LocaleSearch(c.Entity.Name, GroupName)
|
||||
)
|
||||
: null;
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ internal class ExternalProcessor(AonprdProcessorInput input) : AbstractProcessor
|
||||
{
|
||||
AonprdTarget = new()
|
||||
{
|
||||
Name = AonprdImporter.SourceName,
|
||||
Name = AonprdImporter.TargetName,
|
||||
Icon = "http://www.google.com/s2/favicons?domain=2e.aonprd.com",
|
||||
Prefix = "https://2e.aonprd.com",
|
||||
};
|
||||
|
||||
+7
-5
@@ -7,8 +7,15 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("skill", [], false)]
|
||||
internal class SkillProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
|
||||
{
|
||||
/// <summary>
|
||||
/// Обучаемый объект.
|
||||
/// </summary>
|
||||
public Training Training = null!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save() => Context.Trainings.Add(Training);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
Training = new()
|
||||
@@ -17,9 +24,4 @@ internal class SkillProcessor(AonprdProcessorInput input) : AbstractProcessor(in
|
||||
Type = TrainingType.Skill,
|
||||
};
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
Context.Trainings.Add(Training);
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -6,8 +6,12 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("source", [], false)]
|
||||
internal class SourceProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
|
||||
{
|
||||
/// <summary>
|
||||
/// Источник сущности.
|
||||
/// </summary>
|
||||
protected Source Source = null!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
Source = new()
|
||||
@@ -18,6 +22,7 @@ internal class SourceProcessor(AonprdProcessorInput input) : AbstractProcessor(i
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save()
|
||||
{
|
||||
Context.Sources.Add(Source);
|
||||
|
||||
+7
-4
@@ -1,6 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Common;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
@@ -12,12 +10,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("apparition", ["class", "spell", "skill"])]
|
||||
internal class ApparitionProcessor(AonprdProcessorInput input) : AbstractSubclassProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Animist";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Apparition";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
// Spells
|
||||
@@ -52,7 +54,7 @@ internal class ApparitionProcessor(AonprdProcessorInput input) : AbstractSubclas
|
||||
|
||||
foreach (string name in trainingNames)
|
||||
{
|
||||
Training? training = trainings.FirstOrDefault(t => t.Name!.Contains(name));
|
||||
Training? training = trainings.FirstOrDefault(t => t.Name.Contains(name));
|
||||
training ??= new()
|
||||
{
|
||||
Name = new() { { LocaleType.English, name } },
|
||||
@@ -70,6 +72,7 @@ internal class ApparitionProcessor(AonprdProcessorInput input) : AbstractSubclas
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription()
|
||||
+ AddedDescription(["skill_markdown", "spell_markdown", "summary_markdown"]);
|
||||
|
||||
+16
-14
@@ -5,12 +5,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("action")]
|
||||
internal class ActionProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Action;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Pf2eModel.Model.Structure.Action Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Actions.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
if (Input.GetRequired<string>("markdown").StartsWith("**Activate**"))
|
||||
@@ -21,20 +25,18 @@ internal class ActionProcessor(AonprdProcessorInput input) : AbstractEntityProce
|
||||
base.Process();
|
||||
|
||||
Object.Actions = Input.EnumOptional("actions", ActionEnum.Passive);
|
||||
Object.Extra = GenerateExtra("school");
|
||||
Object.Extra = GenerateJsonString("school");
|
||||
}
|
||||
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription()
|
||||
+ AddedDescription(
|
||||
[
|
||||
"cost_markdown",
|
||||
"trait_markdown",
|
||||
"source_markdown",
|
||||
"search_markdown",
|
||||
"requirement_markdown",
|
||||
"trigger_markdown",
|
||||
"summary_markdown",
|
||||
]
|
||||
);
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
[
|
||||
"cost_markdown",
|
||||
"trait_markdown",
|
||||
"source_markdown",
|
||||
"search_markdown",
|
||||
"requirement_markdown",
|
||||
"trigger_markdown",
|
||||
"summary_markdown",
|
||||
];
|
||||
}
|
||||
|
||||
+4
@@ -7,12 +7,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("ancestry", ["trait", "source", "language"])]
|
||||
internal class AncestryProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Ancestry;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Ancestry Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Ancestries.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
string type = Input.GetRequired<string>("type");
|
||||
|
||||
+7
-3
@@ -6,12 +6,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("archetype")]
|
||||
internal class ArchetypeProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Archetype;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Archetype Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Archetypes.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
@@ -19,7 +23,7 @@ internal class ArchetypeProcessor(AonprdProcessorInput input) : AbstractEntityPr
|
||||
Object.Level = Input.GetOptional<int>("level");
|
||||
}
|
||||
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription()
|
||||
+ AddedDescription(["prerequisite_markdown", "requirement_markdown", "summary_markdown"]);
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
["prerequisite_markdown", "requirement_markdown", "summary_markdown"];
|
||||
}
|
||||
|
||||
+7
-5
@@ -8,12 +8,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("background")]
|
||||
internal class BackgroundProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Background;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Background Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Backgrounds.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
@@ -31,9 +35,7 @@ internal class BackgroundProcessor(AonprdProcessorInput input) : AbstractEntityP
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription()
|
||||
+ AddedDescription(
|
||||
["feat_markdown", "summary_markdown", "skill_markdown", "prerequisite_markdown"]
|
||||
);
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
["feat_markdown", "summary_markdown", "skill_markdown", "prerequisite_markdown"];
|
||||
}
|
||||
|
||||
+44
-12
@@ -6,18 +6,23 @@ using Pf2eModel.Model.Common.I18n;
|
||||
using Pf2eModel.Model.Enum;
|
||||
using Pf2eModel.Model.Structure;
|
||||
using Pf2eModel.Model.Structure.ClassRelation;
|
||||
using static System.Text.RegularExpressions.Regex;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
|
||||
|
||||
[AonprdProcessor("class", ["trait", "source", "skill"])]
|
||||
internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
internal partial class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Class;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Class Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Classes.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
@@ -79,9 +84,13 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
break;
|
||||
}
|
||||
|
||||
ProcessDependecies();
|
||||
ProcessClassTrait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение коллекции обучений, связанных с классом.
|
||||
/// </summary>
|
||||
/// <returns>Коллекция обучения.</returns>
|
||||
protected ICollection<ClassTraining> GetSkillTrainings()
|
||||
{
|
||||
List<string> skillNames = Input
|
||||
@@ -103,6 +112,12 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка массива со списком обучений.
|
||||
/// </summary>
|
||||
/// <param name="array">JSON-массив обучений.</param>
|
||||
/// <param name="type">Тип обучения.</param>
|
||||
/// <returns>Коллекция обучений.</returns>
|
||||
protected ICollection<ClassTraining> ProcessTraining(JArray array, TrainingType type)
|
||||
{
|
||||
const string pattern = @"^(.*?) in\s+(?:the)?\s*(.*)$";
|
||||
@@ -110,18 +125,16 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
|
||||
foreach (string value in array.Select(token => token.ToString()))
|
||||
{
|
||||
MatchCollection m = Regex.Matches(value, pattern);
|
||||
MatchCollection m = Matches(value, pattern);
|
||||
|
||||
if (m.Count == 0)
|
||||
{
|
||||
throw new Exception($"Can't parse training string '{value}'");
|
||||
throw new($"Can't parse training string '{value}'");
|
||||
}
|
||||
|
||||
TrainingRank rank = Input.Enum<TrainingRank>(m[0].Groups[1].Value);
|
||||
|
||||
string[] parts = GetTrainingString(m[0].Groups[2].Value);
|
||||
|
||||
foreach (string part in parts)
|
||||
foreach (string part in GetTrainingString(m[0].Groups[2].Value))
|
||||
{
|
||||
Training? training = Context.Trainings.FirstOrDefault(t =>
|
||||
Pf2eDbContext.LocaleSearch(t.Name, part) && t.Type == type
|
||||
@@ -129,7 +142,7 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
|
||||
if (training == null)
|
||||
{
|
||||
training = new Training()
|
||||
training = new()
|
||||
{
|
||||
Name = new() { [LocaleType.English] = part },
|
||||
Type = type,
|
||||
@@ -152,17 +165,26 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
return trainings;
|
||||
}
|
||||
|
||||
protected string[] GetTrainingString(string value)
|
||||
/// <summary>
|
||||
/// Получение коллекции названий обучений из строкового представления из Архивов Нефиса.
|
||||
/// </summary>
|
||||
/// <param name="value">Строковое представление обучений из Архивов Нефиса.</param>
|
||||
/// <returns>Коллекция названий обучений.</returns>
|
||||
protected ICollection<string> GetTrainingString(string value)
|
||||
{
|
||||
return value.StartsWith("favored weapon of your deity") ? ["Favored weapon of your deity"]
|
||||
: value == "simple and martial weapons" ? ["Simple weapon", "Martial weapon"]
|
||||
: Regex
|
||||
.Split(value, @"\s*(?:,|and)\s*")
|
||||
: TrainingStringSeparatorRegex()
|
||||
.Split(value)
|
||||
.Where(s => s != "")
|
||||
.Select(s => string.Concat(s[0].ToString().ToUpper(), s.AsSpan(1)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение CLR-представления обучений спасброскам.
|
||||
/// </summary>
|
||||
/// <returns>Коллекция обучений.</returns>
|
||||
protected ICollection<ClassTraining> GetSaveTrainings()
|
||||
{
|
||||
Dictionary<string, Training> result = Context
|
||||
@@ -201,7 +223,10 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
.ToList();
|
||||
}
|
||||
|
||||
protected void ProcessDependecies()
|
||||
/// <summary>
|
||||
/// Объявление соотеветсвующего классу признака классовым.
|
||||
/// </summary>
|
||||
protected void ProcessClassTrait()
|
||||
{
|
||||
Trait? classTrait = Context.Traits.FirstOrDefault(t => t.Entity.Name == Entity.Name);
|
||||
|
||||
@@ -212,4 +237,11 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
|
||||
classTrait.Type = TraitType.Class;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение регулярного выражения для разделения строки строкового представления обучений.
|
||||
/// </summary>
|
||||
/// <returns>Регулярное выражение.</returns>
|
||||
[GeneratedRegex(@"\s*(?:,|and)\s*")]
|
||||
protected static partial Regex TrainingStringSeparatorRegex();
|
||||
}
|
||||
|
||||
+6
-1
@@ -8,12 +8,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("feat", ["trait", "source", "archetype"])]
|
||||
internal class FeatProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Feat;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Feat Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Feats.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
@@ -33,7 +37,7 @@ internal class FeatProcessor(AonprdProcessorInput input) : AbstractEntityProcess
|
||||
Object.IsPassive = action.IsPassive;
|
||||
Object.IsReaction = action.IsReaction;
|
||||
|
||||
Object.Extra = GenerateExtra(
|
||||
Object.Extra = GenerateJsonString(
|
||||
"archetype",
|
||||
"element",
|
||||
"heighten",
|
||||
@@ -68,6 +72,7 @@ internal class FeatProcessor(AonprdProcessorInput input) : AbstractEntityProcess
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription()
|
||||
+ AddedDescription(
|
||||
|
||||
+3
@@ -6,9 +6,12 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("language")]
|
||||
internal class LanguageProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Language;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Language Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Languages.Add(Object);
|
||||
}
|
||||
|
||||
+6
-8
@@ -1,30 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model.Common;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
using Pf2eModel.Model.Enum;
|
||||
using Pf2eModel.Model.Structure;
|
||||
using Pf2eModel.Model.Structure.RitualRelation;
|
||||
using Pf2eModel.Model.Structure.SpellRelation;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
|
||||
|
||||
[AonprdProcessor("ritual", ["trait", "source", "skill"])]
|
||||
internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Ritual;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Ritual Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Rituals.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
@@ -365,6 +362,7 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
[
|
||||
"summary_markdown",
|
||||
|
||||
+17
-5
@@ -1,11 +1,8 @@
|
||||
using System.Data;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
using Pf2eModel.Model.Enum;
|
||||
using Pf2eModel.Model.Structure;
|
||||
using Rule = Pf2eModel.Model.Structure.Rule;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules;
|
||||
@@ -13,14 +10,21 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("rules", ["rules-toc"])]
|
||||
internal class RuleProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Rule;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Rule Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Rules.Add(Object);
|
||||
|
||||
public readonly RuleTocCollection Toc = RuleTocCollection.Instance;
|
||||
/// <summary>
|
||||
/// Структура генерации содержания правил.
|
||||
/// </summary>
|
||||
public RuleTocCollection Toc { get; } = RuleTocCollection.Instance;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
// base.Process();
|
||||
@@ -93,6 +97,10 @@ internal class RuleProcessor(AonprdProcessorInput input) : AbstractEntityProcess
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение источника, к которому относится правило.
|
||||
/// </summary>
|
||||
/// <returns>Источник, к которому относится правило.</returns>
|
||||
private Source GetSource()
|
||||
{
|
||||
foreach (EntitySource entitySource in Entity.Sources)
|
||||
@@ -103,6 +111,10 @@ internal class RuleProcessor(AonprdProcessorInput input) : AbstractEntityProcess
|
||||
throw new("Source not found");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение внешней ссылки, из которой получено правило.
|
||||
/// </summary>
|
||||
/// <returns>Внешняя ссылка.</returns>
|
||||
private EntityExternal GetExternal()
|
||||
{
|
||||
foreach (EntityExternal entityExternal in Entity.Externals)
|
||||
|
||||
+124
-6
@@ -1,17 +1,27 @@
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LinqToDB.DataProvider.ClickHouse;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules;
|
||||
|
||||
/// <summary>
|
||||
/// Класс коллекции книг, содержащих правила.
|
||||
/// Реализует шаблон <c>Singleton</c>.
|
||||
/// </summary>
|
||||
internal 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
|
||||
@@ -29,6 +39,9 @@ internal class RuleTocCollection
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск книги по имени.
|
||||
/// </summary>
|
||||
public RuleTocBook? FindName(string name)
|
||||
{
|
||||
foreach (RuleTocBook book in Books)
|
||||
@@ -42,6 +55,9 @@ internal class RuleTocCollection
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск правила по идентификатору.
|
||||
/// </summary>
|
||||
public RuleTocItem? FindEntityId(int id)
|
||||
{
|
||||
foreach (RuleTocBook book in Books)
|
||||
@@ -57,13 +73,17 @@ internal class RuleTocCollection
|
||||
return null;
|
||||
}
|
||||
|
||||
public static RuleTocCollection Instance { get; } = new();
|
||||
|
||||
/// <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 =
|
||||
@@ -74,15 +94,41 @@ internal class RuleTocCollection
|
||||
|
||||
writer.Write(ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Экземпляр класса <see cref="RuleTocCollection"/>.
|
||||
/// </summary>
|
||||
public static RuleTocCollection Instance { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс представляет собой книгу, содержащую правила.
|
||||
/// </summary>
|
||||
internal 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;
|
||||
@@ -110,6 +156,12 @@ internal class RuleTocBook(Source source)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -123,6 +175,12 @@ internal class RuleTocBook(Source source)
|
||||
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)
|
||||
@@ -143,6 +201,10 @@ internal class RuleTocBook(Source source)
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает строковое представление книги в файле YAML.
|
||||
/// </summary>
|
||||
/// <returns>Строковое представление книги.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"\"{SourceName}\":\n"
|
||||
@@ -153,28 +215,67 @@ internal class RuleTocBook(Source source)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс представляет собой элемент дерева правил.
|
||||
/// </summary>
|
||||
internal 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 =
|
||||
@@ -200,6 +301,12 @@ internal class RuleTocItem(string name, RuleTocItem? parent)
|
||||
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();
|
||||
@@ -210,10 +317,21 @@ internal class RuleTocItem(string name, RuleTocItem? parent)
|
||||
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 = " ";
|
||||
}
|
||||
|
||||
+43
-3
@@ -14,10 +14,17 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("rules-order", null, ["rules"], false)]
|
||||
internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
|
||||
{
|
||||
/// <summary>
|
||||
/// Структура генерации содержания правил.
|
||||
/// </summary>
|
||||
public readonly RuleTocCollection Toc = RuleTocCollection.Instance;
|
||||
|
||||
private readonly List<OrderItem> OrderItems = [];
|
||||
/// <summary>
|
||||
/// Список записей о порядке правил в своих главах.
|
||||
/// </summary>
|
||||
private readonly List<OrderItem> _orderItems = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
Toc.SaveFile();
|
||||
@@ -38,6 +45,10 @@ internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcess
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка конкретного правила.
|
||||
/// </summary>
|
||||
/// <param name="rule"></param>
|
||||
private void Process(Rule rule)
|
||||
{
|
||||
RuleTocItem? item = Toc.FindEntityId(rule.Id);
|
||||
@@ -52,6 +63,10 @@ internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcess
|
||||
// Дети будут раставлены автоматически по связи
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построение списка записей о порядке правил из дочерних элементов содержания.
|
||||
/// </summary>
|
||||
/// <param name="item">Элемент содержания, дочерние элементы которого необходимо обработать.</param>
|
||||
private void BuildIdOrderDictionary(RuleTocItem item)
|
||||
{
|
||||
List<RuleTocItem> items = [];
|
||||
@@ -88,6 +103,10 @@ internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcess
|
||||
BuildIdOrderDictionary(items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построение списка записей о порядке правил из списка элементов содержания.
|
||||
/// </summary>
|
||||
/// <param name="items">Список элементов содержания для обработки.</param>
|
||||
private void BuildIdOrderDictionary(List<RuleTocItem> items)
|
||||
{
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
@@ -104,14 +123,20 @@ internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcess
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавление элемента содержания.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор правила.</param>
|
||||
/// <param name="order">Порядок правила.</param>
|
||||
/// <param name="parentId">Идентификатор главы, в которой находится глава.</param>
|
||||
private void AddOrderItem(int id, int order, int? parentId)
|
||||
{
|
||||
if (OrderItems.Any(item => item.Id == id))
|
||||
if (_orderItems.Any(item => item.Id == id))
|
||||
{
|
||||
throw new($"Order item with id {id} already exists");
|
||||
}
|
||||
|
||||
OrderItems.Add(
|
||||
_orderItems.Add(
|
||||
new()
|
||||
{
|
||||
Id = id,
|
||||
@@ -121,15 +146,30 @@ internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcess
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save()
|
||||
{
|
||||
Context.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запись, указывающая порядок правила в своей главе.
|
||||
/// </summary>
|
||||
private record OrderItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор правила.
|
||||
/// </summary>
|
||||
public int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Порядок правила.
|
||||
/// </summary>
|
||||
public int Order { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор главы, в которой находится глава.
|
||||
/// </summary>
|
||||
public int? ParentId { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
+28
-2
@@ -9,11 +9,22 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("rules-toc", null, ["source"], false)]
|
||||
internal partial class RulesTocProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
|
||||
{
|
||||
/// <summary>
|
||||
/// Селектор HTML-элемента, в котором находится содержание.
|
||||
/// </summary>
|
||||
private const string Selector = "#ctl00_RadDrawer1_Content_MainContent_DetailedOutput";
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на страницу с содержанием.
|
||||
/// </summary>
|
||||
private const string Url = "https://2e.aonprd.com/Rules.aspx";
|
||||
|
||||
/// <summary>
|
||||
/// Структура генерации содержания правил.
|
||||
/// </summary>
|
||||
public readonly RuleTocCollection Toc = RuleTocCollection.Instance;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
IDocument document = DownloadPage();
|
||||
@@ -64,6 +75,12 @@ internal partial class RulesTocProcessor(AonprdProcessorInput input) : AbstractP
|
||||
Toc.SaveFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка ссылки в содержании для создания нового элемента содержания..
|
||||
/// </summary>
|
||||
/// <param name="value">Строка с ссылкой.</param>
|
||||
/// <param name="parent">Родительсий элемент, к которому будет относиться новый элемент содержания.</param>
|
||||
/// <returns>Новый элемент содержания.</returns>
|
||||
private RuleTocItem ParseLink(string value, RuleTocItem? parent = null)
|
||||
{
|
||||
Match linkMatch = LinkRegex().Match(value);
|
||||
@@ -79,6 +96,10 @@ internal partial class RulesTocProcessor(AonprdProcessorInput input) : AbstractP
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Скачивание страницы, указанной в <see cref="Url"/>, и получение очищенного элемента, указанного в <see cref="Selector"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IDocument DownloadPage()
|
||||
{
|
||||
IConfiguration config = Configuration.Default.WithDefaultLoader();
|
||||
@@ -89,7 +110,7 @@ internal partial class RulesTocProcessor(AonprdProcessorInput input) : AbstractP
|
||||
|
||||
string html = root.OuterHtml;
|
||||
|
||||
foreach (string s in new string[] { "br", "\r", "\n", "<u>", "</u>", "&NoRedirect=1" })
|
||||
foreach (string s in new[] { "br", "\r", "\n", "<u>", "</u>", "&NoRedirect=1" })
|
||||
{
|
||||
html = html.Replace(s, "");
|
||||
}
|
||||
@@ -97,8 +118,13 @@ internal partial class RulesTocProcessor(AonprdProcessorInput input) : AbstractP
|
||||
return context.OpenAsync(req => req.Content(html)).Result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save() { }
|
||||
|
||||
[GeneratedRegex(@"<a href=""Rules\.aspx\?ID=(\d+)"">(.*?)</a>")]
|
||||
/// <summary>
|
||||
/// Получить скомпилированное регулярное выражение для получения HTML-ссылки.
|
||||
/// </summary>
|
||||
/// <returns>Регулярное выражение получения HTML-ссылки.</returns>
|
||||
[GeneratedRegex("""<a href="Rules\.aspx\?ID=(\d+)">(.*?)</a>""")]
|
||||
public static partial Regex LinkRegex();
|
||||
}
|
||||
|
||||
+24
-19
@@ -9,12 +9,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("spell")]
|
||||
internal class SpellProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Spell;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Spell Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Spells.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
@@ -51,6 +55,9 @@ internal class SpellProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
ProcessArea();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка области применения заклинания.
|
||||
/// </summary>
|
||||
private void ProcessArea()
|
||||
{
|
||||
List<string> areaTypes = (Input.GetOptional<string[]>("area_type") ?? []).ToList();
|
||||
@@ -112,23 +119,21 @@ internal class SpellProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription()
|
||||
+ AddedDescription(
|
||||
[
|
||||
"range",
|
||||
"range_raw",
|
||||
"bloodline_markdown",
|
||||
"cost_markdown",
|
||||
"deity_markdown",
|
||||
"domain_markdown",
|
||||
"lesson_markdown",
|
||||
"mystery_markdown",
|
||||
"requirement_markdown",
|
||||
"summary_markdown",
|
||||
"tradition_markdown",
|
||||
"patron_theme_markdown",
|
||||
"saving_throw_markdown",
|
||||
]
|
||||
);
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
[
|
||||
"range",
|
||||
"range_raw",
|
||||
"bloodline_markdown",
|
||||
"cost_markdown",
|
||||
"deity_markdown",
|
||||
"domain_markdown",
|
||||
"lesson_markdown",
|
||||
"mystery_markdown",
|
||||
"requirement_markdown",
|
||||
"summary_markdown",
|
||||
"tradition_markdown",
|
||||
"patron_theme_markdown",
|
||||
"saving_throw_markdown",
|
||||
];
|
||||
}
|
||||
|
||||
+6
@@ -6,12 +6,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processo
|
||||
[AonprdProcessor("trait", ["external"], false)]
|
||||
internal class TraitProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Trait;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Trait Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Add() => Context.Traits.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
@@ -21,11 +25,13 @@ internal class TraitProcessor(AonprdProcessorInput input) : AbstractEntityProces
|
||||
Context.Traits.Add(Object);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void AddTraits()
|
||||
{
|
||||
// Необходимо пропускать
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription() + AddedDescription(["summary_markdown", "search_markdown"]);
|
||||
}
|
||||
|
||||
+35
-3
@@ -4,20 +4,34 @@ using Pf2eDataScrapper.SchemaBuilder;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Класс создания схемы данных с сайта Архивов Нефеса.
|
||||
/// </summary>
|
||||
/// <param name="loader">Загузчик данных с сайта Архивов Нефеса.</param>
|
||||
internal class AonprdSchemaBuilder(AonprdLoader loader)
|
||||
{
|
||||
/// <summary>
|
||||
/// Загузчик данных с сайта Архивов Нефеса.
|
||||
/// </summary>
|
||||
public readonly AonprdLoader Loader = loader;
|
||||
|
||||
/// <inheritdoc cref="AonprdLoader.Compendium"/>
|
||||
public readonly AonprdCompendium Compendium = loader.Compendium;
|
||||
|
||||
// public readonly AonprdSchemaTypeResolver Resolver = new();
|
||||
|
||||
/// <summary>
|
||||
/// Дополнительные схемы данных.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, SchemaNode> Schemas = new()
|
||||
{
|
||||
{ "_subclass", new SchemaNode {Path = "_subclass"} }
|
||||
["_subclass"] = new() { Path = "_subclass" },
|
||||
};
|
||||
|
||||
private string[] _subclassSchemaTypes =
|
||||
/// <summary>
|
||||
/// Список типов, являющихся подклассами.
|
||||
/// </summary>
|
||||
private readonly string[] _subclassSchemaTypes =
|
||||
[
|
||||
"research-field",
|
||||
"apparition",
|
||||
@@ -49,6 +63,11 @@ internal class AonprdSchemaBuilder(AonprdLoader loader)
|
||||
"epithet",
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Выбор целевого элемента схемы данных для ввода.
|
||||
/// </summary>
|
||||
/// <param name="entry">Входные данные.</param>
|
||||
/// <returns>Целевой элемент схемы данных.</returns>
|
||||
private SchemaNode GetSchema(AonprdLoader.LoaderEntry entry)
|
||||
{
|
||||
string key = entry.Type;
|
||||
@@ -58,7 +77,7 @@ internal class AonprdSchemaBuilder(AonprdLoader loader)
|
||||
return value;
|
||||
}
|
||||
|
||||
value = new SchemaNode
|
||||
value = new()
|
||||
{
|
||||
// Resolver = Resolver,
|
||||
Path = key,
|
||||
@@ -68,6 +87,9 @@ internal class AonprdSchemaBuilder(AonprdLoader loader)
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построение схемы данных.
|
||||
/// </summary>
|
||||
public void BuildSchemas()
|
||||
{
|
||||
Console.WriteLine("Building schemas...");
|
||||
@@ -89,6 +111,10 @@ internal class AonprdSchemaBuilder(AonprdLoader loader)
|
||||
bar.Refresh(Loader.ItemCount, "Done!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сохранение постренных схем данных.
|
||||
/// </summary>
|
||||
/// <param name="targetDir">Целевой каталог.</param>
|
||||
public void Save(string targetDir)
|
||||
{
|
||||
Console.WriteLine("Dumping schemas...");
|
||||
@@ -105,6 +131,12 @@ internal class AonprdSchemaBuilder(AonprdLoader loader)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сохранение отдельной схемы данных.
|
||||
/// </summary>
|
||||
/// <param name="dir">Целевой каталог.</param>
|
||||
/// <param name="name">Название схемы.</param>
|
||||
/// <param name="schema">Сохраняемая схема.</param>
|
||||
protected void SaveSchema(string dir, string name, SchemaNode schema)
|
||||
{
|
||||
char[] invalids = Path.GetInvalidFileNameChars();
|
||||
|
||||
@@ -4,10 +4,23 @@ using Pf2eModel.Model.Common.I18n;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс для работы с JSON-объектом.
|
||||
/// </summary>
|
||||
/// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param>
|
||||
internal class ProcessorInput(JObject input)
|
||||
{
|
||||
public readonly JObject Input = input;
|
||||
/// <summary>
|
||||
/// Входящий JSON-объект, который содержит данные для обработки.
|
||||
/// </summary>
|
||||
public JObject Input { get; } = input;
|
||||
|
||||
/// <summary>
|
||||
/// Значение из JSON по заданному пути, которое обязательно должно существовать.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь в JSON.</param>
|
||||
/// <returns>Объект типа `T` или `null`, если не удалось найти значение.</returns>
|
||||
/// <exception cref="NullReferenceException">Если значение не найдено.</exception>
|
||||
public virtual T GetRequired<T>(string path)
|
||||
{
|
||||
JToken token =
|
||||
@@ -16,6 +29,11 @@ internal class ProcessorInput(JObject input)
|
||||
return token.ToObject<T>() ?? throw new NullReferenceException($"'{path}' is null");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Значение из JSON по заданному пути, которое может не существовать.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь в JSON</param>
|
||||
/// <returns>Объект типа `T` или `null`, если значение не найдено</returns>
|
||||
public virtual T? GetOptional<T>(string path)
|
||||
{
|
||||
JToken? token = Input.SelectToken(path) ?? null;
|
||||
@@ -25,6 +43,12 @@ internal class ProcessorInput(JObject input)
|
||||
: token.ToObject<T>() ?? default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Значение из JSON по заданному пути с возможностью предоставить значение по умолчанию.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь в JSON.</param>
|
||||
/// <param name="defaultValue">Значение, которое возвращается, если значение не найдено.</param>
|
||||
/// <returns>Объект типа `T` или `defaultValue`, если значение не найдено.</returns>
|
||||
public virtual T GetOptional<T>(string path, T defaultValue)
|
||||
{
|
||||
JToken? token = Input.SelectToken(path) ?? null;
|
||||
@@ -35,6 +59,11 @@ internal class ProcessorInput(JObject input)
|
||||
return token.ToObject<T>() ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразование значения из JSON по заданному пути в перечисление с использованием `PgName` атрибута.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь в JSON.</param>
|
||||
/// <returns>Перечисление T или выбрасывает исключение, если значение не соответствует жодному варианту.</returns>
|
||||
public virtual T EnumRequired<T>(string path)
|
||||
where T : Enum
|
||||
{
|
||||
@@ -45,6 +74,12 @@ internal class ProcessorInput(JObject input)
|
||||
return Enum<T>(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразование значения из JSON по заданному пути в перечисление с возможностью предоставить значение по умолчанию.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь в JSON</param>
|
||||
/// <param name="defaultValue">Значение, которое возвращается, если значение не найдено.</param>
|
||||
/// <returns>Значение перечисления T или defaultValue</returns>
|
||||
public virtual T EnumOptional<T>(string path, T defaultValue)
|
||||
where T : Enum
|
||||
{
|
||||
@@ -65,11 +100,23 @@ internal class ProcessorInput(JObject input)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразование значения из JSON по заданному пути в строку локализации для заданного типа локализации.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь в JSON.</param>
|
||||
/// <param name="type">Тип локализации.</param>
|
||||
/// <returns>Объект <see cref="LocaleString"/> с добавленным значением для заданного типа.</returns>
|
||||
public virtual LocaleString GetRequiredLocale(string path, LocaleType type)
|
||||
{
|
||||
return new() { [type] = GetRequired<string>(path) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразование значения из JSON по заданному пути в строку локализации для заданного типа локализации.
|
||||
/// </summary>
|
||||
/// <param name="path">Путь в JSON.</param>
|
||||
/// <param name="type">Тип локализации.</param>
|
||||
/// <returns>Объект <see cref="LocaleString"/> или null, если значение не найдено.</returns>
|
||||
public virtual LocaleString? GetOptionalLocale(string path, LocaleType type)
|
||||
{
|
||||
string? value = GetOptional<string>(path);
|
||||
@@ -80,6 +127,11 @@ internal class ProcessorInput(JObject input)
|
||||
return new() { [type] = value };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразование значения из JSON в перечисление.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение, которое нужно преобразовать</param>
|
||||
/// <returns>Объект типа `T`, если преобразование успешно, иначе выбрасывает исключение</returns>
|
||||
public virtual T Enum<T>(string value)
|
||||
where T : Enum
|
||||
{
|
||||
@@ -104,6 +156,11 @@ internal class ProcessorInput(JObject input)
|
||||
throw new InvalidEnumStringException(typeof(T), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исключение при неверном значении для перечисленния.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип перечисленния.</param>
|
||||
/// <param name="value">Некорректное значение.</param>
|
||||
public class InvalidEnumStringException(Type type, string value)
|
||||
: Exception($"Invalid value '{value}' for enum type {type.FullName}.") { }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
namespace Pf2eDataScrapper.Importers.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Класс абстрактного репозитория.
|
||||
/// </summary>
|
||||
/// <param name="importer">Импортёр, использующий репозиторий.</param>
|
||||
internal abstract class AbstractImporterRepository(AbstractImporter importer)
|
||||
{
|
||||
/// <summary>
|
||||
/// Импортёр, использующий репозиторий.
|
||||
/// </summary>
|
||||
public readonly AbstractImporter Importer = importer;
|
||||
|
||||
/// <inheritdoc cref="AbstractImporter.Dir"/>
|
||||
public string RepositoryDirPath => Importer.Dir;
|
||||
|
||||
/// <summary>
|
||||
/// CLR-представкление директории с данными.
|
||||
/// </summary>
|
||||
public DirectoryInfo RepositoryDir => new(RepositoryDirPath);
|
||||
|
||||
/// <summary>
|
||||
/// Обновление данных в репозитории.
|
||||
/// </summary>
|
||||
public abstract void Update();
|
||||
}
|
||||
|
||||
@@ -2,27 +2,31 @@
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Репозиторий, загружающий данные из GIT-источника.
|
||||
/// </summary>
|
||||
/// <param name="importer">Импортёр, использующий репозиторий.</param>
|
||||
/// <param name="url">Ссылка на GIT-репозиторий.</param>
|
||||
internal class GitRepository(AbstractImporter importer, string url)
|
||||
: AbstractImporterRepository(importer)
|
||||
{
|
||||
public readonly string Url = url;
|
||||
|
||||
/// <summary>
|
||||
/// Обновление импортёра
|
||||
/// Ссылка на GIT-репозиторий.
|
||||
/// </summary>
|
||||
public string Url { get; } = url;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update()
|
||||
{
|
||||
string gitPath = Path.Combine(RepositoryDirPath, Importer.Slug);
|
||||
|
||||
if (Directory.Exists(gitPath))
|
||||
if (Directory.Exists(RepositoryDirPath))
|
||||
{
|
||||
// git pull
|
||||
Process process = new()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = "git",
|
||||
WorkingDirectory = gitPath,
|
||||
WorkingDirectory = RepositoryDirPath,
|
||||
Arguments = "pull",
|
||||
},
|
||||
};
|
||||
@@ -33,10 +37,10 @@ internal class GitRepository(AbstractImporter importer, string url)
|
||||
// git clone
|
||||
Process process = new()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = "git",
|
||||
WorkingDirectory = RepositoryDirPath,
|
||||
WorkingDirectory = EnvSettings.DataDirPath,
|
||||
Arguments = $"clone {Url} {Importer.Slug}",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,11 +3,24 @@ using Npgsql;
|
||||
|
||||
namespace Pf2eDataScrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Main class for database connection and operations.
|
||||
/// </summary>
|
||||
public partial class Pf2eDatabase
|
||||
{
|
||||
public NpgsqlConnection Connection;
|
||||
/// <summary>
|
||||
/// Подключение к базе данных.
|
||||
/// </summary>
|
||||
public readonly NpgsqlConnection Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Имя пользователя для подключения к базе данных.
|
||||
/// </summary>
|
||||
public readonly string Username;
|
||||
|
||||
/// <summary>
|
||||
/// Название базы данных.
|
||||
/// </summary>
|
||||
public readonly string DatabaseName;
|
||||
|
||||
private Pf2eDatabase()
|
||||
@@ -15,7 +28,7 @@ public partial class Pf2eDatabase
|
||||
Username = UsernamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value;
|
||||
DatabaseName = DatabaseNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value;
|
||||
|
||||
Connection = new NpgsqlConnection(EnvSettings.DatabaseConnection);
|
||||
Connection = new(EnvSettings.DatabaseConnection);
|
||||
Connection.Open();
|
||||
}
|
||||
|
||||
@@ -24,10 +37,19 @@ public partial class Pf2eDatabase
|
||||
Connection.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singleton instance of the database connection manager.
|
||||
/// </summary>
|
||||
public static Pf2eDatabase Instance { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Current transaction associated with this database connection.
|
||||
/// </summary>
|
||||
public NpgsqlTransaction? Transaction { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Begin a new transaction on the database connection.
|
||||
/// </summary>
|
||||
public void StartTransaction()
|
||||
{
|
||||
if (Transaction != null)
|
||||
@@ -38,6 +60,9 @@ public partial class Pf2eDatabase
|
||||
Transaction = Connection.BeginTransaction();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commit the current transaction to the database.
|
||||
/// </summary>
|
||||
public void Commit()
|
||||
{
|
||||
if (Transaction == null)
|
||||
@@ -48,6 +73,10 @@ public partial class Pf2eDatabase
|
||||
Transaction.Commit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a SQL command against the database.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL command to execute</param>
|
||||
public void ExecuteCommand(string sql)
|
||||
{
|
||||
NpgsqlCommand command = GetCommand(sql);
|
||||
@@ -55,9 +84,14 @@ public partial class Pf2eDatabase
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new SQL command for the database connection.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL command string</param>
|
||||
/// <returns>A new NpgsqlCommand object</returns>
|
||||
public NpgsqlCommand GetCommand(string sql)
|
||||
{
|
||||
return new NpgsqlCommand(sql, Connection);
|
||||
return new(sql, Connection);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Pf2eDataScrapper;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd;
|
||||
|
||||
//Console.SetWindowSize(Console.LargestWindowWidth - 20, Console.LargestWindowHeight - 10);
|
||||
// Console.SetWindowSize(Console.LargestWindowWidth - 20, Console.LargestWindowHeight - 10);
|
||||
|
||||
EnvSettings.Load();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user