Глобальный рефакторинг пространств имён
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Путь к папке для распаковки репозиториев обрабатываемых данных
|
||||
DATA_DIR=D:\path\to\directory\with\repositories
|
||||
|
||||
# Путь к месту сохранения схем
|
||||
OUTPUT_DIR=D:\path\to\output\directory
|
||||
|
||||
# Строка подключения к БД
|
||||
DATABASE_CONNECTION=Host=localhost;Port=5433;Database=usersdb2;Username=postgres;Password=password
|
||||
|
||||
# Максимальное количество потоков.
|
||||
MAX_THREADS=60
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using dotenv.net;
|
||||
using dotenv.net.Utilities;
|
||||
|
||||
namespace Pf2e.Scrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Настройки программы.
|
||||
/// </summary>
|
||||
internal class ApplicationSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="ApplicationSettings"/>.
|
||||
/// </summary>
|
||||
public ApplicationSettings()
|
||||
{
|
||||
DotEnv.Load();
|
||||
|
||||
DataDirPath = EnvReader.GetStringValue("DATA_DIR");
|
||||
OutputDirPath = EnvReader.GetStringValue("OUTPUT_DIR");
|
||||
DatabaseConnection = EnvReader.GetStringValue("DATABASE_CONNECTION");
|
||||
MaxThreads = Convert.ToInt32(EnvReader.GetStringValue("MAX_THREADS"));
|
||||
}
|
||||
|
||||
#region Настройки из .env
|
||||
|
||||
/// <summary>
|
||||
/// Путь к папке с входными данными.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[ReadOnly(true)]
|
||||
[DisplayName("Путь к папке с входными данными.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public string DataDirPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Папка в входными данными.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public DirectoryInfo DataDir => new(DataDirPath);
|
||||
|
||||
/// <summary>
|
||||
/// Путь к папке для сохранения результатов.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[ReadOnly(true)]
|
||||
[DisplayName("Путь к папке для сохранения результатов.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public string OutputDirPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Папка для сохранения результатов.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public string OutputDir => new(OutputDirPath);
|
||||
|
||||
/// <summary>
|
||||
/// Строка подключения к базе данных.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[ReadOnly(true)]
|
||||
[DisplayName("Строка подключения к базе данных.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public string DatabaseConnection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Максимальное количество потоков.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[ReadOnly(true)]
|
||||
[DisplayName("Максимальное количество потоков.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public int MaxThreads { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Основные настройки
|
||||
|
||||
//
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
namespace Pf2e.Scrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Модель классов ORM EntityFramework.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Классы-помощники, используемые во всех частях проекта.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers
|
||||
{
|
||||
/// <summary>
|
||||
/// Описание импортёров, которые определяют наборы обработчиков данных и элементов, необходимых для их работы.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd
|
||||
{
|
||||
/// <summary>
|
||||
/// Импортёр данных из сайта <see href="https://aonprd.com/">Archives of Nethys Pathfinder 2e</see>.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Compendium
|
||||
{
|
||||
/// <summary>
|
||||
/// Компендиум данных из сайта <see href="https://aonprd.com/">Archives of Nethys Pathfinder 2e</see>.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Класс-помощники, которые используются в импортере данных из сайта <see href="https://aonprd.com/">Archives of Nethys Pathfinder 2e</see>.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработчики данных из сайта <see href="https://aonprd.com/">Archives of Nethys Pathfinder 2e</see>.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Preparation
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработчики для подготовки данных <see href="https://aonprd.com/">Archives of Nethys Pathfinder 2e</see>.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработчики подклассов с сайта <see href="https://aonprd.com/">Archives of Nethys Pathfinder 2e</see>.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработчики основных сущностей с сайта <see href="https://aonprd.com/">Archives of Nethys Pathfinder 2e</see>.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types.Rules
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработчики правил с сайта <see href="https://aonprd.com/">Archives of Nethys Pathfinder 2e</see>.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn
|
||||
{
|
||||
/// <summary>
|
||||
/// Импортёр данных из модуля <see href="https://github.com/foundryvtt/pf2e">pf2e</see> для FoundryVTT.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Compendium
|
||||
{
|
||||
/// <summary>
|
||||
/// Компендиум данных из модуля <see href="https://github.com/foundryvtt/pf2e">pf2e</see> для FoundryVTT.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Preparation
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработчики для подготовки данных из модуля <see href="https://github.com/foundryvtt/pf2e">pf2e</see> для FoundryVTT.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// Общее описание репозиториев для хранения данных, которые используются в импортёрах и процессорах.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Общее описание обработчиков данных.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Processors.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Классы-аттрибуты, которые используются для описания обработчиков данных и их свойств.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Processors.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// Описание коллекции классов обработчиков данных.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Processors.Definitions
|
||||
{
|
||||
/// <summary>
|
||||
/// Общее описание классов и интерфейсов, которые используются в обработчиках данных.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.Processors.Factory
|
||||
{
|
||||
/// <summary>
|
||||
/// Фабрики для создания экземпляров обработчиков данных на основе их атрибутов и конфигурации.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.SchemaBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Классы обработки вводимых JSON-данных и создание схемы данных для использовании при формировании обработчиков данных.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.SchemaBuilder.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Типы полей, которые используются в схемах данных для описания структуры вводимых JSON-данных.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF
|
||||
{
|
||||
/// <summary>
|
||||
/// WPF-приложение для запуска обработчиков и просмотра данных Pathfinder 2e.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.AuthForm
|
||||
{
|
||||
/// <summary>
|
||||
/// WPF-приложение для авторизации пользователя во внешнем сервисе.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.EntityViewer
|
||||
{
|
||||
/// <summary>
|
||||
/// WPF-окно для просмотра сущностей Pathfinder 2e, с возможностью фильтрации и поиска по данным.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.EntityViewer.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Компоненты, которые используются в WPF-окне просмотра сущностей Pathfinder 2e.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.EntityViewer.EntityViewStructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Структура представления сущностей Pathfinder 2e в WPF-приложении, включая классы и интерфейсы для отображения данных.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.EntityViewer.Stores
|
||||
{
|
||||
/// <summary>
|
||||
/// Хранилища данных для WPF-приложения просмотра сущностей Pathfinder 2e, которые управляют состоянием и данными приложения.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.EntityViewer.ViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ViewModel для WPF-приложения просмотра сущностей Pathfinder 2e, которые связывают данные и представление.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.ProgressForm
|
||||
{
|
||||
/// <summary>
|
||||
/// WPF-окно для отображения прогресса выполнения операций, таких как импорт данных или обработка сущностей Pathfinder 2e.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.StartForm
|
||||
{
|
||||
/// <summary>
|
||||
/// WPF-окно для запуска приложения, которое позволяет пользователю выбрать импортёр и начать обработку данных Pathfinder 2e.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
|
||||
namespace Pf2e.Scrapper.WPF.StartForm.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Компоненты, которые используются в WPF-окне запуска приложения для выбора импортёра и настройки параметров обработки данных Pathfinder 2e.
|
||||
/// </summary>
|
||||
internal static class NamespaceDoc { }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.IO;
|
||||
using dotenv.net;
|
||||
using dotenv.net.Utilities;
|
||||
|
||||
namespace Pf2e.Scrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Класс настроек.
|
||||
/// </summary>
|
||||
public static class EnvSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Загрузка файла настроек `.env`.
|
||||
/// </summary>
|
||||
public static void Load()
|
||||
{
|
||||
DotEnv.Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Путь к папке с входными данными.
|
||||
/// </summary>
|
||||
public static string DataDirPath => EnvReader.GetStringValue("DATA_DIR");
|
||||
|
||||
/// <summary>
|
||||
/// Папка в входными данными.
|
||||
/// </summary>
|
||||
public static DirectoryInfo DataDir => new(DataDirPath);
|
||||
|
||||
/// <summary>
|
||||
/// Путь к папке для сохранения результатов.
|
||||
/// </summary>
|
||||
public static string OutputDirPath => EnvReader.GetStringValue("OUTPUT_DIR");
|
||||
|
||||
/// <summary>
|
||||
/// Папка для сохранения результатов.
|
||||
/// </summary>
|
||||
public static string OutputDir => new(OutputDirPath);
|
||||
|
||||
/// <summary>
|
||||
/// Строка подключения к базе данных.
|
||||
/// </summary>
|
||||
public static string DatabaseConnection => EnvReader.GetStringValue("DATABASE_CONNECTION");
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Pf2e.Database;
|
||||
using Slugify;
|
||||
|
||||
namespace Pf2e.Scrapper.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс предоставляет методы для работы с базой данных.
|
||||
/// </summary>
|
||||
public class DatabaseHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Context для доступа к базе данных.
|
||||
/// </summary>
|
||||
public Pf2eDbContext Context { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Сервис для работы с URL-Slug. Используется для преобразования названий в URL-friendly формат.
|
||||
/// </summary>
|
||||
public SlugHelper Slugger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Последний найденный идентификатор сущности.
|
||||
/// Используется для генерации новых ID сущностей.
|
||||
/// </summary>
|
||||
private int _id;
|
||||
|
||||
/// <summary>
|
||||
/// Генерирует новый уникальный идентификатор сущности. и возвращает его.
|
||||
/// </summary>
|
||||
public int NextEntityId => ++_id;
|
||||
|
||||
/// <summary>
|
||||
/// Блокировщик для генерации идентификаторов сущностей.
|
||||
/// </summary>
|
||||
public Lock IdLock { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="DatabaseHelper"/>
|
||||
/// </summary>
|
||||
/// <param name="slugHelper">[DI] Сервис для работы с URL-Slug</param>
|
||||
public DatabaseHelper(SlugHelper slugHelper)
|
||||
{
|
||||
Slugger = slugHelper;
|
||||
_id = Context.Entities.Any() ? Context.Entities.Max(e => e.Id) : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерирует slug из названия.
|
||||
/// </summary>
|
||||
public string GenerateSlug(string name)
|
||||
{
|
||||
return Slugger.GenerateSlug(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
|
||||
namespace Pf2e.Scrapper.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс, обрабатывающий запросы к сущностям и подключающий необходимые данные.
|
||||
/// </summary>
|
||||
public class EntityView
|
||||
{
|
||||
/// <summary>
|
||||
/// Корневая сущность.
|
||||
/// </summary>
|
||||
public Entity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Основная сущность.
|
||||
/// </summary>
|
||||
public IEntity? SubEntity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Контекст базы данных, используемый для получения сущностей.
|
||||
/// </summary>
|
||||
private Pf2eDbContext Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса, который получает сущность из базы данных по типу и идентификатору и заполняет её данными.
|
||||
/// </summary>
|
||||
/// <param name="context">Контекст базы данных.</param>
|
||||
/// <param name="type">Тип запрашиваемой сущности.</param>
|
||||
/// <param name="id">Идентификатор запрашиваемой сущности.</param>
|
||||
public EntityView(Pf2eDbContext context, EntityType type, int id)
|
||||
{
|
||||
Context = context;
|
||||
Entity = GetEntity(type, id);
|
||||
SubEntity = GetSubEntity(type, id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса, который получает сущность из базы данных по переданной сущности и заполняет её данными.
|
||||
/// </summary>
|
||||
/// <param name="context">Контекст базы данных.</param>
|
||||
/// <param name="entity">Сущность, которую нужно обработать.</param>
|
||||
public EntityView(Pf2eDbContext context, Entity entity)
|
||||
{
|
||||
Context = context;
|
||||
Entity = entity;
|
||||
SubEntity = GetSubEntity(entity.Type, entity.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает сущность из базы данных по типу и идентификатору.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип сущности.</param>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <returns>Сущность, соответствующая указанному типу и идентификатору.</returns>
|
||||
private Entity GetEntity(EntityType type, int id) =>
|
||||
Context
|
||||
.Entities.Where(e => e.Id == id && e.Type == type)
|
||||
.Include(e => e.Sources)
|
||||
.ThenInclude(s => s.Source)
|
||||
.Include(e => e.Keywords)
|
||||
.Include(e => e.Externals)
|
||||
.ThenInclude(x => x.Target)
|
||||
.Include(e => e.Traits)
|
||||
.ThenInclude(t => t.Entity)
|
||||
.FirstOrDefault() ?? throw new("Entity is null");
|
||||
|
||||
/// <summary>
|
||||
/// Получает подчинённую сущность по типу и идентификатору.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип сущности.</param>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <returns>Подчинённая сущность, соответствующая указанному типу и идентификатору.</returns>
|
||||
private IEntity? GetSubEntity(EntityType type, int id)
|
||||
{
|
||||
Getter? getter = _getters.FirstOrDefault(g => g.Type == type);
|
||||
return (getter != null)
|
||||
? getter.Get(id, this.Context)
|
||||
: (IEntity?)Context.Find(EntityOfTypeAttribute.Resolve(Entity), id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Словарь, содержащий методы получения сущностей из базы данных.
|
||||
/// </summary>
|
||||
private readonly ICollection<Getter> _getters =
|
||||
[
|
||||
new Getter<Ancestry>(
|
||||
EntityType.Ancestry,
|
||||
context => context.Ancestries.Include(a => a.AncestryBoosts)
|
||||
),
|
||||
new Getter<Archetype>(
|
||||
EntityType.Archetype,
|
||||
context =>
|
||||
context
|
||||
.Archetypes.Include(a => a.DedicationFeat)
|
||||
.ThenInclude(f => f.Entity)
|
||||
.Include(a => a.Feats)
|
||||
.ThenInclude(f => f.Entity)
|
||||
),
|
||||
new Getter<Background>(
|
||||
EntityType.Background,
|
||||
context =>
|
||||
context
|
||||
.Backgrounds.Include(b => b.BackgroundBoosts)
|
||||
.Include(b => b.BackgroundSkills)
|
||||
.ThenInclude(t => t.Training)
|
||||
.Include(b => b.BackgroundItems)
|
||||
),
|
||||
new Getter<Class>(
|
||||
EntityType.Class,
|
||||
context =>
|
||||
context
|
||||
.Classes.Include(c => c.ClassKeyAbilities)
|
||||
.Include(c => c.ClassTrainings)
|
||||
.ThenInclude(t => t.Training)
|
||||
.Include(c => c.ClassProgressions)
|
||||
.Include(c => c.SubclassGroups)
|
||||
),
|
||||
new Getter<Feat>(
|
||||
EntityType.Feat,
|
||||
context =>
|
||||
context.Feats.Include(f => f.Archetypes).ThenInclude(a => a.Entity).AsQueryable()
|
||||
),
|
||||
new Getter<Spell>(
|
||||
EntityType.Spell,
|
||||
context => context.Spells.Include(s => s.SpellAreas).Include(s => s.SpellDefenses)
|
||||
),
|
||||
new Getter<Ritual>(
|
||||
EntityType.Ritual,
|
||||
context =>
|
||||
context
|
||||
.Rituals.Include(r => r.Area)
|
||||
.Include(r => r.Checks)
|
||||
.ThenInclude(c => c.Training)
|
||||
.AsQueryable()
|
||||
),
|
||||
new Getter<Subclass>(
|
||||
EntityType.Subclass,
|
||||
context =>
|
||||
context
|
||||
.Subclasses.Include(s => s.Trainings)
|
||||
.ThenInclude(t => t.Training)
|
||||
.Include(s => s.Entities)
|
||||
),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс, который определяет метод получения сущности из базы данных.
|
||||
/// </summary>
|
||||
/// <param name="type">Связанный тип сущности.</param>
|
||||
private abstract class Getter(EntityType type)
|
||||
{
|
||||
public readonly EntityType Type = type;
|
||||
|
||||
public abstract IEntity? Get(int id, Pf2eDbContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс, определяющий метод получения сущности из базы данных.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип данных основной сущности.</typeparam>
|
||||
/// <param name="type">Связанный тип сущности.</param>
|
||||
/// <param name="method">Метод получения сущности из базы данных.</param>
|
||||
private class Getter<T>(EntityType type, Func<Pf2eDbContext, IQueryable<T>> method)
|
||||
: Getter(type)
|
||||
where T : IEntity
|
||||
{
|
||||
public override IEntity? Get(int id, Pf2eDbContext context)
|
||||
{
|
||||
return method.Invoke(context).FirstOrDefault(c => c.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Pf2e.Scrapper.Helpers;
|
||||
|
||||
internal abstract class AbstractLogger
|
||||
{
|
||||
public abstract void Message(LogLevel level, string message);
|
||||
|
||||
public void Important(string message) => Message(LogLevel.Important, message);
|
||||
|
||||
public void Warning(string message) => Message(LogLevel.Warning, message);
|
||||
|
||||
public void Information(string message) => Message(LogLevel.Information, message);
|
||||
|
||||
public void Progress(string message) => Message(LogLevel.Progress, message);
|
||||
}
|
||||
|
||||
internal enum LogLevel
|
||||
{
|
||||
Important,
|
||||
Warning,
|
||||
Information,
|
||||
Progress,
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
|
||||
namespace Pf2e.Scrapper.Helpers;
|
||||
|
||||
internal class ProcessProgress : AbstractLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Прогресс обработки обработчиков <see cref="IProcessor"/>.
|
||||
/// </summary>
|
||||
public ProgressItem Processors { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Прогресс работы конкретного обработчика <see cref="IProcessor"/>.
|
||||
/// </summary>
|
||||
public ProgressItem Local { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="ProcessProgress"/>.
|
||||
/// </summary>
|
||||
public ProcessProgress()
|
||||
{
|
||||
Processors.Update += Update;
|
||||
Local.Update += Update;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Событие обновления
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
ProgressUpdate?.Invoke(this);
|
||||
}
|
||||
|
||||
public override void Message(LogLevel level, string message)
|
||||
{
|
||||
ProgressMessage?.Invoke(this, level, message);
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
ProgressComplete?.Invoke(this);
|
||||
}
|
||||
|
||||
public delegate void ProgressUpdateDelegate(ProcessProgress sender);
|
||||
public event ProgressUpdateDelegate? ProgressUpdate;
|
||||
|
||||
public event ProgressUpdateDelegate? ProgressComplete;
|
||||
|
||||
public delegate void ProgressMessageDelegate(
|
||||
ProcessProgress sender,
|
||||
LogLevel level,
|
||||
string message
|
||||
);
|
||||
public event ProgressMessageDelegate? ProgressMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Класс прогресса.
|
||||
/// </summary>
|
||||
public class ProgressItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Количество обрабатываемых процессов.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get => _count;
|
||||
set
|
||||
{
|
||||
_count = value;
|
||||
Update?.Invoke();
|
||||
}
|
||||
}
|
||||
private int _count;
|
||||
|
||||
/// <summary>
|
||||
/// Индекс текущего обрабатываемого процесса.
|
||||
/// </summary>
|
||||
public int Current
|
||||
{
|
||||
get => _current;
|
||||
set
|
||||
{
|
||||
_current = value;
|
||||
Update?.Invoke();
|
||||
}
|
||||
}
|
||||
private int _current;
|
||||
|
||||
/// <summary>
|
||||
/// Название текущего обрабатываемого процесса.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get => _text;
|
||||
set
|
||||
{
|
||||
_text = value;
|
||||
Update?.Invoke();
|
||||
}
|
||||
}
|
||||
private string _text = "";
|
||||
|
||||
/// <summary>
|
||||
/// Значение прогресса.
|
||||
/// </summary>
|
||||
public double Percent => Count > 0 ? 1.0 * Current / Count : 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// Сброс прогресса до нуля и установка нового максимального значения.
|
||||
/// </summary>
|
||||
/// <param name="max"></param>
|
||||
public void Reset(int max = 0)
|
||||
{
|
||||
_count = max;
|
||||
_current = 0;
|
||||
_text = "";
|
||||
Update?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отмечает начало работы над процессом, описываемым строкой <paramref name="text"/>.
|
||||
/// </summary>
|
||||
/// <param name="text">Строка, описывающая процесс.</param>
|
||||
public void Start(string text)
|
||||
{
|
||||
_text = text;
|
||||
Update?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отмечает конец работы над процессом и увеличивает значение прогресса.
|
||||
/// </summary>
|
||||
public void End()
|
||||
{
|
||||
_current++;
|
||||
Update?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отмечает прогресс завершённым.
|
||||
/// </summary>
|
||||
public void Complete()
|
||||
{
|
||||
_current = _count;
|
||||
_text = "Готово!";
|
||||
Update?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение строкового представления прогресса.
|
||||
/// </summary>
|
||||
/// <returns>Строковое представление прогресса.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Text} [{Current} / {Count}; {Percent:P}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Делегат события обновления.
|
||||
/// </summary>
|
||||
public delegate void UpdateDelegate();
|
||||
|
||||
/// <summary>
|
||||
/// Событие обновление.
|
||||
/// </summary>
|
||||
public event UpdateDelegate? Update;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Collection;
|
||||
using Pf2e.Scrapper.WPF.EntityViewer;
|
||||
using Pf2e.Scrapper.WPF.ProgressForm;
|
||||
using Pf2e.Scrapper.WPF.StartForm;
|
||||
using Pf2e.Database;
|
||||
using Slugify;
|
||||
|
||||
namespace Pf2e.Scrapper;
|
||||
|
||||
internal sealed class ImportApplication : Application
|
||||
{
|
||||
public ImportApplication(
|
||||
IServiceProvider serviceProvider,
|
||||
ApplicationSettings settings,
|
||||
ProcessorCollection processors
|
||||
)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
Settings = settings;
|
||||
Processors = processors;
|
||||
|
||||
CurrentStage = ApplicationStage.InitialSettings;
|
||||
//CurrentStage = ApplicationStage.Viewer;
|
||||
}
|
||||
|
||||
#region "Поля"
|
||||
|
||||
/// <summary>
|
||||
/// Текущий этап выполнения программы.
|
||||
/// </summary>
|
||||
public ApplicationStage CurrentStage
|
||||
{
|
||||
get => _currentStage;
|
||||
set
|
||||
{
|
||||
ApplicationStage oldValue = _currentStage;
|
||||
_currentStage = value;
|
||||
|
||||
CloseStage(oldValue);
|
||||
StartStage(_currentStage);
|
||||
}
|
||||
}
|
||||
private ApplicationStage _currentStage = ApplicationStage.BootUp;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Жизненный цикл"
|
||||
|
||||
private StartForm? _startFormWpf;
|
||||
|
||||
private ProgressForm? _progressFormWpf;
|
||||
|
||||
private EntityForm? _entityForm;
|
||||
|
||||
private Window? CurrentWpfForm { get; set; }
|
||||
|
||||
public delegate void ApplicationStateChangeDelegate(object sender, ApplicationStage nextStage);
|
||||
|
||||
private void StartStage(ApplicationStage stage)
|
||||
{
|
||||
switch (stage)
|
||||
{
|
||||
case ApplicationStage.BootUp:
|
||||
throw new(
|
||||
$"{CurrentStage} никогда не должен быть установлен через поле \"CurrentStage\"."
|
||||
);
|
||||
|
||||
case ApplicationStage.InitialSettings:
|
||||
_startFormWpf = ActivatorUtilities.CreateInstance<StartForm>(ServiceProvider);
|
||||
_startFormWpf.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
_startFormWpf.Closing += Wpf_HandleFormClosing;
|
||||
_startFormWpf.ApplicationStateChange += ApplicationStateChange;
|
||||
_startFormWpf.Show();
|
||||
|
||||
CurrentWpfForm = _startFormWpf;
|
||||
break;
|
||||
|
||||
case ApplicationStage.ImportProcess:
|
||||
ProcessorExecutor executor = ActivatorUtilities.CreateInstance<ProcessorExecutor>(
|
||||
ServiceProvider
|
||||
);
|
||||
|
||||
_progressFormWpf = ActivatorUtilities.CreateInstance<ProgressForm>(ServiceProvider);
|
||||
_progressFormWpf.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
_progressFormWpf.Closing += Wpf_HandleFormClosing;
|
||||
_progressFormWpf.ApplicationStateChange += ApplicationStateChange;
|
||||
_progressFormWpf.Show();
|
||||
|
||||
CurrentWpfForm = _progressFormWpf;
|
||||
|
||||
executor.Start();
|
||||
break;
|
||||
|
||||
case ApplicationStage.Viewer:
|
||||
_entityForm = ActivatorUtilities.CreateInstance<EntityForm>(ServiceProvider);
|
||||
_entityForm.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
_entityForm.Closing += Wpf_HandleFormClosing;
|
||||
_entityForm.ApplicationStateChange += ApplicationStateChange;
|
||||
_entityForm.Show();
|
||||
|
||||
CurrentWpfForm = _entityForm;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseStage(ApplicationStage stage)
|
||||
{
|
||||
CurrentWpfForm = null;
|
||||
|
||||
switch (stage)
|
||||
{
|
||||
case ApplicationStage.BootUp:
|
||||
// Нечего закрывать
|
||||
break;
|
||||
|
||||
case ApplicationStage.InitialSettings:
|
||||
_startFormWpf!.Hide();
|
||||
_startFormWpf!.Close();
|
||||
_startFormWpf = null;
|
||||
break;
|
||||
|
||||
case ApplicationStage.ImportProcess:
|
||||
_progressFormWpf!.Hide();
|
||||
_progressFormWpf!.Close();
|
||||
_progressFormWpf = null;
|
||||
break;
|
||||
|
||||
case ApplicationStage.Viewer:
|
||||
_entityForm!.Hide();
|
||||
_entityForm!.Close();
|
||||
_entityForm = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(stage));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "События"
|
||||
|
||||
public void ApplicationStateChange(object sender, ApplicationStage nextStage)
|
||||
{
|
||||
if (sender?.Equals(CurrentWpfForm) ?? false)
|
||||
{
|
||||
CurrentStage = nextStage;
|
||||
}
|
||||
}
|
||||
|
||||
private void Wpf_HandleFormClosing(object? sender, CancelEventArgs e)
|
||||
{
|
||||
if (sender?.Equals(CurrentWpfForm) ?? false)
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Внедрение зависимостей"
|
||||
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
public ApplicationSettings Settings { get; }
|
||||
public ProcessorCollection Processors { get; }
|
||||
|
||||
public static void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
// Контекст базы данных
|
||||
services.AddEntityFrameworkNpgsql();
|
||||
|
||||
services.AddDbContext<Pf2eDbContext>();
|
||||
//services.AddDbContextPool<Pf2eDbContext>(options =>
|
||||
//{
|
||||
// options.Us
|
||||
//});
|
||||
|
||||
// Основная программа
|
||||
services.AddSingleton<ImportApplication>();
|
||||
|
||||
// Настройки программы
|
||||
services.AddSingleton<ApplicationSettings>();
|
||||
|
||||
// Набор процессоров
|
||||
services.AddSingleton<ProcessorCollection>();
|
||||
|
||||
// Помощник идентификации базы данных
|
||||
services.AddSingleton<DatabaseHelper>();
|
||||
|
||||
// Помощник создание URL-совместимых названий
|
||||
services.AddTransient<SlugHelper>();
|
||||
|
||||
// Счётчик прогресса
|
||||
services.AddSingleton<ProcessProgress>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal enum ApplicationStage
|
||||
{
|
||||
BootUp,
|
||||
InitialSettings,
|
||||
ImportProcess,
|
||||
Viewer,
|
||||
Rules,
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Importers.Repositories;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Компендиум данных.
|
||||
/// </summary>
|
||||
internal abstract class AbstractCompendium
|
||||
{
|
||||
/// <summary>
|
||||
/// Репозиторий данных.
|
||||
/// </summary>
|
||||
public abstract AbstractImporterRepository Repository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Путь к хранению исходных данных.
|
||||
/// </summary>
|
||||
public string Dir => Repository.RepositoryDirPath;
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает количество элементов для указанного типа данных.
|
||||
/// </summary>
|
||||
/// <param name="type">Типы данных.</param>
|
||||
/// <returns>Количество элементов.</returns>
|
||||
public abstract int Count(params ICollection<string> type);
|
||||
|
||||
/// <summary>
|
||||
/// Получение итератора сущностей. Если указан тип <paramref name="type"/> - то только указанного типа.
|
||||
/// </summary>
|
||||
/// <param name="type">Типы сущностей, которые нужно получить.</param>
|
||||
/// <returns>Итератор сущностей.</returns>
|
||||
public abstract IEnumerable<Tuple<AbstractCompendiumItem, JObject>> Iterate(
|
||||
params ICollection<string> type
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Pf2e.Scrapper.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактная ссылка на сущность.
|
||||
/// </summary>
|
||||
internal abstract class AbstractCompendiumItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор сущности.
|
||||
/// </summary>
|
||||
public abstract string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности.
|
||||
/// </summary>
|
||||
public abstract string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Строковое представление имени сущности.
|
||||
/// </summary>
|
||||
/// <returns>Строковое представление.</returns>
|
||||
public override string ToString() => Id;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс импортёра.
|
||||
/// </summary>
|
||||
internal abstract class AbstractImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Отображаемое имя импортёра.
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Короткое имя импортёра.
|
||||
/// </summary>
|
||||
public abstract string Slug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Путь к директории с данными для импортёра.
|
||||
/// </summary>
|
||||
public string Dir => Path.Combine(EnvSettings.DataDirPath, Slug);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd;
|
||||
|
||||
using StringHelper = Helpers.StringHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Класс обработки данных с сайта Архивов Нефеса.
|
||||
/// </summary>
|
||||
internal class AonprdImporter : AbstractImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Название внешней ссылки.
|
||||
/// </summary>
|
||||
public const string TargetName = "Archives of Nethys Pathfinder 2e";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Name => TargetName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Slug => "aonprd";
|
||||
|
||||
#region "Внедрение зависимостей"
|
||||
|
||||
public static void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<AonprdImporter>();
|
||||
services.AddSingleton<AonprdRepository>();
|
||||
services.AddSingleton<AonprdCompendium>();
|
||||
|
||||
services.AddSingleton<AonprdBuilderGlobals>();
|
||||
services.AddSingleton<RuleTocCollection>();
|
||||
services.AddTransient<StringHelper>();
|
||||
|
||||
AonprdProcessorInputExtension.RegisterServices(services);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Repositories;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd;
|
||||
|
||||
/// <summary>
|
||||
/// Основной класс для работы с репозиторием данных Aonprd.
|
||||
/// </summary>
|
||||
internal class AonprdRepository(AonprdImporter importer) : AbstractImporterRepository(importer)
|
||||
{
|
||||
/// <summary>
|
||||
/// URL для доступа к API Aonprd.
|
||||
/// </summary>
|
||||
public const string SearchUrl = "https://elasticsearch.aonprd.com";
|
||||
|
||||
/// <summary>
|
||||
/// Текущая ревизия данных. Значение обновляется при обновлении репозитория.
|
||||
/// </summary>
|
||||
public int Revision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Метод для загрузки данных из API Aonprd.
|
||||
/// </summary>
|
||||
private string Load(string url)
|
||||
{
|
||||
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
|
||||
{
|
||||
url = SearchUrl + url;
|
||||
}
|
||||
|
||||
using HttpClient client = new();
|
||||
using Task<Stream> stream = client.GetStreamAsync(url);
|
||||
using StreamReader reader = new(stream.Result);
|
||||
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update(ProcessProgress progress)
|
||||
{
|
||||
if (CheckRevision())
|
||||
{
|
||||
progress.Local.Complete();
|
||||
progress.Information($"Данные Aonprd актуальны, текущая ревизия {Revision}.");
|
||||
return;
|
||||
}
|
||||
|
||||
progress.Information(
|
||||
$"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..."
|
||||
);
|
||||
|
||||
progress.Local.Reset(1);
|
||||
|
||||
if (Directory.Exists(RepositoryDirPath))
|
||||
{
|
||||
Directory.Delete(RepositoryDirPath, true);
|
||||
}
|
||||
Directory.CreateDirectory(RepositoryDirPath);
|
||||
|
||||
WriteFile("revision.txt", Revision.ToString());
|
||||
|
||||
progress.Local.Start("Загрузка индекса...");
|
||||
|
||||
string indexContent = Load($"/json-data/aon{Revision}-index.json");
|
||||
WriteFile("index.json", indexContent);
|
||||
|
||||
JObject indexJson = JObject.Parse(indexContent);
|
||||
|
||||
progress.Local.Reset(indexJson.Count + 1);
|
||||
progress.Local.End();
|
||||
|
||||
foreach (KeyValuePair<string, JToken?> kvp in indexJson)
|
||||
{
|
||||
string fileName = kvp.Key;
|
||||
progress.Local.Start(fileName);
|
||||
|
||||
string fileContent = Load($"/json-data/{fileName}.json");
|
||||
WriteFile($"{fileName}.json", fileContent);
|
||||
|
||||
progress.Local.End();
|
||||
}
|
||||
|
||||
progress.Local.Complete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверка актуальности данных.
|
||||
/// </summary>
|
||||
/// <returns>true - актуальная ревизия, false - устаревшая/отсутсвует.</returns>
|
||||
private bool CheckRevision()
|
||||
{
|
||||
JObject page = JObject.Parse(Load("/aon/_doc/feat-1"));
|
||||
|
||||
Revision = Convert.ToInt32(
|
||||
page.SelectToken("_index")?.ToObject<string>()?.Replace("aon", "")
|
||||
?? throw new("Не удаётся получить ревизию Архивов Нефиса.")
|
||||
);
|
||||
|
||||
string revisionFile = Path.Combine(RepositoryDirPath, "revision.txt");
|
||||
if (!File.Exists(revisionFile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using StreamReader reader = new(revisionFile);
|
||||
string lastRevision = reader.ReadToEnd();
|
||||
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);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
using StreamWriter writer = new(filePath);
|
||||
writer.Write(content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Класс хранения данных компендиума.
|
||||
/// </summary>
|
||||
internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCompendium
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AonprdRepository Repository { get; } = repository;
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция всех записей компендиума.
|
||||
/// </summary>
|
||||
public ConcurrentBag<AonprdCompendiumItem> Items { get; } = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Count(params ICollection<string> types) =>
|
||||
types.Count == 0 ? Items.Count : Items.Count(i => types.Contains(i.Type));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<Tuple<AbstractCompendiumItem, JObject>> Iterate(
|
||||
params ICollection<string> types
|
||||
)
|
||||
{
|
||||
IEnumerable<string> files =
|
||||
types.Count == 0
|
||||
? Items.Select(item => item.File).Distinct()
|
||||
: Items
|
||||
.Where(item => types.Contains(item.Type))
|
||||
.Select(item => item.File)
|
||||
.Distinct();
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
Dictionary<string, AonprdCompendiumItem> fileReferences = Items
|
||||
.Where(i => i.File == file)
|
||||
.ToDictionary(i => i.Id);
|
||||
|
||||
using StreamReader fileReader = new(
|
||||
Path.Combine(Repository.RepositoryDirPath, $"{file}.json")
|
||||
);
|
||||
JArray array = JArray.Parse(fileReader.ReadToEnd());
|
||||
|
||||
foreach (JToken item in array)
|
||||
{
|
||||
string? id = item.SelectToken("id")?.ToString();
|
||||
|
||||
if (
|
||||
id is null
|
||||
|| !fileReferences.TryGetValue(id, out AonprdCompendiumItem? reference)
|
||||
|| (types.Count > 0 && !types.Contains(reference.Type))
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return new(reference, (JObject)item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на сущность с указанием файла, в котором она хранится.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <param name="file">Имя файла.</param>
|
||||
internal class AonprdCompendiumItem(string id, string file) : AbstractCompendiumItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор сущности.
|
||||
/// </summary>
|
||||
public override string Id { get; } = id;
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности.
|
||||
/// </summary>
|
||||
public override string Type { get; } = ResolveType(id);
|
||||
|
||||
/// <summary>
|
||||
/// Имя файла, в котором хранится сущность.
|
||||
/// </summary>
|
||||
public string File { get; } = file;
|
||||
|
||||
/// <summary>
|
||||
/// Определение типа сущности как префикса идентификатора.
|
||||
/// </summary>
|
||||
public static string ResolveType(string id)
|
||||
{
|
||||
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")
|
||||
? $"{cat}-bonus"
|
||||
: $"{cat}-sub"
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс-помощник, хранящий необходимые глобальные данные.
|
||||
/// </summary>
|
||||
internal class AonprdBuilderGlobals
|
||||
{
|
||||
/// <summary>
|
||||
/// Цель внешних ссылок Архивов Нефисов.
|
||||
/// </summary>
|
||||
public ExternalTarget AonprdTarget { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Словарь идентификаторов источников.
|
||||
/// </summary>
|
||||
public Dictionary<string, int> SourceIds { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь идентификаторов признаков.
|
||||
/// </summary>
|
||||
public Dictionary<string, int> TraitIds { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Список идентификаторов сущностей, которые нужно игнорировать при обработке.
|
||||
/// </summary>
|
||||
public List<string> Ignore { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарей суффиксов для сущностей
|
||||
/// </summary>
|
||||
public Dictionary<string, string> SlugSuffixes { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь допольнительных проверок связей.
|
||||
/// Ключ - устаревшая сущность, значение - обновлённая.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> LegacyRemasterExtra { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь сущностей, которые нужно слить с другой/другими.
|
||||
/// Ключ - пропускаемая сущность. Значение - целевая сущность.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> MergeSources { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь сущностей, которые нужно слить с другой/другими.
|
||||
/// Ключ - пропускаемая сущность. Значение - целевая сущность.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> SubclassGroups { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
|
||||
/// </summary>
|
||||
public AonprdBuilderGlobals()
|
||||
{
|
||||
using StreamReader fileReader = new("Resources\\ImportAdaptation.json");
|
||||
|
||||
JObject? json = JObject
|
||||
.Parse(fileReader.ReadToEnd())
|
||||
.SelectToken("Aonprd")
|
||||
?.ToObject<JObject>();
|
||||
|
||||
if (json is not null)
|
||||
{
|
||||
Ignore = json.SelectToken("Ignore")?.ToObject<List<string>>() ?? [];
|
||||
|
||||
SlugSuffixes = JsonToDictionary(json, "SlugSuffixes");
|
||||
LegacyRemasterExtra = JsonToDictionary(json, "LegacyRemasterExtra");
|
||||
MergeSources = JsonToDictionary(json, "MergeSources");
|
||||
SubclassGroups = JsonToDictionary(json, "SubclassGroups");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить суффикс для ссылки.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор Aonprd.</param>
|
||||
/// <returns>Суффикс для ссылки или пустая строка.</returns>
|
||||
public string GetSlugSuffix(string id)
|
||||
{
|
||||
return SlugSuffixes.TryGetValue(id, out string? slug) ? $"-{slug}" : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразовать JSON-объект в словарь.
|
||||
/// </summary>
|
||||
/// <param name="json">Исходный JSON-объект.</param>
|
||||
/// <param name="tokenPath">Путь к токену в JSON.</param>
|
||||
/// <returns>Результирующий словарь.</returns>
|
||||
public static Dictionary<string, string> JsonToDictionary(JObject json, string tokenPath)
|
||||
{
|
||||
return json.SelectToken(tokenPath)?.ToObject<Dictionary<string, string>>() ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Проверка уникальности:
|
||||
|
||||
SELECT
|
||||
E.type,
|
||||
E.slug,
|
||||
COUNT(*),
|
||||
string_agg(E.id::text, ', ' ORDER BY E.id),
|
||||
string_agg(EE.external_id::text, ', ' ORDER BY E.id)
|
||||
FROM public.entities AS E
|
||||
INNER JOIN public.entity_externals AS EE ON EE.entity_id = E.id
|
||||
GROUP BY E.type, E.slug
|
||||
HAVING COUNT(*) > 1 AND E.type NOT IN ('rule', 'action')
|
||||
*/
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
internal static class AonprdProcessorInputExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработать строку, содержащую информацию о количестве действий.
|
||||
/// </summary>
|
||||
/// <param name="_"></param>
|
||||
/// <param name="input">Входная строка с данными.</param>
|
||||
/// <returns>Структура, хранящая информацию о количетсве действий.</returns>
|
||||
public static ActionStruct ResolveActionString(this ProcessorInput _, string input)
|
||||
{
|
||||
ActionStruct result = new();
|
||||
|
||||
switch (input)
|
||||
{
|
||||
case "Single Action":
|
||||
result.Actions = result.MaxActions = 1;
|
||||
break;
|
||||
|
||||
case "Two Actions":
|
||||
result.Actions = result.MaxActions = 2;
|
||||
break;
|
||||
|
||||
case "Three Actions":
|
||||
result.Actions = result.MaxActions = 3;
|
||||
break;
|
||||
|
||||
case "Reaction":
|
||||
result.IsReaction = true;
|
||||
break;
|
||||
|
||||
case "Free Action":
|
||||
result.IsFreeAction = false;
|
||||
break;
|
||||
|
||||
case "Single Action to Three Actions":
|
||||
case "Single Action or more Actions":
|
||||
result.Actions = 1;
|
||||
result.MaxActions = 3;
|
||||
break;
|
||||
|
||||
case "Single Action or Two Actions":
|
||||
case "Single Action to Two Actions":
|
||||
result.Actions = 1;
|
||||
result.MaxActions = 2;
|
||||
break;
|
||||
|
||||
case "Two Actions or Three Actions":
|
||||
result.Actions = 2;
|
||||
result.MaxActions = 3;
|
||||
break;
|
||||
|
||||
case "Two Actions to 2 rounds":
|
||||
result.Actions = 2;
|
||||
result.MaxActions = 3;
|
||||
result.TwoRounds = true;
|
||||
break;
|
||||
|
||||
case "Reaction or One Action":
|
||||
result.IsReaction = true;
|
||||
result.Actions = result.MaxActions = 1;
|
||||
break;
|
||||
|
||||
case "Reaction or Two Actions":
|
||||
result.IsReaction = true;
|
||||
result.Actions = result.MaxActions = 2;
|
||||
break;
|
||||
|
||||
case "Free Action or One Action":
|
||||
result.IsFreeAction = false;
|
||||
result.Actions = result.MaxActions = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
result.IsPassive = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс структуры, хранящей информацию о количетсве действий.
|
||||
/// </summary>
|
||||
public class ActionStruct
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
|
||||
public class SourceCategorySolver : EnumSolver<SourceCategory>
|
||||
{
|
||||
public override Dictionary<string, SourceCategory> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Rulebooks", SourceCategory.Rulebooks },
|
||||
{ "Adventure Paths", SourceCategory.AdventurePaths },
|
||||
{ "Lost Omens", SourceCategory.LostOmens },
|
||||
{ "Adventures", SourceCategory.Adventures },
|
||||
{ "Comics", SourceCategory.Comics },
|
||||
{ "Blog Posts", SourceCategory.BlogPosts },
|
||||
{ "Society", SourceCategory.Society },
|
||||
};
|
||||
}
|
||||
|
||||
public class VisionEnumSolver : EnumSolver<VisionEnum>
|
||||
{
|
||||
public override Dictionary<string, VisionEnum> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Low-Light VisionEnum", VisionEnum.LowLightVision },
|
||||
{ "Darkvision", VisionEnum.DarkVision },
|
||||
};
|
||||
}
|
||||
|
||||
public class AttributeValuesSolver : EnumSolver<AttributeValues>
|
||||
{
|
||||
public override Dictionary<string, AttributeValues> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Consitution", AttributeValues.Constitution },
|
||||
{ "Other", AttributeValues.Free },
|
||||
{ "other", AttributeValues.Free },
|
||||
};
|
||||
}
|
||||
|
||||
public class ActionEnumSolver : EnumSolver<ActionEnum>
|
||||
{
|
||||
public override Dictionary<string, ActionEnum> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Single Action", ActionEnum.SingleAction },
|
||||
{ "Two Actions", ActionEnum.TwoActions },
|
||||
{ "Three Actions", ActionEnum.ThreeActions },
|
||||
{ "Reaction", ActionEnum.Reaction },
|
||||
{ "Free Action", ActionEnum.FreeAction },
|
||||
{ "Single Action to Three Actions", ActionEnum.SingleActionToThreeActions },
|
||||
{ "Single Action or more Actions", ActionEnum.SingleActionToThreeActions },
|
||||
{ "Single Action or Two Actions", ActionEnum.SingleActionToTwoActions },
|
||||
{ "Single Action to Two Actions", ActionEnum.SingleActionToTwoActions },
|
||||
{ "Two Actions or Three Actions", ActionEnum.TwoActionsToThreeActions },
|
||||
{ "Two Actions to 2 rounds", ActionEnum.TwoActionsToTwoRounds },
|
||||
{ "Reaction or One Action", ActionEnum.ReactionOrOneAction },
|
||||
{ "Reaction or Two Actions", ActionEnum.ReactionOrTwoAction },
|
||||
{ "Reaction or Three Actions", ActionEnum.ReactionOrThreeAction },
|
||||
{ "Free Action or One Action", ActionEnum.FreeActionOrOneAction },
|
||||
{ "1 Minute", ActionEnum.OneMinute },
|
||||
{ "1 minute", ActionEnum.OneMinute },
|
||||
{ "5 minutes", ActionEnum.FiveMinutes },
|
||||
{ "10 minutes", ActionEnum.TenMinutes },
|
||||
{ "30 minutes", ActionEnum.ThirtyMinutes },
|
||||
{ "1 hour", ActionEnum.OneHour },
|
||||
{ "<i>Nethys Note: No cast time was printed</i>", ActionEnum.Unknown },
|
||||
};
|
||||
}
|
||||
|
||||
public static void RegisterServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddTransient<IEnumSolver, SourceCategorySolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, VisionEnumSolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, AttributeValuesSolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, ActionEnumSolver>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс коллекции книг, содержащих правила.
|
||||
/// Реализует шаблон <c>Singleton</c>.
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
RuleTocBook? result = Books.FirstOrDefault(i => i.SourceId == source.Id);
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = new(source);
|
||||
Books.Add(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск книги по имени.
|
||||
/// </summary>
|
||||
public RuleTocBook? FindName(string name)
|
||||
{
|
||||
foreach (RuleTocBook book in Books)
|
||||
{
|
||||
if (book.IsNameExists(name))
|
||||
{
|
||||
return book;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск правила по идентификатору.
|
||||
/// </summary>
|
||||
public RuleTocItem? FindEntityId(int id)
|
||||
{
|
||||
foreach (RuleTocBook book in Books)
|
||||
{
|
||||
RuleTocItem? result = book.GetByEntityId(id);
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает строковое представление коллекции в файле YAML.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join("\n", Books.Select(book => book.ToString()).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сохраняет коллекцию в файле YAML.
|
||||
/// </summary>
|
||||
public void SaveFile()
|
||||
{
|
||||
string fileName =
|
||||
$"rules-toc.{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()}.debug.yaml";
|
||||
|
||||
using FileStream fileStream = File.Create(fileName);
|
||||
using StreamWriter writer = new(fileStream);
|
||||
|
||||
writer.Write(ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Экземпляр класса <see cref="RuleTocCollection"/>.
|
||||
/// </summary>
|
||||
public static RuleTocCollection Instance { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс представляет собой книгу, содержащую правила.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
while (true)
|
||||
{
|
||||
List<RuleTocItem> children = node?.Children ?? Children;
|
||||
|
||||
if (breadcrumbs.Length == 0)
|
||||
{
|
||||
return node!;
|
||||
}
|
||||
|
||||
string root = breadcrumbs[0];
|
||||
RuleTocItem? item = children.FirstOrDefault(i => i.Name == root);
|
||||
|
||||
if (item is null)
|
||||
{
|
||||
item = new(root, node);
|
||||
children.Add(item);
|
||||
}
|
||||
|
||||
breadcrumbs = breadcrumbs[1..];
|
||||
node = item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, существует ли имя в текущей книге или дочерних элементах.
|
||||
/// </summary>
|
||||
/// <param name="name">Имя для проверки.</param>
|
||||
/// <param name="node">Начальный элемент для поиска.</param>
|
||||
/// <returns>true если имя.</returns>
|
||||
public bool IsNameExists(string name, RuleTocItem? node = null)
|
||||
{
|
||||
foreach (RuleTocItem item in node?.Children ?? Children)
|
||||
{
|
||||
if (item.Name == name || IsNameExists(name, item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Находит элемент по идентификатору сущности.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <param name="node">Начальный узел для поиска.</param>
|
||||
/// <returns><see cref="RuleTocItem"/> или <c>null</c> если элемент не найден.</returns>
|
||||
public RuleTocItem? GetByEntityId(int id, RuleTocItem? node = null)
|
||||
{
|
||||
if (node?.RuleId == id)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
foreach (RuleTocItem item in node?.Children ?? Children)
|
||||
{
|
||||
RuleTocItem? result = GetByEntityId(id, item);
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает строковое представление книги в файле YAML.
|
||||
/// </summary>
|
||||
/// <returns>Строковое представление книги.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"\"{SourceName}\":\n"
|
||||
+ string.Join(
|
||||
"\n",
|
||||
Children.Select(child => RuleTocItem.Indent(child.ToString())).ToList()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс представляет собой элемент дерева правил.
|
||||
/// </summary>
|
||||
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 =
|
||||
[
|
||||
$"{Name}:",
|
||||
$"{Tab}aonprd: {Id ?? "null"}",
|
||||
$"{Tab}entity: {RuleId?.ToString() ?? "null"}",
|
||||
];
|
||||
|
||||
if (Children.Count == 0)
|
||||
{
|
||||
// rows.Add($"{TAB}children: None");
|
||||
return string.Join("\n", rows);
|
||||
}
|
||||
|
||||
rows.Add($"{Tab}children:");
|
||||
|
||||
foreach (RuleTocItem child in Children)
|
||||
{
|
||||
rows.Add(child.Indent(2));
|
||||
}
|
||||
|
||||
return string.Join("\n", rows);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Увеличение отступа строкового представления элемента содержания в файле YAML.
|
||||
/// </summary>
|
||||
/// <param name="amount">Количество отступов.</param>
|
||||
/// <returns>Строковое представление элемента содержания с отступом.</returns>
|
||||
/// <seealso cref="Tab"/>
|
||||
public string Indent(int amount = 1)
|
||||
{
|
||||
string result = ToString();
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
result = Indent(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Увеличение отступа строки.
|
||||
/// </summary>
|
||||
/// <param name="str">Входная строка.</param>
|
||||
/// <returns>Строка с отступом.</returns>
|
||||
/// <seealso cref="Tab"/>
|
||||
public static string Indent(string str)
|
||||
{
|
||||
return Tab + str.Replace("\n", $"\n{Tab}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Строка отступа строки.
|
||||
/// </summary>
|
||||
/// <seealso cref="Indent(int)"/>
|
||||
/// <seealso cref="Indent(string)"/>
|
||||
public const string Tab = " ";
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс-помощник, для обработки строкового ввода с Архивов Нефиса.
|
||||
/// Реализует шаблон <c>Singleton</c>.
|
||||
/// </summary>
|
||||
internal partial class StringHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="StringHelper"/>
|
||||
/// </summary>
|
||||
public StringHelper() { }
|
||||
|
||||
/// <summary>
|
||||
/// Разбиение строки по нестабильным строковым разделителям (например, <c>", "</c>, <c>" or "</c> и т.д.).
|
||||
/// </summary>
|
||||
/// <param name="value">Исходная строка.</param>
|
||||
/// <returns>Коллекция строк.</returns>
|
||||
public List<string> RowSplit(string value)
|
||||
{
|
||||
return value
|
||||
.Replace(", or", ",")
|
||||
.Replace(" or ", ", ")
|
||||
.Replace(";", ",")
|
||||
.Split(", ")
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Разбор списка ссылок в исходной строке.
|
||||
/// </summary>
|
||||
/// <param name="input">Исходная строка.</param>
|
||||
/// <returns>Структура, содеражащая информацию разбиении строки по ссылкам.</returns>
|
||||
public MarkdownLinkSplit ParseMarkdownLinks(string input)
|
||||
{
|
||||
MarkdownLinkSplit result = new();
|
||||
|
||||
MatchCollection matches = LinkRegex().Matches(input);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
result.Links.Add(
|
||||
new()
|
||||
{
|
||||
Prefix = match.Groups[1].Value.Trim(),
|
||||
Text = match.Groups[2].Value.Trim(),
|
||||
Url = match.Groups[3].Value.Trim(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
result.Suffix = LinkRegex().Replace(input, "");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Извлечение суффикса из описания источника.
|
||||
/// </summary>
|
||||
/// <param name="input">Исходная строка.</param>
|
||||
/// <returns>Суффикс.</returns>
|
||||
public string ResolveSourceSuffix(string input)
|
||||
{
|
||||
Match match = SourceSuffixRegex().Match(input);
|
||||
return match.Captures.Count > 0 ? match.Groups[1].Value : "";
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
/// <summary>
|
||||
/// Список содержащихся ссылок.
|
||||
/// </summary>
|
||||
public List<MarkdownLink> Links { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Завершающая часть ссылки.
|
||||
/// </summary>
|
||||
public string Suffix { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Информация о ссылке в строке.
|
||||
/// </summary>
|
||||
internal class MarkdownLink
|
||||
{
|
||||
/// <summary>
|
||||
/// Данные, содержащиеся до ссылки.
|
||||
/// </summary>
|
||||
public string Prefix { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Текст ссылки.
|
||||
/// </summary>
|
||||
public string Text { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// URL ссылки.
|
||||
/// </summary>
|
||||
public string Url { get; init; } = "";
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик сущностей, полученных из Архивов Нефиса.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="ProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">Объект обработки вводимого JSON.</param>
|
||||
/// <param name="stringHelper">[DI] Помощник обработки строк.</param>
|
||||
/// <param name="helper">[DI] Помощник работы с БД.</param>
|
||||
/// <param name="globals">[DI] Глобальные параметра обработки Архивов Нефиса.</param>
|
||||
/// <seealso cref="ProcessorAttribute"/>
|
||||
internal abstract class AbstractEntityProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractInputProcessor(input)
|
||||
{
|
||||
/// <inheritdoc cref="Pf2eDbContext"/>
|
||||
public Pf2eDbContext Context { get; } = new();
|
||||
|
||||
/// <inheritdoc cref="StringHelper"/>
|
||||
public Helpers_StringHelper StringHelper { get; } = stringHelper;
|
||||
|
||||
/// <inheritdoc cref="DatabaseHelper"/>
|
||||
public DatabaseHelper Helper { get; } = helper;
|
||||
|
||||
/// <inheritdoc cref="AonprdBuilderGlobals"/>
|
||||
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||
|
||||
/// <summary>
|
||||
/// Корневая сущность.
|
||||
/// </summary>
|
||||
public Entity Entity { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности.
|
||||
/// </summary>
|
||||
public abstract EntityType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Основная сущность.
|
||||
/// </summary>
|
||||
public abstract IEntity Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Название сущности, которое будет использоваться для обработки данных.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _name ??= GetEntityName(); }
|
||||
}
|
||||
private string? _name = null;
|
||||
|
||||
/// <summary>
|
||||
/// Добавление основной сущности в контекст базы данных.
|
||||
/// </summary>
|
||||
protected abstract void Add();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
Check();
|
||||
|
||||
AddEntity();
|
||||
AddSource();
|
||||
AddExternal();
|
||||
AddTraits();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save()
|
||||
{
|
||||
Add();
|
||||
Context.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверки необходимости импорта сущности.
|
||||
/// </summary>
|
||||
public virtual void Check()
|
||||
{
|
||||
string id = Input.GetRequired<string>("id");
|
||||
|
||||
if (Globals.Ignore.Contains(id))
|
||||
{
|
||||
throw new SkipItemException(Name, "игнорируется.");
|
||||
}
|
||||
|
||||
if (Globals.MergeSources.ContainsKey(id))
|
||||
{
|
||||
throw new SkipItemException(Name, "помечено на слияние.");
|
||||
}
|
||||
}
|
||||
|
||||
#region "Генерация описания"
|
||||
|
||||
/// <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<object?>(path)?.ToString() ?? ""}";
|
||||
|
||||
/// <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());
|
||||
|
||||
/// <summary>
|
||||
/// Генерация JSON-строки из объектов, располагаюхися по путям, указанным в <paramref name="paths"/>.
|
||||
/// </summary>
|
||||
/// <param name="paths">Коллекция путей, из которых необходимо достатить объекты.</param>
|
||||
/// <returns>JSON-строка.</returns>
|
||||
protected string GenerateJsonString(params ICollection<string> paths)
|
||||
{
|
||||
Dictionary<string, object?> data = [];
|
||||
|
||||
foreach (string key in paths)
|
||||
{
|
||||
data.Add(key, Input.Input.SelectToken(key));
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(data, new JsonSerializerSettings());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Генерация сущности"
|
||||
|
||||
/// <summary>
|
||||
/// Получает имя сущности из входных данных.
|
||||
/// </summary>
|
||||
/// <returns>Имя сущности.</returns>
|
||||
protected virtual string GetEntityName() => Input.GetRequired<string>("name").Replace("’", "'");
|
||||
|
||||
/// <summary>
|
||||
/// Генерация основных полей корневой сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddEntity()
|
||||
{
|
||||
lock (Helper.IdLock)
|
||||
{
|
||||
Entity.Id = Helper.NextEntityId;
|
||||
}
|
||||
|
||||
Entity.Type = Type;
|
||||
Entity.Name[LocaleType.English] = Name;
|
||||
Entity.Img = "";
|
||||
Entity.Description = GetDescription();
|
||||
Entity.Rarity = Input.EnumOptional("rarity", EntityRarity.Common);
|
||||
Entity.Slug =
|
||||
Helper.GenerateSlug(Entity.Name[LocaleType.English])
|
||||
+ Globals.GetSlugSuffix(Input.GetRequired<string>("id"));
|
||||
Entity.IsTranslated = false;
|
||||
Entity.IsChecked = false;
|
||||
|
||||
Object.Entity = Entity;
|
||||
Object.Id = Entity.Id;
|
||||
|
||||
Context.Entities.Add(Entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерация источников сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddSource()
|
||||
{
|
||||
string[] sourceNames = Input
|
||||
.GetRequired<JArray>("source")
|
||||
.Select(token => token.ToString())
|
||||
.ToArray();
|
||||
|
||||
if (sourceNames.Length == 0)
|
||||
{
|
||||
throw new(
|
||||
$"Источники для сущности \"{Entity.Name[LocaleType.English]}\" "
|
||||
+ $"(Aonprd id \"{Input.GetRequired<string>("id")}\") не найдены."
|
||||
);
|
||||
}
|
||||
|
||||
foreach (string sourceName in sourceNames)
|
||||
{
|
||||
EntitySource entitySource = new()
|
||||
{
|
||||
Entity = Entity,
|
||||
SourceId = Globals.SourceIds[sourceName.Trim()],
|
||||
Suffix = StringHelper.ResolveSourceSuffix(
|
||||
Input.GetRequired<string>("source_markdown")
|
||||
),
|
||||
IsRemaster = Input.GetOptional<JArray>("legacy_id") is not null,
|
||||
};
|
||||
|
||||
Entity.Sources.Add(entitySource);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерация внешних ссылок сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddExternal()
|
||||
{
|
||||
EntityExternal external = new()
|
||||
{
|
||||
Entity = Entity,
|
||||
TargetId = Globals.AonprdTarget.Id,
|
||||
ExternalId = Input.GetRequired<string>("id"),
|
||||
Suffix = Input.GetRequired<string>("url"),
|
||||
Md5 = Input.Md5(),
|
||||
};
|
||||
|
||||
Entity.Externals.Add(external);
|
||||
Context.EntityExternals.Add(external);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерация списка признаков сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddTraits()
|
||||
{
|
||||
JToken? traitsJson = Input.Input.SelectToken("trait");
|
||||
|
||||
if (traitsJson?.Type != JTokenType.Array)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> names = traitsJson.ToArray().Select(jToken => jToken.ToString()).ToList();
|
||||
|
||||
List<int> traitIds = names.Select(name => Globals.TraitIds[name]).ToList();
|
||||
|
||||
List<Trait> traits = Context.Traits.Where(t => traitIds.Contains(t.Id)).ToList();
|
||||
|
||||
foreach (Trait trait in traits)
|
||||
{
|
||||
Entity.Traits.Add(trait);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Common;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик сущностей подклассов, полученных из Архивов Нефиса.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="ProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
internal abstract class AbstractSubclassProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <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/>
|
||||
protected 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()
|
||||
{
|
||||
return Context.SubclassGroups.First(g =>
|
||||
Pf2eDbContext.LocaleSearch(g.Entity.Name, GroupName)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавление заклинаний из строкового представления.
|
||||
/// </summary>
|
||||
/// <param name="spellMd">Строковое представление списка заклинаний.</param>
|
||||
public void AddSpells(string spellMd)
|
||||
{
|
||||
List<string> spellUrls = StringHelper
|
||||
.ParseMarkdownLinks(spellMd)
|
||||
.Links.Select(l => l.Url)
|
||||
.ToList();
|
||||
|
||||
List<int> spellIds = Context
|
||||
.EntityExternals.Where(x => spellUrls.Contains(x.Suffix))
|
||||
.Select(x => x.Entity.Id)
|
||||
.ToList();
|
||||
|
||||
List<Entity> spells = Context
|
||||
.Spells.Where(s => spellIds.Contains(s.Id))
|
||||
.Select(s => s.Entity)
|
||||
.ToList();
|
||||
|
||||
foreach (Entity spellEntity in spells)
|
||||
{
|
||||
Object.Entities.Add(spellEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавление тренировок из строкового представления.
|
||||
/// </summary>
|
||||
/// <param name="trainingMd">Строковое представление списка тренировок.</param>
|
||||
public void AddTrainings(string trainingMd)
|
||||
{
|
||||
List<string> trainingNames = trainingMd.Split(", ").ToList();
|
||||
List<Training> trainings = Context
|
||||
.Trainings.Where(t => Pf2eDbContext.LocaleSearchArray(t.Name, trainingNames))
|
||||
.ToList();
|
||||
|
||||
foreach (string name in trainingNames)
|
||||
{
|
||||
Training? training = trainings.FirstOrDefault(t => t.Name.Contains(name));
|
||||
training ??= new()
|
||||
{
|
||||
Name = new() { { LocaleType.English, name } },
|
||||
Type = TrainingType.Lore,
|
||||
};
|
||||
|
||||
Object.Trainings.Add(
|
||||
new()
|
||||
{
|
||||
Subclass = Object,
|
||||
Training = training,
|
||||
Rank = TrainingRank.Trained,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Finalization;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:legacy-remaster",
|
||||
InnerType = null,
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Связь устаревших и обновлённых сущностей",
|
||||
Group = ProcessorGroupEnum.AonprdFinalization,
|
||||
Parallel = true
|
||||
)]
|
||||
internal class LegacyRemasterProcessors(ProcessorInput input) : AbstractInputProcessor(input)
|
||||
{
|
||||
private Pf2eDbContext Context { get; } = new();
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
string aonprdId = Input.GetRequired<string>("id");
|
||||
EntityExternal entity =
|
||||
Context.EntityExternals.AsNoTracking().FirstOrDefault(x => x.ExternalId == aonprdId)
|
||||
?? throw new SilentSkipItemException();
|
||||
int entityId = entity.EntityId;
|
||||
Entity legacyEntity =
|
||||
Context.Entities.FirstOrDefault(e => e.Id == entityId)
|
||||
?? throw new($"Неизвестная сущность с ID {entityId}");
|
||||
|
||||
legacyEntity.IsLegacy = true;
|
||||
|
||||
foreach (int remasterId in GetEntityIds("remaster_id"))
|
||||
{
|
||||
Context.LegacyRemasters.Add(new() { LegacyId = entityId, RemasterId = remasterId });
|
||||
Entity remasterEntity =
|
||||
Context.Entities.AsNoTracking().FirstOrDefault(e => e.Id == remasterId)
|
||||
?? throw new($"Неизвестная сущность с ID {remasterId}");
|
||||
|
||||
if (legacyEntity.Slug == remasterEntity.Slug)
|
||||
{
|
||||
legacyEntity.Slug += "-legacy";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<int> GetEntityIds(string path)
|
||||
{
|
||||
List<string> aonprdIds = (Input.GetOptional<JArray>(path) ?? [])
|
||||
.Select(token => token.ToString())
|
||||
.ToList();
|
||||
|
||||
return Context
|
||||
.EntityExternals.AsNoTracking()
|
||||
.Where(x => aonprdIds.Contains(x.ExternalId!))
|
||||
.Select(x => x.EntityId)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
Context.SaveChanges();
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Scrapper.Processors.Factory;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Finalization;
|
||||
|
||||
[ProcessorFactory(
|
||||
"aonprd:fix-legacy-remaster",
|
||||
ProcessorType = typeof(MissingLegacyRemasterProcessor),
|
||||
Description = "Дополнение связяй устаревших и обновлённых сущностей",
|
||||
Group = ProcessorGroupEnum.AonprdFinalization,
|
||||
Parallel = true
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:legacy-remaster"])]
|
||||
internal class MissingLegacyRemasterProcessorFactory(
|
||||
IServiceProvider serviceProvider,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractProcessorFactory
|
||||
{
|
||||
public IServiceProvider ServiceProvider { get; } = serviceProvider;
|
||||
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||
|
||||
public override int Count() => Globals.LegacyRemasterExtra.Count;
|
||||
|
||||
public override IEnumerable<Tuple<string, IProcessor>> GetEnumerator()
|
||||
{
|
||||
foreach ((string key, string value) in Globals.LegacyRemasterExtra)
|
||||
{
|
||||
yield return new(
|
||||
$"{key} -> {value}",
|
||||
ActivatorUtilities.CreateInstance<MissingLegacyRemasterProcessor>(
|
||||
ServiceProvider,
|
||||
key,
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class MissingLegacyRemasterProcessor(string legacyId, string remasterId) : IProcessor
|
||||
{
|
||||
public Pf2eDbContext Context { get; } = new();
|
||||
public string LegacyId { get; } = legacyId;
|
||||
public string RemasterId { get; } = remasterId;
|
||||
|
||||
private Entity GetEntity(string aonprdId)
|
||||
{
|
||||
EntityExternal entity =
|
||||
Context.EntityExternals.AsNoTracking().FirstOrDefault(x => x.ExternalId == aonprdId)
|
||||
?? throw new SilentSkipItemException();
|
||||
|
||||
int entityId = entity.EntityId;
|
||||
|
||||
return Context.Entities.FirstOrDefault(e => e.Id == entityId)
|
||||
?? throw new($"Неизвестная сущность с ID {entityId}");
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
Entity legacyEntity = GetEntity(LegacyId);
|
||||
Entity remasterEntity = GetEntity(RemasterId);
|
||||
|
||||
TestExist(legacyEntity, remasterEntity);
|
||||
|
||||
Context.LegacyRemasters.Add(
|
||||
new() { LegacyId = legacyEntity.Id, RemasterId = remasterEntity.Id }
|
||||
);
|
||||
|
||||
legacyEntity.IsLegacy = true;
|
||||
|
||||
if (legacyEntity.Slug == remasterEntity.Slug)
|
||||
{
|
||||
legacyEntity.Slug += "-legacy";
|
||||
}
|
||||
}
|
||||
|
||||
public void TestExist(Entity legacyEntity, Entity remasterEntity)
|
||||
{
|
||||
if (
|
||||
Context
|
||||
.LegacyRemasters.AsNoTracking()
|
||||
.Any(x => x.LegacyId == legacyEntity.Id && x.RemasterId == remasterEntity.Id)
|
||||
)
|
||||
{
|
||||
throw new SilentSkipItemException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Context.SaveChanges();
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"aonprd:compendium",
|
||||
Description = "Загрузка компендиума",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:repository"])]
|
||||
internal class CompendiumLoadProcessor(
|
||||
AonprdRepository repository,
|
||||
AonprdCompendium compendium,
|
||||
ProcessProgress progress
|
||||
) : IProcessor
|
||||
{
|
||||
public AonprdRepository Repository { get; } = repository;
|
||||
public AonprdCompendium Compendium { get; } = compendium;
|
||||
public ProcessProgress Progress { get; } = progress;
|
||||
|
||||
public void Process()
|
||||
{
|
||||
using StreamReader indexFile = new(
|
||||
Path.Combine(Repository.RepositoryDirPath, "index.json")
|
||||
);
|
||||
JObject indexJson = JObject.Parse(indexFile.ReadToEnd());
|
||||
|
||||
Progress.Local.Reset(indexJson.Count);
|
||||
|
||||
foreach ((string file, JToken? value) in indexJson)
|
||||
{
|
||||
Progress.Local.Start(file);
|
||||
|
||||
JArray items = (JArray)value!;
|
||||
|
||||
foreach (JToken item in items)
|
||||
{
|
||||
AonprdCompendiumItem compendiumItem = new(item.ToString(), file);
|
||||
|
||||
Compendium.Items.Add(compendiumItem);
|
||||
}
|
||||
|
||||
Progress.Local.End();
|
||||
}
|
||||
|
||||
Progress.Local.Complete();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
List<AonprdCompendiumItem> result = Compendium.Items.OrderBy(item => item.Id).ToList();
|
||||
|
||||
Compendium.Items.Clear();
|
||||
foreach (AonprdCompendiumItem item in result)
|
||||
{
|
||||
Compendium.Items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"aonprd:external",
|
||||
Description = "Создание цели внешних ссылок",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:repository"])]
|
||||
internal class ExternalProcessor(AonprdBuilderGlobals globals) : AbstractSimpleProcessor
|
||||
{
|
||||
public Pf2eDbContext Context { get; } = new();
|
||||
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||
|
||||
public ExternalTarget AonprdTarget = null!;
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
AonprdTarget = new()
|
||||
{
|
||||
Name = AonprdImporter.TargetName,
|
||||
Icon = "http://www.google.com/s2/favicons?domain=2e.aonprd.com",
|
||||
Prefix = "https://2e.aonprd.com",
|
||||
};
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
Context.ExternalTargets.Add(AonprdTarget);
|
||||
Context.SaveChanges();
|
||||
|
||||
Globals.AonprdTarget = AonprdTarget;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"aonprd:repository",
|
||||
Description = "Обновление репозитория",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
internal class RepositoryUpdateProcessor(AonprdRepository repository, ProcessProgress progress)
|
||||
: IProcessor
|
||||
{
|
||||
public AonprdRepository Repository { get; } = repository;
|
||||
public ProcessProgress Progress { get; } = progress;
|
||||
|
||||
public void Process()
|
||||
{
|
||||
Repository.Update(Progress);
|
||||
}
|
||||
|
||||
public void Save() { }
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.SchemaBuilder;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"aonprd:schema",
|
||||
Description = "Построение схемы данных",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:repository", "aonprd:compendium"])]
|
||||
internal class SchemaBuildProcessor(
|
||||
AonprdImporter importer,
|
||||
AonprdCompendium compendium,
|
||||
ProcessProgress progress,
|
||||
ApplicationSettings settings
|
||||
) : AbstractSchemaBuildProcessor
|
||||
{
|
||||
/// <inheritdoc cref="AonprdImporter"/>
|
||||
protected AonprdImporter Importer { get; } = importer;
|
||||
|
||||
/// <inheritdoc cref="AonprdCompendium"/>
|
||||
protected override AonprdCompendium Compendium { get; } = compendium;
|
||||
|
||||
/// <inheritdoc cref="ProcessProgress"/>
|
||||
protected override ProcessProgress Progress { get; } = progress;
|
||||
|
||||
/// <inheritdoc cref="ApplicationSettings"/>
|
||||
protected ApplicationSettings Settings { get; } = settings;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string SavePath => Path.Combine(Settings.OutputDirPath, Importer.Slug);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override AonprdTypeResolver Resolver { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Список типов, являющихся подклассами.
|
||||
/// </summary>
|
||||
private readonly string[] _subclassSchemaTypes =
|
||||
[
|
||||
"research-field",
|
||||
"apparition",
|
||||
"practice",
|
||||
"instinct",
|
||||
"muse",
|
||||
"cause",
|
||||
"tenet",
|
||||
"doctrine",
|
||||
"druidic-order",
|
||||
"methodology",
|
||||
"element",
|
||||
"hybrid-study",
|
||||
"mystery",
|
||||
"conscious-mind",
|
||||
"subconscious-mind",
|
||||
"hunters-edge",
|
||||
"racket",
|
||||
"bloodline",
|
||||
"style",
|
||||
"implement",
|
||||
"lesson",
|
||||
"patron",
|
||||
"arcane-school",
|
||||
"arcane-thesis",
|
||||
"way",
|
||||
"innovation",
|
||||
"ikon",
|
||||
"epithet",
|
||||
];
|
||||
|
||||
protected override ICollection<string> GetSchemaNodes(AbstractCompendiumItem item)
|
||||
{
|
||||
List<string> nodes = [item.Type];
|
||||
|
||||
// Нода подкласса
|
||||
if (_subclassSchemaTypes.Contains(item.Type))
|
||||
{
|
||||
nodes.Add("_subclass");
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class AonprdTypeResolver : TypeResolver
|
||||
{
|
||||
//
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Common;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:skill",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
InnerType = "skill",
|
||||
Description = "Навыки",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:compendium"])]
|
||||
internal class SkillProcessor(ProcessorInput input, Pf2eDbContext context)
|
||||
: AbstractInputProcessor(input)
|
||||
{
|
||||
public Pf2eDbContext Context { get; } = context;
|
||||
|
||||
/// <summary>
|
||||
/// Обучаемый объект.
|
||||
/// </summary>
|
||||
public Training Training = null!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save() => Context.Trainings.Add(Training);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
Training = new()
|
||||
{
|
||||
Name = new() { { LocaleType.English, Input.GetRequired<string>("name") } },
|
||||
Type = TrainingType.Skill,
|
||||
};
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:source",
|
||||
InnerType = "source",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Источники",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:compendium"])]
|
||||
internal class SourceProcessor(ProcessorInput input, AonprdBuilderGlobals globals)
|
||||
: AbstractInputProcessor(input)
|
||||
{
|
||||
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||
public Pf2eDbContext Context { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Источник сущности.
|
||||
/// </summary>
|
||||
protected Source Source = null!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
Source = new()
|
||||
{
|
||||
Name = Input.GetRequired<string>("name"),
|
||||
Category = Input.EnumRequired<SourceCategory>("primary_source_category"),
|
||||
ReleaseDate = DateOnly.Parse(Input.GetRequired<string>("release_date")),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save()
|
||||
{
|
||||
Context.Sources.Add(Source);
|
||||
Context.SaveChanges();
|
||||
|
||||
Globals.SourceIds.Add(Source.Name, Source.Id);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:apparition",
|
||||
InnerType = "apparition",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Apparitions",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal partial class ApparitionProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Animist";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Apparition";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
AddSpells(Input.GetRequired<string>("spell_markdown"));
|
||||
AddFocal(Entity.Description);
|
||||
AddTrainings(Input.GetRequired<string>("skill_markdown"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавление заклинания сосуда.
|
||||
/// </summary>
|
||||
/// <param name="description"></param>
|
||||
public void AddFocal(string description)
|
||||
{
|
||||
Match match = VesselSpellMarkdownRegex().Match(description);
|
||||
string aonprdId = $"spell-{match.Groups[1].Value}";
|
||||
|
||||
Entity entity = Context
|
||||
.EntityExternals.Where(x => x.ExternalId == aonprdId)
|
||||
.Select(x => x.Entity)
|
||||
.First();
|
||||
|
||||
Object.Entities.Add(entity);
|
||||
}
|
||||
|
||||
//protected override string GetDescription() =>
|
||||
// base.GetDescription()
|
||||
// + AddedDescription(["skill_markdown", "spell_markdown", "summary_markdown"]);
|
||||
|
||||
[GeneratedRegex(@"\*\*Vessel Spell\*\* \[.*?\]\(/Spells\.aspx\?ID=(\d+)\)")]
|
||||
private static partial Regex VesselSpellMarkdownRegex();
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:arcane-school",
|
||||
InnerType = "arcane-school",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Arcane Schools",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class ArcaneSchoolProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Wizard";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Arcane School";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
AddSpells(Input.GetRequired<string>("spell_markdown"));
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:arcane-thesis",
|
||||
InnerType = "arcane-thesis",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Arcane Theses",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class ArcaneThesisProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Wizard";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Arcane Thesis";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:bloodline",
|
||||
InnerType = "bloodline",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Bloodlines",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class BloodlineProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Sorcerer";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Bloodline";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
AddSpells(Input.GetRequired<string>("spell_markdown"));
|
||||
|
||||
Object.Extra = GenerateJsonString("tradition");
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:cause",
|
||||
InnerType = "cause",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Causes",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class CauseProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Champion";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Cause";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
Object.Extra = GenerateJsonString("alignment");
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:conscious-mind",
|
||||
InnerType = "conscious-mind",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Conscious Minds",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class ConsciousMindProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Psychic";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Conscious Mind";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
AddSpells(Input.GetRequired<string>("spell_markdown"));
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:doctrine",
|
||||
InnerType = "doctrine",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Doctrines",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class DoctrineProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Cleric";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Doctrine";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:druidic-order",
|
||||
InnerType = "druidic-order",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Druidic Orders",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class DruidicOrderProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Druid";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Druidic Order";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:eidolon",
|
||||
InnerType = "eidolon",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Eidolons",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class EidolonProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Summoner";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Eidolon";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
AddTrainings(Input.GetRequired<string>("skill_markdown"));
|
||||
|
||||
Object.Extra = GenerateJsonString(
|
||||
"alignment",
|
||||
"language_markdown",
|
||||
"size",
|
||||
"tradition",
|
||||
"speed"
|
||||
);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:element",
|
||||
InnerType = "element",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Elements",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class ElementProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Kineticist";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Element";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
Object.Extra = GenerateJsonString("element");
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Collection;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Common;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:epithet",
|
||||
InnerType = "epithet",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Epithets",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class EpithetProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Exemplar";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Epithet";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
Object.Extra = GenerateJsonString("level", "type");
|
||||
// No additional processing needed
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
// TODO: Придумать логику для обработки под-архетипа (Hellknight Order)
|
||||
|
||||
//[ProcessorCollection(
|
||||
// "aonprd:hellknight-order",
|
||||
// InnerType = "hellknight-order",
|
||||
// CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
// Description = "Hellknight Orders",
|
||||
// Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
//)]
|
||||
//[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class HellknightOrderProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Hellknight";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Order";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
Object.Extra = GenerateJsonString("favored_weapon_markdown");
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:hunters-edge",
|
||||
InnerType = "hunters-edge",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Hunter's Edges",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class HuntersEdgeProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Ranger";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Hunter's Edge";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:hybrid-study",
|
||||
InnerType = "hybrid-study",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Hybrid Studies",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class HybridStudyProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Magus";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Hybrid Study";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:ikon",
|
||||
InnerType = "ikon",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Ikon",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class IkonProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Exemplar";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Ikon";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
Object.Extra = GenerateJsonString("type", "usage_markdown");
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:implement",
|
||||
InnerType = "implement",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Implements",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class ImplementProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Thaumaturge";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Implement";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:innovation",
|
||||
InnerType = "innovation",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Innovations",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class InnovationProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Inventor";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Innovation";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:instinct",
|
||||
InnerType = "instinct",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Instincts",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class InstinctProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Barbarian";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Instinct";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:lesson",
|
||||
InnerType = "lesson",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Lessons",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class LessonProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Witch";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Lesson";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
Object.Extra = GenerateJsonString("lesson_type");
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:methodology",
|
||||
InnerType = "methodology",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Methodologies",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class MethodologyProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Investigator";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Methodology";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:muse",
|
||||
InnerType = "muse",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Muses",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class MuseProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Bard";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Muse";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:mystery",
|
||||
InnerType = "mystery",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Mysteries",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class MysteryProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Oracle";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Mystery";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
AddSpells(Input.GetRequired<string>("spell_markdown"));
|
||||
|
||||
// TODO: Добавть проверку на упоминание Lore в skill_markdown
|
||||
// AddTrainings(Input.GetRequired<string>("skill_markdown"));
|
||||
|
||||
Object.Extra = GenerateJsonString("domain", "skill_markdown");
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Collection;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Common;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:patron",
|
||||
InnerType = "patron",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Patrons",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class PatronProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Witch";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Patron";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
AddTrainings(Input.GetRequired<string>("skill_markdown"));
|
||||
|
||||
Object.Extra = GenerateJsonString("tradition", "skill_markdown");
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:practice",
|
||||
InnerType = "practice",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Practices",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class PracticeProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Animist";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Practice";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:racket",
|
||||
InnerType = "racket",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Rackets",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class RacketProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Rogue";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Racket";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:research-field",
|
||||
InnerType = "research-field",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Research Fields",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class ResearchFieldProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Alchemist";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Research Field";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:style",
|
||||
InnerType = "style",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Styles",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class StyleProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Swashbuckler";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Style";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Scrapper.Processors.Factory;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorFactory(
|
||||
"aonprd:subclass-group",
|
||||
ProcessorType = typeof(SubclassGroupProcessor),
|
||||
Description = "Группы подклассов",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses,
|
||||
Parallel = true
|
||||
)]
|
||||
[ProcessorDependencies(
|
||||
["aonprd:trait", "aonprd:source", "aonprd:class", "aonprd:spell", "aonprd:skill"]
|
||||
)]
|
||||
internal class SubclassGroupProcessorFactory(
|
||||
IServiceProvider serviceProvider,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractProcessorFactory
|
||||
{
|
||||
public IServiceProvider ServiceProvider { get; } = serviceProvider;
|
||||
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||
|
||||
public override int Count() => Globals.SubclassGroups.Count;
|
||||
|
||||
public override IEnumerable<Tuple<string, IProcessor>> GetEnumerator()
|
||||
{
|
||||
foreach ((string key, string value) in Globals.SubclassGroups)
|
||||
{
|
||||
var processor =
|
||||
value == "_null"
|
||||
? ActivatorUtilities.CreateInstance<SubclassGroupProcessor>(
|
||||
ServiceProvider,
|
||||
key
|
||||
)
|
||||
: ActivatorUtilities.CreateInstance<SubclassGroupProcessor>(
|
||||
ServiceProvider,
|
||||
key,
|
||||
value
|
||||
);
|
||||
|
||||
yield return new($"{key} -> {value}", processor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class SubclassGroupProcessor(string groupName, string? className, DatabaseHelper helper)
|
||||
: IProcessor
|
||||
{
|
||||
public string GroupName { get; } = groupName;
|
||||
public string? ClassName { get; } = className;
|
||||
public DatabaseHelper Helper { get; } = helper;
|
||||
|
||||
public SubclassGroupProcessor(string groupName, DatabaseHelper helper)
|
||||
: this(groupName, null, helper) { }
|
||||
|
||||
public Pf2eDbContext Context { get; } = new();
|
||||
|
||||
public void Process()
|
||||
{
|
||||
Class? cl = ClassName is not null
|
||||
? Context.Classes.FirstOrDefault(c =>
|
||||
Pf2eDbContext.LocaleSearch(c.Entity.Name, ClassName)
|
||||
)
|
||||
: null;
|
||||
|
||||
int id;
|
||||
lock (Helper.IdLock)
|
||||
{
|
||||
id = Helper.NextEntityId;
|
||||
}
|
||||
|
||||
Entity groupEntity = new()
|
||||
{
|
||||
Id = id,
|
||||
Type = EntityType.SubclassGroup,
|
||||
Img = "",
|
||||
Description = "",
|
||||
Rarity = EntityRarity.Common,
|
||||
IsTranslated = false,
|
||||
IsChecked = false,
|
||||
Name = { [LocaleType.English] = GroupName },
|
||||
Slug = Helper.GenerateSlug(GroupName),
|
||||
};
|
||||
|
||||
Context.Entities.Add(groupEntity);
|
||||
|
||||
SubclassGroup group = new() { Entity = groupEntity, Class = cl };
|
||||
Context.SubclassGroups.Add(group);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Context.SaveChanges();
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:subconscious-mind",
|
||||
InnerType = "subconscious-mind",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Subconscious Minds",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class SubconsciousMindProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Psychic";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Subconscious Mind";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject()
|
||||
{
|
||||
Object.Extra = GenerateJsonString("attribute");
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:tenet",
|
||||
InnerType = "tenet",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Tenets",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class TenetProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Champion";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Tenet";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:way",
|
||||
InnerType = "way",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Ways",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:subclass-group"])]
|
||||
internal class WayProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractSubclassProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Subclass Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ClassName => "Gunslinger";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GroupName => "Way";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ProcessObject() { }
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:action",
|
||||
InnerType = "action",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Действия",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
|
||||
internal partial class ActionProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Action;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Database.Model.Structure.Action Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Actions.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
Check();
|
||||
|
||||
base.Process();
|
||||
|
||||
Object.Actions = Input.EnumOptional("actions", ActionEnum.Passive);
|
||||
Object.Extra = GenerateJsonString("school");
|
||||
}
|
||||
|
||||
public override void Check()
|
||||
{
|
||||
base.Check();
|
||||
|
||||
if (Input.GetRequired<string>("markdown").StartsWith("**Activate"))
|
||||
{
|
||||
throw new SkipItemException(Input.GetRequired<string>("name"), "activating action");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
[
|
||||
"cost_markdown",
|
||||
"trait_markdown",
|
||||
"source_markdown",
|
||||
"search_markdown",
|
||||
"requirement_markdown",
|
||||
"trigger_markdown",
|
||||
"summary_markdown",
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Collection;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:ancestry",
|
||||
InnerType = "ancestry",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Родословные",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:language"])]
|
||||
internal class AncestryProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Ancestry;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Ancestry Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Ancestries.Add(Object);
|
||||
|
||||
public override void Check()
|
||||
{
|
||||
base.Check();
|
||||
|
||||
if (Input.GetRequired<string>("type") != "Ancestry")
|
||||
{
|
||||
throw new SkipItemException(Input.GetRequired<string>("name"), "не родословная");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
Object.Size = Input
|
||||
.GetRequired<JArray>("size")
|
||||
.Select(t => Input.Enum<SizeEnum>(t.ToString()))
|
||||
.ToArray();
|
||||
|
||||
string hp = Input.GetRequired<string>("hp_raw");
|
||||
Object.Hp = Convert.ToInt32((hp != "") ? hp : "0");
|
||||
|
||||
Object.Speed = Convert.ToInt32(Input.GetOptional<int>("speed.land"));
|
||||
|
||||
Object.VisionEnum = Input.EnumOptional("vision", VisionEnum.Normal);
|
||||
|
||||
AddBoosts("attribute", true);
|
||||
AddBoosts("attribute_flaw", false);
|
||||
|
||||
// TODO - import languages from Foundry!
|
||||
// ProcessLanguages();
|
||||
}
|
||||
|
||||
protected void AddBoosts(string path, bool isBoost)
|
||||
{
|
||||
List<string> boosts =
|
||||
Input.GetOptional<JArray>(path)?.ToArray().Select(t => t.ToString()).ToList() ?? [];
|
||||
|
||||
if (boosts.Contains("Two free ability boosts"))
|
||||
{
|
||||
boosts.Remove("Two free ability boosts");
|
||||
boosts.Add("Free");
|
||||
boosts.Add("Free");
|
||||
}
|
||||
|
||||
foreach (string boost in boosts)
|
||||
{
|
||||
Context.AncestryBoosts.Add(
|
||||
new()
|
||||
{
|
||||
Ancestry = Object,
|
||||
Boost = isBoost,
|
||||
Value = Input.Enum<AttributeValues>(boost),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Collection;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:archetype",
|
||||
InnerType = "archetype",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Архетипы",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
|
||||
internal class ArchetypeProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Archetype;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Archetype Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Archetypes.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
Object.Level = Input.GetOptional<int>("level");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
["prerequisite_markdown", "requirement_markdown", "summary_markdown"];
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Collection;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Pf2e.Database.Model.Structure.BackgroundRelation;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:background",
|
||||
InnerType = "background",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Предыстории",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
|
||||
internal class BackgroundProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Background;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Background Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Backgrounds.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
foreach (JToken token in Input.GetOptional<JArray>("attribute") ?? [])
|
||||
{
|
||||
BackgroundBoost boost = new()
|
||||
{
|
||||
Background = Object,
|
||||
Attribute = Input.Enum<AttributeValues>(token.ToString()),
|
||||
};
|
||||
|
||||
Object.BackgroundBoosts.Add(boost);
|
||||
Context.BackgroundBoosts.Add(boost);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
["feat_markdown", "summary_markdown", "skill_markdown", "prerequisite_markdown"];
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Common;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Pf2e.Database.Model.Structure.ClassRelation;
|
||||
using static System.Text.RegularExpressions.Regex;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:class",
|
||||
InnerType = "class",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Классы",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])]
|
||||
internal partial class ClassProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Class;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Class Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Classes.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
Object.Hp = Convert.ToInt32(Input.GetRequired<string>("hp_raw"));
|
||||
|
||||
AttributeValues[] attributes = Input
|
||||
.GetRequired<JArray>("attribute")
|
||||
.Select(token => Input.Enum<AttributeValues>(token.ToString()))
|
||||
.ToArray();
|
||||
|
||||
foreach (AttributeValues attribute in attributes)
|
||||
{
|
||||
ClassKeyAttribute classKeyAttribute = new() { Class = Object, Value = attribute };
|
||||
|
||||
Context.ClassKeyAbilities.Add(classKeyAttribute);
|
||||
Object.ClassKeyAbilities.Add(classKeyAttribute);
|
||||
}
|
||||
|
||||
ClassTraining[] classTrainings =
|
||||
[
|
||||
.. ProcessTraining(
|
||||
Input.GetRequired<JArray>("attack_proficiency"),
|
||||
TrainingType.Weapon
|
||||
),
|
||||
.. ProcessTraining(
|
||||
Input.GetRequired<JArray>("defense_proficiency"),
|
||||
TrainingType.Armor
|
||||
),
|
||||
.. GetSkillTrainings(),
|
||||
.. GetSaveTrainings(),
|
||||
];
|
||||
|
||||
foreach (ClassTraining classTraining in classTrainings)
|
||||
{
|
||||
Context.ClassTrainings.Add(classTraining);
|
||||
Object.ClassTrainings.Add(classTraining);
|
||||
}
|
||||
|
||||
JArray tradition = Input.GetOptional<JArray>("tradition") ?? [];
|
||||
|
||||
switch (tradition.Count)
|
||||
{
|
||||
case 0:
|
||||
Object.Tradition = SpellTradition.None;
|
||||
break;
|
||||
case 1:
|
||||
Object.Tradition = Input.Enum<SpellTradition>(tradition[0].ToString());
|
||||
break;
|
||||
default:
|
||||
Object.Tradition = SpellTradition.All;
|
||||
break;
|
||||
}
|
||||
|
||||
ProcessClassTrait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение коллекции обучений, связанных с классом.
|
||||
/// </summary>
|
||||
/// <returns>Коллекция обучения.</returns>
|
||||
protected ICollection<ClassTraining> GetSkillTrainings()
|
||||
{
|
||||
List<string> skillNames = Input
|
||||
.GetRequired<JArray>("skill_proficiency")
|
||||
.Select(token => token.ToString())
|
||||
.ToList();
|
||||
|
||||
Training[] skills = Context
|
||||
.Trainings.Where(t => Pf2eDbContext.LocaleSearchArray(t.Name, skillNames))
|
||||
.ToArray();
|
||||
|
||||
return skills
|
||||
.Select(skill => new ClassTraining()
|
||||
{
|
||||
Class = Object,
|
||||
Training = skill,
|
||||
Rank = TrainingRank.Trained,
|
||||
})
|
||||
.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*(.*)$";
|
||||
List<ClassTraining> trainings = [];
|
||||
|
||||
foreach (string value in array.Select(token => token.ToString()))
|
||||
{
|
||||
MatchCollection m = Matches(value, pattern);
|
||||
|
||||
if (m.Count == 0)
|
||||
{
|
||||
throw new($"Can't parse training string '{value}'");
|
||||
}
|
||||
|
||||
TrainingRank rank = Input.Enum<TrainingRank>(m[0].Groups[1].Value);
|
||||
|
||||
foreach (string part in GetTrainingString(m[0].Groups[2].Value))
|
||||
{
|
||||
Training? training = Context.Trainings.FirstOrDefault(t =>
|
||||
Pf2eDbContext.LocaleSearch(t.Name, part) && t.Type == type
|
||||
);
|
||||
|
||||
if (training == null)
|
||||
{
|
||||
training = new()
|
||||
{
|
||||
Name = new() { [LocaleType.English] = part },
|
||||
Type = type,
|
||||
};
|
||||
|
||||
Context.Trainings.Add(training);
|
||||
}
|
||||
|
||||
ClassTraining classTraining = new()
|
||||
{
|
||||
Class = Object,
|
||||
Training = training,
|
||||
Rank = rank,
|
||||
};
|
||||
|
||||
trainings.Add(classTraining);
|
||||
}
|
||||
}
|
||||
|
||||
return trainings;
|
||||
}
|
||||
|
||||
/// <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"]
|
||||
: 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
|
||||
.Trainings.Where(t => t.Type == TrainingType.Save)
|
||||
.ToDictionary(t => t.Name[LocaleType.English]);
|
||||
|
||||
foreach (string name in new[] { "Will", "Fortitude", "Reflex", "Perception" })
|
||||
{
|
||||
if (result.ContainsKey(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Training training = new()
|
||||
{
|
||||
Name = new() { [LocaleType.English] = name },
|
||||
Type = (name == "Perception") ? TrainingType.Perception : TrainingType.Save,
|
||||
};
|
||||
|
||||
result.Add(name, training);
|
||||
Context.Trainings.Add(training);
|
||||
}
|
||||
|
||||
return result
|
||||
.Select(pair =>
|
||||
{
|
||||
ClassTraining classTraining = new()
|
||||
{
|
||||
Class = Object,
|
||||
Training = pair.Value,
|
||||
Rank = Input.EnumRequired<TrainingRank>($"{pair.Key.ToLower()}_proficiency"),
|
||||
};
|
||||
|
||||
return classTraining;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Объявление соотеветсвующего классу признака классовым.
|
||||
/// </summary>
|
||||
protected void ProcessClassTrait()
|
||||
{
|
||||
Trait? classTrait = Context.Traits.FirstOrDefault(t => t.Entity.Name == Entity.Name);
|
||||
|
||||
if (classTrait is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
classTrait.Type = TraitType.Class;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение регулярного выражения для разделения строки строкового представления обучений.
|
||||
/// </summary>
|
||||
/// <returns>Регулярное выражение.</returns>
|
||||
[GeneratedRegex(@"\s*(?:,|and)\s*")]
|
||||
protected static partial Regex TrainingStringSeparatorRegex();
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:deity-category",
|
||||
InnerType = "deity-category",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Категории божеств",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
internal class DeityCategoryProcessor(
|
||||
ProcessorInput input,
|
||||
StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.DeityCategory;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override DeityCategory Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.DeityCategories.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
// Обработка описания категории
|
||||
ProcessDescription();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка описания категории.
|
||||
/// </summary>
|
||||
private void ProcessDescription()
|
||||
{
|
||||
// Получаем markdown с описанием из summary_markdown или другого поля
|
||||
// В данных deity-category.yaml нет summary_markdown, используем пустую строку
|
||||
string descriptionMarkdown = string.Empty;
|
||||
|
||||
// Преобразуем markdown в текст (можно добавить более сложную обработку)
|
||||
// Пока просто сохраняем как есть
|
||||
Object.Description[LocaleType.English] = descriptionMarkdown;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Common;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Pf2e.Database.Model.Structure.DeityRelation;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:deity",
|
||||
InnerType = "deity",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Божества",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:domain", "aonprd:spell", "aonprd:trait", "aonprd:source"])]
|
||||
internal partial class DeityProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Deity;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Deity Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Deities.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
// Установка категории божества
|
||||
string categoryName = Input.GetRequired<string>("deity_category");
|
||||
Object.Category = categoryName;
|
||||
|
||||
// Связываем с сущностью категории
|
||||
ProcessCategory(categoryName);
|
||||
|
||||
ProcessFonts();
|
||||
|
||||
// Обработка атрибутов
|
||||
ProcessAttributes();
|
||||
|
||||
// Обработка доменов
|
||||
ProcessDomains();
|
||||
|
||||
// Обработка заклинаний
|
||||
ProcessSpells();
|
||||
|
||||
// Обработка оружия
|
||||
ProcessWeapons();
|
||||
|
||||
// Обработка навыков
|
||||
ProcessSkills();
|
||||
|
||||
// Обработка текстов описания
|
||||
ProcessTexts();
|
||||
|
||||
// Обработка освящения - в Foundry!
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void AddTraits()
|
||||
{
|
||||
// У божеств нет признаков. Не знаю, зачем они на Архивах.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка божественной силы.
|
||||
/// </summary>
|
||||
private void ProcessFonts()
|
||||
{
|
||||
JArray fonts = Input.GetOptional<JArray>("divine_font") ?? [];
|
||||
|
||||
switch (fonts.Count)
|
||||
{
|
||||
case 0:
|
||||
Object.Fonts = DeityFonts.None;
|
||||
return;
|
||||
|
||||
// Если есть и Heal, и Harm
|
||||
case 2:
|
||||
Object.Fonts = DeityFonts.Any;
|
||||
return;
|
||||
|
||||
default:
|
||||
{
|
||||
// Определяем тип по первому элементу
|
||||
string font = fonts[0].ToString();
|
||||
Object.Fonts = Input.Enum<DeityFonts>(font);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка атрибутов божества.
|
||||
/// </summary>
|
||||
private void ProcessAttributes()
|
||||
{
|
||||
JArray attributes = Input.GetOptional<JArray>("attribute") ?? [];
|
||||
foreach (JToken token in attributes)
|
||||
{
|
||||
AttributeValues attribute = Input.Enum<AttributeValues>(token.ToString());
|
||||
Object.DeityAttributes.Add(new() { Deity = Object, Attribute = attribute });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка доменов божества.
|
||||
/// </summary>
|
||||
private void ProcessDomains()
|
||||
{
|
||||
// Обработка основных доменов
|
||||
string primaryDomainsMarkdown = Input.GetRequired<string>("domain_primary_markdown");
|
||||
ProcessDomainMarkdown(primaryDomainsMarkdown, true);
|
||||
|
||||
// Обработка альтернативных доменов
|
||||
string alternateDomainsMarkdown = Input.GetRequired<string>("domain_alternate_markdown");
|
||||
ProcessDomainMarkdown(alternateDomainsMarkdown, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка markdown-строки с доменами.
|
||||
/// </summary>
|
||||
/// <param name="markdown">Markdown-строка с доменами.</param>
|
||||
/// <param name="isPrimary">Флаг, указывающий, являются ли домены основными.</param>
|
||||
private void ProcessDomainMarkdown(string markdown, bool isPrimary)
|
||||
{
|
||||
// Разделяем markdown на отдельные ссылки
|
||||
MatchCollection matches = MarkdownLinkRegex().Matches(markdown);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
string domainName = match.Groups[1].Value;
|
||||
|
||||
// Ищем домен в базе данных
|
||||
Domain? domain = Context.Domains.FirstOrDefault(d =>
|
||||
Pf2eDbContext.LocaleSearch(d.Entity.Name, domainName)
|
||||
);
|
||||
|
||||
if (domain != null)
|
||||
{
|
||||
Object.DeityDomains.Add(
|
||||
new()
|
||||
{
|
||||
Deity = Object,
|
||||
Domain = domain,
|
||||
IsPrimary = isPrimary,
|
||||
Value = domainName,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка заклинаний божества.
|
||||
/// </summary>
|
||||
private void ProcessSpells()
|
||||
{
|
||||
string spellsMarkdown = Input.GetRequired<string>("spell_markdown");
|
||||
MatchCollection matches = MarkdownLinkRegex().Matches(spellsMarkdown);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
string spellName = match.Groups[1].Value;
|
||||
|
||||
// Ищем заклинание в базе данных
|
||||
Spell? spell = Context.Spells.FirstOrDefault(s =>
|
||||
Pf2eDbContext.LocaleSearch(s.Entity.Name, spellName)
|
||||
);
|
||||
|
||||
if (spell != null)
|
||||
{
|
||||
Object.DeitySpells.Add(
|
||||
new()
|
||||
{
|
||||
Deity = Object,
|
||||
Spell = spell,
|
||||
Level = spell.Level,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка оружия божества.
|
||||
/// </summary>
|
||||
private void ProcessWeapons()
|
||||
{
|
||||
string weaponsMarkdown = Input.GetRequired<string>("favored_weapon_markdown");
|
||||
MatchCollection matches = MarkdownLinkRegex().Matches(weaponsMarkdown);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
string weaponName = match.Groups[1].Value;
|
||||
Object.DeityWeapons.Add(new() { Deity = Object, Weapon = weaponName });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка навыков божества.
|
||||
/// </summary>
|
||||
private void ProcessSkills()
|
||||
{
|
||||
string skillsMarkdown = Input.GetRequired<string>("skill_markdown");
|
||||
MatchCollection matches = MarkdownLinkRegex().Matches(skillsMarkdown);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
string skillName = match.Groups[1].Value;
|
||||
|
||||
// Ищем навык в базе данных
|
||||
Training? training = Context.Trainings.FirstOrDefault(t =>
|
||||
Pf2eDbContext.LocaleSearch(t.Name, skillName)
|
||||
);
|
||||
|
||||
if (training != null)
|
||||
{
|
||||
Object.Skills.Add(training);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка категории божества.
|
||||
/// </summary>
|
||||
/// <param name="categoryName">Название категории.</param>
|
||||
private void ProcessCategory(string categoryName)
|
||||
{
|
||||
// Ищем категорию в базе данных
|
||||
DeityCategory? category = Context.DeityCategories.FirstOrDefault(dc =>
|
||||
Pf2eDbContext.LocaleSearch(dc.Entity.Name, categoryName)
|
||||
);
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
Object.CategoryId = category.Id;
|
||||
Object.CategoryEntity = category;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка текстов описания божества.
|
||||
/// </summary>
|
||||
private void ProcessTexts()
|
||||
{
|
||||
Object.Edicts[LocaleType.English] = Input.GetRequired<string>("edict");
|
||||
Object.Anathema[LocaleType.English] = Input.GetRequired<string>("anathema");
|
||||
Object.ReligiousSymbol[LocaleType.English] = Input.GetOptional<string>(
|
||||
"religious_symbol",
|
||||
""
|
||||
);
|
||||
Object.SacredAnimal[LocaleType.English] = Input.GetOptional<string>("sacred_animal", "");
|
||||
Object.AreaOfConcern[LocaleType.English] = Input.GetOptional<string>(
|
||||
"area_of_concern_raw",
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Регулярное выражение для извлечения названия из markdown-ссылки.
|
||||
/// </summary>
|
||||
/// <returns>Скомпилированное регулярное выражение.</returns>
|
||||
[GeneratedRegex(@"\[([^\]]+)\]\([^)]+\)")]
|
||||
private static partial Regex MarkdownLinkRegex();
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:domain",
|
||||
InnerType = "domain",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Домены",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:spell"])]
|
||||
internal partial class DomainProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Domain;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Domain Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Domains.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
// Обработка заклинаний домена
|
||||
ProcessDomainSpells();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка заклинаний, связанных с доменом.
|
||||
/// </summary>
|
||||
private void ProcessDomainSpells()
|
||||
{
|
||||
// Обработка основного заклинания домена
|
||||
string domainSpellMarkdown = Input.GetRequired<string>("domain_spell_markdown");
|
||||
Object.DomainSpellId = GetSpellIdFromMarkdown(domainSpellMarkdown);
|
||||
|
||||
// Обработка продвинутого заклинания домена
|
||||
string advancedDomainSpellMarkdown = Input.GetRequired<string>(
|
||||
"advanced_domain_spell_markdown"
|
||||
);
|
||||
Object.AdvancedDomainSpellId = GetSpellIdFromMarkdown(advancedDomainSpellMarkdown);
|
||||
|
||||
// Обработка апокрифического заклинания (если есть)
|
||||
string? apocryphalSpellMarkdown = Input.GetOptional<string>("apocryphal_spell_markdown");
|
||||
if (!string.IsNullOrEmpty(apocryphalSpellMarkdown))
|
||||
{
|
||||
Object.ApocryphalSpellId = GetSpellIdFromMarkdown(apocryphalSpellMarkdown);
|
||||
}
|
||||
|
||||
// Обработка продвинутого апокрифического заклинания (если есть)
|
||||
string? advancedApocryphalSpellMarkdown = Input.GetOptional<string>(
|
||||
"advanced_apocryphal_spell_markdown"
|
||||
);
|
||||
if (!string.IsNullOrEmpty(advancedApocryphalSpellMarkdown))
|
||||
{
|
||||
Object.AdvancedApocryphalSpellId = GetSpellIdFromMarkdown(
|
||||
advancedApocryphalSpellMarkdown
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение идентификатора заклинания из markdown-строки.
|
||||
/// </summary>
|
||||
/// <param name="markdown">Markdown-строка с информацией о заклинании.</param>
|
||||
/// <returns>Идентификатор заклинания.</returns>
|
||||
private int GetSpellIdFromMarkdown(string markdown)
|
||||
{
|
||||
// Используем регулярное выражение для извлечения названия заклинания
|
||||
// Формат: [Название заклинания](/Spells.aspx?ID=XXX)
|
||||
Match match = MarkdownLinkRegex().Match(markdown);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid markdown format: {markdown}");
|
||||
}
|
||||
|
||||
string spellName = match.Groups[1].Value;
|
||||
|
||||
// Поиск заклинания в базе данных
|
||||
Spell? spell = Context.Spells.FirstOrDefault(s =>
|
||||
Pf2eDbContext.LocaleSearch(s.Entity.Name, spellName)
|
||||
);
|
||||
|
||||
if (spell == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Spell '{spellName}' not found in database");
|
||||
}
|
||||
|
||||
return spell.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Регулярное выражение для извлечения названия из markdown-ссылки.
|
||||
/// </summary>
|
||||
/// <returns>Скомпилированное регулярное выражение.</returns>
|
||||
[GeneratedRegex(@"\[([^\]]+)\]\([^)]+\)")]
|
||||
private static partial Regex MarkdownLinkRegex();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:feat",
|
||||
InnerType = "feat",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Черты",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:archetype"])]
|
||||
internal class FeatProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Feat;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Feat Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Feats.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
Object.Level = Input.GetRequired<int>("level");
|
||||
|
||||
Object.Category = Entity.Traits.Any(t => t.Type == TraitType.Class)
|
||||
? FeatCategory.Class
|
||||
: FeatCategory.Uncategorized;
|
||||
|
||||
Object.Actions = Input.EnumOptional("actions", ActionEnum.Unknown);
|
||||
|
||||
Object.Extra = GenerateJsonString(
|
||||
"archetype",
|
||||
"element",
|
||||
"heighten",
|
||||
"heighten_group",
|
||||
"heighten_level",
|
||||
"school",
|
||||
"frequency"
|
||||
);
|
||||
|
||||
string[] archetypeStrings =
|
||||
Input.GetOptional<JArray>("archetype")?.Select(t => t.ToString()).ToArray() ?? [];
|
||||
|
||||
Archetype[] archetypes = Context
|
||||
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
|
||||
.Archetypes.Where(a => Pf2eDbContext.LocaleSearchArray(a.Entity.Name, archetypeStrings))
|
||||
// .Where(a => archetypeStrings.Contains(a.Entity.Group.ToString()))
|
||||
.ToArray();
|
||||
|
||||
if (archetypes.Length != 0)
|
||||
{
|
||||
Object.Category = FeatCategory.Archetype;
|
||||
}
|
||||
|
||||
foreach (Archetype archetype in archetypes)
|
||||
{
|
||||
Object.Archetypes.Add(archetype);
|
||||
|
||||
if (Entity.Name.Any(l => l.Value.Contains("Dedication")))
|
||||
{
|
||||
archetype.DedicationFeat = Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription()
|
||||
+ AddedDescription(
|
||||
[
|
||||
"cost_markdown",
|
||||
"feat_markdown",
|
||||
"summary_markdown",
|
||||
"prerequisite_markdown",
|
||||
"requirement_markdown",
|
||||
"trigger_markdown",
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:heritage",
|
||||
InnerType = "heritage",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Наследства",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:ancestry"])]
|
||||
internal class HeritageProcessor(
|
||||
ProcessorInput input,
|
||||
StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Heritage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Heritage Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Heritages.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:language",
|
||||
InnerType = "language",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Языки",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
|
||||
internal class LanguageProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Language;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Language Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Languages.Add(Object);
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Pf2e.Database.Model.Structure.RitualRelation;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:ritual",
|
||||
InnerType = "ritual",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Ритуалы",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])]
|
||||
internal partial class RitualProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Ritual;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Ritual Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Rituals.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
Object.Level = Input.GetRequired<int>("level");
|
||||
Object.Duration = Input.GetOptional("duration", -1);
|
||||
|
||||
Object.Range = Input.GetOptional("range", -1);
|
||||
Object.RangeString = Input.GetOptionalLocale("range_raw", LocaleType.English);
|
||||
|
||||
Object.CastTime = CalculateCastTime(Input.GetRequired<string>("actions"));
|
||||
|
||||
Object.Cost = Input.GetOptionalLocale("cost_markdown", LocaleType.English);
|
||||
|
||||
Object.Target = Input.GetOptionalLocale("target_markdown", LocaleType.English);
|
||||
Object.Requirement = Input.GetOptionalLocale("requirement_markdown", LocaleType.English);
|
||||
|
||||
Object.Elements = (Input.GetOptional<JArray>("element") ?? [])
|
||||
.Select(token => Input.Enum<ElementEnum>(token.ToString()))
|
||||
.ToArray();
|
||||
|
||||
ProcessArea();
|
||||
|
||||
ProcessPrimaryChecks();
|
||||
ProcessSecondaryChecks();
|
||||
|
||||
ProcessSecondaryCasters();
|
||||
}
|
||||
|
||||
private int CalculateCastTime(string value)
|
||||
{
|
||||
string[] parts = value.Trim().ToLower().Split(" ");
|
||||
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
throw new($"Can't determine cast time of \"{value}\"");
|
||||
}
|
||||
|
||||
int number = int.Parse(parts[0]);
|
||||
|
||||
string unit = parts[1].ToLower();
|
||||
int unitSeconds = unit switch
|
||||
{
|
||||
"hour" => 3600,
|
||||
"hours" => 3600,
|
||||
"day" => 86400,
|
||||
"days" => 86400,
|
||||
"week" => 7 * 86400,
|
||||
"weeks" => 7 * 86400,
|
||||
_ => throw new($"Can't determine cast time of \"{value}\""),
|
||||
};
|
||||
|
||||
return number * unitSeconds;
|
||||
}
|
||||
|
||||
private void ProcessArea()
|
||||
{
|
||||
string? areaRaw = Input.GetOptional<string>("area_raw");
|
||||
|
||||
if (string.IsNullOrEmpty(areaRaw))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string areaType = Input.GetRequired<string[]>("area_type")[0];
|
||||
RitualArea ritualArea = new()
|
||||
{
|
||||
Ritual = Object,
|
||||
Type = Input.Enum<SpellAreaType>(areaType),
|
||||
Details = new() { [LocaleType.English] = areaRaw },
|
||||
};
|
||||
|
||||
Object.Area = ritualArea;
|
||||
Context.RitualAreas.Add(ritualArea);
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, string> _castersCleanup = new()
|
||||
{
|
||||
[", up to a maximum of "] = "-",
|
||||
[" or more"] = "-999",
|
||||
[" (or more)"] = "-999",
|
||||
["up to "] = "0-",
|
||||
[" to "] = "-",
|
||||
["at least 1"] = "1-999",
|
||||
};
|
||||
|
||||
private void ProcessSecondaryCasters()
|
||||
{
|
||||
string? input = Input.GetOptional<string>("secondary_casters_raw");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in _castersCleanup)
|
||||
{
|
||||
input = input.Replace(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
Match match = CasterRegex().Match(input);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new($"Can't parse secondary casters: \"{input}\"");
|
||||
}
|
||||
|
||||
Object.SecondaryCasters = int.Parse(M(1));
|
||||
Object.SecondaryCastersMax = (M(2) != "") ? int.Parse(M(2)) : Object.SecondaryCasters;
|
||||
|
||||
if (M(3) != "")
|
||||
{
|
||||
Object.SecondaryCastersSuffix = new() { [LocaleType.English] = M(3) };
|
||||
}
|
||||
return;
|
||||
|
||||
string M(int i) => match.Groups[i].Value;
|
||||
}
|
||||
|
||||
private void ProcessPrimaryChecks()
|
||||
{
|
||||
string? check = Input.GetOptional<string>("primary_check_markdown");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(check))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> parts = CheckStringPrepare(check);
|
||||
|
||||
string suffix = "";
|
||||
|
||||
// Обработка частей
|
||||
List<RitualCheck> ritualChecks = parts
|
||||
.Select(part => ResolveCheck(part.Trim(), true, out suffix))
|
||||
.ToList();
|
||||
|
||||
if (
|
||||
ritualChecks.Any(c => c.Rank == TrainingRank.Untrained)
|
||||
&& ritualChecks.Any(c => c.Rank != TrainingRank.Untrained)
|
||||
)
|
||||
{
|
||||
// Смешанные ранги
|
||||
TrainingRank[] ranks = ritualChecks
|
||||
.Select(c => c.Rank)
|
||||
.Distinct()
|
||||
.Where(r => r != TrainingRank.Untrained)
|
||||
.ToArray();
|
||||
|
||||
if (ranks.Length > 1)
|
||||
{
|
||||
throw new($"Can't resolve mixed ranks: {string.Join(", ", ranks)}");
|
||||
}
|
||||
|
||||
foreach (
|
||||
RitualCheck ritualCheck in ritualChecks.Where(r => r.Rank == TrainingRank.Untrained)
|
||||
)
|
||||
{
|
||||
ritualCheck.Rank = ranks[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (suffix != "")
|
||||
{
|
||||
Object.PrimaryCheckSuffix = new() { [LocaleType.English] = suffix };
|
||||
}
|
||||
|
||||
foreach (RitualCheck ritualCheck in ritualChecks)
|
||||
{
|
||||
Object.Checks.Add(ritualCheck);
|
||||
Context.RitualChecks.Add(ritualCheck);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSecondaryChecks()
|
||||
{
|
||||
string? check = Input.GetOptional<string>("secondary_check_markdown");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(check))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> parts = CheckStringPrepare(check);
|
||||
|
||||
foreach (string part in parts)
|
||||
{
|
||||
RitualCheck ritualCheck = ResolveCheck(part.Trim(), false, out string suffix);
|
||||
|
||||
if (suffix != "")
|
||||
{
|
||||
if (ritualCheck.TrainingString is not null)
|
||||
{
|
||||
ritualCheck.TrainingString[LocaleType.English] += $" ({suffix})";
|
||||
}
|
||||
else
|
||||
{
|
||||
ritualCheck.TrainingString = new() { [LocaleType.English] = suffix };
|
||||
}
|
||||
}
|
||||
|
||||
Object.Checks.Add(ritualCheck);
|
||||
Context.RitualChecks.Add(ritualCheck);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, string> _checksCleanupReplaces = new()
|
||||
{
|
||||
["DC 35 Arcana"] = "Arcana <DC 35>",
|
||||
[")or"] = ") or",
|
||||
["[Lore](/Skills.aspx?ID=41) (any)"] = "[Any Lore](/Skills.aspx?ID=41)",
|
||||
["[Lore](/Skills.aspx?ID=8) (any)"] = "[Any Lore](/Skills.aspx?ID=8)",
|
||||
};
|
||||
|
||||
private readonly Dictionary<Regex, string> _checksCleanupRegexes = new()
|
||||
{
|
||||
[new(@"(\w+) \[Lore\]", RegexOptions.Compiled | RegexOptions.Multiline)] = "[$1 Lore]",
|
||||
};
|
||||
|
||||
private List<string> CheckStringPrepare(string input)
|
||||
{
|
||||
// Отчистка проблемных частей
|
||||
foreach (KeyValuePair<string, string> pair in _checksCleanupReplaces)
|
||||
{
|
||||
input = input.Replace(pair.Key, pair.Value);
|
||||
}
|
||||
foreach (KeyValuePair<Regex, string> pair in _checksCleanupRegexes)
|
||||
{
|
||||
input = pair.Key.Replace(input, pair.Value);
|
||||
}
|
||||
|
||||
// Замена символа ссылок
|
||||
input = Helpers_StringHelper.LinkRegex().Replace(input, "$1[$2]{$3}");
|
||||
input = input.Replace("(", "<").Replace(")", ">").Replace("{", "(").Replace("}", ")");
|
||||
|
||||
if (input.EndsWith(';') || input.EndsWith(','))
|
||||
{
|
||||
input = input[..^1].Trim();
|
||||
}
|
||||
|
||||
// Временная замена скобок ранга и дополнения
|
||||
#pragma warning disable SYSLIB1045
|
||||
MatchCollection rankMatches = new Regex(@"<(.*?)>").Matches(input);
|
||||
#pragma warning restore SYSLIB1045
|
||||
for (int i = 0; i < rankMatches.Count; i++)
|
||||
{
|
||||
input = input.Replace(rankMatches[i].Value, $"{{rank-{i}}}");
|
||||
}
|
||||
|
||||
// Разбиваем строку на части
|
||||
List<string> parts = StringHelper.RowSplit(input);
|
||||
|
||||
// Возвращаем скобки ранга и дополнения
|
||||
for (int i = 0; i < rankMatches.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < parts.Count; j++)
|
||||
{
|
||||
parts[j] = parts[j].Replace($"{{rank-{i}}}", rankMatches[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
private RitualCheck ResolveCheck(string input, bool isPrimary, out string suffix)
|
||||
{
|
||||
RitualCheck result = new() { Ritual = Object, IsPrimary = isPrimary };
|
||||
string? skillName = null;
|
||||
string s = "";
|
||||
|
||||
Dictionary<Regex, Action<Match>> regexDictionary = new()
|
||||
{
|
||||
// General match
|
||||
// [name](url) ? <rank?> ?
|
||||
[PrimaryRegex1()] = match =>
|
||||
{
|
||||
skillName = match.Groups[1].Value;
|
||||
try
|
||||
{
|
||||
result.Rank = Input.Enum<TrainingRank>(match.Groups[4].Value.ToLower());
|
||||
s += match.Groups[3].Value + match.Groups[5].Value + match.Groups[6].Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
s +=
|
||||
match.Groups[3].Value
|
||||
+ match.Groups[4].Value
|
||||
+ match.Groups[5].Value
|
||||
+ match.Groups[6].Value;
|
||||
}
|
||||
},
|
||||
|
||||
// ? [name](url)
|
||||
[PrimaryRegex2()] = match =>
|
||||
{
|
||||
skillName = match.Groups[2].Value;
|
||||
result.Rank = TrainingRank.Untrained;
|
||||
s += match.Groups[1].Value;
|
||||
},
|
||||
|
||||
// name <rank>
|
||||
[PrimaryRegex3()] = match =>
|
||||
{
|
||||
skillName = match.Groups[1].Value;
|
||||
try
|
||||
{
|
||||
result.Rank = Input.Enum<TrainingRank>(match.Groups[2].Value.ToLower());
|
||||
}
|
||||
catch
|
||||
{
|
||||
s += match.Groups[2].Value;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
foreach (KeyValuePair<Regex, Action<Match>> pair in regexDictionary)
|
||||
{
|
||||
Match match = pair.Key.Match(input);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
pair.Value.Invoke(match);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
suffix = s;
|
||||
|
||||
skillName ??= input;
|
||||
|
||||
result.Training = Context.Trainings.FirstOrDefault(t =>
|
||||
Pf2eDbContext.LocaleSearch(t.Name, skillName)
|
||||
);
|
||||
|
||||
result.TrainingString =
|
||||
(result.Training is not null) ? null : new() { [LocaleType.English] = skillName };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
[
|
||||
"summary_markdown",
|
||||
"primary_check_markdown",
|
||||
"secondary_casters_raw",
|
||||
"secondary_check_markdown",
|
||||
];
|
||||
|
||||
[GeneratedRegex(
|
||||
@"^ *\[\s*(.*?)\s*\]\((.*?)\)\s*([a-z' ]*?)<(.*?)(?:[;,]?\s*(.*?))?>([a-z' ]*?)$",
|
||||
RegexOptions.Multiline
|
||||
)]
|
||||
private static partial Regex PrimaryRegex1();
|
||||
|
||||
[GeneratedRegex(@"^ *(.*?) *\[\s*(.*?)\s*\]\((.*?)\) *$", RegexOptions.Multiline)]
|
||||
private static partial Regex PrimaryRegex2();
|
||||
|
||||
[GeneratedRegex(@"^ *(.*?)\s*<(.*?)> *$", RegexOptions.Multiline)]
|
||||
private static partial Regex PrimaryRegex3();
|
||||
|
||||
[GeneratedRegex(@"^(\d+)(?:\-(\d+))?,? *\(?(.*?)\)?$", RegexOptions.Multiline)]
|
||||
private static partial Regex CasterRegex();
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using Rule = Pf2e.Database.Model.Structure.Rule;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types.Rules;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:rules",
|
||||
InnerType = "rules",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Правила",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])]
|
||||
internal class RuleProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Rule;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Rule Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Rules.Add(Object);
|
||||
|
||||
/// <summary>
|
||||
/// Структура генерации содержания правил.
|
||||
/// </summary>
|
||||
public RuleTocCollection Toc { get; } = RuleTocCollection.Instance;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
// base.Process();
|
||||
AddEntity();
|
||||
AddSource();
|
||||
AddExternal();
|
||||
AddTraits();
|
||||
|
||||
RuleTocBook book = Toc[GetSourceId()];
|
||||
|
||||
string[] breadcrumbs = (Input.GetOptional<JArray>("breadcrumbs") ?? [])
|
||||
.Select(token => token.ToString())
|
||||
.ToArray();
|
||||
|
||||
string id = GetExternal().Suffix.Replace("/Rules.aspx?ID=", "rules-");
|
||||
RuleTocItem? item;
|
||||
|
||||
if (breadcrumbs.Length > 0)
|
||||
{
|
||||
RuleTocItem parent = book.Get(breadcrumbs);
|
||||
item = parent.Children.FirstOrDefault(child => child.Id == id);
|
||||
|
||||
if (item is null)
|
||||
{
|
||||
item = new(Entity.Name[LocaleType.English], parent);
|
||||
parent.Children.Add(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item = book.Children.First(child => child.Id == id);
|
||||
}
|
||||
|
||||
item.Id = Input.GetRequired<string>("id");
|
||||
item.RuleId = Object.Id;
|
||||
item.IsHandled = true;
|
||||
|
||||
if (item.Parent?.IsHandled ?? false)
|
||||
{
|
||||
Object.ParentId = item.Parent.RuleId!;
|
||||
Object.Order = item.Parent.ChildrenOrder?.IndexOf(item.Id) ?? -1;
|
||||
}
|
||||
|
||||
// ЗАМЕТКА
|
||||
//
|
||||
// Индекс Архивов не включает поля "next_link" и "previous_link"!
|
||||
|
||||
/*
|
||||
if (item.ChildrenOrder is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
item.PreviousId = Input
|
||||
.GetOptional<string>("previous_link")
|
||||
?.Replace("/Rules.aspx?ID=", "rules-");
|
||||
item.NextId = Input.GetOptional<string>("next_link")?.Replace("/Rules.aspx?ID=", "rules-");
|
||||
|
||||
if (item.Parent is not null)
|
||||
{
|
||||
if (item.PreviousId is null)
|
||||
{
|
||||
item.Parent.FirstChildId = item.Id;
|
||||
}
|
||||
if (item.NextId is null)
|
||||
{
|
||||
item.Parent.LastChildId = item.Id;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение источника, к которому относится правило.
|
||||
/// </summary>
|
||||
/// <returns>Источник, к которому относится правило.</returns>
|
||||
private int GetSourceId()
|
||||
{
|
||||
foreach (EntitySource entitySource in Entity.Sources)
|
||||
{
|
||||
return entitySource.SourceId;
|
||||
}
|
||||
|
||||
throw new("Source not found");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение внешней ссылки, из которой получено правило.
|
||||
/// </summary>
|
||||
/// <returns>Внешняя ссылка.</returns>
|
||||
private EntityExternal GetExternal()
|
||||
{
|
||||
foreach (EntityExternal entityExternal in Entity.Externals)
|
||||
{
|
||||
return entityExternal;
|
||||
}
|
||||
|
||||
throw new("External not found");
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// ЗАМЕТКА
|
||||
//
|
||||
// В теории, этот метод должен был дополнительно устанавливать порядок разделов.
|
||||
// Но индекс Архивов не включает поля "next_link" и "previous_link",
|
||||
// так что ставить порядок ВСЕХ правил в архиве не получится, только тех, что есть в содержании.
|
||||
// Ну а пока просто расставляем родительские и дочерние элементы.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Database;
|
||||
using Rule = Pf2e.Database.Model.Structure.Rule;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types.Rules;
|
||||
|
||||
[Processor(
|
||||
"aonprd:rules-order",
|
||||
Description = "Формирование порядка правил",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:rules"])]
|
||||
internal class RulesOrderProcessor(Pf2eDbContext context) : IProcessor
|
||||
{
|
||||
public Pf2eDbContext Context { get; } = context;
|
||||
|
||||
/// <summary>
|
||||
/// Структура генерации содержания правил.
|
||||
/// </summary>
|
||||
public readonly RuleTocCollection Toc = RuleTocCollection.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Список записей о порядке правил в своих главах.
|
||||
/// </summary>
|
||||
private readonly List<OrderItem> _orderItems = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Process()
|
||||
{
|
||||
Toc.SaveFile();
|
||||
|
||||
/*
|
||||
foreach (RuleTocBook book in Toc.Books)
|
||||
{
|
||||
BuildIdOrderDictionary(book.Children);
|
||||
}
|
||||
*/
|
||||
|
||||
int count = Context.Rules.Count();
|
||||
const int perPage = 50;
|
||||
|
||||
for (int skip = 0; skip < count; skip += perPage)
|
||||
{
|
||||
Context.Rules.Skip(skip).Take(perPage).ToList().ForEach(Process);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка конкретного правила.
|
||||
/// </summary>
|
||||
/// <param name="rule"></param>
|
||||
private void Process(Rule rule)
|
||||
{
|
||||
RuleTocItem? item = Toc.FindEntityId(rule.Id);
|
||||
|
||||
if (item is null)
|
||||
{
|
||||
throw new($"Rule with id {rule.Id} not found in TOC");
|
||||
}
|
||||
|
||||
rule.ParentId = item.Parent?.RuleId;
|
||||
|
||||
// Дети будут раставлены автоматически по связи
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построение списка записей о порядке правил из дочерних элементов содержания.
|
||||
/// </summary>
|
||||
/// <param name="item">Элемент содержания, дочерние элементы которого необходимо обработать.</param>
|
||||
private void BuildIdOrderDictionary(RuleTocItem item)
|
||||
{
|
||||
List<RuleTocItem> items = [];
|
||||
|
||||
if (item.ChildrenOrder is null)
|
||||
{
|
||||
RuleTocItem? child = item.Children.FirstOrDefault(i => i.Id == item.FirstChildId);
|
||||
|
||||
while (child is not null)
|
||||
{
|
||||
items.Add(child);
|
||||
child = item.Children.FirstOrDefault(i => i.Id == child.NextId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string id in item.ChildrenOrder)
|
||||
{
|
||||
RuleTocItem? child = item.Children.FirstOrDefault(i => i.Id == id);
|
||||
if (child is null)
|
||||
{
|
||||
throw new($"Child with id {id} not found in {item.Name}");
|
||||
}
|
||||
|
||||
items.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (items.Count != item.Children.Count)
|
||||
{
|
||||
throw new($"Children count mismatch for {item.Name}");
|
||||
}
|
||||
|
||||
BuildIdOrderDictionary(items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построение списка записей о порядке правил из списка элементов содержания.
|
||||
/// </summary>
|
||||
/// <param name="items">Список элементов содержания для обработки.</param>
|
||||
private void BuildIdOrderDictionary(List<RuleTocItem> items)
|
||||
{
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
RuleTocItem item = items[i];
|
||||
|
||||
if (item.RuleId is null)
|
||||
{
|
||||
throw new($"RuleId is null for {item.Name} (id {item.Id}");
|
||||
}
|
||||
|
||||
AddOrderItem((int)item.RuleId!, i, null);
|
||||
BuildIdOrderDictionary(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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))
|
||||
{
|
||||
throw new($"Order item with id {id} already exists");
|
||||
}
|
||||
|
||||
_orderItems.Add(
|
||||
new()
|
||||
{
|
||||
Id = id,
|
||||
Order = order,
|
||||
ParentId = parentId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public 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; }
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types.Rules;
|
||||
|
||||
[Processor(
|
||||
"aonprd:rules-toc",
|
||||
Description = "Загрузка содержания правил",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:source"])]
|
||||
internal partial class RulesTocProcessor(Pf2eDbContext context) : IProcessor
|
||||
{
|
||||
public Pf2eDbContext Context { get; } = context;
|
||||
|
||||
/// <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 void Process()
|
||||
{
|
||||
IDocument document = DownloadPage();
|
||||
|
||||
foreach (IElement block in document.QuerySelector(Selector)!.Children)
|
||||
{
|
||||
IElement summaryElement =
|
||||
block.QuerySelector("summary") ?? throw new("Can't determine <summary> element");
|
||||
|
||||
string sourceName = summaryElement.TextContent;
|
||||
Source source =
|
||||
Context.Sources.FirstOrDefault(s => s.Name == sourceName)
|
||||
?? throw new($"Source {sourceName} not found");
|
||||
|
||||
RuleTocBook book = Toc[source];
|
||||
|
||||
for (int i = 1; i < block.Children.Length; i++)
|
||||
{
|
||||
IElement element = block.Children[i];
|
||||
|
||||
if (element.TagName != "B")
|
||||
{
|
||||
throw new($"Expected <B> tag, but got <{element.TagName}>");
|
||||
}
|
||||
|
||||
RuleTocItem item = ParseLink(element.InnerHtml);
|
||||
item.ChildrenOrder = [];
|
||||
|
||||
book.Children.Add(item);
|
||||
|
||||
if (element.NextElementSibling?.TagName != "UL")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IElement ul = block.Children[++i];
|
||||
|
||||
foreach (IElement li in ul.Children)
|
||||
{
|
||||
RuleTocItem child = ParseLink(li.InnerHtml);
|
||||
|
||||
item.Children.Add(child);
|
||||
item.ChildrenOrder.Add(child.Id!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!linkMatch.Success)
|
||||
{
|
||||
throw new($"Can't parse link {value}");
|
||||
}
|
||||
|
||||
return new(HttpUtility.HtmlDecode(linkMatch.Groups[2].Value).Trim(), parent)
|
||||
{
|
||||
Id = $"rules-{linkMatch.Groups[1].Value}",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Скачивание страницы, указанной в <see cref="Url"/>, и получение очищенного элемента, указанного в <see cref="Selector"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IDocument DownloadPage()
|
||||
{
|
||||
IConfiguration config = Configuration.Default.WithDefaultLoader();
|
||||
IBrowsingContext context = BrowsingContext.New(config);
|
||||
IDocument document = null!;
|
||||
bool completed = false;
|
||||
|
||||
while (!completed)
|
||||
{
|
||||
try
|
||||
{
|
||||
document = context.OpenAsync(Url).Result;
|
||||
completed = true;
|
||||
}
|
||||
catch (AggregateException)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
IElement root = document.QuerySelector(Selector) ?? throw new("Broken page download");
|
||||
|
||||
string html = root.OuterHtml;
|
||||
|
||||
foreach (string s in new[] { "br", "\r", "\n", "<u>", "</u>", "&NoRedirect=1" })
|
||||
{
|
||||
html = html.Replace(s, "");
|
||||
}
|
||||
|
||||
return context.OpenAsync(req => req.Content(html)).Result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Save() { }
|
||||
|
||||
/// <summary>
|
||||
/// Получить скомпилированное регулярное выражение для получения HTML-ссылки.
|
||||
/// </summary>
|
||||
/// <returns>Регулярное выражение получения HTML-ссылки.</returns>
|
||||
[GeneratedRegex("""<a href="Rules\.aspx\?ID=(\d+)">(.*?)</a>""")]
|
||||
public static partial Regex LinkRegex();
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:skill-general-action",
|
||||
InnerType = "skill-general-action",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Действия скиллов",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
|
||||
internal class SkillActionsProcessor(
|
||||
ProcessorInput input,
|
||||
StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Action;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Database.Model.Structure.Action Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Actions.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
Check();
|
||||
|
||||
base.Process();
|
||||
|
||||
Object.Actions = ActionEnum.Other;
|
||||
Object.Category = ActionCategory.SkillAction;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string[] AddedDescriptionPaths() =>
|
||||
[
|
||||
"cost_markdown",
|
||||
"trait_markdown",
|
||||
"source_markdown",
|
||||
"search_markdown",
|
||||
"requirement_markdown",
|
||||
"trigger_markdown",
|
||||
"summary_markdown",
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Pf2e.Database.Model.Structure.SpellRelation;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:spell",
|
||||
InnerType = "spell",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Заклинания",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
|
||||
internal class SpellProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Spell;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Spell Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Spells.Add(Object);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
Object.Level = Input.GetRequired<int>("level");
|
||||
Object.Actions = Input.EnumOptional("actions", ActionEnum.Unknown);
|
||||
Object.IsFocus = Input.GetRequired<string>("spell_type") == "Focus";
|
||||
Object.Duration = Input.GetOptional("duration", -1);
|
||||
Object.Range = Input.GetOptional("range", -1);
|
||||
|
||||
Object.Cost = Input.GetOptionalLocale("cost_markdown", LocaleType.English);
|
||||
Object.Trigger = Input.GetOptionalLocale("trigger_markdown", LocaleType.English);
|
||||
Object.Target = Input.GetOptionalLocale("target_markdown", LocaleType.English);
|
||||
Object.Requirement = Input.GetOptionalLocale("requirement_markdown", LocaleType.English);
|
||||
|
||||
Object.Traditions = (Input.GetOptional<JArray>("tradition") ?? [])
|
||||
.Select(token => Input.Enum<SpellTradition>(token.ToString()))
|
||||
.ToArray();
|
||||
|
||||
Object.Elements = (Input.GetOptional<JArray>("element") ?? [])
|
||||
.Select(token => Input.Enum<ElementEnum>(token.ToString()))
|
||||
.ToArray();
|
||||
|
||||
switch (Input.GetRequired<string>("spell_type"))
|
||||
{
|
||||
case "Focus":
|
||||
Object.IsFocus = true;
|
||||
break;
|
||||
case "Cantrip":
|
||||
Object.Level = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
ProcessArea();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка области применения заклинания.
|
||||
/// </summary>
|
||||
private void ProcessArea()
|
||||
{
|
||||
List<string> areaTypes = (Input.GetOptional<string[]>("area_type") ?? []).ToList();
|
||||
|
||||
switch (areaTypes.Count)
|
||||
{
|
||||
case 0:
|
||||
return;
|
||||
|
||||
case 1:
|
||||
A(
|
||||
new()
|
||||
{
|
||||
Spell = Object,
|
||||
Type = Input.Enum<SpellAreaType>(areaTypes[0]),
|
||||
Details = Input.GetOptionalLocale("area_raw", LocaleType.English),
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> areaDetails = (Input.GetOptional<string>("area_raw") ?? "")
|
||||
.Replace(", or", ",")
|
||||
.Replace(" or ", ", ")
|
||||
.Split(", ")
|
||||
.ToList();
|
||||
|
||||
if (areaTypes.Remove("square"))
|
||||
{
|
||||
A(new() { Spell = Object, Type = SpellAreaType.Square });
|
||||
}
|
||||
if (areaDetails.Contains("fungi"))
|
||||
{
|
||||
areaDetails = [areaDetails[0] + " or " + areaDetails[1]];
|
||||
}
|
||||
|
||||
if (areaTypes.Count != areaDetails.Count)
|
||||
{
|
||||
throw new(
|
||||
$"Area details count does not match area types count for spell {Entity.Name[LocaleType.English]}"
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < areaTypes.Count; i++)
|
||||
{
|
||||
A(
|
||||
new()
|
||||
{
|
||||
Spell = Object,
|
||||
Type = Input.Enum<SpellAreaType>(areaTypes[i]),
|
||||
Details = new() { [LocaleType.English] = areaDetails[i] },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void A(SpellArea spellArea)
|
||||
{
|
||||
Object.SpellAreas.Add(spellArea);
|
||||
Context.SpellAreas.Add(spellArea);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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",
|
||||
];
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Scrapper.Processors.Factory;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorFactory(
|
||||
"aonprd:trait-alignments",
|
||||
ProcessorType = typeof(TraitAlignmentsProcessor),
|
||||
Description = "Трейты мировоззрений",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation,
|
||||
Parallel = false
|
||||
)]
|
||||
internal class TraitAlignmentsProcessorFactory(IServiceProvider serviceProvider)
|
||||
: AbstractProcessorFactory
|
||||
{
|
||||
public IServiceProvider ServiceProvider { get; } = serviceProvider;
|
||||
public List<string> Alignments { get; } = ["LG", "NG", "CG", "LN", "N", "CN", "LE", "NE", "CE"];
|
||||
|
||||
public override int Count()
|
||||
{
|
||||
return Alignments.Count;
|
||||
}
|
||||
|
||||
public override IEnumerable<Tuple<string, IProcessor>> GetEnumerator()
|
||||
{
|
||||
foreach (string alignment in Alignments)
|
||||
{
|
||||
TraitAlignmentsProcessor processor =
|
||||
ActivatorUtilities.CreateInstance<TraitAlignmentsProcessor>(
|
||||
ServiceProvider,
|
||||
alignment
|
||||
);
|
||||
|
||||
yield return new($"{alignment}", processor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TraitAlignmentsProcessor(
|
||||
string alignment,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : IProcessor
|
||||
{
|
||||
public string Alignment { get; } = alignment;
|
||||
public DatabaseHelper Helper { get; } = helper;
|
||||
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||
public Pf2eDbContext Context { get; } = new();
|
||||
|
||||
public Entity Entity { get; set; } = null!;
|
||||
public Trait Trait { get; set; } = null!;
|
||||
|
||||
public void Process()
|
||||
{
|
||||
Entity = new()
|
||||
{
|
||||
Name = new() { ["en"] = Alignment },
|
||||
Type = EntityType.Trait,
|
||||
Img = "",
|
||||
Description = "",
|
||||
Rarity = EntityRarity.Common,
|
||||
IsTranslated = false,
|
||||
IsChecked = false,
|
||||
Slug = Helper.GenerateSlug(Alignment),
|
||||
};
|
||||
|
||||
Trait = new() { Entity = Entity, Type = TraitType.Alignment };
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Context.Entities.Add(Entity);
|
||||
Context.Traits.Add(Trait);
|
||||
|
||||
Globals.TraitIds[Alignment] = Entity.Id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Helpers_StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
using StringHelper = Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:trait",
|
||||
InnerType = "trait",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Признаки",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(
|
||||
["aonprd:external", "aonprd:source", "aonprd:source", "aonprd:trait-alignments"]
|
||||
)]
|
||||
internal class TraitProcessor(
|
||||
ProcessorInput input,
|
||||
Helpers_StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Trait;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Trait Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add()
|
||||
{
|
||||
Context.Traits.Add(Object);
|
||||
|
||||
Globals.TraitIds[Entity.Name[LocaleType.English]] = Entity.Id;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
Object.Type = TraitType.Regular;
|
||||
|
||||
Context.Traits.Add(Object);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void AddTraits()
|
||||
{
|
||||
// Необходимо пропускать
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetDescription() =>
|
||||
base.GetDescription() + AddedDescription(["summary_markdown", "search_markdown"]);
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:versitile-heritage",
|
||||
InnerType = "ancestry",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Универсальные наследства",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:ancestry"])]
|
||||
internal class VersitileHeritageProcessor(
|
||||
ProcessorInput input,
|
||||
StringHelper stringHelper,
|
||||
DatabaseHelper helper,
|
||||
AonprdBuilderGlobals globals
|
||||
) : AbstractEntityProcessor(input, stringHelper, helper, globals)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override EntityType Type => EntityType.Heritage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Heritage Object { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Add() => Context.Heritages.Add(Object);
|
||||
|
||||
public override void Check()
|
||||
{
|
||||
base.Check();
|
||||
|
||||
if (Input.GetRequired<string>("type") == "Ancestry")
|
||||
{
|
||||
throw new SkipItemException(
|
||||
Input.GetRequired<string>("name"),
|
||||
"не универсальное наследие"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Загрузчик данных компендиума.
|
||||
/// </summary>
|
||||
/// <param name="repository"><inheritdoc cref="FoundryEnRepository"/></param>
|
||||
internal sealed class FoundryEnCompendium(FoundryEnRepository repository) : AbstractCompendium
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override FoundryEnRepository Repository { get; } = repository;
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция всех записей компендиума.
|
||||
/// </summary>
|
||||
public ConcurrentBag<FoundryEnCompendiumItem> Items { get; } = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Count(params ICollection<string> types) =>
|
||||
types.Count == 0 ? Items.Count : Items.Count(i => types.Contains(i.Type));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<Tuple<AbstractCompendiumItem, JObject>> Iterate(
|
||||
params ICollection<string> types
|
||||
)
|
||||
{
|
||||
IEnumerable<string> files =
|
||||
types.Count == 0
|
||||
? Items.Select(item => item.File).Distinct()
|
||||
: Items
|
||||
.Where(item => types.Contains(item.Type))
|
||||
.Select(item => item.File)
|
||||
.Distinct();
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
Dictionary<string, FoundryEnCompendiumItem> fileReferences = Items
|
||||
.Where(i => i.File == file)
|
||||
.ToDictionary(i => i.Id);
|
||||
|
||||
using StreamReader fileReader = new(file);
|
||||
JArray array = JArray.Parse(fileReader.ReadToEnd());
|
||||
|
||||
foreach (JToken item in array)
|
||||
{
|
||||
string? id = item.SelectToken("_id")?.ToString();
|
||||
|
||||
if (
|
||||
id is null
|
||||
|| !fileReferences.TryGetValue(id, out FoundryEnCompendiumItem? reference)
|
||||
|| (types.Count > 0 && !types.Contains(reference.Type))
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return new(reference, (JObject)item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на сущность с указанием файла, в котором она хранится.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <param name="type">Тип сущности.</param>
|
||||
/// <param name="file">Имя файла, в котором хранится сущность.</param>
|
||||
internal class FoundryEnCompendiumItem(string id, string type, string file) : AbstractCompendiumItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор сущности.
|
||||
/// </summary>
|
||||
public override string Id { get; } = id;
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности.
|
||||
/// </summary>
|
||||
public override string Type { get; } = type;
|
||||
|
||||
/// <summary>
|
||||
/// Имя файла, в котором хранится сущность.
|
||||
/// </summary>
|
||||
public string File { get; } = file;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Repositories;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn;
|
||||
|
||||
/// <summary>
|
||||
/// Импортёр для модуля Pf2e FoundryVTT.
|
||||
/// </summary>
|
||||
internal class FoundryEnImporter : AbstractImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Название внешней ссылки.
|
||||
/// </summary>
|
||||
public const string TargetName = "Pf2e FoundryVTT module";
|
||||
|
||||
/// <summary>
|
||||
/// Отображаемое имя импортёра.
|
||||
/// </summary>
|
||||
public override string Name => TargetName;
|
||||
|
||||
/// <summary>
|
||||
/// Короткое имя импортёра.
|
||||
/// </summary>
|
||||
public override string Slug => "foundry-en";
|
||||
|
||||
#region "Внедрение зависимостей"
|
||||
|
||||
public static void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<FoundryEnImporter>();
|
||||
services.AddSingleton<FoundryEnRepository>();
|
||||
services.AddSingleton<FoundryEnCompendium>();
|
||||
|
||||
// Помощники
|
||||
services.AddSingleton<FoundryEnGlobals>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Git-репозиторий данных FoundryVTT-модуля "pf2e".
|
||||
/// </summary>
|
||||
/// <param name="importer"><inheritdoc cref="FoundryEnImporter"/></param>
|
||||
internal class FoundryEnRepository(FoundryEnImporter importer) : AbstractGitHubRepository(importer)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override string Repository => "foundryvtt/pf2e";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string AssetName => "json-assets.zip";
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using AngleSharp.Dom;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Глобальные константы и утилиты для импорта данных в формате Foundry En.
|
||||
/// </summary>
|
||||
internal class FoundryEnGlobals
|
||||
{
|
||||
/// <summary>
|
||||
/// Цель внешних ссылок для Foundry En.
|
||||
/// </summary>
|
||||
public ExternalTarget FoundryTarget { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Список идентификаторв пропускаемых сущностей.
|
||||
/// </summary>
|
||||
public List<string> Ignore { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарей суффиксов для сущностей.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> SlugSuffixes { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь соответствий идентификаторов Aonprd к Foundry En.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> MatchToAonprd { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="FoundryEnGlobals"/>.
|
||||
/// </summary>
|
||||
public FoundryEnGlobals()
|
||||
{
|
||||
using StreamReader fileReader = new("Resources\\ImportAdaptation.json");
|
||||
|
||||
JObject? json = JObject
|
||||
.Parse(fileReader.ReadToEnd())
|
||||
.SelectToken("Foundry")
|
||||
?.ToObject<JObject>();
|
||||
|
||||
if (json is not null)
|
||||
{
|
||||
Ignore = json.SelectToken("Ignore")?.ToObject<List<string>>() ?? []; // JsonToDictionary(json, "Ignore");
|
||||
|
||||
SlugSuffixes = JsonToDictionary(json, "SlugSuffixes");
|
||||
MatchToAonprd = JsonToDictionary(json, "MatchToAonprd");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить суффикс для ссылки.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор Aonprd.</param>
|
||||
/// <returns>Суффикс для ссылки или пустая строка.</returns>
|
||||
public string GetSlugSuffix(string id)
|
||||
{
|
||||
return SlugSuffixes.TryGetValue(id, out string? slug) ? $"-{slug}" : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразовать JSON-объект в словарь.
|
||||
/// </summary>
|
||||
/// <param name="json">Исходный JSON-объект.</param>
|
||||
/// <param name="tokenPath">Путь к токену в JSON.</param>
|
||||
/// <returns>Результирующий словарь.</returns>
|
||||
public static Dictionary<string, string> JsonToDictionary(JObject json, string tokenPath)
|
||||
{
|
||||
return json.SelectToken(tokenPath)?.ToObject<Dictionary<string, string>>() ?? [];
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
||||
|
||||
[ProcessorCollection(
|
||||
"foundry-en:action",
|
||||
InnerType = "action",
|
||||
CompendiumType = ProcessorCompendiumEnum.FoundryEn,
|
||||
Description = "Действия",
|
||||
Group = ProcessorGroupEnum.FoundryEnEntities,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:compendium", "foundry-en:external"])]
|
||||
internal partial class ActionProcessor(
|
||||
DatabaseHelper databaseHelper,
|
||||
Pf2eDbContext context,
|
||||
FoundryEnGlobals globals,
|
||||
ProcessorInput input
|
||||
)
|
||||
: FoundryEnAbstractProcessor<Database.Model.Structure.Action>(
|
||||
databaseHelper,
|
||||
context,
|
||||
globals,
|
||||
input
|
||||
)
|
||||
{
|
||||
public override EntityType Type => EntityType.Action;
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Collection;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
||||
|
||||
[ProcessorCollection(
|
||||
"foundry-en:condition",
|
||||
InnerType = "Item.condition",
|
||||
CompendiumType = ProcessorCompendiumEnum.FoundryEn,
|
||||
Description = "Состояния",
|
||||
Group = ProcessorGroupEnum.FoundryEnEntities,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:compendium"])]
|
||||
internal class ConditionProcessor(
|
||||
DatabaseHelper databaseHelper,
|
||||
Pf2eDbContext context,
|
||||
FoundryEnGlobals globals,
|
||||
ProcessorInput input
|
||||
) : FoundryEnAbstractProcessor<Condition>(databaseHelper, context, globals, input)
|
||||
{
|
||||
public override EntityType Type => EntityType.Condition;
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
/*
|
||||
JObject extra = new()
|
||||
{
|
||||
{ "duration", Input.Input.SelectToken("system.duration") },
|
||||
{ "overrides", Input.Input.SelectToken("system.overrides") },
|
||||
{ "references", Input.Input.SelectToken("system.references") },
|
||||
{ "value", Input.Input.SelectToken("system.value") },
|
||||
};
|
||||
|
||||
Object.Extra = extra.ToString(Newtonsoft.Json.Formatting.None);
|
||||
Object.Group = Input.EnumOptional("system.group", ConditionGroup.Unknown);
|
||||
|
||||
Context.Conditions.Add(Object);
|
||||
*/
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Pf2e.Database.Model.Structure.DeityRelation;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
||||
|
||||
[ProcessorCollection(
|
||||
"foundry-en:deity",
|
||||
InnerType = "deity",
|
||||
CompendiumType = ProcessorCompendiumEnum.FoundryEn,
|
||||
Description = "Божества",
|
||||
Group = ProcessorGroupEnum.FoundryEnEntities,
|
||||
Active = true
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:compendium", "foundry-en:external"])]
|
||||
internal class DeityProcessor(
|
||||
DatabaseHelper databaseHelper,
|
||||
Pf2eDbContext context,
|
||||
FoundryEnGlobals globals,
|
||||
ProcessorInput input
|
||||
) : FoundryEnAbstractProcessor<Deity>(databaseHelper, context, globals, input)
|
||||
{
|
||||
public override EntityType Type => EntityType.Deity;
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
ProcessSanctification();
|
||||
ProcessWeapons();
|
||||
}
|
||||
|
||||
private void ProcessSanctification()
|
||||
{
|
||||
JObject? sanctification = Input.GetOptional<JObject>("system.sanctification");
|
||||
if (sanctification == null)
|
||||
{
|
||||
Object.Sanctification = DeitySanctification.Any;
|
||||
Object.SanctificationMust = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
sanctification["what"] is not JArray sanctificationArray
|
||||
|| sanctificationArray.Count == 0
|
||||
|| sanctificationArray.Count == 2
|
||||
&& sanctificationArray.Any(t => t.ToString() == "holy")
|
||||
&& sanctificationArray.Any(t => t.ToString() == "unholy")
|
||||
)
|
||||
{
|
||||
Object.Sanctification = DeitySanctification.Any;
|
||||
}
|
||||
else if (sanctificationArray.Any(t => t.ToString() == "holy"))
|
||||
{
|
||||
Object.Sanctification = DeitySanctification.Holy;
|
||||
}
|
||||
else if (sanctificationArray.Any(t => t.ToString() == "unholy"))
|
||||
{
|
||||
Object.Sanctification = DeitySanctification.Unholy;
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.Sanctification = DeitySanctification.Any;
|
||||
}
|
||||
|
||||
Object.SanctificationMust = sanctification["modal"]?.ToString() == "must";
|
||||
}
|
||||
|
||||
private void ProcessWeapons()
|
||||
{
|
||||
List<string> weapons = Input
|
||||
.GetRequired<JArray>("system.weapons")
|
||||
.Select(token => token.ToString())
|
||||
.ToList();
|
||||
|
||||
foreach (string weapon in weapons)
|
||||
{
|
||||
Object.DeityWeapons.Add(new() { DeityId = Object.Id, Weapon = weapon });
|
||||
}
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Helpers;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик сущностей, полученных из молуля Foundry pf2e.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="ProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="databaseHelper">[DI] <inheritdoc cref="DatabaseHelper"/></param>
|
||||
/// <param name="context">[DI] <inheritdoc cref="Pf2eDbContext"/></param>
|
||||
/// <param name="globals">[DI] <inheritdoc cref="FoundryEnGlobals"/></param>
|
||||
/// <param name="input">Объект обработки вводимого JSON.</param>
|
||||
internal abstract class FoundryEnAbstractProcessor<TEntity>(
|
||||
DatabaseHelper databaseHelper,
|
||||
Pf2eDbContext context,
|
||||
FoundryEnGlobals globals,
|
||||
ProcessorInput input
|
||||
) : AbstractInputProcessor(input)
|
||||
where TEntity : class, IEntity
|
||||
{
|
||||
/// <inheritdoc cref="DatabaseHelper"/>
|
||||
protected DatabaseHelper Helper { get; } = databaseHelper;
|
||||
|
||||
/// <inheritdoc cref="Pf2eDbContext"/>
|
||||
public Pf2eDbContext Context { get; } = context;
|
||||
|
||||
/// <inheritdoc cref="FoundryEnGlobals"/>
|
||||
public FoundryEnGlobals Globals { get; } = globals;
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности.
|
||||
/// </summary>
|
||||
public abstract EntityType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Представление сущности, которое будет использоваться для обработки данных.
|
||||
/// </summary>
|
||||
public EntityView View { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Основная сущность, которая будет использоваться для обработки данных.
|
||||
/// </summary>
|
||||
public Entity Entity => View.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// Подчинённая сущность, которая будет использоваться для обработки данных.
|
||||
/// </summary>
|
||||
public TEntity Object =>
|
||||
View.SubEntity as TEntity
|
||||
?? throw new InvalidOperationException(
|
||||
$"Сущность {View.SubEntity} некорректного типа, ожидался тип {typeof(TEntity)}."
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Название сущности, которое будет использоваться для обработки данных.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _name ??= GetEntityName(); }
|
||||
}
|
||||
private string? _name = null;
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет входные данные на наличие идентификатора сущности и игнорирует её, если она указана в списке игнорируемых.
|
||||
/// </summary>
|
||||
public virtual void CheckEntity()
|
||||
{
|
||||
string id = Input.GetRequired<string>("_id");
|
||||
|
||||
if (Globals.Ignore.Any(s => s == id))
|
||||
{
|
||||
throw new SkipItemException(
|
||||
id,
|
||||
$"Сущность с идентификатором \"{id}\" пропущена, так как она указана в списке игнорируемых."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Разрешает сущность, основываясь на входных данных.
|
||||
/// </summary>
|
||||
/// <returns>Идентификатор сущности.</returns>
|
||||
public virtual int ResolveEntity()
|
||||
{
|
||||
string name = Name;
|
||||
|
||||
if (Globals.MatchToAonprd.TryGetValue(name, out string? aonprdId))
|
||||
{
|
||||
return Context
|
||||
.EntityExternals.Where(ex => ex.ExternalId == aonprdId)
|
||||
.Select(ex => ex.EntityId)
|
||||
.First();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<Entity> entities = Context
|
||||
.Entities.AsNoTracking()
|
||||
.Include(e => e.Traits)
|
||||
.ThenInclude(t => t.Entity)
|
||||
.Where(e => Pf2eDbContext.LocaleSearch(e.Name, name) && e.Type == Type)
|
||||
.ToList();
|
||||
|
||||
switch (entities.Count)
|
||||
{
|
||||
case 0:
|
||||
throw new("сущность не найдена.");
|
||||
case 1:
|
||||
return entities[0].Id;
|
||||
}
|
||||
|
||||
JArray? traitArray = Input.GetOptional<JArray>("system.traits.value");
|
||||
|
||||
if (traitArray is not null)
|
||||
{
|
||||
List<string> inputTraits = traitArray
|
||||
.Select(token => token.ToString())
|
||||
.Order()
|
||||
.ToList();
|
||||
|
||||
foreach (Entity entity in entities)
|
||||
{
|
||||
List<string> entityTraits = entity
|
||||
.Traits.Select(t => t.Entity.Name[LocaleType.English])
|
||||
.Order()
|
||||
.ToList();
|
||||
|
||||
if (entityTraits.SequenceEqual(inputTraits))
|
||||
{
|
||||
return entity.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new SkipItemException(name, ex.Message);
|
||||
}
|
||||
|
||||
throw new SkipItemException(
|
||||
name,
|
||||
$"Несколько сущностей \"{name}\" найдено, но ни одна не совпадает с входными данными по признакам."
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
CheckEntity();
|
||||
|
||||
View = new(Context, Type, ResolveEntity());
|
||||
|
||||
AddExternal();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save()
|
||||
{
|
||||
Context.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить имя сущности из входных данных.
|
||||
/// </summary>
|
||||
/// <returns>Имя сущности.</returns>
|
||||
protected virtual string GetEntityName() => Input.GetRequired<string>("name");
|
||||
|
||||
/// <summary>
|
||||
/// Генерация внешних ссылок сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddExternal()
|
||||
{
|
||||
// External
|
||||
EntityExternal external = new()
|
||||
{
|
||||
EntityId = Entity.Id,
|
||||
TargetId = Globals.FoundryTarget.Id,
|
||||
ExternalId = Input.GetOptional<string>("_id"),
|
||||
Suffix = Input.GetRequired<string>("_id"),
|
||||
Md5 = Input.Md5(),
|
||||
};
|
||||
Context.EntityExternals.Add(external);
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"foundry-en:compendium",
|
||||
Description = "Загрузка компендиума",
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:repository"])]
|
||||
internal class CompendiumProcessor(
|
||||
FoundryEnRepository repository,
|
||||
FoundryEnCompendium compendium,
|
||||
ProcessProgress progress
|
||||
) : IProcessor
|
||||
{
|
||||
public FoundryEnRepository Repository { get; } = repository;
|
||||
public FoundryEnCompendium Compendium { get; } = compendium;
|
||||
public ProcessProgress Progress { get; } = progress;
|
||||
|
||||
public void Process()
|
||||
{
|
||||
string packsDir = Path.Combine(Repository.RepositoryDirPath, "packs");
|
||||
|
||||
var jsonFiles = Directory.GetFiles(packsDir, "*.json");
|
||||
|
||||
Progress.Local.Reset(jsonFiles.Length);
|
||||
|
||||
foreach (string packFile in jsonFiles)
|
||||
{
|
||||
Progress.Local.Start(Path.GetFileName(packFile));
|
||||
|
||||
if (packFile.EndsWith("folders.json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Progress.Local.End();
|
||||
continue;
|
||||
}
|
||||
|
||||
using StreamReader fileReader = new(packFile, Encoding.UTF8);
|
||||
string fileContent = fileReader.ReadToEnd();
|
||||
|
||||
JArray packData = JArray.Parse(fileContent);
|
||||
foreach (JToken item in packData)
|
||||
{
|
||||
string id = item.SelectToken("_id")?.ToString()!;
|
||||
string? type = item.SelectToken("type")?.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FoundryEnCompendiumItem compendiumItem = new(id, type, packFile);
|
||||
Compendium.Items.Add(compendiumItem);
|
||||
}
|
||||
|
||||
Progress.Local.End();
|
||||
}
|
||||
|
||||
Progress.Local.Complete();
|
||||
}
|
||||
|
||||
public void Save() { }
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Helpers;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
using Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"foundry-en:external",
|
||||
Description = "Создание цели внешних ссылок",
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:repository"])]
|
||||
internal class ExternalProcessor(FoundryEnGlobals enGlobals) : AbstractSimpleProcessor
|
||||
{
|
||||
public Pf2eDbContext Context { get; } = new();
|
||||
public FoundryEnGlobals EnGlobals { get; } = enGlobals;
|
||||
|
||||
public ExternalTarget FoundryTarget = null!;
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
FoundryTarget = new()
|
||||
{
|
||||
Name = FoundryEnImporter.TargetName,
|
||||
Icon = "http://www.google.com/s2/favicons?domain=foundryvtt.com",
|
||||
Prefix = "",
|
||||
};
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
Context.ExternalTargets.Add(FoundryTarget);
|
||||
Context.SaveChanges();
|
||||
|
||||
EnGlobals.FoundryTarget = FoundryTarget;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.Processors.Definitions;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"foundry-en:repository",
|
||||
Description = "Обновление репозитория",
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||
Active = false
|
||||
)]
|
||||
internal class RepositoryUpdateProcessor(FoundryEnRepository repository, ProcessProgress progress)
|
||||
: IProcessor
|
||||
{
|
||||
public FoundryEnRepository Repository { get; } = repository;
|
||||
public ProcessProgress Progress { get; } = progress;
|
||||
|
||||
public void Process()
|
||||
{
|
||||
Repository.Update(Progress);
|
||||
}
|
||||
|
||||
public void Save() { }
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
using Pf2e.Scrapper.Processors.Attributes;
|
||||
using Pf2e.Scrapper.SchemaBuilder;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"foundry-en:schema",
|
||||
Description = "Построение схемы данных",
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:compendium"])]
|
||||
internal class SchemaBuildProcessor(
|
||||
FoundryEnImporter importer,
|
||||
FoundryEnRepository repository,
|
||||
FoundryEnCompendium compendium,
|
||||
ProcessProgress progress,
|
||||
ApplicationSettings settings
|
||||
) : AbstractSchemaBuildProcessor
|
||||
{
|
||||
/// <inheritdoc cref="FoundryEnImporter"/>
|
||||
protected FoundryEnImporter Importer { get; } = importer;
|
||||
|
||||
/// <inheritdoc cref="FoundryEnRepository"/>
|
||||
protected FoundryEnRepository Repository { get; } = repository;
|
||||
|
||||
/// <inheritdoc cref="FoundryEnCompendium"/>
|
||||
protected override FoundryEnCompendium Compendium { get; } = compendium;
|
||||
|
||||
/// <inheritdoc cref="ProcessProgress"/>
|
||||
protected override ProcessProgress Progress { get; } = progress;
|
||||
|
||||
/// <inheritdoc cref="ApplicationSettings"/>
|
||||
protected ApplicationSettings Settings { get; } = settings;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string SavePath => Path.Combine(Settings.OutputDirPath, Importer.Slug);
|
||||
|
||||
/// <summary>
|
||||
/// Определитель типов данных.
|
||||
/// </summary>
|
||||
protected override SchemaTypeResolver Resolver { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Список типов, являющихся экипировкой.
|
||||
/// </summary>
|
||||
private readonly string[] _equipmentTypes =
|
||||
[
|
||||
"equipment",
|
||||
"armor",
|
||||
"backpack",
|
||||
"consumable",
|
||||
"kit",
|
||||
"shield",
|
||||
"treasure",
|
||||
"weapon",
|
||||
];
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ICollection<string> GetSchemaNodes(AbstractCompendiumItem item)
|
||||
{
|
||||
List<string> nodes = [item.Type];
|
||||
|
||||
// Нода подкласса
|
||||
if (_equipmentTypes.Contains(item.Type))
|
||||
{
|
||||
nodes.Add("_equipment");
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.SchemaBuilder;
|
||||
using Pf2e.Scrapper.SchemaBuilder.Types;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
|
||||
|
||||
internal class SchemaTypeResolver : TypeResolver
|
||||
{
|
||||
public class FoundryRulesSchemaType(SchemaNode parent) : IgnoreSchemaType(parent)
|
||||
{
|
||||
public override string GetTypeName()
|
||||
{
|
||||
return "ignored (Foundry rules object)";
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly List<string> DictionaryPaths =
|
||||
[
|
||||
"character.items.[].system.items",
|
||||
"character.items.[].system.overlays",
|
||||
"ancestry.system.items",
|
||||
"ancestry.system.boosts",
|
||||
"background.system.boosts",
|
||||
"background.system.items",
|
||||
"class.system.items",
|
||||
"consumable.system.spell.system.damage",
|
||||
"consumable.system.spell.system.overlays",
|
||||
"deity.system.spells",
|
||||
"feat.system.subfeatures.proficiencies",
|
||||
"kit.system.items",
|
||||
"kit.system.items.[value].items",
|
||||
"spell.system.damage",
|
||||
"spell.system.heightening.damage",
|
||||
"spell.system.heightening.levels",
|
||||
"spell.system.heightening.levels.[value].damage",
|
||||
"spell.system.overlays",
|
||||
"spell.system.overlays.[value].system.damage",
|
||||
"spell.system.overlays.[value].system.heightening",
|
||||
"spell.system.overlays.[value].system.heightening.[value]",
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Частное определение типа поля.
|
||||
/// </summary>
|
||||
/// <param name="node">Поле схемы данных, для которого создаётся тип.</param>
|
||||
/// <param name="value">Добавляемое значение.</param>
|
||||
/// <returns>Новый тип для схемы данных. `null`, что бы использовать значение по умолчанию</returns>
|
||||
protected override AbstractSchemaType? CustomResolve(SchemaNode node, JToken? value)
|
||||
{
|
||||
return CheckPath(node, DictionaryPaths) ? new DictionarySchemaType(node)
|
||||
: node.Path.EndsWith(".rules") ? new FoundryRulesSchemaType(node)
|
||||
: (AbstractSchemaType?)null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Repositories;
|
||||
|
||||
internal abstract class AbstractGitHubRepository(AbstractImporter importer)
|
||||
: AbstractImporterRepository(importer)
|
||||
{
|
||||
public abstract string Repository { get; }
|
||||
public abstract string AssetName { get; }
|
||||
|
||||
public override void Update(ProcessProgress progress)
|
||||
{
|
||||
ReleaseInformation releaseInformation = GetReleaseInformation();
|
||||
string? currentVersion = GetCurrentVersion();
|
||||
|
||||
if (currentVersion == releaseInformation.TagName)
|
||||
{
|
||||
progress.Local.Complete();
|
||||
progress.Information(
|
||||
$"Данные {Repository} актуальны, текущая версия {currentVersion}."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
progress.Information(
|
||||
$"Данные {Repository} устарели, обновляем {RepositoryDirPath} до версии {releaseInformation.TagName}..."
|
||||
);
|
||||
|
||||
string assetFile = DownloadAsset(progress, releaseInformation);
|
||||
|
||||
ExtractAsset(assetFile, RepositoryDirPath, progress);
|
||||
|
||||
File.WriteAllText(
|
||||
Path.Combine(RepositoryDirPath, "version.txt"),
|
||||
releaseInformation.TagName
|
||||
);
|
||||
}
|
||||
|
||||
private ReleaseInformation GetReleaseInformation()
|
||||
{
|
||||
string releasesUrl = $"https://api.github.com/repos/{Repository}/releases/latest";
|
||||
using HttpClient client = new();
|
||||
client.DefaultRequestHeaders.UserAgent.Add(
|
||||
new ProductInfoHeaderValue("Pf2eDataScrapper", "1.0")
|
||||
);
|
||||
string json = client.GetStringAsync(releasesUrl).Result;
|
||||
JObject jObject = JObject.Parse(json);
|
||||
|
||||
return new()
|
||||
{
|
||||
TagName = jObject.SelectToken("tag_name")!.ToObject<string>()!,
|
||||
DownloadUrl = jObject
|
||||
.SelectToken("assets[?(@.name == '" + AssetName + "')].browser_download_url")!
|
||||
.ToObject<string>()!,
|
||||
Size = jObject
|
||||
.SelectToken("assets[?(@.name == '" + AssetName + "')].size")!
|
||||
.ToObject<int>()!,
|
||||
};
|
||||
}
|
||||
|
||||
private string? GetCurrentVersion()
|
||||
{
|
||||
string versionFilePath = Path.Combine(RepositoryDirPath, "version.txt");
|
||||
if (File.Exists(versionFilePath))
|
||||
{
|
||||
return File.ReadAllText(versionFilePath).Trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string DownloadAsset(ProcessProgress progress, ReleaseInformation releaseInformation)
|
||||
{
|
||||
string fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".zip";
|
||||
using var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write);
|
||||
|
||||
progress.Local.Reset(releaseInformation.Size);
|
||||
progress.Local.Start(
|
||||
$"[1/2] Загрузка {releaseInformation.TagName} ({releaseInformation.Size / 1024 / 1024} МБ)..."
|
||||
);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
using HttpClient client = new();
|
||||
using var response = await client.GetAsync(
|
||||
releaseInformation.DownloadUrl,
|
||||
HttpCompletionOption.ResponseHeadersRead
|
||||
);
|
||||
|
||||
using var download = await response.Content.ReadAsStreamAsync();
|
||||
|
||||
var buffer = new byte[81920];
|
||||
int bytesRead;
|
||||
var t = CancellationToken.None;
|
||||
|
||||
while ((bytesRead = await download.ReadAsync(buffer, t).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await fileStream
|
||||
.WriteAsync(buffer.AsMemory(0, bytesRead), t)
|
||||
.ConfigureAwait(false);
|
||||
progress.Local.Current += bytesRead;
|
||||
}
|
||||
})
|
||||
.Wait();
|
||||
|
||||
progress.Local.Complete();
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private void ExtractAsset(string assetFile, string targetDir, ProcessProgress progress)
|
||||
{
|
||||
using var zip = ZipFile.OpenRead(assetFile);
|
||||
|
||||
progress.Local.Reset(zip.Entries.Count);
|
||||
progress.Local.Start("[2/2] Распаковка архива...");
|
||||
|
||||
foreach (var entry in zip.Entries)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entry.Name))
|
||||
continue; // Skip directories
|
||||
|
||||
string destinationPath = Path.Combine(targetDir, entry.FullName);
|
||||
|
||||
string directoryPath = Path.GetDirectoryName(destinationPath)!;
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
entry.ExtractToFile(destinationPath, true);
|
||||
progress.Local.Current++;
|
||||
}
|
||||
|
||||
progress.Local.Complete();
|
||||
}
|
||||
|
||||
private class ReleaseInformation
|
||||
{
|
||||
public string TagName { get; init; } = null!;
|
||||
public string DownloadUrl { get; init; } = null!;
|
||||
public int Size { get; init; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.IO;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
|
||||
namespace Pf2e.Scrapper.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(ProcessProgress progress);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Репозиторий, загружающий данные из GIT-источника.
|
||||
/// </summary>
|
||||
/// <param name="importer">Импортёр, использующий репозиторий.</param>
|
||||
/// <param name="url">Ссылка на GIT-репозиторий.</param>
|
||||
internal class GitRepository(AbstractImporter importer, string url)
|
||||
: AbstractImporterRepository(importer)
|
||||
{
|
||||
/// <summary>
|
||||
/// Ссылка на GIT-репозиторий.
|
||||
/// </summary>
|
||||
public string Url { get; } = url;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update(ProcessProgress progress)
|
||||
{
|
||||
progress.Local.Reset(1);
|
||||
|
||||
if (Directory.Exists(RepositoryDirPath))
|
||||
{
|
||||
progress.Local.Start("git pull");
|
||||
|
||||
Process process = new()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = "git",
|
||||
WorkingDirectory = RepositoryDirPath,
|
||||
Arguments = "pull",
|
||||
},
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.Local.Start("git clone");
|
||||
|
||||
Process process = new()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = "git",
|
||||
WorkingDirectory = EnvSettings.DataDirPath,
|
||||
Arguments = $"clone {Url} {Importer.Slug}",
|
||||
},
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
|
||||
progress.Local.Complete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>False</UseWindowsForms>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<UseWPF>True</UseWPF>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<DefaultDocumentationFolder>..\..\Documentation\$(MSBuildProjectName)</DefaultDocumentationFolder>
|
||||
<DefaultDocumentationFileNameFactory>Name</DefaultDocumentationFileNameFactory>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||
<EmbeddedResource Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||
<None Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
||||
<PackageReference Include="DefaultDocumentation" Version="1.1.0" PrivateAssets="all" />
|
||||
<PackageReference Include="dotenv.net" Version="3.2.1" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||
<PackageReference Include="FontAwesome.Sharp" Version="6.6.0" />
|
||||
<PackageReference Include="linq2db" Version="5.4.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3240.44" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
<PackageReference Include="Slugify.Core" Version="5.1.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Database\Pf2e.Database.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update=".env">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\ImportAdaptation.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="schema.pgsql">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace Pf2e.Scrapper.Processors.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный аттрибут обработчика.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class AbstractProcessorAttribute(string name) : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Название обработчика.
|
||||
/// </summary>
|
||||
public string Name { get; } = name;
|
||||
|
||||
/// <summary>
|
||||
/// Описание обработчика.
|
||||
/// </summary>
|
||||
public string Description { get; init; } = name;
|
||||
|
||||
/// <summary>
|
||||
/// Группа обработчиков, к которому относится обработчик.
|
||||
/// </summary>
|
||||
public ProcessorGroupEnum Group { get; init; } = ProcessorGroupEnum.General;
|
||||
|
||||
/// <summary>
|
||||
/// Процессор включён по умолчанию.
|
||||
/// </summary>
|
||||
public bool Active { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Разрешить выполнять процессов в параллельном режиме
|
||||
/// </summary>
|
||||
public bool Parallel { get; init; } = false;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Pf2e.Scrapper.Processors.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Общий аттрибут обработчика.
|
||||
/// </summary>
|
||||
/// <param name="name">Название обработчика.</param>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class ProcessorAttribute(string name) : AbstractProcessorAttribute(name) { }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user