From a9f9ae4b93eefb6c86a693fe4eee754db58623d3 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Mon, 12 May 2025 16:01:10 +0400 Subject: [PATCH 01/33] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=BE=D1=82=D1=87=D0=B8=D1=81=D1=82=D0=BA=D0=B8=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D1=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Aonprd/DataBuilder/AonprdDataBuilder.cs | 11 +- .../DataBuilder/PF2eFoundryDataBuilder.cs | 8 +- Pf2eDataScrapper/Pf2eDatabase.cs | 152 ------------------ Pf2eModel/EfCleanHelper.cs | 97 +++++++++++ .../Migrations/20250418062358_Function.cs | 19 --- Pf2eModel/Pf2eDbContextModel.cs | 7 +- 6 files changed, 113 insertions(+), 181 deletions(-) delete mode 100644 Pf2eDataScrapper/Pf2eDatabase.cs create mode 100644 Pf2eModel/EfCleanHelper.cs diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs index 1f94777..9fb3ca6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; +using Pf2eModel; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; @@ -11,11 +12,6 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; /// internal class AonprdDataBuilder(AonprdLoader loader) { - /// - /// Класс работы с базой данных. - /// - public readonly Pf2eDatabase Database = Pf2eDatabase.Instance; - /// /// Основной класс для работы с данными Архива Нефиса. /// @@ -38,7 +34,10 @@ internal class AonprdDataBuilder(AonprdLoader loader) { Console.WriteLine("Truncating all old data..."); - Database.ExecuteCommand("SELECT f_truncate_tables();"); + using Pf2eDbContext context = new(); + string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll(); + + Console.WriteLine(cmd); Console.WriteLine("Truncation completed!"); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs index 0249020..cd88174 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs @@ -2,13 +2,12 @@ using Konsole; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; +using Pf2eModel; namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; internal class PF2eFoundryDataBuilder { - public Pf2eDatabase Database = Pf2eDatabase.Instance; - /// /// Загрузчик данных. /// @@ -51,7 +50,10 @@ internal class PF2eFoundryDataBuilder { Console.WriteLine("Truncating all old data..."); - Database.ExecuteCommand("SELECT f_truncate_tables();"); + using Pf2eDbContext context = new(); + string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll(); + + Console.WriteLine(cmd); Console.WriteLine("Truncation completed!"); } diff --git a/Pf2eDataScrapper/Pf2eDatabase.cs b/Pf2eDataScrapper/Pf2eDatabase.cs deleted file mode 100644 index b3197f6..0000000 --- a/Pf2eDataScrapper/Pf2eDatabase.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Text.RegularExpressions; -using Npgsql; - -namespace Pf2eDataScrapper; - -/// -/// Main class for database connection and operations. -/// -public partial class Pf2eDatabase -{ - /// - /// Подключение к базе данных. - /// - public readonly NpgsqlConnection Connection; - - /// - /// Имя пользователя для подключения к базе данных. - /// - public readonly string Username; - - /// - /// Название базы данных. - /// - public readonly string DatabaseName; - - private Pf2eDatabase() - { - Username = UsernamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - DatabaseName = DatabaseNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - - Connection = new(EnvSettings.DatabaseConnection); - Connection.Open(); - } - - ~Pf2eDatabase() - { - Connection.Close(); - } - - /// - /// Singleton instance of the database connection manager. - /// - public static Pf2eDatabase Instance { get; } = new(); - - /// - /// Current transaction associated with this database connection. - /// - public NpgsqlTransaction? Transaction { get; private set; } - - /// - /// Begin a new transaction on the database connection. - /// - public void StartTransaction() - { - if (Transaction != null) - { - throw new InvalidOperationException("Transaction already started."); - } - - Transaction = Connection.BeginTransaction(); - } - - /// - /// Commit the current transaction to the database. - /// - public void Commit() - { - if (Transaction == null) - { - throw new InvalidOperationException("No transaction started."); - } - - Transaction.Commit(); - } - - /// - /// Execute a SQL command against the database. - /// - /// The SQL command to execute - public void ExecuteCommand(string sql) - { - NpgsqlCommand command = GetCommand(sql); - - command.ExecuteNonQuery(); - } - - /// - /// Creates a new SQL command for the database connection. - /// - /// The SQL command string - /// A new NpgsqlCommand object - public NpgsqlCommand GetCommand(string sql) - { - return new(sql, Connection); - } - - /* - public void RebuildDatabase() - { - // Закрываем текущее соединение - Connection.Close(); - - // Получение имён - var databaseNamePattern = DatabaseNamePattern(); - var userConnection = databaseNamePattern.Replace(EnvSettings.DatabaseConnection, ";"); - var databaseName = databaseNamePattern.Match(EnvSettings.DatabaseConnection).Groups[1].Value; - var username = UserNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - - // Временное подключение к базе данных - NpgsqlConnection tmpConnection = new(userConnection); - tmpConnection.Open(); - - // Удаляем базу данных - NpgsqlCommand dropCommand = new($"DROP DATABASE \"{databaseName}\";", tmpConnection); - dropCommand.ExecuteNonQuery(); - - // Создаём новую базу данных - NpgsqlCommand createCommand = new($@" - CREATE DATABASE {databaseName} - WITH OWNER = {username} - ENCODING = 'UTF8' - CONNECTION LIMIT = -1; - ", tmpConnection); - createCommand.ExecuteNonQuery(); - - string[] permissionCommands = [ - $"GRANT TEMPORARY, CONNECT ON DATABASE {databaseName} TO PUBLIC;", - $"GRANT ALL ON DATABASE {databaseName} TO {username} WITH GRANT OPTION;", - $"GRANT ALL ON DATABASE {databaseName} TO postgres;" - ]; - - foreach (var command in permissionCommands) - { - NpgsqlCommand permissionCommand = new(command, tmpConnection); - permissionCommand.ExecuteNonQuery(); - } - - // Закрываем временное соединение - tmpConnection.Close(); - - // Заново создаём соединение - Connection = new NpgsqlConnection(EnvSettings.DatabaseConnection); - } - - */ - - [GeneratedRegex(@"(?:^|;)Database=(.*?)(?:$|;)")] - private static partial Regex DatabaseNamePattern(); - - [GeneratedRegex(@"(?:^|;)User=(.*?)(?:$|;)")] - private static partial Regex UsernamePattern(); -} diff --git a/Pf2eModel/EfCleanHelper.cs b/Pf2eModel/EfCleanHelper.cs new file mode 100644 index 0000000..1cb9cee --- /dev/null +++ b/Pf2eModel/EfCleanHelper.cs @@ -0,0 +1,97 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Pf2eModel; + +/// +/// Класс, обеспецивающий общий способ для отчистки данных таблиц. +/// +/// Источник: https://www.codeproject.com/Articles/5339402/Delete-All-Rows-in-Entity-Framework-Core-6 +/// +public static class EfCleanHelper +{ + /// + /// Определение полного названия таблицы по служебному классу. + /// + /// Служебный класс, определяющий таблицу базы данных. + /// Схема таблицы по умолчанию ("public" для PostgresSQL) + /// + private static string GetName(IEntityType entityType, string defaultSchemaName = "public") + { + object? schema = entityType.FindAnnotation("Relational:Schema")?.Value; + + string tableName = + entityType.GetAnnotation("Relational:TableName")?.Value?.ToString() + ?? throw new($"Не могу определить название таблицы для типа {entityType}"); + + string schemaName = schema?.ToString() ?? defaultSchemaName; + + string name = $"\"{schemaName}\".\"{tableName}\""; + return name; + } + + /// + /// Определение полного названия таблицы по типу сущности. + /// + /// Тип искомой сущности. + /// Контекст, в котором производится поиск таблицы. + /// Строковое представление названия таблицы. + public static string TableName(DbContext dbContext) + where T : class + { + IEntityType entityType = + dbContext.Model.FindEntityType(typeof(T)) + ?? throw new($"Не могу определить название таблицы для класса {typeof(T)}"); + + return GetName(entityType); + } + + /// + /// Определение полного названия таблицы по типу сущности. + /// + /// Тип искомой сущности. + /// CLR-представление таблицы, название которой требуется определить. + /// Строковое представление названия таблицы. + public static string TableName(DbSet dbSet) + where T : class + { + IEntityType entityType = dbSet.EntityType; + return GetName(entityType); + } + + /// + /// Отчистка содержимого таблицы. + /// + /// Тип сущности, таблицу которой необходимо отчистить. + /// CLR-представление таблицы, которую требуется отчистить. + /// Строковое представление выполненной SQL-команды. + public static string Truncate(this DbSet dbSet) + where T : class + { + string cmd = $"TRUNCATE TABLE {TableName(dbSet)} CASCADE;"; + DbContext context = dbSet.GetService().Context; + context.Database.ExecuteSqlRaw(cmd); + return cmd; + } + + /// + /// Получение SQL-команды отчистки содержимого всех таблиц базы данных. + /// + /// Контекст базы данных, в которой необходимо отчистить все строки. + /// Строковое представление выполненных SQL-команды. + public static string TruncateAll(this DbContext context) + { + IEnumerable tables = context.Model.GetEntityTypes(); + List cmdList = []; + + foreach (IEntityType entityType in tables) + { + string cmd = $"TRUNCATE TABLE {GetName(entityType)} CASCADE;"; + context.Database.ExecuteSqlRaw(cmd); + cmdList.Add(cmd); + } + + return string.Join('\n', cmdList); + } +} diff --git a/Pf2eModel/Migrations/20250418062358_Function.cs b/Pf2eModel/Migrations/20250418062358_Function.cs index d735d1d..f26cf8e 100644 --- a/Pf2eModel/Migrations/20250418062358_Function.cs +++ b/Pf2eModel/Migrations/20250418062358_Function.cs @@ -12,25 +12,6 @@ public partial class Function : Migration { migrationBuilder.Sql( """ -CREATE OR REPLACE FUNCTION f_truncate_tables() - RETURNS void - LANGUAGE plpgsql AS -$func$ -BEGIN - EXECUTE - (SELECT 'TRUNCATE TABLE ' - || string_agg(format('%I.%I', schemaname, tablename), ', ') - || ' CASCADE;' - FROM pg_tables - WHERE schemaname = 'public' - AND tablename != '__EFMigrationsHistory' - ); -END -$func$; -""" - ); - migrationBuilder.Sql( - """ CREATE OR REPLACE FUNCTION f_drop_tables() RETURNS void LANGUAGE plpgsql AS diff --git a/Pf2eModel/Pf2eDbContextModel.cs b/Pf2eModel/Pf2eDbContextModel.cs index 5966a03..99e5fb6 100644 --- a/Pf2eModel/Pf2eDbContextModel.cs +++ b/Pf2eModel/Pf2eDbContextModel.cs @@ -52,7 +52,12 @@ public partial class Pf2eDbContext private void BindEntity(EntityTypeBuilder entity) where T : class, IEntity { - entity.HasOne(e => e.Entity).WithOne().HasForeignKey(e => e.Id).IsRequired(); + entity + .HasOne(e => e.Entity) + .WithOne() + .HasForeignKey(e => e.Id) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); } /// From f3d710d7d7c161485d26eeb4bbff0bab4b2ce4f3 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Mon, 12 May 2025 19:17:40 +0400 Subject: [PATCH 02/33] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC=D0=BC=D1=8B?= =?UTF-8?q?=20(=D0=BD=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=BC=D0=B5=D0=B6=D1=83=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/Attributes.cs | 62 +++++ Pf2eDataScrapper/App/ProcessorCollection.cs | 229 ++++++++++++++++++ Pf2eDataScrapper/App/ProcessorGroupEnum.cs | 21 ++ Pf2eDataScrapper/ApplicationSettings.cs | 80 ++++++ Pf2eDataScrapper/Forms/StartForm.Designer.cs | 166 +++++++++++++ Pf2eDataScrapper/Forms/StartForm.cs | 9 + Pf2eDataScrapper/Forms/StartForm.resx | 120 +++++++++ Pf2eDataScrapper/ImportApplication.cs | 58 +++++ .../Aonprd/DataBuilder/AonprdDataBuilder.cs | 1 + .../DataBuilder/AonprdProcessorAttribute.cs | 51 ++-- .../DataBuilder/AonprdProcessorCollection.cs | 2 +- .../DataBuilder/AonprdProcessorInput.cs | 1 + .../Processors/AbstractEntityProcessor.cs | 1 + .../Processors/AbstractProcessor.cs | 49 ---- .../Processors/Common/ExternalProcessor.cs | 3 +- .../Processors/Common/SkillProcessor.cs | 3 +- .../Processors/Common/SourceProcessor.cs | 3 +- .../Processors/Types/FeatProcessor.cs | 2 +- .../Types/Rules/RulesOrderProcessor.cs | 1 + .../Types/Rules/RulesTocProcessor.cs | 1 + .../Compendium/CompendiumFolder.cs | 2 +- .../DataBuilder/AbstractProcessor.cs | 7 +- .../DataBuilder/PF2eFoundryDataBuilder.cs | 1 + .../DataBuilder/Processors/ActionProcessor.cs | 1 + .../Processors/ConditionProcessor.cs | 1 + Pf2eDataScrapper/Pf2eDataScrapper.csproj | 8 +- .../Processors/AbstractInputProcessor.cs | 44 ++++ .../Processors/AbstractProcessor.cs | 35 +++ Pf2eDataScrapper/Processors/IProcessor.cs | 21 ++ .../ProcessorInput.cs | 2 +- Pf2eDataScrapper/Program.cs | 33 +-- Pf2eViewer/Pf2eViewer.csproj | 3 - 32 files changed, 914 insertions(+), 107 deletions(-) create mode 100644 Pf2eDataScrapper/App/Attributes.cs create mode 100644 Pf2eDataScrapper/App/ProcessorCollection.cs create mode 100644 Pf2eDataScrapper/App/ProcessorGroupEnum.cs create mode 100644 Pf2eDataScrapper/ApplicationSettings.cs create mode 100644 Pf2eDataScrapper/Forms/StartForm.Designer.cs create mode 100644 Pf2eDataScrapper/Forms/StartForm.cs create mode 100644 Pf2eDataScrapper/Forms/StartForm.resx create mode 100644 Pf2eDataScrapper/ImportApplication.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractProcessor.cs create mode 100644 Pf2eDataScrapper/Processors/AbstractInputProcessor.cs create mode 100644 Pf2eDataScrapper/Processors/AbstractProcessor.cs create mode 100644 Pf2eDataScrapper/Processors/IProcessor.cs rename Pf2eDataScrapper/{Importers => Processors}/ProcessorInput.cs (99%) diff --git a/Pf2eDataScrapper/App/Attributes.cs b/Pf2eDataScrapper/App/Attributes.cs new file mode 100644 index 0000000..df60b8b --- /dev/null +++ b/Pf2eDataScrapper/App/Attributes.cs @@ -0,0 +1,62 @@ +namespace Pf2eDataScrapper.App; + +/// +/// Общий аттрибут обработчика. +/// +/// Название обработчика. +/// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика. +[AttributeUsage(AttributeTargets.Class)] +public class ProcessAttribute(string name, ICollection dependencies) : Attribute +{ + /// + /// Название обработчика. + /// + public string Name { get; init; } = name; + + /// + /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. + /// + public ICollection Dependencies { get; init; } = dependencies; + + public ProcessAttribute(string name) + : this(name, []) { } +} + +/// +/// Аттрибут обработчика для уточнения группы. +/// +/// Группа обработчиков, к которому относится обработчик. +[AttributeUsage(AttributeTargets.Class)] +internal class ProcessorGroupAttribute(ProcessorGroupEnum group) : Attribute +{ + /// + /// Название обработчика, к которому относится обработчик. + /// + public ProcessorGroupEnum Group { get; init; } = group; +} + +/// +/// Аттрибут обработчика для уточнения списка зависимостей. +/// +/// Список названий зависимостей. +[AttributeUsage(AttributeTargets.Class)] +internal class ProcessorDependenciesAttribute(ICollection dependencies) : Attribute +{ + /// + /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. + /// + public ICollection Dependencies { get; init; } = dependencies; +} + +/// +/// Аттрибут обработчика для внутреннего типа элемента. +/// +/// Внутренний типа элемента. +[AttributeUsage(AttributeTargets.Class)] +internal class ProcessorInnerTypeAttribute(string innerType) : Attribute +{ + /// + /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. + /// + public string InnerType { get; init; } = innerType; +} diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs new file mode 100644 index 0000000..ffea206 --- /dev/null +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -0,0 +1,229 @@ +using System.ComponentModel; +using System.Reflection; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.App; + +/// +/// Класс с коллекцией обработчиков. +/// +internal class ProcessorCollection +{ + /// + /// Коллекция всех доступных обработчиков. + /// + public ICollection AllItems { get; } = []; + + /// + /// Коллекция обработчиков, которые будут обрабатываться. + /// Для изменения необходимо использовать . + /// + public List Items { get; private set; } = []; + + /// + /// Конструктор класса . + /// + public ProcessorCollection() + { + Assembly assembly = Assembly.GetExecutingAssembly(); + + foreach (Type type in assembly.GetTypes()) + { + if (type.GetCustomAttributes(typeof(ProcessAttribute), true).Length <= 0) + { + continue; + } + + AllItems.Add(CreateItem(type)); + } + + CheckDependencies(); + FilterProcessors([]); + } + + /// + /// Создание элемента коллекции обработчиков. + /// + /// Тип обработчика. + /// + private ProcessorCollectionItem CreateItem(Type type) + { + if (!type.IsSubclassOf(typeof(IProcessor))) + { + throw new( + $"'ProcessAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not." + ); + } + + ProcessAttribute processAttribute = type.GetCustomAttribute()!; + DescriptionAttribute? descriptionAttribute = + type.GetCustomAttribute(); + ProcessorGroupAttribute? groupAttribute = + type.GetCustomAttribute(); + ProcessorDependenciesAttribute? dependenciesAttribute = + type.GetCustomAttribute(); + ProcessorInnerTypeAttribute? innerTypeAttribute = + type.GetCustomAttribute(); + + bool isCollection = innerTypeAttribute is not null; + + ConstructorInfo? constructor = isCollection + ? type.GetConstructor([]) + : type.GetConstructor([typeof(ProcessorInput)])!; + + if (constructor is null) + { + throw new($"Не удаётся определить конструктор класса {type}"); + } + + return new() + { + Name = processAttribute.Name, + Type = type, + Description = descriptionAttribute?.Description ?? processAttribute.Name, + Group = groupAttribute?.Group ?? ProcessorGroupEnum.General, + Dependencies = dependenciesAttribute?.Dependencies ?? [], + IsCollectionProcessor = isCollection, + InnerType = innerTypeAttribute?.InnerType, + Constructor = constructor, + }; + } + + /// + /// Проверка, что все зависимости всех обработчиков могут быть удовлетворены. + /// + private void CheckDependencies() + { + foreach (ProcessorCollectionItem item in Items) + { + foreach (string dependency in item.Dependencies) + { + if (Items.All(i => i.Name != dependency)) + { + throw new($"Dependency '{dependency}' for processor '{item.Name}' not found."); + } + } + } + } + + /// + /// Сортировать обработчики в соответсвии со всеми зависимостями. + /// + public void SortItems() + { + Dictionary priorities = []; + + while (priorities.Count < Items.Count) + { + foreach (ProcessorCollectionItem item in Items) + { + if (item.Priority > 0) + { + continue; + } + + IEnumerable dependencyPriorities = item.Dependencies.Select(d => + priorities.GetValueOrDefault(d, -1) + ); + + if (dependencyPriorities.Any(p => p == -1)) + { + continue; + } + + item.Priority = dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1; + priorities.Add(item.Name, item.Priority); + } + } + + Items.Sort((a, b) => a.Priority - b.Priority); + } + + /// + /// Ограничить список используемых обработчиков по названию. + /// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики. + /// + /// Коллекция названиий требуемых обработчиков. + public void FilterProcessors(ICollection processorNames) + { + if (processorNames.Count == 0) + { + Items = [.. AllItems]; + SortItems(); + return; + } + + Dictionary nameFlags = processorNames.ToDictionary(name => name, _ => false); + + while (nameFlags.Values.Any(v => !v)) + { + foreach ( + KeyValuePair pair in nameFlags.Where(pair => !pair.Value).ToList() + ) + { + ProcessorCollectionItem item = AllItems.First(item => item.Name == pair.Key); + + foreach (string dependency in item.Dependencies) + { + nameFlags.TryAdd(dependency, false); + } + + nameFlags[pair.Key] = true; + } + } + + Items = AllItems.Where(item => nameFlags.ContainsKey(item.Name)).ToList(); + } +} + +/// +/// Элемент коллекции обработчиков. +/// +internal class ProcessorCollectionItem +{ + /// + /// Название обработчика. + /// + public required string Name { get; init; } + + /// + /// Тип класса обработчика. + /// + public required Type Type { get; init; } + + /// + /// Описание обработчика. + /// + public string Description { get; init; } = ""; + + /// + /// Группа обработчика. + /// + public required ProcessorGroupEnum Group { get; init; } + + /// + /// Коллекция названий обработчиков, после которых должен выполняться обработчик. + /// + public ICollection Dependencies { get; init; } = []; + + /// + /// Приоритет выполнения обработчика. + /// + public int Priority { get; set; } = -1; + + /// + /// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов. + /// Внутренний тип элементов указывается в . + /// + public required bool IsCollectionProcessor { get; init; } + + /// + /// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов. + /// + public string? InnerType { get; init; } + + /// + /// Конструктор обработчика. + /// + public required ConstructorInfo Constructor { get; init; } +} diff --git a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs new file mode 100644 index 0000000..09d40ac --- /dev/null +++ b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; + +namespace Pf2eDataScrapper.App; + +public enum ProcessorGroupEnum +{ + [Description("Общие процессоры")] + General, + + [Description("Архивы Нефиса: Подготовка")] + AonprdPreparation, + + [Description("Архивы Нефиса: Основные сущности")] + AonprdEntities, + + [Description("Архивы Нефиса: Подклассы")] + AonprdSubclasses, + + [Description("Foundry EN: Подготовка")] + FoundryEnPreparation, +} diff --git a/Pf2eDataScrapper/ApplicationSettings.cs b/Pf2eDataScrapper/ApplicationSettings.cs new file mode 100644 index 0000000..48a80a7 --- /dev/null +++ b/Pf2eDataScrapper/ApplicationSettings.cs @@ -0,0 +1,80 @@ +using System.ComponentModel; +using dotenv.net; +using dotenv.net.Utilities; + +namespace Pf2eDataScrapper; + +/// +/// Настройки программы. +/// +internal class ApplicationSettings +{ + /// + /// Конструктор класса . + /// + public ApplicationSettings() + { + DotEnv.Load(); + } + + #region Настройки из .env + + /// + /// Путь к папке с входными данными. + /// + [Browsable(true)] + [ReadOnly(true)] + [DisplayName("Путь к папке с входными данными.")] + [Description("Редактирование через .env-файл.")] + public string DataDirPath { get; } = EnvReader.GetStringValue("DATA_DIR"); + + /// + /// Папка в входными данными. + /// + [Browsable(false)] + public DirectoryInfo DataDir => new(DataDirPath); + + /// + /// Путь к папке для сохранения результатов. + /// + [Browsable(true)] + [ReadOnly(true)] + [DisplayName("Путь к папке для сохранения результатов.")] + [Description("Редактирование через .env-файл.")] + public string OutputDirPath { get; } = EnvReader.GetStringValue("OUTPUT_DIR"); + + /// + /// Папка для сохранения результатов. + /// + [Browsable(false)] + public string OutputDir => new(OutputDirPath); + + /// + /// Строка подключения к базе данных. + /// + [Browsable(true)] + [ReadOnly(true)] + [DisplayName("Строка подключения к базе данных.")] + [Description("Редактирование через .env-файл.")] + public string DatabaseConnection { get; } = EnvReader.GetStringValue("DATABASE_CONNECTION"); + + #endregion + + #region Основные настройки + + /// + /// Флаг, указывающий, что все старые записи необходимо удалить. + /// + [Browsable(true)] + [DisplayName("Удаление всех записей перед запуском.")] + [Description("Редактирование через .env-файл.")] + public bool TruncateAllTables { get; set; } = true; + + /// + /// Список обработчиков, которые должны быть выполнены. + /// Пустой список означает, что должны быть выполнены все обработчики. + /// + public ICollection ProcessorNames { get; } = new List(); + + #endregion +} diff --git a/Pf2eDataScrapper/Forms/StartForm.Designer.cs b/Pf2eDataScrapper/Forms/StartForm.Designer.cs new file mode 100644 index 0000000..387245b --- /dev/null +++ b/Pf2eDataScrapper/Forms/StartForm.Designer.cs @@ -0,0 +1,166 @@ +namespace Pf2eDataScrapper.Forms; + +partial class StartForm +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + MainSplitContainer = new SplitContainer(); + PropertiesGroupBox = new GroupBox(); + SettingsPropertyGrid = new PropertyGrid(); + StartButton = new Button(); + ProcessorsGroupBox = new GroupBox(); + DeselectAllButton = new Button(); + SelectAllButton = new Button(); + ProcessorsListView = new ListView(); + ((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit(); + MainSplitContainer.Panel1.SuspendLayout(); + MainSplitContainer.Panel2.SuspendLayout(); + MainSplitContainer.SuspendLayout(); + PropertiesGroupBox.SuspendLayout(); + ProcessorsGroupBox.SuspendLayout(); + SuspendLayout(); + // + // MainSplitContainer + // + MainSplitContainer.Dock = DockStyle.Fill; + MainSplitContainer.Location = new Point(0, 0); + MainSplitContainer.Name = "MainSplitContainer"; + // + // MainSplitContainer.Panel1 + // + MainSplitContainer.Panel1.Controls.Add(PropertiesGroupBox); + MainSplitContainer.Panel1MinSize = 300; + // + // MainSplitContainer.Panel2 + // + MainSplitContainer.Panel2.Controls.Add(ProcessorsGroupBox); + MainSplitContainer.Panel2.Controls.Add(StartButton); + MainSplitContainer.Panel2MinSize = 300; + MainSplitContainer.Size = new Size(931, 677); + MainSplitContainer.SplitterDistance = 300; + MainSplitContainer.TabIndex = 0; + // + // PropertiesGroupBox + // + PropertiesGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + PropertiesGroupBox.Controls.Add(SettingsPropertyGrid); + PropertiesGroupBox.Location = new Point(3, 3); + PropertiesGroupBox.Name = "PropertiesGroupBox"; + PropertiesGroupBox.Size = new Size(294, 671); + PropertiesGroupBox.TabIndex = 0; + PropertiesGroupBox.TabStop = false; + PropertiesGroupBox.Text = "Свойства"; + // + // SettingsPropertyGrid + // + SettingsPropertyGrid.BackColor = SystemColors.Control; + SettingsPropertyGrid.Dock = DockStyle.Fill; + SettingsPropertyGrid.Location = new Point(3, 19); + SettingsPropertyGrid.Name = "SettingsPropertyGrid"; + SettingsPropertyGrid.Size = new Size(288, 649); + SettingsPropertyGrid.TabIndex = 0; + // + // StartButton + // + StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold); + StartButton.Location = new Point(3, 611); + StartButton.Name = "StartButton"; + StartButton.Size = new Size(621, 63); + StartButton.TabIndex = 0; + StartButton.Text = "Запуск"; + StartButton.UseVisualStyleBackColor = true; + // + // ProcessorsGroupBox + // + ProcessorsGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + ProcessorsGroupBox.Controls.Add(ProcessorsListView); + ProcessorsGroupBox.Controls.Add(SelectAllButton); + ProcessorsGroupBox.Controls.Add(DeselectAllButton); + ProcessorsGroupBox.Location = new Point(3, 3); + ProcessorsGroupBox.Name = "ProcessorsGroupBox"; + ProcessorsGroupBox.Size = new Size(621, 602); + ProcessorsGroupBox.TabIndex = 1; + ProcessorsGroupBox.TabStop = false; + ProcessorsGroupBox.Text = "Обработчики"; + // + // DeselectAllButton + // + DeselectAllButton.Location = new Point(6, 19); + DeselectAllButton.Name = "DeselectAllButton"; + DeselectAllButton.Size = new Size(137, 23); + DeselectAllButton.TabIndex = 0; + DeselectAllButton.Text = "Снять выделение всех"; + DeselectAllButton.UseVisualStyleBackColor = true; + // + // SelectAllButton + // + SelectAllButton.Location = new Point(149, 19); + SelectAllButton.Name = "SelectAllButton"; + SelectAllButton.Size = new Size(137, 23); + SelectAllButton.TabIndex = 1; + SelectAllButton.Text = "Выделить все"; + SelectAllButton.UseVisualStyleBackColor = true; + // + // ProcessorsListView + // + ProcessorsListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + ProcessorsListView.CheckBoxes = true; + ProcessorsListView.Location = new Point(6, 48); + ProcessorsListView.Name = "ProcessorsListView"; + ProcessorsListView.Size = new Size(606, 548); + ProcessorsListView.TabIndex = 2; + ProcessorsListView.UseCompatibleStateImageBehavior = false; + // + // StartForm + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(931, 677); + Controls.Add(MainSplitContainer); + Name = "StartForm"; + Text = "Настройки импорта данных"; + MainSplitContainer.Panel1.ResumeLayout(false); + MainSplitContainer.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)MainSplitContainer).EndInit(); + MainSplitContainer.ResumeLayout(false); + PropertiesGroupBox.ResumeLayout(false); + ProcessorsGroupBox.ResumeLayout(false); + ResumeLayout(false); + } + + #endregion + + private SplitContainer MainSplitContainer; + private GroupBox PropertiesGroupBox; + private PropertyGrid SettingsPropertyGrid; + private Button StartButton; + private GroupBox ProcessorsGroupBox; + private Button SelectAllButton; + private Button DeselectAllButton; + private ListView ProcessorsListView; +} \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/StartForm.cs b/Pf2eDataScrapper/Forms/StartForm.cs new file mode 100644 index 0000000..70bbf4d --- /dev/null +++ b/Pf2eDataScrapper/Forms/StartForm.cs @@ -0,0 +1,9 @@ +namespace Pf2eDataScrapper.Forms; + +public partial class StartForm : Form +{ + public StartForm() + { + InitializeComponent(); + } +} diff --git a/Pf2eDataScrapper/Forms/StartForm.resx b/Pf2eDataScrapper/Forms/StartForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/Pf2eDataScrapper/Forms/StartForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs new file mode 100644 index 0000000..18cf2ee --- /dev/null +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -0,0 +1,58 @@ +using Microsoft.Extensions.DependencyInjection; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Forms; +using Pf2eModel; + +namespace Pf2eDataScrapper; + +internal sealed class ImportApplication : ApplicationContext +{ + public ImportApplication() + { + InitializeServices(); + CreateStartForm(); + } + + #region "Внедрение зависимостей (DI)" + + public IServiceCollection Services = new ServiceCollection(); + + private void InitializeServices() + { + Services.AddSingleton(this); + Services.AddSingleton(Settings); + + Services.AddSingleton(); + + Services.AddDbContext(); + } + + #endregion + + #region "Настройки" + + public ApplicationSettings Settings = new(); + + #endregion + + #region "Формы" + + private StartForm? _startForm; + + #endregion + + #region "Жизненный цикл" + + private void CreateStartForm() + { + _startForm = new() { StartPosition = FormStartPosition.CenterParent }; + _startForm.Show(); + } + + private void StartProcess() + { + _startForm!.Close(); + } + + #endregion +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs index 9fb3ca6..ae02027 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; +using Pf2eDataScrapper.Processors; using Pf2eModel; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorAttribute.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorAttribute.cs index faac790..301d742 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorAttribute.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorAttribute.cs @@ -1,43 +1,22 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; +using Pf2eDataScrapper.App.Attributes; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; /// /// Атрибут обработчика данных Архивов Нефиса. /// -/// Название обработчика. -/// Тип данных, которые обработчик обрабатывает. -/// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика. -/// Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка. [AttributeUsage(AttributeTargets.Class)] -public class AonprdProcessorAttribute( - string name, - string? type, - string[] dependencies, - bool includeDefaults = true -) : Attribute +public class AonprdProcessorAttribute : Attributes { /// /// Важные общие зависимости для обработчка. /// - private static readonly string[] DefaultDependencies = ["trait", "source"]; - - /// - /// Название обработчика. - /// - public readonly string Name = name; + private static readonly string[] DefaultDependencies = ["aonprd:trait", "aonprd:source"]; /// /// Тип данных, которые обработчик обрабатывает. /// - public readonly string? Type = type; - - /// - /// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика. - /// - public readonly IEnumerable Dependencies = - [ - .. includeDefaults ? DefaultDependencies : [], - .. dependencies, - ]; + public string Type { get; init; } /// /// Конструктор класса со стандартными общими зависимости для обработчка. @@ -62,4 +41,22 @@ public class AonprdProcessorAttribute( /// Тип данных, которые обработчик обрабатывает. public AonprdProcessorAttribute(string name, string type) : this(name, type, DefaultDependencies) { } + + /// + /// Основной конструктор класса . + /// + /// Название обработчика. + /// Тип данных, которые обработчик обрабатывает. + /// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика. + /// Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка. + public AonprdProcessorAttribute( + string name, + string? type, + ICollection dependencies, + bool includeDefaults = true + ) + : base($"aonprd:{name}", [.. includeDefaults ? DefaultDependencies : [], .. dependencies]) + { + Type = type; + } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs index 9d66d8a..4ec1ccf 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs @@ -1,5 +1,5 @@ using System.Reflection; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; +using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs index d369fec..d2fde33 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractEntityProcessor.cs index 7876221..03f14a5 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractEntityProcessor.cs @@ -3,6 +3,7 @@ using System.Text; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model; using Pf2eModel.Model.Base; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractProcessor.cs deleted file mode 100644 index 2129587..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractProcessor.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; -using Pf2eModel; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; - -/// -/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса. -/// Класс предназначен для обработки данных с помощью различных обработчиков, -/// для которых указан атрибут . -/// -/// Объект обработки вводимого JSON. -/// -internal abstract class AbstractProcessor(AonprdProcessorInput input) -{ - /// - /// Вспомогательный класс для работы с базой данных. - /// - protected readonly DatabaseHelper Helper = DatabaseHelper.Instance; - - /// - /// Вспомогательный класс для работы со строками и форматированием. - /// - protected readonly StringHelper StringHelper = StringHelper.Instance; - - /// - /// Вспомогательный класс для работы с глобальными настройками. - /// - protected readonly AonprdBuilderGlobals Globals = AonprdBuilderGlobals.Instance; - - /// - /// Контекст базы данных для работы с данными. - /// - public readonly Pf2eDbContext Context = DatabaseHelper.DbContext; - - /// - /// Объект обработки вводимого JSON. - /// - public readonly AonprdProcessorInput Input = input; - - /// - /// Основной метод обработки данных. - /// - public abstract void Process(); - - /// - /// Метод для сохранения изменений в базе данных. - /// - public abstract void Save(); -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/ExternalProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/ExternalProcessor.cs index fd688fc..18508df 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/ExternalProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/ExternalProcessor.cs @@ -1,4 +1,5 @@ -using Pf2eModel.Model.Base.Targets; +using Pf2eDataScrapper.Processors; +using Pf2eModel.Model.Base.Targets; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs index 26ab2ed..f0b6ef4 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs @@ -1,4 +1,5 @@ -using Pf2eModel.Model.Common; +using Pf2eDataScrapper.Processors; +using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SourceProcessor.cs index 8f0a417..1cd6a68 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SourceProcessor.cs @@ -1,4 +1,5 @@ -using Pf2eModel.Model.Base.Source; +using Pf2eDataScrapper.Processors; +using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/FeatProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/FeatProcessor.cs index aa93139..c863398 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/FeatProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/FeatProcessor.cs @@ -53,7 +53,7 @@ internal class FeatProcessor(AonprdProcessorInput input) : AbstractEntityProcess Archetype[] archetypes = Context // ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall .Archetypes.Where(a => Pf2eDbContext.LocaleSearchArray(a.Entity.Name, archetypeStrings)) - // .Where(a => archetypeStrings.Contains(a.Entity.Name.ToString())) + // .Where(a => archetypeStrings.Contains(a.Entity.Group.ToString())) .ToArray(); if (archetypes.Length != 0) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesOrderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesOrderProcessor.cs index 98c23d3..9d673e3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesOrderProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesOrderProcessor.cs @@ -6,6 +6,7 @@ // Ну а пока просто расставляем родительские и дочерние элементы. using System.Data; +using Pf2eDataScrapper.Processors; using Pf2eModel.Model.Structure; using Rule = Pf2eModel.Model.Structure.Rule; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesTocProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesTocProcessor.cs index 511d4a4..5d76d93 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesTocProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesTocProcessor.cs @@ -2,6 +2,7 @@ using System.Web; using AngleSharp; using AngleSharp.Dom; +using Pf2eDataScrapper.Processors; using Pf2eModel.Model.Base.Source; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules; diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs index 07557b5..e51fe04 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs @@ -83,6 +83,6 @@ internal class CompendiumFolder /// Строковое представление данных о папки компендиума. public override string ToString() { - return $"CompendiumFolder([Id={Id}][Type={Type}][Name={Name}][Path={Path}][Parent={Parent?.Name ?? "null"}])"; + return $"CompendiumFolder([Id={Id}][Type={Type}][Group={Name}][Path={Path}][Parent={Parent?.Name ?? "null"}])"; } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs index 8ff9468..80b7cc3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs @@ -2,6 +2,7 @@ using System.Text; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model; using Pf2eModel.Model.Base; @@ -51,7 +52,7 @@ internal abstract class AbstractProcessor(ProcessorInput input) EntityAlias alias = new() { Type = LocaleType.English, - Alias = Entity.Name, + Alias = Entity.Group, EntityId = Entity.Id, Entity = Entity }; @@ -147,13 +148,13 @@ internal abstract class AbstractProcessor(ProcessorInput input) /* foreach (string name in names) { - if (result.Any(t => t.Entity.Name.Contains(name))) + if (result.Any(t => t.Entity.Group.Contains(name))) { continue; } Trait trait = new() { - Name = name, + Group = name, Slug = name, Type = TraitType.Regular, Description = "", diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs index cd88174..16ca5e1 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs @@ -2,6 +2,7 @@ using Konsole; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; +using Pf2eDataScrapper.Processors; using Pf2eModel; namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ActionProcessor.cs index b17f9b2..5e0d2b5 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ActionProcessor.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Processors; using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs index f549256..acb7ca1 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Processors; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj index a61becb..504d388 100644 --- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj +++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj @@ -1,10 +1,10 @@  - 13.0 - Exe - net9.0 - enable + WinExe + net9.0-windows7.0 enable + true + enable diff --git a/Pf2eDataScrapper/Processors/AbstractInputProcessor.cs b/Pf2eDataScrapper/Processors/AbstractInputProcessor.cs new file mode 100644 index 0000000..a576ae2 --- /dev/null +++ b/Pf2eDataScrapper/Processors/AbstractInputProcessor.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.DependencyInjection; +using Pf2eDataScrapper.App; +using Pf2eModel; + +namespace Pf2eDataScrapper.Processors; + +/// +/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса. +/// Класс предназначен для обработки данных с помощью различных обработчиков, +/// для которых указан атрибут . +/// +/// Провайдер сервисов. +/// Провайдер сервисов. +/// +internal abstract class AbstractInputProcessor( + IServiceProvider serviceProvider, + ProcessorInput input +) : IProcessor +{ + /// + /// Провайдер сервисов. + /// + public IServiceProvider ServiceProvider { get; } = serviceProvider; + + /// + /// Контекст базы данных. + /// + public Pf2eDbContext Context { get; } = serviceProvider.GetService()!; + + /// + /// Ввод. + /// + public ProcessorInput Input { get; } = input; + + /// + /// Основной метод обработки данных. + /// + public abstract void Process(); + + /// + /// Метод для сохранения изменений в базе данных. + /// + public abstract void Save(); +} diff --git a/Pf2eDataScrapper/Processors/AbstractProcessor.cs b/Pf2eDataScrapper/Processors/AbstractProcessor.cs new file mode 100644 index 0000000..155a670 --- /dev/null +++ b/Pf2eDataScrapper/Processors/AbstractProcessor.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; +using Pf2eDataScrapper.App; +using Pf2eModel; + +namespace Pf2eDataScrapper.Processors; + +/// +/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса. +/// Класс предназначен для обработки данных с помощью различных обработчиков, +/// для которых указан атрибут . +/// +/// Провайдер сервисов. +/// +internal abstract class AbstractProcessor(IServiceProvider serviceProvider) : IProcessor +{ + /// + /// Провайдер сервисов. + /// + public IServiceProvider ServiceProvider { get; } = serviceProvider; + + /// + /// Контекст базы данных. + /// + public Pf2eDbContext Context { get; } = serviceProvider.GetService()!; + + /// + /// Основной метод обработки данных. + /// + public abstract void Process(); + + /// + /// Метод для сохранения изменений в базе данных. + /// + public abstract void Save(); +} diff --git a/Pf2eDataScrapper/Processors/IProcessor.cs b/Pf2eDataScrapper/Processors/IProcessor.cs new file mode 100644 index 0000000..33849eb --- /dev/null +++ b/Pf2eDataScrapper/Processors/IProcessor.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Pf2eDataScrapper.Processors; + +public interface IProcessor +{ + /// + /// Провайдер сервисов. + /// + IServiceProvider ServiceProvider { get; } + + /// + /// Основной метод обработки данных. + /// + void Process(); + + /// + /// Метод для сохранения изменений в базе данных. + /// + void Save(); +} diff --git a/Pf2eDataScrapper/Importers/ProcessorInput.cs b/Pf2eDataScrapper/Processors/ProcessorInput.cs similarity index 99% rename from Pf2eDataScrapper/Importers/ProcessorInput.cs rename to Pf2eDataScrapper/Processors/ProcessorInput.cs index 85081e7..cb683be 100644 --- a/Pf2eDataScrapper/Importers/ProcessorInput.cs +++ b/Pf2eDataScrapper/Processors/ProcessorInput.cs @@ -2,7 +2,7 @@ using NpgsqlTypes; using Pf2eModel.Model.Common.I18n; -namespace Pf2eDataScrapper.Importers; +namespace Pf2eDataScrapper.Processors; /// /// Класс для работы с JSON-объектом. diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs index d32ba97..328179e 100644 --- a/Pf2eDataScrapper/Program.cs +++ b/Pf2eDataScrapper/Program.cs @@ -1,18 +1,23 @@ -using Pf2eDataScrapper; -using Pf2eDataScrapper.Importers.Implementations.Aonprd; +namespace Pf2eDataScrapper; -// Console.SetWindowSize(Console.LargestWindowWidth - 20, Console.LargestWindowHeight - 10); +internal static class Program +{ + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); -EnvSettings.Load(); + Application.SetHighDpiMode(HighDpiMode.SystemAware); -/* -Pf2EFoundryImporter importer = new(); -*/ +#pragma warning disable WFO5001 + Application.SetColorMode(SystemColorMode.System); +#pragma warning restore WFO5001 -AonprdImporter importer = new(); - -// importer.ProcessorNames = ["rules", "rules-order"]; - -importer.Prepare(); -importer.Import(); -// importer.BuildSchemas(); + Application.Run(new ImportApplication()); + } +} diff --git a/Pf2eViewer/Pf2eViewer.csproj b/Pf2eViewer/Pf2eViewer.csproj index d479e08..973136e 100644 --- a/Pf2eViewer/Pf2eViewer.csproj +++ b/Pf2eViewer/Pf2eViewer.csproj @@ -26,7 +26,4 @@ Settings.Designer.cs - - - From 5d2982330d259bb0292e9e8eeb6cef0afc35a675 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Tue, 13 May 2025 01:22:59 +0400 Subject: [PATCH 03/33] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B4=D0=BE=D0=BB?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B=20=D0=BD=D0=B0=D0=B4=20=D1=84=D1=83=D0=BD=D0=B4=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=BC=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20(=D0=B2?= =?UTF-8?q?=D1=81=D1=91=20=D0=B5=D1=89=D1=91=20=D0=B1=D0=B5=D0=B7=20=D1=81?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=BA=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/Attributes.cs | 62 ------ Pf2eDataScrapper/App/ProcessorAttribute.cs | 42 ++++ Pf2eDataScrapper/App/ProcessorCollection.cs | 22 +-- Pf2eDataScrapper/Forms/StartForm.Designer.cs | 65 +++--- Pf2eDataScrapper/Forms/StartForm.cs | 61 +++++- .../{Importers => Helpers}/DatabaseHelper.cs | 2 +- Pf2eDataScrapper/ImportApplication.cs | 78 +++++++- .../Importers/AbstractImporter.cs | 18 ++ .../Implementations/Aonprd/AonprdImporter.cs | 68 ++++--- .../{DataBuilder => }/AonprdProcessorInput.cs | 6 +- .../Aonprd/AonprdRepository.cs | 25 ++- .../Aonprd/Compendium/AonprdCompendium.cs | 18 +- .../Aonprd/Compendium/AonprdReference.cs | 2 +- .../Aonprd/Compendium/AonprdType.cs | 3 +- .../Aonprd/DataBuilder/AonprdDataBuilder.cs | 148 -------------- .../DataBuilder/AonprdProcessorAttribute.cs | 62 ------ .../DataBuilder/AonprdProcessorCollection.cs | 187 ------------------ .../Processors/Common/ExternalProcessor.cs | 27 --- .../Processors/Types/ArchetypeProcessor.cs | 29 --- .../Processors/Types/LanguageProcessor.cs | 17 -- .../Processors/Types/TraitProcessor.cs | 37 ---- .../Helpers/AonprdBuilderGlobals.cs | 2 +- .../Helpers/AonprdEnumResolver.cs | 2 +- .../LegacyEntityHandler.cs | 3 +- .../Types/Rules => Helpers}/RuleToc.cs | 2 +- .../{DataBuilder => }/Helpers/StringHelper.cs | 0 .../Processors/AbstractEntityProcessor.cs | 24 ++- .../Processors/AbstractSubclassProcessor.cs | 21 +- .../Common/AonprdRepositoryUpdateProcessor.cs | 26 +++ .../Common/AonprdSchemaBuildProcessor.cs} | 115 ++++++----- .../Processors/Common/ExternalProcessor.cs | 37 ++++ .../Processors/Common/SkillProcessor.cs | 18 +- .../Processors/Common/SourceProcessor.cs | 19 +- .../Subclasses/ApparitionProcessor.cs | 26 ++- .../Processors/Types/ActionProcessor.cs | 25 ++- .../Processors/Types/AncestryProcessor.cs | 23 ++- .../Processors/Types/ArchetypeProcessor.cs | 46 +++++ .../Processors/Types/BackgroundProcessor.cs | 23 ++- .../Processors/Types/ClassProcessor.cs | 22 ++- .../Processors/Types/FeatProcessor.cs | 22 ++- .../Processors/Types/LanguageProcessor.cs | 34 ++++ .../Processors/Types/RitualProcessor.cs | 21 +- .../Processors/Types/Rules/RuleProcessor.cs | 23 ++- .../Types/Rules/RulesOrderProcessor.cs | 1 + .../Types/Rules/RulesTocProcessor.cs | 1 + .../Processors/Types/SpellProcessor.cs | 23 ++- .../Aonprd/Processors/Types/TraitProcessor.cs | 54 +++++ .../DataBuilder/AbstractProcessor.cs | 1 + .../AbstractImporterRepository.cs | 6 +- .../Importers/Repositories/GitRepository.cs | 13 +- .../Processors/AbstractInputProcessor.cs | 28 +-- .../Processors/AbstractProcessor.cs | 17 +- .../Processors/DatabaseTruncateProcessor.cs | 16 ++ Pf2eDataScrapper/Processors/IProcessor.cs | 9 +- .../Processors/ProcessProgress.cs | 156 +++++++++++++++ Pf2eDataScrapper/Processors/ProcessorInput.cs | 8 +- 56 files changed, 1017 insertions(+), 829 deletions(-) delete mode 100644 Pf2eDataScrapper/App/Attributes.cs create mode 100644 Pf2eDataScrapper/App/ProcessorAttribute.cs rename Pf2eDataScrapper/{Importers => Helpers}/DatabaseHelper.cs (98%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/AonprdProcessorInput.cs (95%) delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorAttribute.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/ExternalProcessor.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ArchetypeProcessor.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/LanguageProcessor.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/TraitProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Helpers/AonprdBuilderGlobals.cs (90%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Helpers/AonprdEnumResolver.cs (98%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => Helpers}/LegacyEntityHandler.cs (96%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder/Processors/Types/Rules => Helpers}/RuleToc.cs (99%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Helpers/StringHelper.cs (100%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/AbstractEntityProcessor.cs (93%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/AbstractSubclassProcessor.cs (81%) create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdRepositoryUpdateProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{SchemaBuilder/AonprdSchemaBuilder.cs => Processors/Common/AonprdSchemaBuildProcessor.cs} (76%) create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Common/SkillProcessor.cs (51%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Common/SourceProcessor.cs (52%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Subclasses/ApparitionProcessor.cs (71%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/ActionProcessor.cs (55%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/AncestryProcessor.cs (70%) create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/BackgroundProcessor.cs (56%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/ClassProcessor.cs (91%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/FeatProcessor.cs (75%) create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/RitualProcessor.cs (94%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/Rules/RuleProcessor.cs (81%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/Rules/RulesOrderProcessor.cs (98%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/Rules/RulesTocProcessor.cs (98%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{DataBuilder => }/Processors/Types/SpellProcessor.cs (83%) create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs create mode 100644 Pf2eDataScrapper/Processors/DatabaseTruncateProcessor.cs create mode 100644 Pf2eDataScrapper/Processors/ProcessProgress.cs diff --git a/Pf2eDataScrapper/App/Attributes.cs b/Pf2eDataScrapper/App/Attributes.cs deleted file mode 100644 index df60b8b..0000000 --- a/Pf2eDataScrapper/App/Attributes.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace Pf2eDataScrapper.App; - -/// -/// Общий аттрибут обработчика. -/// -/// Название обработчика. -/// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика. -[AttributeUsage(AttributeTargets.Class)] -public class ProcessAttribute(string name, ICollection dependencies) : Attribute -{ - /// - /// Название обработчика. - /// - public string Name { get; init; } = name; - - /// - /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. - /// - public ICollection Dependencies { get; init; } = dependencies; - - public ProcessAttribute(string name) - : this(name, []) { } -} - -/// -/// Аттрибут обработчика для уточнения группы. -/// -/// Группа обработчиков, к которому относится обработчик. -[AttributeUsage(AttributeTargets.Class)] -internal class ProcessorGroupAttribute(ProcessorGroupEnum group) : Attribute -{ - /// - /// Название обработчика, к которому относится обработчик. - /// - public ProcessorGroupEnum Group { get; init; } = group; -} - -/// -/// Аттрибут обработчика для уточнения списка зависимостей. -/// -/// Список названий зависимостей. -[AttributeUsage(AttributeTargets.Class)] -internal class ProcessorDependenciesAttribute(ICollection dependencies) : Attribute -{ - /// - /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. - /// - public ICollection Dependencies { get; init; } = dependencies; -} - -/// -/// Аттрибут обработчика для внутреннего типа элемента. -/// -/// Внутренний типа элемента. -[AttributeUsage(AttributeTargets.Class)] -internal class ProcessorInnerTypeAttribute(string innerType) : Attribute -{ - /// - /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. - /// - public string InnerType { get; init; } = innerType; -} diff --git a/Pf2eDataScrapper/App/ProcessorAttribute.cs b/Pf2eDataScrapper/App/ProcessorAttribute.cs new file mode 100644 index 0000000..e1b0d7b --- /dev/null +++ b/Pf2eDataScrapper/App/ProcessorAttribute.cs @@ -0,0 +1,42 @@ +namespace Pf2eDataScrapper.App; + +/// +/// Общий аттрибут обработчика. +/// +/// Название обработчика. +[AttributeUsage(AttributeTargets.Class)] +public class ProcessorAttribute(string name) : Attribute +{ + /// + /// Название обработчика. + /// + public string Name { get; init; } = name; + + /// + /// Описание обработчика. + /// + public string Description { get; init; } = name; + + /// + /// Группа обработчиков, к которому относится обработчик. + /// + public ProcessorGroupEnum Group { get; init; } = ProcessorGroupEnum.General; + + /// + /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. + /// + public string? InnerType { get; init; } = null; +} + +/// +/// Аттрибут зависимостей обработчика. +/// +/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. +[AttributeUsage(AttributeTargets.Class)] +public class ProcessorDependenciesAttribute(string[] dependencies) : Attribute +{ + /// + /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. + /// + public ICollection Dependencies { get; } = dependencies; +} diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index ffea206..7dbae40 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -29,7 +29,7 @@ internal class ProcessorCollection foreach (Type type in assembly.GetTypes()) { - if (type.GetCustomAttributes(typeof(ProcessAttribute), true).Length <= 0) + if (type.GetCustomAttributes(typeof(ProcessorAttribute), true).Length <= 0) { continue; } @@ -51,21 +51,15 @@ internal class ProcessorCollection if (!type.IsSubclassOf(typeof(IProcessor))) { throw new( - $"'ProcessAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not." + $"'ProcessorAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not." ); } - ProcessAttribute processAttribute = type.GetCustomAttribute()!; - DescriptionAttribute? descriptionAttribute = - type.GetCustomAttribute(); - ProcessorGroupAttribute? groupAttribute = - type.GetCustomAttribute(); + ProcessorAttribute processorAttribute = type.GetCustomAttribute()!; ProcessorDependenciesAttribute? dependenciesAttribute = type.GetCustomAttribute(); - ProcessorInnerTypeAttribute? innerTypeAttribute = - type.GetCustomAttribute(); - bool isCollection = innerTypeAttribute is not null; + bool isCollection = processorAttribute.InnerType is not null; ConstructorInfo? constructor = isCollection ? type.GetConstructor([]) @@ -78,13 +72,13 @@ internal class ProcessorCollection return new() { - Name = processAttribute.Name, + Name = processorAttribute.Name, Type = type, - Description = descriptionAttribute?.Description ?? processAttribute.Name, - Group = groupAttribute?.Group ?? ProcessorGroupEnum.General, + Description = processorAttribute.Description, + Group = processorAttribute.Group, Dependencies = dependenciesAttribute?.Dependencies ?? [], IsCollectionProcessor = isCollection, - InnerType = innerTypeAttribute?.InnerType, + InnerType = processorAttribute.InnerType, Constructor = constructor, }; } diff --git a/Pf2eDataScrapper/Forms/StartForm.Designer.cs b/Pf2eDataScrapper/Forms/StartForm.Designer.cs index 387245b..31eb6f5 100644 --- a/Pf2eDataScrapper/Forms/StartForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/StartForm.Designer.cs @@ -31,11 +31,11 @@ partial class StartForm MainSplitContainer = new SplitContainer(); PropertiesGroupBox = new GroupBox(); SettingsPropertyGrid = new PropertyGrid(); - StartButton = new Button(); ProcessorsGroupBox = new GroupBox(); - DeselectAllButton = new Button(); - SelectAllButton = new Button(); ProcessorsListView = new ListView(); + SelectAllButton = new Button(); + DeselectAllButton = new Button(); + StartButton = new Button(); ((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit(); MainSplitContainer.Panel1.SuspendLayout(); MainSplitContainer.Panel2.SuspendLayout(); @@ -84,17 +84,6 @@ partial class StartForm SettingsPropertyGrid.Size = new Size(288, 649); SettingsPropertyGrid.TabIndex = 0; // - // StartButton - // - StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold); - StartButton.Location = new Point(3, 611); - StartButton.Name = "StartButton"; - StartButton.Size = new Size(621, 63); - StartButton.TabIndex = 0; - StartButton.Text = "Запуск"; - StartButton.UseVisualStyleBackColor = true; - // // ProcessorsGroupBox // ProcessorsGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; @@ -108,24 +97,6 @@ partial class StartForm ProcessorsGroupBox.TabStop = false; ProcessorsGroupBox.Text = "Обработчики"; // - // DeselectAllButton - // - DeselectAllButton.Location = new Point(6, 19); - DeselectAllButton.Name = "DeselectAllButton"; - DeselectAllButton.Size = new Size(137, 23); - DeselectAllButton.TabIndex = 0; - DeselectAllButton.Text = "Снять выделение всех"; - DeselectAllButton.UseVisualStyleBackColor = true; - // - // SelectAllButton - // - SelectAllButton.Location = new Point(149, 19); - SelectAllButton.Name = "SelectAllButton"; - SelectAllButton.Size = new Size(137, 23); - SelectAllButton.TabIndex = 1; - SelectAllButton.Text = "Выделить все"; - SelectAllButton.UseVisualStyleBackColor = true; - // // ProcessorsListView // ProcessorsListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; @@ -136,6 +107,35 @@ partial class StartForm ProcessorsListView.TabIndex = 2; ProcessorsListView.UseCompatibleStateImageBehavior = false; // + // SelectAllButton + // + SelectAllButton.Location = new Point(149, 19); + SelectAllButton.Name = "SelectAllButton"; + SelectAllButton.Size = new Size(137, 23); + SelectAllButton.TabIndex = 1; + SelectAllButton.Text = "Выделить все"; + SelectAllButton.UseVisualStyleBackColor = true; + // + // DeselectAllButton + // + DeselectAllButton.Location = new Point(6, 19); + DeselectAllButton.Name = "DeselectAllButton"; + DeselectAllButton.Size = new Size(137, 23); + DeselectAllButton.TabIndex = 0; + DeselectAllButton.Text = "Снять выделение всех"; + DeselectAllButton.UseVisualStyleBackColor = true; + // + // StartButton + // + StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold); + StartButton.Location = new Point(3, 611); + StartButton.Name = "StartButton"; + StartButton.Size = new Size(621, 63); + StartButton.TabIndex = 0; + StartButton.Text = "Запуск"; + StartButton.UseVisualStyleBackColor = true; + // // StartForm // AutoScaleDimensions = new SizeF(7F, 15F); @@ -144,6 +144,7 @@ partial class StartForm Controls.Add(MainSplitContainer); Name = "StartForm"; Text = "Настройки импорта данных"; + Load += StartForm_Load; MainSplitContainer.Panel1.ResumeLayout(false); MainSplitContainer.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)MainSplitContainer).EndInit(); diff --git a/Pf2eDataScrapper/Forms/StartForm.cs b/Pf2eDataScrapper/Forms/StartForm.cs index 70bbf4d..58811ff 100644 --- a/Pf2eDataScrapper/Forms/StartForm.cs +++ b/Pf2eDataScrapper/Forms/StartForm.cs @@ -1,9 +1,64 @@ -namespace Pf2eDataScrapper.Forms; +using System.ComponentModel; +using System.Reflection; +using Pf2eDataScrapper.App; -public partial class StartForm : Form +namespace Pf2eDataScrapper.Forms; + +internal partial class StartForm : Form { - public StartForm() + private readonly ProcessorCollection _processorCollection; + + public StartForm(ProcessorCollection processorCollection) { InitializeComponent(); + + _processorCollection = processorCollection; + } + + private void StartForm_Load(object sender, EventArgs e) + { + PopulateListView(); + } + + private void PopulateListView() + { + Dictionary> items = []; + + foreach (ProcessorCollectionItem item in _processorCollection.AllItems) + { + items.TryAdd(item.Group, []); + items[item.Group].Add(item); + } + + ProcessorsListView.Groups.Clear(); + + foreach (FieldInfo field in typeof(ProcessorGroupEnum).GetFields()) + { + ProcessorGroupEnum groupEnum = (ProcessorGroupEnum)field.GetValue(null)!; + + if (!items.TryGetValue(groupEnum, out List? list)) + { + continue; + } + + DescriptionAttribute? attribute = (DescriptionAttribute?) + Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)); + + string name = attribute?.Description ?? field.Name; + + ListViewGroup listViewGroup = new(name); + ProcessorsListView.Groups.Add(listViewGroup); + + foreach (ProcessorCollectionItem item in list) + { + ListViewItem listViewItem = new() + { + Group = listViewGroup, + Text = item.Description, + Selected = true, + }; + ProcessorsListView.Items.Add(listViewItem); + } + } } } diff --git a/Pf2eDataScrapper/Importers/DatabaseHelper.cs b/Pf2eDataScrapper/Helpers/DatabaseHelper.cs similarity index 98% rename from Pf2eDataScrapper/Importers/DatabaseHelper.cs rename to Pf2eDataScrapper/Helpers/DatabaseHelper.cs index 420a102..7b0960e 100644 --- a/Pf2eDataScrapper/Importers/DatabaseHelper.cs +++ b/Pf2eDataScrapper/Helpers/DatabaseHelper.cs @@ -1,7 +1,7 @@ using Pf2eModel; using Slugify; -namespace Pf2eDataScrapper.Importers; +namespace Pf2eDataScrapper.Helpers; /// /// Класс предоставляет методы для работы с базой данных. diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index 18cf2ee..16b8671 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -1,6 +1,10 @@ using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Forms; +using Pf2eDataScrapper.Importers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd; +using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry; +using Pf2eDataScrapper.Processors; using Pf2eModel; namespace Pf2eDataScrapper; @@ -15,23 +19,27 @@ internal sealed class ImportApplication : ApplicationContext #region "Внедрение зависимостей (DI)" - public IServiceCollection Services = new ServiceCollection(); + public readonly IServiceCollection Services = new ServiceCollection(); + + public IServiceProvider ServiceProvider { get; private set; } = null!; private void InitializeServices() { Services.AddSingleton(this); Services.AddSingleton(Settings); - - Services.AddSingleton(); + Services.AddSingleton(Processors); Services.AddDbContext(); + + ServiceProvider = Services.BuildServiceProvider(); } #endregion #region "Настройки" - public ApplicationSettings Settings = new(); + public readonly ApplicationSettings Settings = new(); + public readonly ProcessorCollection Processors = new(); #endregion @@ -45,13 +53,73 @@ internal sealed class ImportApplication : ApplicationContext private void CreateStartForm() { - _startForm = new() { StartPosition = FormStartPosition.CenterParent }; + _startForm = ActivatorUtilities.CreateInstance(ServiceProvider); + _startForm.StartPosition = FormStartPosition.CenterParent; _startForm.Show(); } private void StartProcess() { _startForm!.Close(); + + Processors.SortItems(); + + ProcessProgress progress = new ProcessProgress(); + + foreach (ProcessorCollectionItem item in Processors.Items) + { + if (item.IsCollectionProcessor) + { + RunCollection(item, progress); + } + else + { + RunSingle(item, progress); + } + } + } + + private void RunSingle(ProcessorCollectionItem item, ProcessProgress progress) + { + progress.Local.Reset(1); + progress.Local.Start(""); + + IProcessor processor = (IProcessor) + ActivatorUtilities.CreateInstance(ServiceProvider, item.Type); + + processor.Process(); + processor.Save(); + + progress.Local.Complete(); + } + + private void RunCollection(ProcessorCollectionItem item, ProcessProgress progress) + { + AbstractImporter importer = item.Group switch + { + ProcessorGroupEnum.AonprdPreparation + or ProcessorGroupEnum.AonprdEntities + or ProcessorGroupEnum.AonprdSubclasses => new AonprdImporter(), + ProcessorGroupEnum.FoundryEnPreparation => new Pf2EFoundryImporter(), + _ => throw new("Can't recognize importer"), + }; + + progress.Local.Reset(importer.CountInputs(item.InnerType!)); + + foreach (ProcessorInput input in importer.ResolveInputs(item.InnerType!)) + { + progress.Local.Start(input.Name); + + IProcessor processor = (IProcessor) + ActivatorUtilities.CreateInstance(ServiceProvider, item.Type); + + processor.Process(); + processor.Save(); + + progress.Local.End(); + } + + progress.Local.Complete(); } #endregion diff --git a/Pf2eDataScrapper/Importers/AbstractImporter.cs b/Pf2eDataScrapper/Importers/AbstractImporter.cs index fcddf09..ae2cc29 100644 --- a/Pf2eDataScrapper/Importers/AbstractImporter.cs +++ b/Pf2eDataScrapper/Importers/AbstractImporter.cs @@ -1,4 +1,5 @@ using Pf2eDataScrapper.Importers.Repositories; +using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers; @@ -36,15 +37,32 @@ internal abstract class AbstractImporter /// /// Метод подготовки данных для импорта. /// + [Obsolete] public abstract void Prepare(); /// /// Импорт данных /// + [Obsolete] public abstract void Import(); /// /// Построение схем данных. /// + [Obsolete] public abstract void BuildSchemas(); + + /// + /// Получение количества записей, принаделажащих типу . + /// + /// Тип записей. + /// Количество записей. + public abstract int CountInputs(string type); + + /// + /// Итератор ввода для записей, принаделажащих типу . + /// + /// Тип записей. + /// Итератор. + public abstract IEnumerable ResolveInputs(string type); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs index b6443dc..114573e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs @@ -1,6 +1,8 @@ -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder; +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; using Pf2eDataScrapper.Importers.Repositories; +using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; @@ -9,6 +11,11 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; /// internal class AonprdImporter : AbstractImporter { + /// + /// Название внешней ссылки. + /// + public const string TargetName = "Archives of Nethys Pathfinder 2e"; + /// public override string Name => TargetName; @@ -16,8 +23,7 @@ internal class AonprdImporter : AbstractImporter public override string Slug => "aonprd"; /// - public override AbstractImporterRepository Repository => - new AonprdRepository(this, "https://elasticsearch.aonprd.com"); + public override AbstractImporterRepository Repository { get; } /// /// Инструмент для работы с данными сайта Архивов Нефеса. @@ -29,37 +35,47 @@ internal class AonprdImporter : AbstractImporter /// public AonprdImporter() { + Repository = new AonprdRepository(this, "https://elasticsearch.aonprd.com"); Loader = new(this); } /// - public override void Prepare() + public override void Prepare() { } + + /// + public override void BuildSchemas() { } + + /// + public override void Import() { } + + /// + public override int CountInputs(string type) { - Repository.Update(); - Loader.Prepare(); + return Loader.Compendium.Count(type); } /// - public override void BuildSchemas() + public override IEnumerable ResolveInputs(string type) { - AonprdSchemaBuilder builder = new(Loader); + foreach (string file in Loader.Compendium.Files(type)) + { + using StreamReader fileReader = new(Path.Combine(Dir, $"{file}.json")); + JArray array = JArray.Parse(fileReader.ReadToEnd()); - builder.BuildSchemas(); - builder.Save(EnvSettings.OutputDir); + foreach (JToken item in array) + { + string? id = item.SelectToken("id")?.ToString(); + + if (id is null) + { + continue; + } + + if (AonprdReference.ResolveType(id) == type) + { + yield return new AonprdProcessorInput(id, (JObject)item); + } + } + } } - - /// - public override void Import() - { - AonprdDataBuilder builder = new(Loader); - builder.Processors.UseOnlyNames(ProcessorNames); - - builder.TruncateTables(); - builder.Build(); - } - - /// - /// Название внешней ссылки. - /// - public const string TargetName = "Archives of Nethys Pathfinder 2e"; } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdProcessorInput.cs similarity index 95% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdProcessorInput.cs index d2fde33..31b6bcd 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdProcessorInput.cs @@ -1,14 +1,14 @@ using Newtonsoft.Json.Linq; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; /// /// Класс для работы с JSON-объектом при обработки данных с Архивов Нефиса. /// /// JSON-объект, который будет использоваться для доступа к данным. -internal class AonprdProcessorInput(JObject input) : ProcessorInput(input) +internal class AonprdProcessorInput(string name, JObject input) : ProcessorInput(name, input) { /// public override T Enum(string value) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs index ba05d9b..0a21ba3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs @@ -1,6 +1,7 @@ using Konsole; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Repositories; +using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; @@ -38,18 +39,21 @@ internal class AonprdRepository(AbstractImporter importer, string searchUrl) } /// - public override void Update() + public override void Update(ProcessProgress progress) { if (CheckRevision()) { - Console.WriteLine($"Данные Aonprd актуальны, текущая ревизия {Revision}."); + progress.Local.Complete(); + progress.Message($"Данные Aonprd актуальны, текущая ревизия {Revision}."); return; } - Console.WriteLine( + progress.Message( $"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..." ); + progress.Local.Reset(1); + if (Directory.Exists(RepositoryDirPath)) { Directory.Delete(RepositoryDirPath, true); @@ -58,27 +62,28 @@ internal class AonprdRepository(AbstractImporter importer, string searchUrl) WriteFile("revision.txt", Revision.ToString()); - Console.WriteLine("Загрузка индекса..."); + progress.Local.Start("Загрузка индекса..."); + string indexContent = Load($"/json-data/aon{Revision}-index.json"); WriteFile("index.json", indexContent); JObject indexJson = JObject.Parse(indexContent); - Console.WriteLine("Загрузка индексных файлов..."); - - ProgressBar bar = new(indexJson.Count); - int i = 0; + progress.Local.Reset(indexJson.Count + 1); + progress.Local.End(); foreach (KeyValuePair kvp in indexJson) { string fileName = kvp.Key; - bar.Refresh(i++, fileName); + progress.Local.Start(fileName); string fileContent = Load($"/json-data/{fileName}.json"); WriteFile($"{fileName}.json", fileContent); + + progress.Local.End(); } - bar.Refresh(indexJson.Count, "Готово!"); + progress.Local.Complete(); } /// diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs index e7df834..4b5242c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs @@ -35,18 +35,32 @@ internal class AonprdCompendium /// Количество элементов. public int Count() => Types.Values.Sum(t => t.Count); + /// + /// Возвращает количество элементов для указанного типа данных. + /// + /// Тип данных. + /// Количество элементов. + public int Count(string type) => Types.Values.First(t => t.Type == type).Count; + /// /// Возвращает количество элементов для указанных типов данных. /// - /// Список типов. + /// Список типов данных. /// Количество элементов. public int Count(IEnumerable types) => Types.Values.Where(t => types.Contains(t.Type)).Sum(t => t.Count); + /// + /// Возвращает список файлов для выбора сущностей указанного типа данных. + /// + /// Тип данных. + /// Список искомых файлов. + public IEnumerable Files(string type) => Types.Values.First(t => t.Type == type).Files; + /// /// Возвращает список файлов для выбора сущностей указанных типов данных. /// - /// Список типов. + /// Список типов данных. /// Список искомых файлов. public IEnumerable Files(IEnumerable types) => Types.Values.Where(t => types.Contains(t.Type)).SelectMany(t => t.Files).Distinct(); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs index 98ebbdd..c1fcd51 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs @@ -25,7 +25,7 @@ internal class AonprdReference(string id, string file) /// /// Определение типа сущности как префикса идентификатора. /// - private static string ResolveType(string id) + public static string ResolveType(string id) { string type = string.Join('-', id.Split('-')[..^1]); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs index 8f43ff7..e55f1b8 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs @@ -1,8 +1,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; /// -/// Класс AonprdType представляет собой уникальный тип данных, связанный с Aon (Advanced Opportunity Network). -/// Это основной класс, который хранит информацию о различных элементах системы. +/// Класс типа элементов с Архива Нефиса. /// internal class AonprdType(string type) { diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs deleted file mode 100644 index ae02027..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Konsole; -using Newtonsoft.Json.Linq; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; -using Pf2eDataScrapper.Processors; -using Pf2eModel; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; - -/// -/// Класс для создание схемы данных Архива Нефиса. -/// -/// -internal class AonprdDataBuilder(AonprdLoader loader) -{ - /// - /// Основной класс для работы с данными Архива Нефиса. - /// - public readonly AonprdLoader Loader = loader; - - /// - /// Коллекция папок компендиума. - /// - public AonprdCompendium Compendium => Loader.Compendium; - - /// - /// Коллекция обработчиков данных. - /// - public readonly AonprdProcessorCollection Processors = new(); - - /// - /// Очистить таблицы в базе данных. - /// - public void TruncateTables() - { - Console.WriteLine("Truncating all old data..."); - - using Pf2eDbContext context = new(); - string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll(); - - Console.WriteLine(cmd); - - Console.WriteLine("Truncation completed!"); - } - - /// - /// Конфигурация интерфейса вывода результатов. - /// - protected ConcurrentWriter Window = null!; - - /// - /// Основное окно для вывода информации. - /// - protected Window Output = null!; - - /// - /// Полоса прогресса для основного процесса обработки данных. - /// - protected ProgressBar ProcessProgressBar = null!; - - /// - /// Полоса прогресса для под-процессов обработки данных. - /// - protected ProgressBar SubProcessProgressBar = null!; - - /// - /// Метод для настройки интерфейса. - /// - protected void ConfigureWindow() - { - Window = new Window(0, 0, Console.WindowWidth, Console.WindowHeight).Concurrent(); - - IConsole bars = Window.OpenBox( - "Progress", - Console.WindowWidth, - 4, - new() { Body = Colors.WhiteOnBlack } - ); - - Output = new(0, 4, Console.WindowWidth, Console.WindowHeight - 4, Window); - - ProcessProgressBar = new(bars, Processors.Items.Count, 15); - - SubProcessProgressBar = new(bars, 1, 15); - } - - /// - /// Обработка отдельного процессора данных. - /// - protected void RunProcessor(AonprdProcessorCollection.ProcessorItem item, JObject json) - { - AonprdProcessorInput input = new(json); - AbstractProcessor processor = (AbstractProcessor)item.Constructor.Invoke([input]); - - try - { - processor.Process(); - processor.Save(); - } - catch (LegacyItemException ex) - { - Output.WriteLine(ex.Message); - } - catch (SkipItemException ex) - { - Output.WriteLine(ex.Message); - } - } - - /// - /// Полная обработка и сохранение данных. - /// - public void Build() - { - ConfigureWindow(); - - ProcessProgressBar.Refresh(-1, ""); - - foreach (AonprdProcessorCollection.ProcessorItem item in Processors.Iterate()) - { - ProcessProgressBar.Next(item.Attribute.Name); - - SubProcessProgressBar.Refresh(-1, "Starting..."); - - if (item.Attribute.Type is null) - { - RunProcessor(item, []); - continue; - } - - SubProcessProgressBar.Max = Compendium.Count([item.Attribute.Type]); - - foreach (AonprdLoader.LoaderEntry entry in Loader.Entries(item.Attribute.Type)) - { - SubProcessProgressBar.Next($"{entry.Id}"); - RunProcessor(item, entry.Json); - } - - SubProcessProgressBar.Next("Ready"); - - DatabaseHelper.ResetContext(); - } - - ProcessProgressBar.Next("Done!"); - - Window.Clear(); - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorAttribute.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorAttribute.cs deleted file mode 100644 index 301d742..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorAttribute.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Pf2eDataScrapper.App.Attributes; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; - -/// -/// Атрибут обработчика данных Архивов Нефиса. -/// -[AttributeUsage(AttributeTargets.Class)] -public class AonprdProcessorAttribute : Attributes -{ - /// - /// Важные общие зависимости для обработчка. - /// - private static readonly string[] DefaultDependencies = ["aonprd:trait", "aonprd:source"]; - - /// - /// Тип данных, которые обработчик обрабатывает. - /// - public string Type { get; init; } - - /// - /// Конструктор класса со стандартными общими зависимости для обработчка. - /// - /// Тип данных, которые обработчик обрабатывает. Также будет использоваться в качестве названия. - public AonprdProcessorAttribute(string type) - : this(type, type, DefaultDependencies) { } - - /// - /// Конструктор класса . - /// - /// Тип данных, которые обработчик обрабатывает. Также будет использоваться в качестве названия. - /// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика. - /// Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка. - public AonprdProcessorAttribute(string type, string[] dependencies, bool includeDefaults = true) - : this(type, type, dependencies, includeDefaults) { } - - /// - /// Конструктор класса со стандартными общими зависимости для обработчка. - /// - /// Название обработчика. - /// Тип данных, которые обработчик обрабатывает. - public AonprdProcessorAttribute(string name, string type) - : this(name, type, DefaultDependencies) { } - - /// - /// Основной конструктор класса . - /// - /// Название обработчика. - /// Тип данных, которые обработчик обрабатывает. - /// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика. - /// Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка. - public AonprdProcessorAttribute( - string name, - string? type, - ICollection dependencies, - bool includeDefaults = true - ) - : base($"aonprd:{name}", [.. includeDefaults ? DefaultDependencies : [], .. dependencies]) - { - Type = type; - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs deleted file mode 100644 index 4ec1ccf..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System.Reflection; -using Pf2eDataScrapper.Processors; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; - -/// -/// Класс отвечает за управление обработчиками, отмеченных атрибутом . -/// -internal class AonprdProcessorCollection -{ - /// - /// Элемент коллекции обработчика. - /// - /// Атрибут, связанный с обаботчиком. - /// Конструктор обработчика. - public class ProcessorItem(AonprdProcessorAttribute attribute, ConstructorInfo constructor) - { - /// - /// Атрибут, связанный с обаботчиком. - /// - public readonly AonprdProcessorAttribute Attribute = attribute; - - /// - /// Конструктор обработчика. - /// - public readonly ConstructorInfo Constructor = constructor; - - /// - /// Приоритет выполнения обработчика. - /// - public int Priority = -1; - } - - /// - /// Список всех объявленных обработчиков. - /// - private readonly List _allItems; - - /// - /// Список используемых обработчиков. - /// - public List Items { get; private set; } = []; - - /// - /// Конструктор класса , загружающий все обработчики. - /// - public AonprdProcessorCollection() - { - foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()) - { - if (type.GetCustomAttributes(typeof(AonprdProcessorAttribute), true).Length <= 0) - { - continue; - } - - if (!type.IsSubclassOf(typeof(AbstractProcessor))) - { - throw new( - $"'AonprdProcessor' attribute should only be applied to children of 'AbstractProcessor' class. '{type.FullName}' is not." - ); - } - - AonprdProcessorAttribute attribute = - type.GetCustomAttribute()!; - - ConstructorInfo constructor = type.GetConstructor([typeof(AonprdProcessorInput)])!; - - ProcessorItem? collision = Items.FirstOrDefault(i => - i.Attribute.Name == attribute.Name - ); - if (collision is not null) - { - throw new( - $"Multiply attributes 'AonprdProcessor' have same name '{attribute.Name}'." - + $" They are added to classes: '{type.FullName}' and '{collision.Constructor.DeclaringType!.FullName}'" - ); - } - - Items.Add(new(attribute, constructor)); - } - - _allItems = Items.ToList(); - } - - /// - /// Проверка, что все зависимости всех обработчиков могут быть удовлетворены. - /// - private void CheckDependencies() - { - foreach (ProcessorItem item in Items) - { - foreach (string dependency in item.Attribute.Dependencies) - { - if (Items.All(i => i.Attribute.Name != dependency)) - { - throw new( - $"Dependency '{dependency}' for processor '{item.Attribute.Name}' not found." - ); - } - } - } - } - - /// - /// Сортировать обработчики в соответсвии со всеми зависимостями. - /// - private void SortItems() - { - CheckDependencies(); - - Dictionary priorities = []; - - while (priorities.Count < Items.Count) - { - foreach (ProcessorItem item in Items) - { - if (item.Priority > 0) - { - continue; - } - - IEnumerable dependencyPriorities = item.Attribute.Dependencies.Select(d => - priorities.GetValueOrDefault(d, -1) - ); - - if (dependencyPriorities.Any(p => p == -1)) - { - continue; - } - - item.Priority = dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1; - priorities.Add(item.Attribute.Name, item.Priority); - } - } - - Items.Sort((a, b) => a.Priority - b.Priority); - } - - /// - /// Итерация через все используемые обработчики. - /// - /// Итератор. - public IEnumerable Iterate() - { - SortItems(); - - foreach (ProcessorItem item in Items) - { - yield return item; - } - } - - /// - /// Ограничить список используемых обработчиков по названию. - /// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики. - /// - /// Коллекция названиий требуемых обработчиков. - public void UseOnlyNames(ICollection processorNames) - { - if (processorNames.Count == 0) - { - Items = _allItems; - return; - } - - Dictionary nameFlags = processorNames.ToDictionary(name => name, _ => false); - - while (nameFlags.Values.Any(v => !v)) - { - foreach ( - KeyValuePair pair in nameFlags.Where(pair => !pair.Value).ToList() - ) - { - ProcessorItem item = _allItems.First(item => item.Attribute.Name == pair.Key); - - foreach (string dependency in item.Attribute.Dependencies) - { - nameFlags.TryAdd(dependency, false); - } - - nameFlags[pair.Key] = true; - } - } - - Items = _allItems.Where(item => nameFlags.ContainsKey(item.Attribute.Name)).ToList(); - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/ExternalProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/ExternalProcessor.cs deleted file mode 100644 index 18508df..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/ExternalProcessor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Pf2eDataScrapper.Processors; -using Pf2eModel.Model.Base.Targets; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common; - -[AonprdProcessor("external", null, [], false)] -internal class ExternalProcessor(AonprdProcessorInput input) : AbstractProcessor(input) -{ - 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); - - Globals.AonprdTarget = AonprdTarget; - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ArchetypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ArchetypeProcessor.cs deleted file mode 100644 index a4f5c0b..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ArchetypeProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Pf2eModel.Model.Enum; -using Pf2eModel.Model.Structure; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; - -[AonprdProcessor("archetype")] -internal class ArchetypeProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) -{ - /// - public override EntityType Type => EntityType.Archetype; - - /// - public override Archetype Object { get; } = new(); - - /// - public override void Add() => Context.Archetypes.Add(Object); - - /// - public override void Process() - { - base.Process(); - - Object.Level = Input.GetOptional("level"); - } - - /// - protected override string[] AddedDescriptionPaths() => - ["prerequisite_markdown", "requirement_markdown", "summary_markdown"]; -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/LanguageProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/LanguageProcessor.cs deleted file mode 100644 index 206746f..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/LanguageProcessor.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Pf2eModel.Model.Enum; -using Pf2eModel.Model.Structure; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; - -[AonprdProcessor("language")] -internal class LanguageProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) -{ - /// - public override EntityType Type => EntityType.Language; - - /// - public override Language Object { get; } = new(); - - /// - public override void Add() => Context.Languages.Add(Object); -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/TraitProcessor.cs deleted file mode 100644 index a2d729f..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/TraitProcessor.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Pf2eModel.Model.Enum; -using Pf2eModel.Model.Structure; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; - -[AonprdProcessor("trait", ["external"], false)] -internal class TraitProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) -{ - /// - public override EntityType Type => EntityType.Trait; - - /// - public override Trait Object { get; } = new(); - - /// - public override void Add() => Context.Traits.Add(Object); - - /// - public override void Process() - { - base.Process(); - - Object.Type = TraitType.Regular; - - Context.Traits.Add(Object); - } - - /// - protected override void AddTraits() - { - // Необходимо пропускать - } - - /// - protected override string GetDescription() => - base.GetDescription() + AddedDescription(["summary_markdown", "search_markdown"]); -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdBuilderGlobals.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs similarity index 90% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdBuilderGlobals.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs index def6837..f065e5b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdBuilderGlobals.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs @@ -1,6 +1,6 @@ using Pf2eModel.Model.Base.Targets; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; /// /// Класс-помощник, хранящий необходимые глобальные данные. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdEnumResolver.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdEnumResolver.cs similarity index 98% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdEnumResolver.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdEnumResolver.cs index e4f395c..1f43999 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdEnumResolver.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdEnumResolver.cs @@ -1,6 +1,6 @@ using Pf2eModel.Model.Enum; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; /// /// Класс, помогающий разрешить строковые значения с Архивов Нефиса в перечисления. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/LegacyEntityHandler.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs similarity index 96% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/LegacyEntityHandler.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs index d5cfc57..240b5ff 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/LegacyEntityHandler.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs @@ -1,9 +1,10 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; using Pf2eModel.Model.Base; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Common.I18n; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; /// /// Обработчик сущностей, помеченных как устаревшие. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RuleToc.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs similarity index 99% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RuleToc.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs index 7d59e6c..6ab17bc 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RuleToc.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs @@ -1,6 +1,6 @@ using Pf2eModel.Model.Base.Source; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; /// /// Класс коллекции книг, содержащих правила. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/StringHelper.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs similarity index 100% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/StringHelper.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs similarity index 93% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractEntityProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index 03f14a5..c0ad18f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -3,6 +3,10 @@ using System.Text; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model; @@ -13,18 +17,28 @@ using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; /// /// Абстрактный класс-обработчик сущностей, полученных из Архивов Нефиса. /// Класс предназначен для обработки данных с помощью различных обработчиков, -/// для которых указан атрибут . +/// для которых указан атрибут . /// /// Объект обработки вводимого JSON. -/// -internal abstract class AbstractEntityProcessor(AonprdProcessorInput input) - : AbstractProcessor(input) +/// +internal abstract class AbstractEntityProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractInputProcessor(input) { + public Pf2eDbContext Context { get; } = context; + public StringHelper StringHelper { get; } = stringHelper; + public DatabaseHelper Helper { get; } = helper; + public AonprdBuilderGlobals Globals { get; } = globals; + /// /// Корневая сущность. /// diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractSubclassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs similarity index 81% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractSubclassProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs index df85831..03e0bc6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/AbstractSubclassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs @@ -1,20 +1,27 @@ -using Pf2eModel; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; using Pf2eModel.Model.Base; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; /// /// Абстрактный класс-обработчик сущностей подклассов, полученных из Архивов Нефиса. /// Класс предназначен для обработки данных с помощью различных обработчиков, -/// для которых указан атрибут . +/// для которых указан атрибут . /// -/// Объект обработки вводимого JSON. -/// -internal abstract class AbstractSubclassProcessor(AonprdProcessorInput input) - : AbstractEntityProcessor(input) +internal abstract class AbstractSubclassProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Subclass; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdRepositoryUpdateProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdRepositoryUpdateProcessor.cs new file mode 100644 index 0000000..d4c921d --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdRepositoryUpdateProcessor.cs @@ -0,0 +1,26 @@ +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; + +[Processor( + "aonprd:repository", + Description = "Обновление репозитория", + Group = ProcessorGroupEnum.AonprdPreparation +)] +internal class AonprdRepositoryUpdateProcessor(AonprdImporter importer, ProcessProgress progress) + : IProcessor +{ + private AonprdImporter Importer { get; } = importer; + public ProcessProgress Progress { get; } = progress; + + public void Process() + { + Importer.Repository.Update(Progress); + } + + public void Save() + { + Importer.Loader.Prepare(); + } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/SchemaBuilder/AonprdSchemaBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdSchemaBuildProcessor.cs similarity index 76% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/SchemaBuilder/AonprdSchemaBuilder.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdSchemaBuildProcessor.cs index 3551a72..8d09197 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/SchemaBuilder/AonprdSchemaBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdSchemaBuildProcessor.cs @@ -1,24 +1,29 @@ -using Konsole; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.SchemaBuilder; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; -/// -/// Класс создания схемы данных с сайта Архивов Нефеса. -/// -/// Загузчик данных с сайта Архивов Нефеса. -internal class AonprdSchemaBuilder(AonprdLoader loader) +[Processor( + "aonprd:schema", + Description = "Построение схемы данных", + Group = ProcessorGroupEnum.AonprdPreparation +)] +[ProcessorDependencies(["aonprd:repository"])] +internal class AonprdSchemaBuildProcessor( + AonprdImporter importer, + ProcessProgress progress, + ApplicationSettings settings +) : IProcessor { + public AonprdImporter Importer { get; } = importer; + public ProcessProgress Progress { get; } = progress; + public ApplicationSettings Settings { get; } = settings; + /// /// Загузчик данных с сайта Архивов Нефеса. /// - public readonly AonprdLoader Loader = loader; - - /// - public readonly AonprdCompendium Compendium = loader.Compendium; - - // public readonly AonprdSchemaTypeResolver Resolver = new(); + public AonprdLoader Loader => Importer.Loader; /// /// Дополнительные схемы данных. @@ -28,6 +33,44 @@ internal class AonprdSchemaBuilder(AonprdLoader loader) ["_subclass"] = new() { Path = "_subclass" }, }; + /// + /// Построение схемы данных. + /// + public void Process() + { + Progress.Local.Reset(Loader.ItemCount); + + foreach (AonprdLoader.LoaderEntry entry in Loader.Entries()) + { + Progress.Local.Start(entry.Type); + GetSchema(entry).AddValue(entry.Json); + + if (_subclassSchemaTypes.Contains(entry.Type)) + { + Schemas["_subclass"].AddValue(entry.Json); + } + + Progress.Local.End(); + } + + Progress.Local.Complete(); + } + + /// + /// Сохранение постренных схем данных. + /// + public void Save() + { + string targetDir = Settings.OutputDir; + + targetDir = Path.Combine(targetDir, Loader.Importer.Slug); + + foreach (KeyValuePair kvp in Schemas) + { + SaveSchema(targetDir, kvp.Key, kvp.Value); + } + } + /// /// Список типов, являющихся подклассами. /// @@ -87,50 +130,6 @@ internal class AonprdSchemaBuilder(AonprdLoader loader) return value; } - /// - /// Построение схемы данных. - /// - public void BuildSchemas() - { - Console.WriteLine("Building schemas..."); - - ProgressBar bar = new(Loader.ItemCount); - int i = 0; - - foreach (AonprdLoader.LoaderEntry entry in Loader.Entries()) - { - bar.Refresh(i++, entry.Type); - GetSchema(entry).AddValue(entry.Json); - - if (_subclassSchemaTypes.Contains(entry.Type)) - { - Schemas["_subclass"].AddValue(entry.Json); - } - } - - bar.Refresh(Loader.ItemCount, "Done!"); - } - - /// - /// Сохранение постренных схем данных. - /// - /// Целевой каталог. - public void Save(string targetDir) - { - Console.WriteLine("Dumping schemas..."); - - targetDir = Path.Combine(targetDir, Loader.Importer.Slug); - - ProgressBar bar = new(Schemas.Count); - int i = 0; - - foreach (KeyValuePair kvp in Schemas) - { - bar.Refresh(i++, kvp.Key); - SaveSchema(targetDir, kvp.Key, kvp.Value); - } - } - /// /// Сохранение отдельной схемы данных. /// diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs new file mode 100644 index 0000000..c5f99cf --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs @@ -0,0 +1,37 @@ +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Processors; +using Pf2eModel; +using Pf2eModel.Model.Base.Targets; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; + +[Processor( + "aonprd:external", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdPreparation +)] +[ProcessorDependencies(["aonprd:repository"])] +internal class ExternalProcessor(Pf2eDbContext context) : AbstractProcessor +{ + public Pf2eDbContext Context { get; } = context; + + 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); + + // TODO + // Globals.AonprdTarget = AonprdTarget; + } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs similarity index 51% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs index f0b6ef4..dcf3081 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs @@ -1,13 +1,23 @@ -using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Processors; +using Pf2eModel; using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; -[AonprdProcessor("skill", [], false)] -internal class SkillProcessor(AonprdProcessorInput input) : AbstractProcessor(input) +[Processor( + "aonprd:skill", + InnerType = "skill", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdPreparation +)] +internal class SkillProcessor(AonprdProcessorInput input, Pf2eDbContext context) + : AbstractInputProcessor(input) { + public Pf2eDbContext Context { get; } = context; + /// /// Обучаемый объект. /// diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs similarity index 52% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SourceProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs index 1cd6a68..3cea58e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs @@ -1,12 +1,23 @@ -using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Processors; +using Pf2eModel; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Enum; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; -[AonprdProcessor("source", [], false)] -internal class SourceProcessor(AonprdProcessorInput input) : AbstractProcessor(input) +[Processor( + "aonprd:source", + InnerType = "source", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdPreparation +)] +[ProcessorDependencies(["aonprd:repository"])] +internal class SourceProcessor(AonprdProcessorInput input, Pf2eDbContext context) + : AbstractInputProcessor(input) { + public Pf2eDbContext Context { get; } = context; + /// /// Источник сущности. /// diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs similarity index 71% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Subclasses/ApparitionProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs index b89bf55..c66bc42 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs @@ -1,14 +1,32 @@ -using Pf2eModel; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; using Pf2eModel.Model.Base; using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Subclasses; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses; -[AonprdProcessor("apparition", ["class", "spell", "skill"])] -internal class ApparitionProcessor(AonprdProcessorInput input) : AbstractSubclassProcessor(input) +[Processor( + "aonprd:apparition", + InnerType = "apparition", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdSubclasses +)] +[ProcessorDependencies( + ["aonprd:trait", "aonprd:source", "aonprd:class", "aonprd:spell", "aonprd:skill"] +)] +internal class ApparitionProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractSubclassProcessor(input, context, stringHelper, helper, globals) { /// public override Subclass Object { get; } = new(); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs similarity index 55% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ActionProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs index 8fd6b55..86eac1d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs @@ -1,9 +1,26 @@ -using Pf2eModel.Model.Enum; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; +using Pf2eModel.Model.Enum; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[AonprdProcessor("action")] -internal class ActionProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +[Processor( + "aonprd:action", + InnerType = "action", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source"])] +internal class ActionProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Action; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/AncestryProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs similarity index 70% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/AncestryProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs index d0f16b0..841ab9a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/AncestryProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs @@ -1,11 +1,28 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[AonprdProcessor("ancestry", ["trait", "source", "language"])] -internal class AncestryProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +[Processor( + "aonprd:ancestry", + InnerType = "ancestry", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:language"])] +internal class AncestryProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Ancestry; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs new file mode 100644 index 0000000..dcab3e8 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs @@ -0,0 +1,46 @@ +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; +using Pf2eModel.Model.Enum; +using Pf2eModel.Model.Structure; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; + +[Processor( + "aonprd:archetype", + InnerType = "archetype", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source"])] +internal class ArchetypeProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +{ + /// + public override EntityType Type => EntityType.Archetype; + + /// + public override Archetype Object { get; } = new(); + + /// + public override void Add() => Context.Archetypes.Add(Object); + + /// + public override void Process() + { + base.Process(); + + Object.Level = Input.GetOptional("level"); + } + + /// + protected override string[] AddedDescriptionPaths() => + ["prerequisite_markdown", "requirement_markdown", "summary_markdown"]; +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/BackgroundProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs similarity index 56% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/BackgroundProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs index cc1b8e2..82a266b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/BackgroundProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs @@ -1,12 +1,29 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.BackgroundRelation; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[AonprdProcessor("background")] -internal class BackgroundProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +[Processor( + "aonprd:background", + InnerType = "background", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source"])] +internal class BackgroundProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Background; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs similarity index 91% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ClassProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index c70d24d..86d2fe8 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -1,5 +1,9 @@ using System.Text.RegularExpressions; using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eModel; using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; @@ -8,10 +12,22 @@ using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.ClassRelation; using static System.Text.RegularExpressions.Regex; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[AonprdProcessor("class", ["trait", "source", "skill"])] -internal partial class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +[Processor( + "aonprd:class", + InnerType = "class", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])] +internal partial class ClassProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Class; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/FeatProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs similarity index 75% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/FeatProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs index c863398..d281bb4 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/FeatProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs @@ -1,12 +1,28 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[AonprdProcessor("feat", ["trait", "source", "archetype"])] -internal class FeatProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +[Processor( + "aonprd:feat", + InnerType = "feat", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:archetype"])] +internal class FeatProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Feat; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs new file mode 100644 index 0000000..d9a723a --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs @@ -0,0 +1,34 @@ +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; +using Pf2eModel.Model.Enum; +using Pf2eModel.Model.Structure; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; + +[Processor( + "aonprd:language", + InnerType = "language", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source"])] +internal class LanguageProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +{ + /// + public override EntityType Type => EntityType.Language; + + /// + public override Language Object { get; } = new(); + + /// + public override void Add() => Context.Languages.Add(Object); +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/RitualProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs similarity index 94% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/RitualProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs index b4e7430..7e9b611 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/RitualProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs @@ -1,16 +1,31 @@ using System.Text.RegularExpressions; using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eModel; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.RitualRelation; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[AonprdProcessor("ritual", ["trait", "source", "skill"])] -internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +[Processor( + "aonprd:ritual", + InnerType = "ritual", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])] +internal partial class RitualProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Ritual; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RuleProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs similarity index 81% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RuleProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs index 43cbd04..b3f4e53 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RuleProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs @@ -1,14 +1,31 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Rule = Pf2eModel.Model.Structure.Rule; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules; -[AonprdProcessor("rules", ["rules-toc"])] -internal class RuleProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +[Processor( + "aonprd:rules", + InnerType = "rules", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])] +internal class RuleProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Rule; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesOrderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs similarity index 98% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesOrderProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs index 9d673e3..7a1d6f0 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesOrderProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs @@ -6,6 +6,7 @@ // Ну а пока просто расставляем родительские и дочерние элементы. using System.Data; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel.Model.Structure; using Rule = Pf2eModel.Model.Structure.Rule; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesTocProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs similarity index 98% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesTocProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs index 5d76d93..9ecc12d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/Rules/RulesTocProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs @@ -2,6 +2,7 @@ using System.Web; using AngleSharp; using AngleSharp.Dom; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel.Model.Base.Source; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/SpellProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs similarity index 83% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/SpellProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs index b1c4b71..27573de 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/SpellProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs @@ -1,13 +1,30 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.SpellRelation; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[AonprdProcessor("spell")] -internal class SpellProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +[Processor( + "aonprd:spell", + InnerType = "spell", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:trait", "aonprd:source"])] +internal class SpellProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Spell; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs new file mode 100644 index 0000000..9ac249e --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -0,0 +1,54 @@ +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eModel; +using Pf2eModel.Model.Enum; +using Pf2eModel.Model.Structure; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; + +[Processor( + "aonprd:trait", + InnerType = "trait", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:external"])] +internal class TraitProcessor( + AonprdProcessorInput input, + Pf2eDbContext context, + StringHelper stringHelper, + DatabaseHelper helper, + AonprdBuilderGlobals globals +) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +{ + /// + public override EntityType Type => EntityType.Trait; + + /// + public override Trait Object { get; } = new(); + + /// + public override void Add() => Context.Traits.Add(Object); + + /// + public override void Process() + { + base.Process(); + + Object.Type = TraitType.Regular; + + Context.Traits.Add(Object); + } + + /// + protected override void AddTraits() + { + // Необходимо пропускать + } + + /// + protected override string GetDescription() => + base.GetDescription() + AddedDescription(["summary_markdown", "search_markdown"]); +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs index 80b7cc3..709ae64 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs @@ -2,6 +2,7 @@ using System.Text; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model; diff --git a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs index a3dedef..c88d524 100644 --- a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs @@ -1,4 +1,6 @@ -namespace Pf2eDataScrapper.Importers.Repositories; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.Importers.Repositories; /// /// Класс абстрактного репозитория. @@ -22,5 +24,5 @@ internal abstract class AbstractImporterRepository(AbstractImporter importer) /// /// Обновление данных в репозитории. /// - public abstract void Update(); + public abstract void Update(ProcessProgress progress); } diff --git a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs index d0974eb..ab73ea2 100644 --- a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Repositories; @@ -16,11 +17,14 @@ internal class GitRepository(AbstractImporter importer, string url) public string Url { get; } = url; /// - public override void Update() + public override void Update(ProcessProgress progress) { + progress.Local.Reset(1); + if (Directory.Exists(RepositoryDirPath)) { - // git pull + progress.Local.Start("git pull"); + Process process = new() { StartInfo = new() @@ -34,7 +38,8 @@ internal class GitRepository(AbstractImporter importer, string url) } else { - // git clone + progress.Local.Start("git clone"); + Process process = new() { StartInfo = new() @@ -46,5 +51,7 @@ internal class GitRepository(AbstractImporter importer, string url) }; process.Start(); } + + progress.Local.Complete(); } } diff --git a/Pf2eDataScrapper/Processors/AbstractInputProcessor.cs b/Pf2eDataScrapper/Processors/AbstractInputProcessor.cs index a576ae2..aae8786 100644 --- a/Pf2eDataScrapper/Processors/AbstractInputProcessor.cs +++ b/Pf2eDataScrapper/Processors/AbstractInputProcessor.cs @@ -1,34 +1,18 @@ -using Microsoft.Extensions.DependencyInjection; -using Pf2eDataScrapper.App; -using Pf2eModel; +using Pf2eDataScrapper.App; namespace Pf2eDataScrapper.Processors; /// /// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса. /// Класс предназначен для обработки данных с помощью различных обработчиков, -/// для которых указан атрибут . +/// для которых указан атрибут . /// -/// Провайдер сервисов. -/// Провайдер сервисов. -/// -internal abstract class AbstractInputProcessor( - IServiceProvider serviceProvider, - ProcessorInput input -) : IProcessor +/// Входные данные обработчика. +/// +internal abstract class AbstractInputProcessor(ProcessorInput input) : IProcessor { /// - /// Провайдер сервисов. - /// - public IServiceProvider ServiceProvider { get; } = serviceProvider; - - /// - /// Контекст базы данных. - /// - public Pf2eDbContext Context { get; } = serviceProvider.GetService()!; - - /// - /// Ввод. + /// Входные данные обработчика. /// public ProcessorInput Input { get; } = input; diff --git a/Pf2eDataScrapper/Processors/AbstractProcessor.cs b/Pf2eDataScrapper/Processors/AbstractProcessor.cs index 155a670..0731b1c 100644 --- a/Pf2eDataScrapper/Processors/AbstractProcessor.cs +++ b/Pf2eDataScrapper/Processors/AbstractProcessor.cs @@ -7,22 +7,11 @@ namespace Pf2eDataScrapper.Processors; /// /// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса. /// Класс предназначен для обработки данных с помощью различных обработчиков, -/// для которых указан атрибут . +/// для которых указан атрибут . /// -/// Провайдер сервисов. -/// -internal abstract class AbstractProcessor(IServiceProvider serviceProvider) : IProcessor +/// +internal abstract class AbstractProcessor : IProcessor { - /// - /// Провайдер сервисов. - /// - public IServiceProvider ServiceProvider { get; } = serviceProvider; - - /// - /// Контекст базы данных. - /// - public Pf2eDbContext Context { get; } = serviceProvider.GetService()!; - /// /// Основной метод обработки данных. /// diff --git a/Pf2eDataScrapper/Processors/DatabaseTruncateProcessor.cs b/Pf2eDataScrapper/Processors/DatabaseTruncateProcessor.cs new file mode 100644 index 0000000..9967454 --- /dev/null +++ b/Pf2eDataScrapper/Processors/DatabaseTruncateProcessor.cs @@ -0,0 +1,16 @@ +using Pf2eDataScrapper.App; +using Pf2eModel; + +namespace Pf2eDataScrapper.Processors; + +[Processor("truncate", Description = "Отчистка таблиц БД", Group = ProcessorGroupEnum.General)] +internal class DatabaseTruncateProcessor : IProcessor +{ + public void Process() + { + using Pf2eDbContext context = new(); + string _ = context.Entities.Truncate() + "\n" + context.TruncateAll(); + } + + public void Save() { } +} diff --git a/Pf2eDataScrapper/Processors/IProcessor.cs b/Pf2eDataScrapper/Processors/IProcessor.cs index 33849eb..09c675a 100644 --- a/Pf2eDataScrapper/Processors/IProcessor.cs +++ b/Pf2eDataScrapper/Processors/IProcessor.cs @@ -1,14 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Pf2eDataScrapper.Processors; +namespace Pf2eDataScrapper.Processors; public interface IProcessor { - /// - /// Провайдер сервисов. - /// - IServiceProvider ServiceProvider { get; } - /// /// Основной метод обработки данных. /// diff --git a/Pf2eDataScrapper/Processors/ProcessProgress.cs b/Pf2eDataScrapper/Processors/ProcessProgress.cs new file mode 100644 index 0000000..5e2f7cb --- /dev/null +++ b/Pf2eDataScrapper/Processors/ProcessProgress.cs @@ -0,0 +1,156 @@ +namespace Pf2eDataScrapper.Processors; + +internal class ProcessProgress +{ + /// + /// Прогресс обработки обработчиков . + /// + public ProgressItem Processors { get; } = new(); + + /// + /// Прогресс работы конкретного обработчика . + /// + public ProgressItem Local { get; } = new(); + + /// + /// Конструктор класса . + /// + public ProcessProgress() + { + Processors.Update += Update; + Local.Update += Update; + } + + /// + /// Событие обновления + /// + private void Update() + { + ProgressUpdate?.Invoke(this); + } + + public void Message(string message) + { + ProgressMessage?.Invoke(this, message); + } + + public delegate void ProgressUpdateDelegate(ProcessProgress sender); + public event ProgressUpdateDelegate? ProgressUpdate; + + public delegate void ProgressMessageDelegate(ProcessProgress sender, string message); + public event ProgressMessageDelegate? ProgressMessage; + + /// + /// Класс прогресса. + /// + public class ProgressItem + { + /// + /// Количество обрабатываемых процессов. + /// + public int Count + { + get => _count; + set + { + _count = value; + Update?.Invoke(); + } + } + private int _count = 0; + + /// + /// Индекс текущего обрабатываемого процесса. + /// + public int Current + { + get => _current; + set + { + _current = value; + Update?.Invoke(); + } + } + private int _current = 0; + + /// + /// Название текущего обрабатываемого процесса. + /// + public string Text + { + get => _text; + set + { + _text = value; + Update?.Invoke(); + } + } + private string _text = ""; + + /// + /// Значение прогресса. + /// + public double Percent => Count > 0 ? 1.0 * Current / Count : 1.0; + + /// + /// Сброс прогресса до нуля и установка нового максимального значения. + /// + /// + public void Reset(int max = 0) + { + _count = max; + _current = 0; + _text = ""; + Update?.Invoke(); + } + + /// + /// Отмечает начало работы над процессом, описываемым строкой . + /// + /// Строка, описывающая процесс. + public void Start(string text) + { + _text = text; + Update?.Invoke(); + } + + /// + /// Отмечает конец работы над процессом и увеличивает значение прогресса. + /// + public void End() + { + _text = ""; + _current++; + Update?.Invoke(); + } + + /// + /// Отмечает прогресс завершённым. + /// + public void Complete() + { + _current = _count; + _text = "Готово!"; + Update?.Invoke(); + } + + /// + /// Получение строкового представления прогресса. + /// + /// Строковое представление прогресса. + public override string ToString() + { + return $"{Text} [{Current} / {Count}; {Percent:P}]"; + } + + /// + /// Делегат события обновления. + /// + public delegate void UpdateDelegate(); + + /// + /// Событие обновление. + /// + public event UpdateDelegate? Update; + } +} diff --git a/Pf2eDataScrapper/Processors/ProcessorInput.cs b/Pf2eDataScrapper/Processors/ProcessorInput.cs index cb683be..c6acd01 100644 --- a/Pf2eDataScrapper/Processors/ProcessorInput.cs +++ b/Pf2eDataScrapper/Processors/ProcessorInput.cs @@ -7,9 +7,15 @@ namespace Pf2eDataScrapper.Processors; /// /// Класс для работы с JSON-объектом. /// +/// Название объекта, ассоциированного с вводом. /// JSON-объект, который будет использоваться для доступа к данным. -internal class ProcessorInput(JObject input) +internal class ProcessorInput(string name, JObject input) { + /// + /// Название объекта, ассоциированного с вводом. + /// + public string Name { get; } = name; + /// /// Входящий JSON-объект, который содержит данные для обработки. /// From aad2081912c7c61033f3b5e442339536e3f8ad0f Mon Sep 17 00:00:00 2001 From: kozhilya Date: Tue, 13 May 2025 15:17:50 +0400 Subject: [PATCH 04/33] =?UTF-8?q?=D0=9A=D0=BE=D1=80=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BD=D0=B0=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D1=81=20null=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20LocaleString?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eModel/Pf2eDbContextModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Pf2eModel/Pf2eDbContextModel.cs b/Pf2eModel/Pf2eDbContextModel.cs index 99e5fb6..56a8917 100644 --- a/Pf2eModel/Pf2eDbContextModel.cs +++ b/Pf2eModel/Pf2eDbContextModel.cs @@ -76,7 +76,10 @@ public partial class Pf2eDbContext { entity .Property(func) - .HasConversion(v => v.Serialize(), v => LocaleString.Deserialize(v)) + .HasConversion( + v => v == null ? null : v.Serialize(), + v => v == null ? null : LocaleString.Deserialize(v) + ) .HasColumnType("jsonb") .IsRequired(required); } From 83ee91499195a188392fe56cd35dfa9e194a6265 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Tue, 13 May 2025 19:22:41 +0400 Subject: [PATCH 05/33] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B4=D0=BE=D0=BB?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/ProcessorCollection.cs | 24 +-- Pf2eDataScrapper/App/ProcessorGroupEnum.cs | 3 + Pf2eDataScrapper/ApplicationSettings.cs | 12 +- Pf2eDataScrapper/Forms/StartForm.Designer.cs | 1 + Pf2eDataScrapper/Forms/StartForm.cs | 25 ++- Pf2eDataScrapper/Helpers/DatabaseHelper.cs | 44 ++-- .../ProcessProgress.cs | 0 Pf2eDataScrapper/ImportApplication.cs | 110 ++++------ .../Importers/AbstractCompendium.cs | 42 ++++ .../Importers/AbstractCompendiumItem.cs | 23 ++ .../Importers/AbstractImporter.cs | 44 +--- .../Implementations/Aonprd/AonprdImporter.cs | 75 ++----- .../Implementations/Aonprd/AonprdLoader.cs | 105 ---------- .../Aonprd/AonprdRepository.cs | 10 +- .../Aonprd/Compendium/AonprdCompendium.cs | 101 ++++++--- ...rdReference.cs => AonprdCompendiumItem.cs} | 8 +- .../Aonprd/Compendium/AonprdType.cs | 41 ---- .../AonprdProcessorInputExtension.cs} | 20 +- .../Aonprd/Helpers/LegacyEntityHandler.cs | 3 +- .../Processors/AbstractEntityProcessor.cs | 2 +- .../Processors/AbstractSubclassProcessor.cs | 3 +- .../Common/AonprdSchemaBuildProcessor.cs | 170 --------------- ...cessor.cs => RepositoryUpdateProcessor.cs} | 15 +- .../Processors/Common/SchemaBuildProcessor.cs | 92 ++++++++ .../Processors/Common/SkillProcessor.cs | 2 +- .../Processors/Common/SourceProcessor.cs | 2 +- .../Subclasses/ApparitionProcessor.cs | 3 +- .../Processors/Types/ActionProcessor.cs | 3 +- .../Processors/Types/AncestryProcessor.cs | 3 +- .../Processors/Types/ArchetypeProcessor.cs | 3 +- .../Processors/Types/BackgroundProcessor.cs | 3 +- .../Aonprd/Processors/Types/ClassProcessor.cs | 3 +- .../Aonprd/Processors/Types/FeatProcessor.cs | 5 +- .../Processors/Types/LanguageProcessor.cs | 3 +- .../Processors/Types/RitualProcessor.cs | 3 +- .../Processors/Types/Rules/RuleProcessor.cs | 3 +- .../Types/Rules/RulesOrderProcessor.cs | 21 +- .../Types/Rules/RulesTocProcessor.cs | 19 +- .../Aonprd/Processors/Types/SpellProcessor.cs | 3 +- .../Aonprd/Processors/Types/TraitProcessor.cs | 3 +- .../Compendium/CompendiumDocumentType.cs | 2 +- .../Compendium/CompendiumFolder.cs | 25 ++- .../Compendium/CompendiumFolderCollection.cs | 31 +++ .../Compendium/FoundryEnCompendiumItem.cs | 25 +++ .../FoundryEnAbstractProcessor.cs} | 18 +- .../DataBuilder/Processors/ActionProcessor.cs | 18 +- .../Processors/ConditionProcessor.cs | 18 +- .../FoundryEn/FoundryEnCompendium.cs | 63 ++++++ .../FoundryEn/FoundryEnImporter.cs | 57 +++++ .../Preparation/CompendiumFolderProcessor.cs | 123 +++++++++++ .../Preparation/CompendiumItemsProcessor.cs | 104 +++++++++ .../Preparation/RepositoryUpdateProcessor.cs | 23 ++ .../Preparation/SchemaBuildProcessor.cs | 99 +++++++++ .../Preparation/SchemaTypeResolver.cs} | 4 +- .../Compendium/CompendiumFolderCollection.cs | 26 --- .../Pf2eFoundry/CompendiumLoader.cs | 178 ---------------- .../DataBuilder/PF2eFoundryDataBuilder.cs | 88 -------- .../DataBuilder/Pf2eTypeProcessor.cs | 9 - .../Pf2eFoundry/Pf2eFoundryImporter.cs | 67 ------ .../SchemaBuilder/CompendiumSchemaBuilder.cs | 197 ------------------ .../DatabaseTruncateProcessor.cs | 2 +- .../Processors/ProcessorExecutor.cs | 106 ++++++++++ Pf2eDataScrapper/Program.cs | 26 ++- .../AbstractSchemaBuildProcessor.cs | 117 +++++++++++ 64 files changed, 1252 insertions(+), 1229 deletions(-) rename Pf2eDataScrapper/{Processors => Helpers}/ProcessProgress.cs (100%) create mode 100644 Pf2eDataScrapper/Importers/AbstractCompendium.cs create mode 100644 Pf2eDataScrapper/Importers/AbstractCompendiumItem.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdLoader.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/{AonprdReference.cs => AonprdCompendiumItem.cs} (84%) delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/{AonprdProcessorInput.cs => Helpers/AonprdProcessorInputExtension.cs} (83%) delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdSchemaBuildProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/{AonprdRepositoryUpdateProcessor.cs => RepositoryUpdateProcessor.cs} (51%) create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/{Pf2eFoundry => FoundryEn}/Compendium/CompendiumDocumentType.cs (75%) rename Pf2eDataScrapper/Importers/Implementations/{Pf2eFoundry => FoundryEn}/Compendium/CompendiumFolder.cs (84%) create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolderCollection.cs create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendiumItem.cs rename Pf2eDataScrapper/Importers/Implementations/{Pf2eFoundry/DataBuilder/AbstractProcessor.cs => FoundryEn/DataBuilder/FoundryEnAbstractProcessor.cs} (91%) rename Pf2eDataScrapper/Importers/Implementations/{Pf2eFoundry => FoundryEn}/DataBuilder/Processors/ActionProcessor.cs (62%) rename Pf2eDataScrapper/Importers/Implementations/{Pf2eFoundry => FoundryEn}/DataBuilder/Processors/ConditionProcessor.cs (61%) create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnImporter.cs create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/{Pf2eFoundry/SchemaBuilder/CompendiumSchemaTypeResolver.cs => FoundryEn/Processors/Preparation/SchemaTypeResolver.cs} (93%) delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolderCollection.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/CompendiumLoader.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Pf2eTypeProcessor.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Pf2eFoundryImporter.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaBuilder.cs rename Pf2eDataScrapper/Processors/{ => General}/DatabaseTruncateProcessor.cs (89%) create mode 100644 Pf2eDataScrapper/Processors/ProcessorExecutor.cs create mode 100644 Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index 7dbae40..c73748c 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -48,7 +48,7 @@ internal class ProcessorCollection /// private ProcessorCollectionItem CreateItem(Type type) { - if (!type.IsSubclassOf(typeof(IProcessor))) + if (!type.IsAssignableTo(typeof(IProcessor))) { throw new( $"'ProcessorAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not." @@ -61,15 +61,6 @@ internal class ProcessorCollection bool isCollection = processorAttribute.InnerType is not null; - ConstructorInfo? constructor = isCollection - ? type.GetConstructor([]) - : type.GetConstructor([typeof(ProcessorInput)])!; - - if (constructor is null) - { - throw new($"Не удаётся определить конструктор класса {type}"); - } - return new() { Name = processorAttribute.Name, @@ -79,7 +70,6 @@ internal class ProcessorCollection Dependencies = dependenciesAttribute?.Dependencies ?? [], IsCollectionProcessor = isCollection, InnerType = processorAttribute.InnerType, - Constructor = constructor, }; } @@ -111,7 +101,7 @@ internal class ProcessorCollection { foreach (ProcessorCollectionItem item in Items) { - if (item.Priority > 0) + if (item.Priority >= 0) { continue; } @@ -125,7 +115,10 @@ internal class ProcessorCollection continue; } - item.Priority = dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1; + item.Priority = Math.Min( + dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1, + (int)item.Group * 100 + ); priorities.Add(item.Name, item.Priority); } } @@ -215,9 +208,4 @@ internal class ProcessorCollectionItem /// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов. /// public string? InnerType { get; init; } - - /// - /// Конструктор обработчика. - /// - public required ConstructorInfo Constructor { get; init; } } diff --git a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs index 09d40ac..85b918f 100644 --- a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs +++ b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs @@ -18,4 +18,7 @@ public enum ProcessorGroupEnum [Description("Foundry EN: Подготовка")] FoundryEnPreparation, + + [Description("Foundry EN: Основные сущности")] + FoundryEnEntities, } diff --git a/Pf2eDataScrapper/ApplicationSettings.cs b/Pf2eDataScrapper/ApplicationSettings.cs index 48a80a7..4dc914d 100644 --- a/Pf2eDataScrapper/ApplicationSettings.cs +++ b/Pf2eDataScrapper/ApplicationSettings.cs @@ -15,6 +15,10 @@ internal class ApplicationSettings public ApplicationSettings() { DotEnv.Load(); + + DataDirPath = EnvReader.GetStringValue("DATA_DIR"); + OutputDirPath = EnvReader.GetStringValue("OUTPUT_DIR"); + DatabaseConnection = EnvReader.GetStringValue("DATABASE_CONNECTION"); } #region Настройки из .env @@ -26,7 +30,7 @@ internal class ApplicationSettings [ReadOnly(true)] [DisplayName("Путь к папке с входными данными.")] [Description("Редактирование через .env-файл.")] - public string DataDirPath { get; } = EnvReader.GetStringValue("DATA_DIR"); + public string DataDirPath { get; } /// /// Папка в входными данными. @@ -41,7 +45,7 @@ internal class ApplicationSettings [ReadOnly(true)] [DisplayName("Путь к папке для сохранения результатов.")] [Description("Редактирование через .env-файл.")] - public string OutputDirPath { get; } = EnvReader.GetStringValue("OUTPUT_DIR"); + public string OutputDirPath { get; } /// /// Папка для сохранения результатов. @@ -56,7 +60,7 @@ internal class ApplicationSettings [ReadOnly(true)] [DisplayName("Строка подключения к базе данных.")] [Description("Редактирование через .env-файл.")] - public string DatabaseConnection { get; } = EnvReader.GetStringValue("DATABASE_CONNECTION"); + public string DatabaseConnection { get; } #endregion @@ -74,7 +78,7 @@ internal class ApplicationSettings /// Список обработчиков, которые должны быть выполнены. /// Пустой список означает, что должны быть выполнены все обработчики. /// - public ICollection ProcessorNames { get; } = new List(); + public ICollection ProcessorNames { get; } = []; #endregion } diff --git a/Pf2eDataScrapper/Forms/StartForm.Designer.cs b/Pf2eDataScrapper/Forms/StartForm.Designer.cs index 31eb6f5..88ccf32 100644 --- a/Pf2eDataScrapper/Forms/StartForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/StartForm.Designer.cs @@ -135,6 +135,7 @@ partial class StartForm StartButton.TabIndex = 0; StartButton.Text = "Запуск"; StartButton.UseVisualStyleBackColor = true; + StartButton.Click += StartButton_Click; // // StartForm // diff --git a/Pf2eDataScrapper/Forms/StartForm.cs b/Pf2eDataScrapper/Forms/StartForm.cs index 58811ff..ac9466e 100644 --- a/Pf2eDataScrapper/Forms/StartForm.cs +++ b/Pf2eDataScrapper/Forms/StartForm.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Reflection; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; namespace Pf2eDataScrapper.Forms; @@ -32,19 +33,14 @@ internal partial class StartForm : Form ProcessorsListView.Groups.Clear(); - foreach (FieldInfo field in typeof(ProcessorGroupEnum).GetFields()) + foreach (ProcessorGroupEnum group in Enum.GetValues()) { - ProcessorGroupEnum groupEnum = (ProcessorGroupEnum)field.GetValue(null)!; - - if (!items.TryGetValue(groupEnum, out List? list)) + if (!items.TryGetValue(group, out List? list)) { continue; } - DescriptionAttribute? attribute = (DescriptionAttribute?) - Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)); - - string name = attribute?.Description ?? field.Name; + string name = Enum.GetDescription(group); ListViewGroup listViewGroup = new(name); ProcessorsListView.Groups.Add(listViewGroup); @@ -61,4 +57,17 @@ internal partial class StartForm : Form } } } + + #region "Кнопка старта" + + private void StartButton_Click(object sender, EventArgs e) + { + StartButtonPressed?.Invoke(); + } + + public delegate void StartButtonPressedDelegate(); + + public event StartButtonPressedDelegate? StartButtonPressed; + + #endregion } diff --git a/Pf2eDataScrapper/Helpers/DatabaseHelper.cs b/Pf2eDataScrapper/Helpers/DatabaseHelper.cs index 7b0960e..85bebfa 100644 --- a/Pf2eDataScrapper/Helpers/DatabaseHelper.cs +++ b/Pf2eDataScrapper/Helpers/DatabaseHelper.cs @@ -8,6 +8,16 @@ namespace Pf2eDataScrapper.Helpers; /// public class DatabaseHelper { + /// + /// Context для доступа к базе данных. + /// + public Pf2eDbContext Context { get; } + + /// + /// Сервис для работы с URL-Slug. Используется для преобразования названий в URL-friendly формат. + /// + public SlugHelper Slugger { get; } + /// /// Последний найденный идентификатор сущности. /// Используется для генерации новых ID сущностей. @@ -19,18 +29,10 @@ public class DatabaseHelper /// public int NextEntityId => ++_id; - /// - /// Сервис для работы с URL-Slug. Используется для преобразования названий в URL-friendly формат. - /// - public readonly SlugHelper Slugger = new(); - - /// - /// Context для доступа к базе данных. - /// - public readonly Pf2eDbContext Context = new(); - - private DatabaseHelper() + private DatabaseHelper(Pf2eDbContext context, SlugHelper slugHelper) { + Context = context; + Slugger = slugHelper; _id = Context.Entities.Any() ? Context.Entities.Max(e => e.Id) : 0; } @@ -41,24 +43,4 @@ public class DatabaseHelper { return Slugger.GenerateSlug(name); } - - /// - /// Возвращает singleton-инстанцию класса . - /// - public static readonly DatabaseHelper Instance = new(); - - /// - /// Контекст для доступа к базе данных. - /// - public static Pf2eDbContext DbContext => Instance.Context; - - /// - /// Обновляет контекст и сохраняет изменения в базе данных. - /// - public static void ResetContext() - { - Instance.Context.SaveChanges(); - // Instance.Context.Dispose(); - // Instance.Context = new(); - } } diff --git a/Pf2eDataScrapper/Processors/ProcessProgress.cs b/Pf2eDataScrapper/Helpers/ProcessProgress.cs similarity index 100% rename from Pf2eDataScrapper/Processors/ProcessProgress.cs rename to Pf2eDataScrapper/Helpers/ProcessProgress.cs diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index 16b8671..a7e5e09 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -1,45 +1,37 @@ using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Forms; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Importers.Implementations.Aonprd; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn; using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry; using Pf2eDataScrapper.Processors; using Pf2eModel; +using Slugify; namespace Pf2eDataScrapper; internal sealed class ImportApplication : ApplicationContext { - public ImportApplication() + public ImportApplication( + IServiceProvider serviceProvider, + ApplicationSettings settings, + ProcessorCollection processors + ) { - InitializeServices(); + ServiceProvider = serviceProvider; + Settings = settings; + Processors = processors; + CreateStartForm(); } - #region "Внедрение зависимостей (DI)" - - public readonly IServiceCollection Services = new ServiceCollection(); - - public IServiceProvider ServiceProvider { get; private set; } = null!; - - private void InitializeServices() - { - Services.AddSingleton(this); - Services.AddSingleton(Settings); - Services.AddSingleton(Processors); - - Services.AddDbContext(); - - ServiceProvider = Services.BuildServiceProvider(); - } - - #endregion - #region "Настройки" - public readonly ApplicationSettings Settings = new(); - public readonly ProcessorCollection Processors = new(); + public IServiceProvider ServiceProvider { get; } + public ApplicationSettings Settings { get; } + public ProcessorCollection Processors { get; } #endregion @@ -55,6 +47,7 @@ internal sealed class ImportApplication : ApplicationContext { _startForm = ActivatorUtilities.CreateInstance(ServiceProvider); _startForm.StartPosition = FormStartPosition.CenterParent; + _startForm.StartButtonPressed += StartProcess; _startForm.Show(); } @@ -62,64 +55,39 @@ internal sealed class ImportApplication : ApplicationContext { _startForm!.Close(); - Processors.SortItems(); + ProcessorExecutor executor = ActivatorUtilities.CreateInstance( + ServiceProvider + ); - ProcessProgress progress = new ProcessProgress(); - - foreach (ProcessorCollectionItem item in Processors.Items) - { - if (item.IsCollectionProcessor) - { - RunCollection(item, progress); - } - else - { - RunSingle(item, progress); - } - } + executor.Start(); } - private void RunSingle(ProcessorCollectionItem item, ProcessProgress progress) + #endregion + + #region "Внедрение зависимостей" + + public static void RegisterServices(IServiceCollection services) { - progress.Local.Reset(1); - progress.Local.Start(""); + // Контекст базы данных + services.AddDbContext(); - IProcessor processor = (IProcessor) - ActivatorUtilities.CreateInstance(ServiceProvider, item.Type); + // Основная программа + services.AddSingleton(); - processor.Process(); - processor.Save(); + // Настройки программы + services.AddSingleton(); - progress.Local.Complete(); - } + // Набор процессоров + services.AddSingleton(); - private void RunCollection(ProcessorCollectionItem item, ProcessProgress progress) - { - AbstractImporter importer = item.Group switch - { - ProcessorGroupEnum.AonprdPreparation - or ProcessorGroupEnum.AonprdEntities - or ProcessorGroupEnum.AonprdSubclasses => new AonprdImporter(), - ProcessorGroupEnum.FoundryEnPreparation => new Pf2EFoundryImporter(), - _ => throw new("Can't recognize importer"), - }; + // Помощник идентификации базы данных + services.AddSingleton(); - progress.Local.Reset(importer.CountInputs(item.InnerType!)); + // Помощник создание URL-совместимых названий + services.AddTransient(); - foreach (ProcessorInput input in importer.ResolveInputs(item.InnerType!)) - { - progress.Local.Start(input.Name); - - IProcessor processor = (IProcessor) - ActivatorUtilities.CreateInstance(ServiceProvider, item.Type); - - processor.Process(); - processor.Save(); - - progress.Local.End(); - } - - progress.Local.Complete(); + // Счётчик прогресса + services.AddSingleton(); } #endregion diff --git a/Pf2eDataScrapper/Importers/AbstractCompendium.cs b/Pf2eDataScrapper/Importers/AbstractCompendium.cs new file mode 100644 index 0000000..96818ef --- /dev/null +++ b/Pf2eDataScrapper/Importers/AbstractCompendium.cs @@ -0,0 +1,42 @@ +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Importers.Repositories; + +namespace Pf2eDataScrapper.Importers; + +/// +/// Компендиум данных. +/// +internal abstract class AbstractCompendium +{ + /// + /// Репозиторий данных. + /// + public abstract AbstractImporterRepository Repository { get; } + + /// + /// Путь к хранению исходных данных. + /// + public string Dir => Repository.RepositoryDirPath; + + /// + /// Возвращает количество элементов в компендиуме. + /// + /// Количество элементов. + public abstract int Count(); + + /// + /// Возвращает количество элементов для указанного типа данных. + /// + /// Тип данных. + /// Количество элементов. + public abstract int Count(string type); + + /// + /// Получение итератора сущностей. Если указан тип - то только указанного типа. + /// + /// Тип сущностей, которые нужно получить. + /// Итератор сущностей. + public abstract IEnumerable> Iterate( + string? type = null + ); +} diff --git a/Pf2eDataScrapper/Importers/AbstractCompendiumItem.cs b/Pf2eDataScrapper/Importers/AbstractCompendiumItem.cs new file mode 100644 index 0000000..eeca1a2 --- /dev/null +++ b/Pf2eDataScrapper/Importers/AbstractCompendiumItem.cs @@ -0,0 +1,23 @@ +namespace Pf2eDataScrapper.Importers; + +/// +/// Абстрактная ссылка на сущность. +/// +internal abstract class AbstractCompendiumItem +{ + /// + /// Идентификатор сущности. + /// + public abstract string Id { get; } + + /// + /// Тип сущности. + /// + public abstract string Type { get; } + + /// + /// Строковое представление имени сущности. + /// + /// Строковое представление. + public override string ToString() => Id; +} diff --git a/Pf2eDataScrapper/Importers/AbstractImporter.cs b/Pf2eDataScrapper/Importers/AbstractImporter.cs index ae2cc29..020ec30 100644 --- a/Pf2eDataScrapper/Importers/AbstractImporter.cs +++ b/Pf2eDataScrapper/Importers/AbstractImporter.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Importers.Repositories; -using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers; @@ -23,46 +22,13 @@ internal abstract class AbstractImporter /// public abstract AbstractImporterRepository Repository { get; } + /// + /// Компендиум данных импортёра. + /// + public abstract AbstractCompendium Compendium { get; } + /// /// Путь к директории с данными для импортёра. /// public string Dir => Path.Combine(EnvSettings.DataDirPath, Slug); - - /// - /// Перечисление процессоров, которые будут использоваться для обработки данных. - /// При пустом массиве будут обработаны все доступные процессоры. - /// - public string[] ProcessorNames { get; set; } = []; - - /// - /// Метод подготовки данных для импорта. - /// - [Obsolete] - public abstract void Prepare(); - - /// - /// Импорт данных - /// - [Obsolete] - public abstract void Import(); - - /// - /// Построение схем данных. - /// - [Obsolete] - public abstract void BuildSchemas(); - - /// - /// Получение количества записей, принаделажащих типу . - /// - /// Тип записей. - /// Количество записей. - public abstract int CountInputs(string type); - - /// - /// Итератор ввода для записей, принаделажащих типу . - /// - /// Тип записей. - /// Итератор. - public abstract IEnumerable ResolveInputs(string type); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs index 114573e..b4b6063 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs @@ -1,15 +1,15 @@ -using Newtonsoft.Json.Linq; +using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; -using Pf2eDataScrapper.Importers.Repositories; -using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; /// /// Класс обработки данных с сайта Архивов Нефеса. /// -internal class AonprdImporter : AbstractImporter +internal class AonprdImporter(AonprdRepository repository, AonprdCompendium compendium) + : AbstractImporter { /// /// Название внешней ссылки. @@ -23,59 +23,24 @@ internal class AonprdImporter : AbstractImporter public override string Slug => "aonprd"; /// - public override AbstractImporterRepository Repository { get; } + public override AonprdRepository Repository { get; } = repository; - /// - /// Инструмент для работы с данными сайта Архивов Нефеса. - /// - public AonprdLoader Loader { get; } + /// + public override AonprdCompendium Compendium { get; } = compendium; - /// - /// Конструктор класса . - /// - public AonprdImporter() + #region "Внедрение зависимостей" + + public static void RegisterServices(IServiceCollection services) { - Repository = new AonprdRepository(this, "https://elasticsearch.aonprd.com"); - Loader = new(this); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); } - /// - public override void Prepare() { } - - /// - public override void BuildSchemas() { } - - /// - public override void Import() { } - - /// - public override int CountInputs(string type) - { - return Loader.Compendium.Count(type); - } - - /// - public override IEnumerable ResolveInputs(string type) - { - foreach (string file in Loader.Compendium.Files(type)) - { - using StreamReader fileReader = new(Path.Combine(Dir, $"{file}.json")); - JArray array = JArray.Parse(fileReader.ReadToEnd()); - - foreach (JToken item in array) - { - string? id = item.SelectToken("id")?.ToString(); - - if (id is null) - { - continue; - } - - if (AonprdReference.ResolveType(id) == type) - { - yield return new AonprdProcessorInput(id, (JObject)item); - } - } - } - } + #endregion } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdLoader.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdLoader.cs deleted file mode 100644 index d97b8ec..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdLoader.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Newtonsoft.Json.Linq; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; - -/// -/// Класс загрузки данных с сайта Архивов Нефеса. -/// -internal class AonprdLoader(AonprdImporter importer) -{ - /// - /// Референс на импортер, используемый для загрузки данных. - /// - public AonprdImporter Importer { get; } = importer; - - /// - /// Компендиум данных с сайта Архивов Нефеса. - /// - public AonprdCompendium Compendium { get; } = new(); - - /// - /// Число элементов в компендарии. - /// - public int ItemCount => Compendium.Count(); - - /// - /// Подготовка данных перед загрузкой. - /// - public void Prepare() - { - using StreamReader indexFile = new(Path.Combine(Importer.Dir, "index.json")); - JObject indexJson = JObject.Parse(indexFile.ReadToEnd()); - - foreach ((string file, JToken? value) in indexJson) - { - JArray items = (JArray)value!; - - foreach (JToken item in items) - { - Compendium.AddReference(item.ToString(), file); - } - } - } - - /// - /// Возвращает коллекцию всех доступных загружаемых элементов. - /// - /// Коллекция всех загружаемых элементов. - public IEnumerable Entries() - { - return Entries([.. Compendium.Types.Keys]); - } - - /// - /// Возвращает коллекцию доступных загружаемых элементов. - /// - /// Тип, которые нужно включить в результат. - /// Коллекция загружаемых элементов, соответствующих предоставленному типу. - public IEnumerable Entries(string types) - { - return Entries([types]); - } - - /// - /// Возвращает коллекцию доступных загружаемых элементов. - /// - /// Типы, которые нужно включить в результат. - /// Коллекция загружаемых элементов, соответствующих предоставленным типам. - public IEnumerable Entries(string[] types) - { - foreach (string file in Compendium.Files(types)) - { - using StreamReader fileReader = new(Path.Combine(Importer.Dir, $"{file}.json")); - JArray array = JArray.Parse(fileReader.ReadToEnd()); - - foreach (JToken item in array) - { - string? id = item.SelectToken("id")?.ToString(); - - if (id is null) - { - continue; - } - - LoaderEntry entry = new(id, file, (JObject)item); - - if (types.Contains(entry.Type)) - { - yield return entry; - } - } - } - } - - /// - /// Класс, представляющий загружаемый элемент. - /// - public class LoaderEntry(string id, string file, JObject itemJson) : AonprdReference(id, file) - { - /// - /// Возвращает JSON-объект, содерждающий данные о элементе. - /// - public JObject Json { get; } = itemJson; - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs index 0a21ba3..4fd6785 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs @@ -1,5 +1,4 @@ -using Konsole; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Repositories; using Pf2eDataScrapper.Processors; @@ -8,13 +7,12 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; /// /// Основной класс для работы с репозиторием данных Aonprd. /// -internal class AonprdRepository(AbstractImporter importer, string searchUrl) - : AbstractImporterRepository(importer) +internal class AonprdRepository(AonprdImporter importer) : AbstractImporterRepository(importer) { /// /// URL для доступа к API Aonprd. /// - public readonly string SearchUrl = searchUrl; + public readonly string SearchUrl = "https://elasticsearch.aonprd.com"; /// /// Текущая ревизия данных. Значение обновляется при обновлении репозитория. @@ -96,7 +94,7 @@ internal class AonprdRepository(AbstractImporter importer, string searchUrl) Revision = Convert.ToInt32( page.SelectToken("_index")?.ToObject()?.Replace("aon", "") - ?? throw new Exception("Can't get revision") + ?? throw new("Не удаётся получить ревизию Архивов Нефиса.") ); string revisionFile = Path.Combine(RepositoryDirPath, "revision.txt"); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs index 4b5242c..3980c48 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs @@ -1,67 +1,106 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; +using Newtonsoft.Json.Linq; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; /// /// Класс хранения данных компендиума. /// -internal class AonprdCompendium +internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCompendium { - /// - /// Словарь данных компендиума. - /// - public readonly Dictionary Types = []; + public override AonprdRepository Repository { get; } = repository; /// - /// Добавить ссылку на сущность с указанием файла, в котором она хранится. + /// Загрузка данных компендиума. /// - /// Идентификатор сущности. - /// Имя файла. - public void AddReference(string id, string file) + public void Load() { - AonprdReference reference = new(id, file); + using StreamReader indexFile = new( + Path.Combine(Repository.RepositoryDirPath, "index.json") + ); + JObject indexJson = JObject.Parse(indexFile.ReadToEnd()); - if (Types.TryGetValue(reference.Type, out AonprdType? type)) + foreach ((string file, JToken? value) in indexJson) { - type.Add(reference); - } - else - { - Types.Add(reference.Type, new(reference)); + JArray items = (JArray)value!; + + foreach (JToken item in items) + { + AonprdCompendiumItem compendiumItem = new(item.ToString(), file); + + Items.Add(compendiumItem); + } } } + /// + /// Коллекция всех записей компендиума. + /// + public List Items { get; } = []; + /// /// Возвращает количество элементов в компендиуме. /// /// Количество элементов. - public int Count() => Types.Values.Sum(t => t.Count); + public override int Count() => Items.Count; /// /// Возвращает количество элементов для указанного типа данных. /// /// Тип данных. /// Количество элементов. - public int Count(string type) => Types.Values.First(t => t.Type == type).Count; + public override int Count(string type) => Items.Count(i => i.Type == type); /// - /// Возвращает количество элементов для указанных типов данных. + /// Возвращает список всех файлов. /// - /// Список типов данных. - /// Количество элементов. - public int Count(IEnumerable types) => - Types.Values.Where(t => types.Contains(t.Type)).Sum(t => t.Count); + /// Список искомых файлов. + private IEnumerable Files() => Items.Select(t => t.File).Distinct(); /// - /// Возвращает список файлов для выбора сущностей указанного типа данных. + /// Возвращает список файлов, в которых есть сущности указанного типа данных. /// /// Тип данных. /// Список искомых файлов. - public IEnumerable Files(string type) => Types.Values.First(t => t.Type == type).Files; + private IEnumerable Files(string type) => + Items.Where(t => t.Type == type).Select(t => t.File).Distinct(); /// - /// Возвращает список файлов для выбора сущностей указанных типов данных. + /// Получение итератора сущностей. Если указан тип - то только указанного типа. /// - /// Список типов данных. - /// Список искомых файлов. - public IEnumerable Files(IEnumerable types) => - Types.Values.Where(t => types.Contains(t.Type)).SelectMany(t => t.Files).Distinct(); + /// Тип сущностей, которые нужно получить. + /// Итератор сущностей. + public override IEnumerable> Iterate(string? type = null) + { + foreach (string file in type is null ? Files() : Files(type)) + { + Dictionary 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) + ) + { + continue; + } + + if (type is not null && reference.Type != type) + { + continue; + } + + yield return new(reference, (JObject)item); + } + } + } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendiumItem.cs similarity index 84% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendiumItem.cs index c1fcd51..695929b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendiumItem.cs @@ -5,22 +5,22 @@ /// /// Идентификатор сущности. /// Имя файла. -internal class AonprdReference(string id, string file) +internal class AonprdCompendiumItem(string id, string file) : AbstractCompendiumItem { /// /// Идентификатор сущности. /// - public readonly string Id = id; + public override string Id { get; } = id; /// /// Тип сущности. /// - public readonly string Type = ResolveType(id); + public override string Type { get; } = ResolveType(id); /// /// Имя файла, в котором хранится сущность. /// - public readonly string File = file; + public string File { get; } = file; /// /// Определение типа сущности как префикса идентификатора. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs deleted file mode 100644 index e55f1b8..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; - -/// -/// Класс типа элементов с Архива Нефиса. -/// -internal class AonprdType(string type) -{ - /// - /// Идентификатор типа данных. - /// - public readonly string Type = type; - - /// - /// Список ссылок на сущности, связанных с этим типом. - /// - public readonly List Items = []; - - public AonprdType(AonprdReference reference) - : this(reference.Type) - { - Items.Add(reference); - } - - /// - /// Добавляет новую ссылку в список. - /// - public void Add(AonprdReference reference) - { - Items.Add(reference); - } - - /// - /// Возвращает уникальные файлы, использованные в ссылках. - /// - public List Files => Items.Select(x => x.File).Distinct().ToList(); - - /// - /// Число элементов в списке ссылок. - /// - public int Count => Items.Count; -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdProcessorInput.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs similarity index 83% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdProcessorInput.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs index 31b6bcd..ed5f530 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdProcessorInput.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs @@ -1,17 +1,11 @@ -using Newtonsoft.Json.Linq; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; -using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.Processors; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; -/// -/// Класс для работы с JSON-объектом при обработки данных с Архивов Нефиса. -/// -/// JSON-объект, который будет использоваться для доступа к данным. -internal class AonprdProcessorInput(string name, JObject input) : ProcessorInput(name, input) +internal static class AonprdProcessorInputExtension { - /// - public override T Enum(string value) + public static T Enum(this ProcessorInput input, string value) + where T : Enum { try { @@ -19,7 +13,7 @@ internal class AonprdProcessorInput(string name, JObject input) : ProcessorInput } catch { - return base.Enum(value); + return input.Enum(value); } } @@ -28,7 +22,7 @@ internal class AonprdProcessorInput(string name, JObject input) : ProcessorInput /// /// Входная строка с данными. /// Структура, хранящая информацию о количетсве действий. - public ActionStruct ResolveActionString(string input) + public static ActionStruct ResolveActionString(this ProcessorInput _, string input) { ActionStruct result = new(); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs index 240b5ff..24b378e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs @@ -1,5 +1,4 @@ -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; using Pf2eModel.Model.Base; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Common.I18n; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index c0ad18f..442eec0 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -27,7 +27,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; /// Объект обработки вводимого JSON. /// internal abstract class AbstractEntityProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs index 03e0bc6..8b3a1aa 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs @@ -2,6 +2,7 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Base; using Pf2eModel.Model.Common.I18n; @@ -16,7 +17,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; /// для которых указан атрибут . /// internal abstract class AbstractSubclassProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdSchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdSchemaBuildProcessor.cs deleted file mode 100644 index 8d09197..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdSchemaBuildProcessor.cs +++ /dev/null @@ -1,170 +0,0 @@ -using Pf2eDataScrapper.App; -using Pf2eDataScrapper.Processors; -using Pf2eDataScrapper.SchemaBuilder; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; - -[Processor( - "aonprd:schema", - Description = "Построение схемы данных", - Group = ProcessorGroupEnum.AonprdPreparation -)] -[ProcessorDependencies(["aonprd:repository"])] -internal class AonprdSchemaBuildProcessor( - AonprdImporter importer, - ProcessProgress progress, - ApplicationSettings settings -) : IProcessor -{ - public AonprdImporter Importer { get; } = importer; - public ProcessProgress Progress { get; } = progress; - public ApplicationSettings Settings { get; } = settings; - - /// - /// Загузчик данных с сайта Архивов Нефеса. - /// - public AonprdLoader Loader => Importer.Loader; - - /// - /// Дополнительные схемы данных. - /// - public readonly Dictionary Schemas = new() - { - ["_subclass"] = new() { Path = "_subclass" }, - }; - - /// - /// Построение схемы данных. - /// - public void Process() - { - Progress.Local.Reset(Loader.ItemCount); - - foreach (AonprdLoader.LoaderEntry entry in Loader.Entries()) - { - Progress.Local.Start(entry.Type); - GetSchema(entry).AddValue(entry.Json); - - if (_subclassSchemaTypes.Contains(entry.Type)) - { - Schemas["_subclass"].AddValue(entry.Json); - } - - Progress.Local.End(); - } - - Progress.Local.Complete(); - } - - /// - /// Сохранение постренных схем данных. - /// - public void Save() - { - string targetDir = Settings.OutputDir; - - targetDir = Path.Combine(targetDir, Loader.Importer.Slug); - - foreach (KeyValuePair kvp in Schemas) - { - SaveSchema(targetDir, kvp.Key, kvp.Value); - } - } - - /// - /// Список типов, являющихся подклассами. - /// - 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", - ]; - - /// - /// Выбор целевого элемента схемы данных для ввода. - /// - /// Входные данные. - /// Целевой элемент схемы данных. - private SchemaNode GetSchema(AonprdLoader.LoaderEntry entry) - { - string key = entry.Type; - - if (Schemas.TryGetValue(key, out SchemaNode? value)) - { - return value; - } - - value = new() - { - // Resolver = Resolver, - Path = key, - }; - Schemas[key] = value; - - return value; - } - - /// - /// Сохранение отдельной схемы данных. - /// - /// Целевой каталог. - /// Название схемы. - /// Сохраняемая схема. - protected void SaveSchema(string dir, string name, SchemaNode schema) - { - char[] invalids = Path.GetInvalidFileNameChars(); - string fileName = string.Join( - "_", - schema.Path.Split(invalids, StringSplitOptions.RemoveEmptyEntries) - ) - .TrimEnd('.'); - string path = Path.Combine(dir, fileName + ".yaml"); - - string? dirPath = Path.GetDirectoryName(path); - if (dirPath is not null && !Directory.Exists(dirPath)) - { - Directory.CreateDirectory(dirPath); - } - - using StreamWriter writer = new(path); - - schema.Beautify(); - - writer.WriteLine( - SchemaNode.FormatYaml( - new Dictionary() - { - ["name"] = name, - ["values"] = schema.Values.ToString(), - }, - schema.Types[0].GetYamlData() - ) - ); - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdRepositoryUpdateProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs similarity index 51% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdRepositoryUpdateProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs index d4c921d..3117e5d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/AonprdRepositoryUpdateProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs @@ -1,4 +1,5 @@ using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; @@ -8,19 +9,23 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; Description = "Обновление репозитория", Group = ProcessorGroupEnum.AonprdPreparation )] -internal class AonprdRepositoryUpdateProcessor(AonprdImporter importer, ProcessProgress progress) - : IProcessor +internal class RepositoryUpdateProcessor( + AonprdRepository repository, + AonprdCompendium compendium, + ProcessProgress progress +) : IProcessor { - private AonprdImporter Importer { get; } = importer; + public AonprdRepository Repository { get; } = repository; + public AonprdCompendium Compendium { get; } = compendium; public ProcessProgress Progress { get; } = progress; public void Process() { - Importer.Repository.Update(Progress); + Repository.Update(Progress); } public void Save() { - Importer.Loader.Prepare(); + Compendium.Load(); } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs new file mode 100644 index 0000000..1b1820d --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs @@ -0,0 +1,92 @@ +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; +using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.SchemaBuilder; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; + +[Processor( + "aonprd:schema", + Description = "Построение схемы данных", + Group = ProcessorGroupEnum.AonprdPreparation +)] +[ProcessorDependencies(["aonprd:repository"])] +internal class SchemaBuildProcessor( + AonprdImporter importer, + AonprdCompendium compendium, + ProcessProgress progress, + ApplicationSettings settings +) : AbstractSchemaBuildProcessor +{ + /// + protected AonprdImporter Importer { get; } = importer; + + /// + protected override AonprdCompendium Compendium { get; } = compendium; + + /// + protected override ProcessProgress Progress { get; } = progress; + + /// + protected ApplicationSettings Settings { get; } = settings; + + /// + protected override string SavePath => Path.Combine(Settings.OutputDirPath, Importer.Slug); + + /// + protected override AonprdTypeResolver Resolver { get; } = new(); + + /// + /// Список типов, являющихся подклассами. + /// + 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 GetSchemaNodes(AbstractCompendiumItem item) + { + List nodes = [item.Type]; + + // Нода подкласса + if (_subclassSchemaTypes.Contains(item.Type)) + { + nodes.Add("_subclass"); + } + + return nodes; + } +} + +/// +internal class AonprdTypeResolver : TypeResolver +{ + // +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs index dcf3081..869aefc 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs @@ -13,7 +13,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; Description = "Создание цели внешних ссылок", Group = ProcessorGroupEnum.AonprdPreparation )] -internal class SkillProcessor(AonprdProcessorInput input, Pf2eDbContext context) +internal class SkillProcessor(ProcessorInput input, Pf2eDbContext context) : AbstractInputProcessor(input) { public Pf2eDbContext Context { get; } = context; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs index 3cea58e..80d7b26 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs @@ -13,7 +13,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; Group = ProcessorGroupEnum.AonprdPreparation )] [ProcessorDependencies(["aonprd:repository"])] -internal class SourceProcessor(AonprdProcessorInput input, Pf2eDbContext context) +internal class SourceProcessor(ProcessorInput input, Pf2eDbContext context) : AbstractInputProcessor(input) { public Pf2eDbContext Context { get; } = context; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs index c66bc42..4be5018 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs @@ -2,6 +2,7 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Base; using Pf2eModel.Model.Common; @@ -21,7 +22,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasse ["aonprd:trait", "aonprd:source", "aonprd:class", "aonprd:spell", "aonprd:skill"] )] internal class ApparitionProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs index 86eac1d..ba385ac 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs @@ -2,6 +2,7 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; @@ -15,7 +16,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class ActionProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs index 841ab9a..526f4a3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs @@ -3,6 +3,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; @@ -17,7 +18,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:language"])] internal class AncestryProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs index dcab3e8..17d848e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs @@ -2,6 +2,7 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; @@ -16,7 +17,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class ArchetypeProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs index 82a266b..b720049 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs @@ -3,6 +3,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; @@ -18,7 +19,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class BackgroundProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index 86d2fe8..72fb8af 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -4,6 +4,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; @@ -22,7 +23,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])] internal partial class ClassProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs index d281bb4..b6ef9d6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs @@ -3,6 +3,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; @@ -17,7 +18,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:archetype"])] internal class FeatProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, @@ -44,7 +45,7 @@ internal class FeatProcessor( ? FeatCategory.Class : FeatCategory.Uncategorized; - AonprdProcessorInput.ActionStruct action = Input.ResolveActionString( + AonprdProcessorInputExtension.ActionStruct action = Input.ResolveActionString( Input.GetOptional("actions") ?? "" ); Object.Actions = action.Actions; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs index d9a723a..8ddf42f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs @@ -2,6 +2,7 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; @@ -16,7 +17,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class LanguageProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs index 7e9b611..0a45e36 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs @@ -4,6 +4,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; @@ -20,7 +21,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])] internal partial class RitualProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs index b3f4e53..aeb25fe 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs @@ -3,6 +3,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; @@ -20,7 +21,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rul )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])] internal class RuleProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs index 7a1d6f0..200bae7 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs @@ -5,17 +5,24 @@ // так что ставить порядок ВСЕХ правил в архиве не получится, только тех, что есть в содержании. // Ну а пока просто расставляем родительские и дочерние элементы. -using System.Data; +using Pf2eDataScrapper.App; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel.Model.Structure; +using Pf2eModel; using Rule = Pf2eModel.Model.Structure.Rule; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules; -[AonprdProcessor("rules-order", null, ["rules"], false)] -internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcessor(input) +[Processor( + "aonprd:rules-order", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:rules"])] +internal class RulesOrderProcessor(Pf2eDbContext context) : IProcessor { + public Pf2eDbContext Context { get; } = context; + /// /// Структура генерации содержания правил. /// @@ -27,7 +34,7 @@ internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcess private readonly List _orderItems = []; /// - public override void Process() + public void Process() { Toc.SaveFile(); @@ -149,7 +156,7 @@ internal class RulesOrderProcessor(AonprdProcessorInput input) : AbstractProcess } /// - public override void Save() + public void Save() { Context.SaveChanges(); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs index 9ecc12d..c062fa2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs @@ -2,15 +2,24 @@ using System.Web; using AngleSharp; using AngleSharp.Dom; +using Pf2eDataScrapper.App; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; +using Pf2eModel; using Pf2eModel.Model.Base.Source; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules; -[AonprdProcessor("rules-toc", null, ["source"], false)] -internal partial class RulesTocProcessor(AonprdProcessorInput input) : AbstractProcessor(input) +[Processor( + "aonprd:rules-toc", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.AonprdEntities +)] +[ProcessorDependencies(["aonprd:source"])] +internal partial class RulesTocProcessor(Pf2eDbContext context) : IProcessor { + public Pf2eDbContext Context { get; } = context; + /// /// Селектор HTML-элемента, в котором находится содержание. /// @@ -27,7 +36,7 @@ internal partial class RulesTocProcessor(AonprdProcessorInput input) : AbstractP public readonly RuleTocCollection Toc = RuleTocCollection.Instance; /// - public override void Process() + public void Process() { IDocument document = DownloadPage(); @@ -121,7 +130,7 @@ internal partial class RulesTocProcessor(AonprdProcessorInput input) : AbstractP } /// - public override void Save() { } + public void Save() { } /// /// Получить скомпилированное регулярное выражение для получения HTML-ссылки. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs index 27573de..3a427b2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs @@ -3,6 +3,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; @@ -19,7 +20,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class SpellProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs index 9ac249e..9809b7f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -2,6 +2,7 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; @@ -16,7 +17,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; )] [ProcessorDependencies(["aonprd:external"])] internal class TraitProcessor( - AonprdProcessorInput input, + ProcessorInput input, Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumDocumentType.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumDocumentType.cs similarity index 75% rename from Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumDocumentType.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumDocumentType.cs index 72928df..3a7acdc 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumDocumentType.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumDocumentType.cs @@ -1,4 +1,4 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; /// /// Тип документов компендиума. diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolder.cs similarity index 84% rename from Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolder.cs index e51fe04..49f1f15 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolder.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json.Linq; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; /// /// Папка компендиума. @@ -10,32 +10,32 @@ internal class CompendiumFolder /// /// Идентификатор папки. /// - public readonly string Id; + public string Id { get; } /// /// Название папки. /// - public readonly string Name; + public string Name { get; } /// /// Путь к папке. /// - public readonly string? Path; + public string? Path { get; } /// /// Указатель, что папка содержится в других папках. /// - public readonly bool IsContained; - - /// - /// Родительская папка. - /// - public CompendiumFolder? Parent; + public bool IsContained { get; } /// /// Тип документов в папке. /// - public readonly CompendiumDocumentType Type; + public CompendiumDocumentType Type { get; } + + /// + /// Родительская папка. + /// + public CompendiumFolder? Parent { get; set; } = null; /// /// Конструктор папки компендиума из JSON-объекта. @@ -60,8 +60,7 @@ internal class CompendiumFolder IsContained = false; } - Type = (CompendiumDocumentType) - Enum.Parse(typeof(CompendiumDocumentType), json["type"]!.ToString()); + Type = Enum.Parse(json["type"]!.ToString()); } /// diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolderCollection.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolderCollection.cs new file mode 100644 index 0000000..69d0bf0 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolderCollection.cs @@ -0,0 +1,31 @@ +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; + +/// +/// Коллекция папок компендиума +/// +internal class CompendiumFolderCollection +{ + /// + /// Список загруженных папок. + /// + public List Items { get; } = []; + + /// + /// Добавление информации о папке. + /// + /// Добавляемая папка. + public void Add(CompendiumFolder folder) + { + Items.Add(folder); + } + + /// + /// Получение папки по идентификатору + /// + /// Искомый идентификатор + /// Искомая папка + public CompendiumFolder? GetById(string id) + { + return Items.FirstOrDefault(x => x.Id == id); + } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendiumItem.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendiumItem.cs new file mode 100644 index 0000000..0e4fa95 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendiumItem.cs @@ -0,0 +1,25 @@ +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; + +/// +/// Ссылка на сущность с указанием файла, в котором она хранится. +/// +/// Идентификатор сущности. +/// Тип сущности. +/// Имя файла, в котором хранится сущность. +internal class FoundryEnCompendiumItem(string id, string type, string file) : AbstractCompendiumItem +{ + /// + /// Идентификатор сущности. + /// + public override string Id { get; } = id; + + /// + /// Тип сущности. + /// + public override string Type { get; } = type; + + /// + /// Имя файла, в котором хранится сущность. + /// + public string File { get; } = file; +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/FoundryEnAbstractProcessor.cs similarity index 91% rename from Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/FoundryEnAbstractProcessor.cs index 709ae64..95d8c2a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/FoundryEnAbstractProcessor.cs @@ -13,15 +13,17 @@ using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder; -internal abstract class AbstractProcessor(ProcessorInput input) +internal abstract class FoundryEnAbstractProcessor( + DatabaseHelper databaseHelper, + Pf2eDbContext context, + ProcessorInput input +) : AbstractInputProcessor(input) { - protected DatabaseHelper Helper = DatabaseHelper.Instance; + protected DatabaseHelper Helper { get; } = databaseHelper; - public readonly Pf2eDbContext Context = DatabaseHelper.DbContext; - - public readonly ProcessorInput Input = input; + public Pf2eDbContext Context { get; } = context; public Entity Entity { get; set; } = new(); @@ -29,7 +31,7 @@ internal abstract class AbstractProcessor(ProcessorInput input) public abstract IEntity Object { get; } - public virtual void Process() + public override void Process() { Entity.Id = Helper.NextEntityId; Entity.Type = Type; @@ -95,7 +97,7 @@ internal abstract class AbstractProcessor(ProcessorInput input) } } - public void Save() + public override void Save() { Context.SaveChanges(); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ActionProcessor.cs similarity index 62% rename from Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ActionProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ActionProcessor.cs index 5e0d2b5..0438961 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ActionProcessor.cs @@ -1,11 +1,25 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder; using Pf2eDataScrapper.Processors; +using Pf2eModel; using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors; -[Pf2eTypeProcessor("Item.action")] -internal class ActionProcessor(ProcessorInput input) : AbstractProcessor(input) +[Processor( + "foundry-en:action", + InnerType = "action", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.FoundryEnEntities +)] +[ProcessorDependencies(["foundry-en:compendium"])] +internal class ActionProcessor( + DatabaseHelper databaseHelper, + Pf2eDbContext context, + ProcessorInput input +) : FoundryEnAbstractProcessor(databaseHelper, context, input) { public override EntityType Type => EntityType.Action; diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ConditionProcessor.cs similarity index 61% rename from Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ConditionProcessor.cs index acb7ca1..ce0accd 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ConditionProcessor.cs @@ -1,12 +1,26 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder; using Pf2eDataScrapper.Processors; +using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors; -[Pf2eTypeProcessor("Item.condition")] -internal class ConditionProcessor(ProcessorInput input) : AbstractProcessor(input) +[Processor( + "foundry-en:condition", + InnerType = "Item.condition", + Description = "Создание цели внешних ссылок", + Group = ProcessorGroupEnum.FoundryEnEntities +)] +[ProcessorDependencies(["foundry-en:compendium"])] +internal class ConditionProcessor( + DatabaseHelper databaseHelper, + Pf2eDbContext context, + ProcessorInput input +) : FoundryEnAbstractProcessor(databaseHelper, context, input) { public override EntityType Type => EntityType.Condition; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs new file mode 100644 index 0000000..bf99a45 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn; + +/// +/// Загрузчик данных компендиума. +/// +/// Репозиторий данных. +internal sealed class FoundryEnCompendium( + FoundryEnRepository repository, + CompendiumFolderCollection folders +) : AbstractCompendium +{ + /// + /// Репозиторий данных. + /// + public override FoundryEnRepository Repository { get; } = repository; + + /// + /// Набор папок компендиума + /// + public CompendiumFolderCollection Folders { get; } = folders; + + /// + /// Коллекция всех записей компендиума. + /// + public List Items { get; } = []; + + /// + /// Возвращает количество элементов в компендиуме. + /// + /// Количество элементов. + public override int Count() => Items.Count; + + /// + /// Возвращает количество элементов для указанного типа данных. + /// + /// Тип данных. + /// Количество элементов. + public override int Count(string type) => Items.Count(i => i.Type == type); + + /// + /// Получение итератора сущностей. Если указан тип - то только указанного типа. + /// + /// Тип сущностей, которые нужно получить. + /// Итератор сущностей. + public override IEnumerable> Iterate(string? type = null) + { + IEnumerable items = type is null + ? Items + : Items.Where(i => i.Type == type); + + foreach (FoundryEnCompendiumItem item in items) + { + using StreamReader fileReader = new( + Path.Combine(Repository.RepositoryDirPath, item.File) + ); + + yield return new(item, JObject.Parse(fileReader.ReadToEnd())); + } + } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnImporter.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnImporter.cs new file mode 100644 index 0000000..dc2bce3 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnImporter.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.DependencyInjection; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder; +using Pf2eDataScrapper.Importers.Repositories; + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn; + +/// +/// Импортёр для модуля Pf2e FoundryVTT. +/// +internal class FoundryEnImporter(FoundryEnRepository repository, FoundryEnCompendium compendium) + : AbstractImporter +{ + /// + /// Название внешней ссылки. + /// + public const string TargetName = "Pf2e FoundryVTT module"; + + /// + /// Отображаемое имя импортёра. + /// + public override string Name => TargetName; + + /// + /// Короткое имя импортёра. + /// + public override string Slug => "foundry-en"; + + public override FoundryEnRepository Repository { get; } = repository; + + /// + /// Загрузчик данных компендиума. + /// + public override FoundryEnCompendium Compendium { get; } = compendium; + + #region "Внедрение зависимостей" + + public static void RegisterServices(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + } + + #endregion +} + +/// +/// Git-репозиторий данных FoundryVTT-модуля "pf2e". +/// +/// +internal class FoundryEnRepository(FoundryEnImporter importer) : GitRepository(importer, GitUrl) +{ + public const string GitUrl = "https://github.com/foundryvtt/pf2e.git"; +} diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs new file mode 100644 index 0000000..a53b9a0 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs @@ -0,0 +1,123 @@ +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation; + +[Processor( + "foundry-en:compendium-folders", + Description = "Загрузка каталогов компендиума", + Group = ProcessorGroupEnum.FoundryEnPreparation +)] +[ProcessorDependencies(["foundry-en:repository"])] +internal class CompendiumFolderProcessor( + FoundryEnRepository repository, + CompendiumFolderCollection folders +) : IProcessor +{ + private FoundryEnRepository Repository { get; } = repository; + private CompendiumFolderCollection Folders { get; } = folders; + + private string Dir => Repository.RepositoryDirPath; + private int NextFolderId { get; set; } = 0; + + public void Process() + { + LoadPacks(); + ScanFolders(); + } + + /// + /// Загрузка основных папок. + /// + private void LoadPacks() + { + using StreamReader systemFile = new(Path.Combine(Dir, "static", "system.json")); + JObject systemData = JObject.Parse(systemFile.ReadToEnd()); + + foreach (JToken pack in systemData["packs"]!) + { + CompendiumFolder folder = new((JObject)pack); + + Folders.Add(folder); + } + + if (systemData.TryGetValue("packFolders", out JToken? packFolders)) + { + ProcessPackFolders((JArray)packFolders, null); + } + } + + /// + /// Обработка папок компендиума в поле `packFolders`. + /// + /// + /// + private void ProcessPackFolders(JArray folders, CompendiumFolder? parent) + { + foreach (JToken token in folders) + { + JObject obj = (JObject)token; + + string id = $"folder-{NextFolderId++}"; + string name = obj["name"]!.ToString(); + + CompendiumFolder folder = new(id, name) { Parent = parent }; + Folders.Add(folder); + + if (obj.TryGetValue("packs", out JToken? packs)) + { + foreach (JToken jToken in packs) + { + string packId = jToken.ToString(); + CompendiumFolder? child = Folders.GetById(packId); + + if (child is not null) + { + child.Parent = folder; + } + } + } + + if (obj.TryGetValue("folders", out JToken? innerFolders)) + { + ProcessPackFolders((JArray)innerFolders, folder); + } + } + } + + private void ScanFolders() + { + // Поиск _folders.json + foreach (CompendiumFolder parent in Folders.Items) + { + if (parent.Path is null) + { + continue; + } + + string dirPath = Path.Combine(Dir, parent.Path); + + foreach ( + string foldersFilePath in Directory.GetFiles( + dirPath, + "_folders.json", + SearchOption.AllDirectories + ) + ) + { + using StreamReader file = new(foldersFilePath); + JArray data = JArray.Parse(file.ReadToEnd()); + + foreach (JToken pack in data) + { + CompendiumFolder folder = new((JObject)pack) { Parent = parent }; + Folders.Add(folder); + } + } + } + } + + public void Save() { } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs new file mode 100644 index 0000000..8afe640 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs @@ -0,0 +1,104 @@ +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation; + +[Processor( + "foundry-en:compendium", + Description = "Загрузка компендиума", + Group = ProcessorGroupEnum.FoundryEnPreparation +)] +[ProcessorDependencies(["foundry-en:compendium-folders"])] +internal class CompendiumItemsProcessor( + FoundryEnRepository repository, + FoundryEnCompendium compendium, + ProcessProgress progress +) : IProcessor +{ + private FoundryEnRepository Repository { get; } = repository; + private FoundryEnCompendium Compendium { get; } = compendium; + public ProcessProgress Progress { get; } = progress; + + private string Dir => Repository.RepositoryDirPath; + private List RootFolders { get; } = + compendium.Folders.Items.FindAll(f => f.Path != null); + + public void Process() + { + Progress.Local.Reset(CountItems()); + + foreach (CompendiumFolder folder in RootFolders) + { + string dirPath = Path.Combine(Dir, folder.Path!); + + foreach ( + string filePath in Directory.GetFiles( + dirPath, + "*.json", + SearchOption.AllDirectories + ) + ) + { + string relativePath = Path.GetRelativePath(filePath, Dir); + Progress.Local.Start(relativePath); + + FoundryEnCompendiumItem? item = ProcessFile(relativePath, folder); + + if (item is not null) + { + Compendium.Items.Add(item); + } + + Progress.Local.End(); + } + } + + Progress.Local.Complete(); + } + + private FoundryEnCompendiumItem? ProcessFile(string relativePath, CompendiumFolder folder) + { + FileInfo file = new(Path.Combine(Dir, relativePath)); + + if (file.Name == "_folders.json") + { + return null; + } + + using StreamReader reader = new(file.FullName); + JObject itemJson = JObject.Parse(reader.ReadToEnd()); + + CompendiumFolder? itemFolder = folder; + + JToken? itemJsonFolder = itemJson.SelectToken("folder"); + if (itemJsonFolder is not null) + { + itemFolder = Compendium.Folders.GetById(itemJsonFolder.ToString()); + } + + if (itemFolder is null) + { + throw new( + $"Не удаётся определить папку, к которой относится элемент {file.Name} (путь: {file.FullName})" + ); + } + + string? type = itemJson.SelectToken("type")?.ToString(); + + return new(file.Name, itemFolder.Type + (type is not null ? $".{type}" : ""), relativePath); + } + + private int CountItems() + { + return RootFolders.Sum(folder => + { + string dirPath = Path.Combine(Dir, folder.Path!); + return Directory.GetFiles(dirPath, "*.json", SearchOption.AllDirectories).Length + - Directory.GetFiles(dirPath, "_folders.json", SearchOption.AllDirectories).Length; + }); + } + + public void Save() { } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs new file mode 100644 index 0000000..ccfb96f --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs @@ -0,0 +1,23 @@ +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation; + +[Processor( + "foundry-en:repository", + Description = "Обновление репозитория", + Group = ProcessorGroupEnum.FoundryEnPreparation +)] +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() { } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs new file mode 100644 index 0000000..34b2cfc --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs @@ -0,0 +1,99 @@ +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; +using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.SchemaBuilder; + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation; + +[Processor( + "foundry-en:schema", + Description = "Построение схемы данных", + Group = ProcessorGroupEnum.AonprdPreparation +)] +[ProcessorDependencies(["foundry-en:compendium"])] +internal class SchemaBuildProcessor( + FoundryEnImporter importer, + FoundryEnRepository repository, + FoundryEnCompendium compendium, + ProcessProgress progress, + ApplicationSettings settings +) : AbstractSchemaBuildProcessor +{ + /// + protected FoundryEnImporter Importer { get; } = importer; + + /// + protected FoundryEnRepository Repository { get; } = repository; + + /// + protected override FoundryEnCompendium Compendium { get; } = compendium; + + /// + protected override ProcessProgress Progress { get; } = progress; + + /// + protected ApplicationSettings Settings { get; } = settings; + + /// + protected override string SavePath => Path.Combine(Settings.OutputDirPath, Importer.Slug); + + /// + /// Определитель типов данных. + /// + protected override SchemaTypeResolver Resolver { get; } = new(); + + /// + /// Список типов, являющихся экипировкой. + /// + private readonly string[] _equipmentTypes = + [ + "Item.equipment", + "Item.armor", + "Item.backpack", + "Item.consumable", + "Item.kit", + "Item.shield", + "Item.treasure", + "Item.weapon", + ]; + + /// + /// Выбор целевых элементом схемы данных для типа элемента. + /// + /// Тип входного элемента. + /// Список целевых элементов схемы данных. + private List GetSchemaNodes(string type) + { + List nodes = []; + + // Общая нода + if (!Schemas.TryGetValue(type, out SchemaNode? typeNode)) + { + typeNode = new() { Resolver = Resolver, Path = type }; + Schemas[type] = typeNode; + } + + nodes.Add(typeNode); + + // Нода подкласса + if (_equipmentTypes.Contains(type)) + { + nodes.Add(Schemas["Equipment"]); + } + + return nodes; + } + + protected override ICollection GetSchemaNodes(AbstractCompendiumItem item) + { + List nodes = [item.Type]; + + // Нода подкласса + if (_equipmentTypes.Contains(item.Type)) + { + nodes.Add("Equipment"); + } + + return nodes; + } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaTypeResolver.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaTypeResolver.cs similarity index 93% rename from Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaTypeResolver.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaTypeResolver.cs index b77f11b..e741c24 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaTypeResolver.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaTypeResolver.cs @@ -2,9 +2,9 @@ using Pf2eDataScrapper.SchemaBuilder; using Pf2eDataScrapper.SchemaBuilder.Types; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.SchemaBuilder; +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation; -internal class CompendiumSchemaTypeResolver : TypeResolver +internal class SchemaTypeResolver : TypeResolver { public class FoundryRulesSchemaType(SchemaNode parent) : IgnoreSchemaType(parent) { diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolderCollection.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolderCollection.cs deleted file mode 100644 index 04c0381..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolderCollection.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; - -/// -/// Коллекция папок компендиума -/// -internal class CompendiumFolderCollection : List -{ - /// - /// Получение папки по идентификатору - /// - /// Искомый идентификатор - /// Искомая папка - public CompendiumFolder? GetById(string id) - { - return this.FirstOrDefault(x => x.Id == id); - } - - /// - /// Фильтрация требуемых папок компендиума. - /// - /// - public CompendiumFolderCollection Filter() - { - return [.. this.FindAll(x => x.Type == CompendiumDocumentType.Item)]; - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/CompendiumLoader.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/CompendiumLoader.cs deleted file mode 100644 index 7010191..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/CompendiumLoader.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Newtonsoft.Json.Linq; -using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; - -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry; - -/// -/// Загрузчик данных компендиума -/// -/// Импортёр -internal class CompendiumLoader(Pf2EFoundryImporter importer) -{ - /// - /// Импортёр - /// - public readonly Pf2EFoundryImporter Importer = importer; - - /// - /// Набор папок компендиума - /// - public CompendiumFolderCollection Collection = []; - - /// - /// Счётчик папок - /// - private int _folderCount; - - /// - /// Общее количество элементов в компендиуме - /// - public int ItemCount { get; protected set; } - - /// - /// Обработка папок компендиума в поле `packFolders`. - /// - /// - /// - protected void ProcessPackFolders(JArray folders, CompendiumFolder? parent) - { - foreach (JToken token in folders) - { - JObject obj = (JObject)token; - - string id = $"folder-{_folderCount++}"; - string name = obj["name"]!.ToString(); - - CompendiumFolder folder = new(id, name) { Parent = parent }; - Collection.Add(folder); - - if (obj.TryGetValue("packs", out JToken? packs)) - { - foreach (JToken jToken in packs) - { - string packId = jToken.ToString(); - CompendiumFolder? child = Collection.GetById(packId); - - if (child is not null) - { - child.Parent = folder; - } - } - } - - if (obj.TryGetValue("folders", out JToken? innerFolders)) - { - ProcessPackFolders((JArray)innerFolders, folder); - } - } - - Collection = Collection.Filter(); - } - - /// - /// Подготовка набора папок компендиума - /// - public void Prepare() - { - using StreamReader systemFile = new(Path.Combine(Importer.Dir, "static", "system.json")); - JObject systemData = JObject.Parse(systemFile.ReadToEnd()); - - foreach (JToken pack in systemData["packs"]!) - { - CompendiumFolder folder = new((JObject)pack); - - Collection.Add(folder); - } - - if (systemData.TryGetValue("packFolders", out JToken? packFolders)) - { - ProcessPackFolders((JArray)packFolders, null); - } - - // Поиск _folders.json - foreach (CompendiumFolder parent in Collection.ToList()) - { - if (parent.Path is null) - { - continue; - } - - string dirPath = Path.Combine(Importer.Dir, parent.Path); - - ItemCount += Directory.GetFiles(dirPath, "*.json", SearchOption.AllDirectories).Length; - - foreach ( - string foldersFilePath in Directory.GetFiles( - dirPath, - "_folders.json", - SearchOption.AllDirectories - ) - ) - { - ItemCount--; - using StreamReader file = new(foldersFilePath); - JArray data = JArray.Parse(file.ReadToEnd()); - - foreach (JToken pack in data) - { - CompendiumFolder folder = new((JObject)pack) { Parent = parent }; - Collection.Add(folder); - } - } - } - - // foreach (var folder in Collection) - // { - // Console.WriteLine(folder); - // } - - Collection = Collection.Filter(); - - Console.WriteLine($"Total items: {ItemCount}"); - } - - public IEnumerable Enrties() - { - foreach (CompendiumFolder folder in Collection.FindAll(f => f.Path is not null)) - { - string dirPath = Path.Combine(Importer.Dir, folder.Path!); - - foreach ( - string filePath in Directory.GetFiles( - dirPath, - "*.json", - SearchOption.AllDirectories - ) - ) - { - FileInfo file = new(filePath); - - if (file.Name == "_folders.json") - { - continue; - } - - using StreamReader reader = new(file.FullName); - JObject itemJson = JObject.Parse(reader.ReadToEnd()); - - CompendiumFolder? itemFolder = folder; - if (itemJson.TryGetValue("folder", out JToken? itemJsonFolder)) - { - itemFolder = Collection.GetById(itemJsonFolder.ToString()); - } - - yield return new LoaderEnrty(file, itemJson, itemFolder ?? folder); - } - } - } - - public class LoaderEnrty(FileInfo file, JObject itemJson, CompendiumFolder folder) - { - public FileInfo File { get; } = file; - public JObject Json { get; } = itemJson; - public CompendiumFolder Folder { get; } = folder; - public string Type { get; } = - itemJson["type"]?.ToString() - ?? throw new Exception($"Expected to have `type` field in file {file.FullName}"); - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs deleted file mode 100644 index 16ca5e1..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Reflection; -using Konsole; -using Newtonsoft.Json.Linq; -using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; -using Pf2eDataScrapper.Processors; -using Pf2eModel; - -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; - -internal class PF2eFoundryDataBuilder -{ - /// - /// Загрузчик данных. - /// - public readonly CompendiumLoader Loader; - - /// - /// Коллекция папок компендиума. - /// - public CompendiumFolderCollection Collection => Loader.Collection; - - public Dictionary Processors = []; - - public PF2eFoundryDataBuilder(CompendiumLoader loader) - { - Loader = loader; - PrepareProcessors(); - } - - public void PrepareProcessors() - { - foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()) - { - if (type.GetCustomAttributes(typeof(Pf2eTypeProcessor), true).Length <= 0) - { - continue; - } - Pf2eTypeProcessor? attribute = type.GetCustomAttribute(); - Processors.Add(attribute!.Type, type.GetConstructor([typeof(ProcessorInput)])!); - } - } - - public AbstractProcessor? GetProcessor(string type, JObject input) - { - return Processors.TryGetValue(type, out ConstructorInfo? constructor) - ? (AbstractProcessor)constructor.Invoke([new ProcessorInput(input)]) - : null; - } - - public void TruncateTables() - { - Console.WriteLine("Truncating all old data..."); - - using Pf2eDbContext context = new(); - string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll(); - - Console.WriteLine(cmd); - - Console.WriteLine("Truncation completed!"); - } - - public void Build() - { - Console.WriteLine("Building data..."); - - ProgressBar bar = new(Loader.ItemCount); - int i = 0; - - foreach (CompendiumLoader.LoaderEnrty entry in Loader.Enrties()) - { - bar.Refresh(i++, $"{entry.Folder.Name}/{entry.File.Name}"); - - AbstractProcessor? processor = GetProcessor( - $"{entry.Folder.Type}.{entry.Type}", - entry.Json - ); - if (processor is null) - { - continue; - } - - processor.Process(); - processor.Save(); - } - - bar.Refresh(Loader.ItemCount, "Done!"); - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Pf2eTypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Pf2eTypeProcessor.cs deleted file mode 100644 index 7e5e37a..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Pf2eTypeProcessor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; - -[AttributeUsage(AttributeTargets.Class)] -internal class Pf2eTypeProcessor(string type, int priority = 0) : Attribute -{ - public readonly string Type = type; - - public readonly int Priority = priority; -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Pf2eFoundryImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Pf2eFoundryImporter.cs deleted file mode 100644 index b98dd8b..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Pf2eFoundryImporter.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; -using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.SchemaBuilder; -using Pf2eDataScrapper.Importers.Repositories; - -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry; - -/// -/// Импортёр для модуля Pf2e FoundryVTT. -/// -internal class Pf2EFoundryImporter : AbstractImporter -{ - /// - /// Отображаемое имя импортёра. - /// - public override string Name => "Pf2e FoundryVTT module"; - - /// - /// Короткое имя импортёра. - /// - public override string Slug => "pf2e"; - - public override AbstractImporterRepository Repository => - new GitRepository(this, "https://github.com/foundryvtt/pf2e.git"); - - /// - /// Загрузчик данных компендиума. - /// - public CompendiumLoader Loader { get; } - - /// - /// Конструктор импортёра. - /// - public Pf2EFoundryImporter() - { - Loader = new CompendiumLoader(this); - } - - /// - /// Метод подготовки данных для импорта. - /// - public override void Prepare() - { - Loader.Prepare(); - } - - /// - /// Импорт данных. - /// - public override void Import() - { - PF2eFoundryDataBuilder builder = new(Loader); - - builder.TruncateTables(); - builder.Build(); - } - - /// - /// Построение схем данных - /// - public override void BuildSchemas() - { - CompendiumSchemaBuilder builder = new(Loader); - - builder.BuildSchemas(); - builder.Save(EnvSettings.OutputDir); - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaBuilder.cs deleted file mode 100644 index 01392f8..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaBuilder.cs +++ /dev/null @@ -1,197 +0,0 @@ -using Konsole; -using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; -using Pf2eDataScrapper.SchemaBuilder; - -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.SchemaBuilder; - -/// -/// Класс построения схемы компендиумов. -/// -/// Загрузчик данных. -internal class CompendiumSchemaBuilder(CompendiumLoader loader) -{ - /// - /// Загрузчик данных. - /// - public CompendiumLoader Loader = loader; - - /// - /// Коллекция папок компендиума. - /// - public CompendiumFolderCollection Collection = loader.Collection; - - /// - /// Объект для определения типов полей в компендиумах PF2E. - /// - public CompendiumSchemaTypeResolver Resolver = new(); - - /// - /// Схемы данных по папкам. - /// - public Dictionary PackSchemas = []; - - /// - /// Схемы данных по типам. - /// - public Dictionary TypeSchemas = []; - - private readonly string[] _equipmentTypes = - [ - "equipment", - "armor", - "backpack", - "consumable", - "kit", - "shield", - "treasure", - "weapon", - ]; - - private SchemaNode GetPackSchema(CompendiumFolder folder) - { - if (!PackSchemas.TryGetValue(folder, out SchemaNode? value)) - { - value = new SchemaNode() { Resolver = Resolver, Path = folder.Name }; - PackSchemas[folder] = value; - } - - return value; - } - - private SchemaNode GetTypeSchema(string key) - { - if (!TypeSchemas.TryGetValue(key, out SchemaNode? value)) - { - value = new SchemaNode() { Resolver = Resolver, Path = key }; - TypeSchemas[key] = value; - } - - return value; - } - - /// - /// Получение набора схем, к которым относится проверяемый объект. - /// - /// Папка компендиума, в которой находится объект. - /// Указанный тип объекта. - /// - protected List GetItemSchemas(CompendiumFolder folder, string? itemType) - { - string key = folder.Type.ToString() + (itemType is not null ? $".{itemType}" : ""); - - return _equipmentTypes.Contains(itemType) - ? [GetPackSchema(folder), GetTypeSchema(key), GetTypeSchema("Equipment")] - : [GetPackSchema(folder), GetTypeSchema(key)]; - } - - /// - /// Построение схем данных. - /// - public void BuildSchemas() - { - Console.WriteLine("Building schemas..."); - - ProgressBar bar = new(Loader.ItemCount); - int i = 0; - - foreach (CompendiumLoader.LoaderEnrty entry in Loader.Enrties()) - { - bar.Refresh(i++, entry.Folder.Name); - - List schemas = GetItemSchemas(entry.Folder, entry.Type); - - foreach (SchemaNode schema in schemas) - { - schema.AddValue(entry.Json); - } - } - - bar.Refresh(Loader.ItemCount, "Done!"); - } - - /// - /// Сохранение данных. - /// - /// Папка для сохранения. - public void Save(string targetDir) - { - Console.WriteLine("Dumping schemas..."); - - ProgressBar bar = new(PackSchemas.Count + TypeSchemas.Count); - int i = 0; - - targetDir = Path.Combine(targetDir, Loader.Importer.Slug); - - if (!Directory.Exists(targetDir)) - { - Directory.CreateDirectory(targetDir); - } - Array.ForEach(Directory.GetFiles(@targetDir), File.Delete); - - // Packs - string packsDir = Path.Combine(targetDir, "packs"); - Directory.CreateDirectory(packsDir); - - foreach (KeyValuePair pair in PackSchemas) - { - bar.Refresh(i++, pair.Key.Name); - SaveSchema(packsDir, pair.Key.Name, pair.Value); - } - - // Types - string typesDir = Path.Combine(targetDir, "types"); - Directory.CreateDirectory(typesDir); - foreach (KeyValuePair pair in TypeSchemas) - { - bar.Refresh(i++, pair.Key); - SaveSchema(typesDir, pair.Key, pair.Value); - } - - Console.WriteLine("Done!"); - } - - /// - /// Сохранение конкретной схемы. - /// - /// Корневая папка для сохранения этой категории схем. - /// Название сохраняемой схемы. - /// Сохраняемая схема. - protected void SaveSchema(string dir, string name, SchemaNode schema) - { - char[] invalids = Path.GetInvalidFileNameChars(); - string fileName = string.Join( - "_", - schema.Path.Split(invalids, StringSplitOptions.RemoveEmptyEntries) - ) - .TrimEnd('.'); - - string path = Path.Combine(dir, fileName + ".yaml"); - - string? dirPath = Path.GetDirectoryName(path); - - if (dirPath is not null && !Directory.Exists(dirPath)) - { - Directory.CreateDirectory(dirPath); - } - - using StreamWriter writer = new(path); - - schema.Beautify(); - - writer.WriteLine( - SchemaNode.FormatYaml( - new Dictionary() - { - ["name"] = name, - ["values"] = schema.Values.ToString(), - }, - schema.Types[0].GetYamlData() - ) - ); - - //writer.WriteLine(name); - //writer.WriteLine($"Different values: {schema.Values}"); - //writer.WriteLine("====================================="); - //writer.Write(schema.ToString()); - } -} diff --git a/Pf2eDataScrapper/Processors/DatabaseTruncateProcessor.cs b/Pf2eDataScrapper/Processors/General/DatabaseTruncateProcessor.cs similarity index 89% rename from Pf2eDataScrapper/Processors/DatabaseTruncateProcessor.cs rename to Pf2eDataScrapper/Processors/General/DatabaseTruncateProcessor.cs index 9967454..ce8a65a 100644 --- a/Pf2eDataScrapper/Processors/DatabaseTruncateProcessor.cs +++ b/Pf2eDataScrapper/Processors/General/DatabaseTruncateProcessor.cs @@ -1,7 +1,7 @@ using Pf2eDataScrapper.App; using Pf2eModel; -namespace Pf2eDataScrapper.Processors; +namespace Pf2eDataScrapper.Processors.General; [Processor("truncate", Description = "Отчистка таблиц БД", Group = ProcessorGroupEnum.General)] internal class DatabaseTruncateProcessor : IProcessor diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs new file mode 100644 index 0000000..bdb313a --- /dev/null +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -0,0 +1,106 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn; + +namespace Pf2eDataScrapper.Processors; + +internal sealed class ProcessorExecutor( + IServiceProvider serviceProvider, + ProcessorCollection collection, + ProcessProgress progress +) +{ + public IServiceProvider ServiceProvider { get; } = serviceProvider; + public ProcessorCollection Collection { get; } = collection; + public ProcessProgress Progress { get; } = progress; + + public void Start() + { + Collection.SortItems(); + + Progress.Processors.Reset(Collection.Items.Count); + + foreach (ProcessorCollectionItem item in Collection.Items) + { + Progress.Processors.Start(item.Description); + + Process(item); + + Progress.Processors.End(); + } + + Progress.Processors.Complete(); + } + + private void Process(ProcessorCollectionItem item) + { + Thread thread = new(() => + { + foreach (IProcessor processor in IterateProcessors(item)) + { + processor.Process(); + processor.Save(); + } + }); + } + + private IEnumerable IterateProcessors(ProcessorCollectionItem item) + { + if (!item.IsCollectionProcessor) + { + Progress.Local.Reset(1); + Progress.Local.Start(item.Description); + yield return (IProcessor)ActivatorUtilities.CreateInstance(ServiceProvider, item.Type); + Progress.Local.Complete(); + yield break; + } + + if (item.Group == ProcessorGroupEnum.General) + { + throw new( + $"Обработчик {item.Description} помечен как общий (поле Group == General) и коллекционный (поле InnerType), что запрещено." + ); + } + + Type importerType = item.Group switch + { + ProcessorGroupEnum.AonprdPreparation + or ProcessorGroupEnum.AonprdEntities + or ProcessorGroupEnum.AonprdSubclasses => typeof(AonprdImporter), + ProcessorGroupEnum.FoundryEnPreparation or ProcessorGroupEnum.FoundryEnEntities => + typeof(FoundryEnImporter), + _ => throw new($"Не могу выбрать импортёр для группы {item.Group}"), + }; + + AbstractImporter importer = (AbstractImporter) + ActivatorUtilities.CreateInstance(ServiceProvider, importerType); + + Progress.Local.Reset(importer.Compendium.Count(item.InnerType!)); + + foreach ( + (AbstractCompendiumItem compendiumItem, JObject json) in importer.Compendium.Iterate( + item.InnerType! + ) + ) + { + Progress.Local.Start(compendiumItem.Id); + + yield return (IProcessor) + ActivatorUtilities.CreateInstance( + ServiceProvider, + item.Type, + new ProcessorInput(compendiumItem.Id, json) + ); + + Progress.Local.End(); + } + + Progress.Local.Complete(); + } +} diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs index 328179e..c848b0e 100644 --- a/Pf2eDataScrapper/Program.cs +++ b/Pf2eDataScrapper/Program.cs @@ -1,4 +1,8 @@ -namespace Pf2eDataScrapper; +using Microsoft.Extensions.DependencyInjection; +using Pf2eDataScrapper.Importers.Implementations.Aonprd; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn; + +namespace Pf2eDataScrapper; internal static class Program { @@ -6,7 +10,7 @@ internal static class Program /// The main entry point for the application. /// [STAThread] - static void Main() + public static void Main() { // To customize application configuration such as set high DPI settings or default font, // see https://aka.ms/applicationconfiguration. @@ -18,6 +22,22 @@ internal static class Program Application.SetColorMode(SystemColorMode.System); #pragma warning restore WFO5001 - Application.Run(new ImportApplication()); + IServiceCollection services = RegisterServices(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + Application.Run(ActivatorUtilities.CreateInstance(serviceProvider)); + } + + private static IServiceCollection RegisterServices() + { + IServiceCollection services = new ServiceCollection(); + + ImportApplication.RegisterServices(services); + + AonprdImporter.RegisterServices(services); + FoundryEnImporter.RegisterServices(services); + + return services; } } diff --git a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs new file mode 100644 index 0000000..8e449d4 --- /dev/null +++ b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs @@ -0,0 +1,117 @@ +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Importers; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.SchemaBuilder; + +internal abstract class AbstractSchemaBuildProcessor : IProcessor +{ + /// + /// Связаный компендиум + /// + protected abstract AbstractCompendium Compendium { get; } + + /// + protected abstract ProcessProgress Progress { get; } + + /// + /// Определитель типов данных. + /// + protected abstract TypeResolver Resolver { get; } + + /// + /// Путь к каталогу для сохранения вывода. + /// + protected abstract string SavePath { get; } + + /// + /// Набор схем данных. + /// + protected Dictionary Schemas { get; } = []; + + /// + /// Получить список названий элементов структуры данных на основе вводимого элемента. + /// + /// Вводимый элемент + /// Коллекция названий элементов структуры данных/ + protected abstract ICollection GetSchemaNodes(AbstractCompendiumItem item); + + /// + /// Построение схемы данных. + /// + public void Process() + { + Progress.Local.Reset(Compendium.Count()); + + foreach ((AbstractCompendiumItem reference, JObject json) in Compendium.Iterate()) + { + Progress.Local.Start(reference.ToString()!); + + foreach (string nodeName in GetSchemaNodes(reference)) + { + if (!Schemas.TryGetValue(nodeName, out SchemaNode? node)) + { + node = new() { Path = nodeName, Resolver = Resolver }; + Schemas.Add(nodeName, node); + } + + node.AddValue(json); + } + + Progress.Local.End(); + } + + Progress.Local.Complete(); + } + + /// + /// Сохранение постренных схем данных. + /// + public void Save() + { + string targetDir = SavePath; + + foreach (KeyValuePair kvp in Schemas) + { + SaveSchema(targetDir, kvp.Key, kvp.Value); + } + } + + /// + /// Сохранение отдельной схемы данных. + /// + /// Целевой каталог. + /// Название схемы. + /// Сохраняемая схема. + protected void SaveSchema(string dir, string name, SchemaNode schema) + { + char[] invalids = Path.GetInvalidFileNameChars(); + string fileName = string.Join( + "_", + schema.Path.Split(invalids, StringSplitOptions.RemoveEmptyEntries) + ) + .TrimEnd('.'); + string path = Path.Combine(dir, fileName + ".yaml"); + + string? dirPath = Path.GetDirectoryName(path); + if (dirPath is not null && !Directory.Exists(dirPath)) + { + Directory.CreateDirectory(dirPath); + } + + using StreamWriter writer = new(path); + + schema.Beautify(); + + writer.WriteLine( + SchemaNode.FormatYaml( + new Dictionary() + { + ["name"] = name, + ["values"] = schema.Values.ToString(), + }, + schema.Types[0].GetYamlData() + ) + ); + } +} From 1b76893b32360c895ff1616d961aed8161ce9a92 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Tue, 13 May 2025 23:12:24 +0400 Subject: [PATCH 06/33] =?UTF-8?q?=D0=9E=D0=BD=D0=BE=20=D1=81=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=BB=D0=BE=D1=81=D1=8C!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/ProcessorCollection.cs | 4 +- .../Forms/ProgressForm.Designer.cs | 104 + Pf2eDataScrapper/Forms/ProgressForm.cs | 50 + Pf2eDataScrapper/Forms/ProgressForm.resx | 120 + Pf2eDataScrapper/Forms/StartForm.Designer.cs | 2 + Pf2eDataScrapper/Forms/StartForm.cs | 16 +- Pf2eDataScrapper/ImportApplication.cs | 128 +- .../Importers/AbstractImporter.cs | 10 - .../Implementations/Aonprd/AonprdImporter.cs | 9 +- .../Aonprd/Processors/Types/ClassProcessor.cs | 6 +- .../FoundryEn/FoundryEnImporter.cs | 11 +- .../Processors/ProcessorExecutor.cs | 59 +- Pf2eDataScrapper/Program.cs | 10 +- output/aonprd/_subclass.yaml | 593 ++--- output/aonprd/action.yaml | 204 +- output/aonprd/ancestry.yaml | 405 ++-- output/aonprd/animal-companion.yaml | 203 +- output/aonprd/arcane-school.yaml | 4 +- output/aonprd/archetype.yaml | 120 +- output/aonprd/article.yaml | 92 +- output/aonprd/background.yaml | 183 +- output/aonprd/class-feature.yaml | 2 +- output/aonprd/creature-family.yaml | 176 +- output/aonprd/creature.yaml | 2071 +++++++++-------- output/aonprd/deity-category.yaml | 4 +- output/aonprd/deity.yaml | 611 +++-- .../deviant-ability-classification.yaml | 117 +- output/aonprd/disease.yaml | 273 ++- output/aonprd/domain.yaml | 284 +-- output/aonprd/druidic-order.yaml | 334 +-- output/aonprd/equipment-bonus.yaml | 275 +-- output/aonprd/equipment-sub.yaml | 241 +- output/aonprd/equipment.yaml | 254 +- output/aonprd/familiar-ability.yaml | 5 +- output/aonprd/familiar-specific.yaml | 18 +- output/aonprd/feat.yaml | 676 +++--- output/aonprd/hazard.yaml | 658 +++--- output/aonprd/instinct.yaml | 363 +-- output/aonprd/ritual.yaml | 238 +- output/aonprd/rules.yaml | 82 +- output/aonprd/sidebar.yaml | 56 +- output/aonprd/source.yaml | 78 +- output/aonprd/spell.yaml | 389 ++-- output/aonprd/trait.yaml | 96 +- output/aonprd/weapon.yaml | 161 +- 45 files changed, 5032 insertions(+), 4763 deletions(-) create mode 100644 Pf2eDataScrapper/Forms/ProgressForm.Designer.cs create mode 100644 Pf2eDataScrapper/Forms/ProgressForm.cs create mode 100644 Pf2eDataScrapper/Forms/ProgressForm.resx diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index c73748c..103a045 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -95,6 +95,8 @@ internal class ProcessorCollection /// public void SortItems() { + Items.ForEach(item => item.Priority = -1); + Dictionary priorities = []; while (priorities.Count < Items.Count) @@ -115,7 +117,7 @@ internal class ProcessorCollection continue; } - item.Priority = Math.Min( + item.Priority = Math.Max( dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1, (int)item.Group * 100 ); diff --git a/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs b/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs new file mode 100644 index 0000000..ec7a110 --- /dev/null +++ b/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs @@ -0,0 +1,104 @@ +namespace Pf2eDataScrapper.Forms; + +partial class ProgressForm +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + ProcessorsProgressBar = new ProgressBar(); + ProcessorsLabel = new Label(); + LocalLabel = new Label(); + LocalProgressBar = new ProgressBar(); + MessageListView = new ListView(); + SuspendLayout(); + // + // ProcessorsProgressBar + // + ProcessorsProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + ProcessorsProgressBar.Location = new Point(12, 27); + ProcessorsProgressBar.Name = "ProcessorsProgressBar"; + ProcessorsProgressBar.Size = new Size(439, 23); + ProcessorsProgressBar.TabIndex = 0; + // + // ProcessorsLabel + // + ProcessorsLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + ProcessorsLabel.Location = new Point(12, 9); + ProcessorsLabel.Name = "ProcessorsLabel"; + ProcessorsLabel.Size = new Size(439, 15); + ProcessorsLabel.TabIndex = 1; + ProcessorsLabel.Text = "ProcessorsLabel"; + // + // LocalLabel + // + LocalLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + LocalLabel.Location = new Point(12, 59); + LocalLabel.Name = "LocalLabel"; + LocalLabel.Size = new Size(439, 15); + LocalLabel.TabIndex = 2; + LocalLabel.Text = "LocalLabel"; + // + // LocalProgressBar + // + LocalProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + LocalProgressBar.Location = new Point(12, 77); + LocalProgressBar.Name = "LocalProgressBar"; + LocalProgressBar.Size = new Size(439, 23); + LocalProgressBar.TabIndex = 3; + // + // MessageListView + // + MessageListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + MessageListView.Location = new Point(12, 114); + MessageListView.Name = "MessageListView"; + MessageListView.Size = new Size(439, 316); + MessageListView.TabIndex = 4; + MessageListView.UseCompatibleStateImageBehavior = false; + MessageListView.View = View.List; + // + // ProgressForm + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(463, 442); + Controls.Add(MessageListView); + Controls.Add(LocalProgressBar); + Controls.Add(LocalLabel); + Controls.Add(ProcessorsLabel); + Controls.Add(ProcessorsProgressBar); + Name = "ProgressForm"; + Text = "ProgressForm"; + ResumeLayout(false); + } + + #endregion + + private ProgressBar ProcessorsProgressBar; + private Label ProcessorsLabel; + private Label LocalLabel; + private ProgressBar LocalProgressBar; + private ListView MessageListView; +} \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/ProgressForm.cs b/Pf2eDataScrapper/Forms/ProgressForm.cs new file mode 100644 index 0000000..fb3d7ad --- /dev/null +++ b/Pf2eDataScrapper/Forms/ProgressForm.cs @@ -0,0 +1,50 @@ +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.Forms; + +internal partial class ProgressForm : Form +{ + public ProcessProgress Progress { get; } + + public ProgressForm(ProcessProgress progress) + { + Progress = progress; + + InitializeComponent(); + + Progress.ProgressUpdate += Progress_ProgressUpdate; + Progress.ProgressMessage += Progress_ProgressMessage; + } + + private void Progress_ProgressUpdate(ProcessProgress sender) + { + if (InvokeRequired) + { + Invoke(Progress_ProgressUpdate, sender); + return; + } + + ProcessorsLabel.Text = sender.Processors.ToString(); + ProcessorsProgressBar.Maximum = sender.Processors.Count; + ProcessorsProgressBar.Value = sender.Processors.Current; + + LocalLabel.Text = sender.Local.ToString(); + LocalProgressBar.Maximum = sender.Local.Count; + LocalProgressBar.Value = sender.Local.Current; + + Application.DoEvents(); + } + + private void Progress_ProgressMessage(ProcessProgress sender, string message) + { + if (InvokeRequired) + { + Invoke(Progress_ProgressMessage, sender, message); + return; + } + + MessageListView.Items.Add(message); + + Application.DoEvents(); + } +} diff --git a/Pf2eDataScrapper/Forms/ProgressForm.resx b/Pf2eDataScrapper/Forms/ProgressForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/Pf2eDataScrapper/Forms/ProgressForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/StartForm.Designer.cs b/Pf2eDataScrapper/Forms/StartForm.Designer.cs index 88ccf32..c439d08 100644 --- a/Pf2eDataScrapper/Forms/StartForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/StartForm.Designer.cs @@ -102,10 +102,12 @@ partial class StartForm ProcessorsListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; ProcessorsListView.CheckBoxes = true; ProcessorsListView.Location = new Point(6, 48); + ProcessorsListView.MultiSelect = false; ProcessorsListView.Name = "ProcessorsListView"; ProcessorsListView.Size = new Size(606, 548); ProcessorsListView.TabIndex = 2; ProcessorsListView.UseCompatibleStateImageBehavior = false; + ProcessorsListView.View = View.SmallIcon; // // SelectAllButton // diff --git a/Pf2eDataScrapper/Forms/StartForm.cs b/Pf2eDataScrapper/Forms/StartForm.cs index ac9466e..feadfe4 100644 --- a/Pf2eDataScrapper/Forms/StartForm.cs +++ b/Pf2eDataScrapper/Forms/StartForm.cs @@ -1,19 +1,18 @@ using System.ComponentModel; using System.Reflection; -using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; namespace Pf2eDataScrapper.Forms; internal partial class StartForm : Form { - private readonly ProcessorCollection _processorCollection; + private ProcessorCollection ProcessorCollection { get; } public StartForm(ProcessorCollection processorCollection) { - InitializeComponent(); + ProcessorCollection = processorCollection; - _processorCollection = processorCollection; + InitializeComponent(); } private void StartForm_Load(object sender, EventArgs e) @@ -25,7 +24,7 @@ internal partial class StartForm : Form { Dictionary> items = []; - foreach (ProcessorCollectionItem item in _processorCollection.AllItems) + foreach (ProcessorCollectionItem item in ProcessorCollection.AllItems) { items.TryAdd(item.Group, []); items[item.Group].Add(item); @@ -40,7 +39,12 @@ internal partial class StartForm : Form continue; } - string name = Enum.GetDescription(group); + string name = + group + .GetType() + .GetField(group.ToString()) + ?.GetCustomAttribute() + ?.Description ?? group.ToString(); ListViewGroup listViewGroup = new(name); ProcessorsListView.Groups.Add(listViewGroup); diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index a7e5e09..aaccccc 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -2,10 +2,6 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Forms; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd; -using Pf2eDataScrapper.Importers.Implementations.FoundryEn; -using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry; using Pf2eDataScrapper.Processors; using Pf2eModel; using Slugify; @@ -24,14 +20,27 @@ internal sealed class ImportApplication : ApplicationContext Settings = settings; Processors = processors; - CreateStartForm(); + CurrentStage = ApplicationStage.InitialSettings; } - #region "Настройки" + #region "Поля" - public IServiceProvider ServiceProvider { get; } - public ApplicationSettings Settings { get; } - public ProcessorCollection Processors { get; } + /// + /// Текущий этап выполнения программы. + /// + public ApplicationStage CurrentStage + { + get => _currentStage; + set + { + ApplicationStage oldValue = _currentStage; + _currentStage = value; + + CloseStage(oldValue); + StartStage(_currentStage); + } + } + private ApplicationStage _currentStage = ApplicationStage.BootUp; #endregion @@ -39,33 +48,104 @@ internal sealed class ImportApplication : ApplicationContext private StartForm? _startForm; + private ProgressForm? _progressForm; + #endregion #region "Жизненный цикл" - private void CreateStartForm() + private void StartStage(ApplicationStage stage) { - _startForm = ActivatorUtilities.CreateInstance(ServiceProvider); - _startForm.StartPosition = FormStartPosition.CenterParent; - _startForm.StartButtonPressed += StartProcess; - _startForm.Show(); + switch (stage) + { + case ApplicationStage.BootUp: + throw new( + $"{CurrentStage} никогда не должен быть установлен через поле \"CurrentStage\"." + ); + + case ApplicationStage.InitialSettings: + _startForm = ActivatorUtilities.CreateInstance(ServiceProvider); + _startForm.StartPosition = FormStartPosition.CenterParent; + _startForm.StartButtonPressed += () => + { + CurrentStage = ApplicationStage.ProcessorsWorking; + }; + _startForm.FormClosing += HandleFormClosing; + _startForm.Show(); + break; + + case ApplicationStage.ProcessorsWorking: + ProcessorExecutor executor = ActivatorUtilities.CreateInstance( + ServiceProvider + ); + + _progressForm = ActivatorUtilities.CreateInstance(ServiceProvider); + _progressForm.StartPosition = FormStartPosition.CenterParent; + _progressForm.FormClosing += HandleFormClosing; + + _progressForm.Show(); + executor.Start(); + break; + + case ApplicationStage.Viewer: + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + Application.DoEvents(); } - private void StartProcess() + private void CloseStage(ApplicationStage stage) { - _startForm!.Close(); + switch (stage) + { + case ApplicationStage.BootUp: + // Нечего закрывать + break; - ProcessorExecutor executor = ActivatorUtilities.CreateInstance( - ServiceProvider - ); + case ApplicationStage.InitialSettings: + _startForm!.Hide(); + _startForm!.Close(); + _startForm = null; + break; - executor.Start(); + case ApplicationStage.ProcessorsWorking: + break; + + case ApplicationStage.Viewer: + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + Application.DoEvents(); + } + + private void HandleFormClosing(object? sender, FormClosingEventArgs e) + { + switch (sender) + { + case StartForm: + e.Cancel = CurrentStage != ApplicationStage.InitialSettings; + if (!e.Cancel) + { + ExitThread(); + } + return; + } } #endregion #region "Внедрение зависимостей" + public IServiceProvider ServiceProvider { get; } + public ApplicationSettings Settings { get; } + public ProcessorCollection Processors { get; } + public static void RegisterServices(IServiceCollection services) { // Контекст базы данных @@ -92,3 +172,11 @@ internal sealed class ImportApplication : ApplicationContext #endregion } + +internal enum ApplicationStage +{ + BootUp, + InitialSettings, + ProcessorsWorking, + Viewer, +} diff --git a/Pf2eDataScrapper/Importers/AbstractImporter.cs b/Pf2eDataScrapper/Importers/AbstractImporter.cs index 020ec30..d8641a6 100644 --- a/Pf2eDataScrapper/Importers/AbstractImporter.cs +++ b/Pf2eDataScrapper/Importers/AbstractImporter.cs @@ -17,16 +17,6 @@ internal abstract class AbstractImporter /// public abstract string Slug { get; } - /// - /// Репозиторий для данных, загруженных из необходимых источников. - /// - public abstract AbstractImporterRepository Repository { get; } - - /// - /// Компендиум данных импортёра. - /// - public abstract AbstractCompendium Compendium { get; } - /// /// Путь к директории с данными для импортёра. /// diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs index b4b6063..84ea587 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs @@ -8,8 +8,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; /// /// Класс обработки данных с сайта Архивов Нефеса. /// -internal class AonprdImporter(AonprdRepository repository, AonprdCompendium compendium) - : AbstractImporter +internal class AonprdImporter : AbstractImporter { /// /// Название внешней ссылки. @@ -22,12 +21,6 @@ internal class AonprdImporter(AonprdRepository repository, AonprdCompendium comp /// public override string Slug => "aonprd"; - /// - public override AonprdRepository Repository { get; } = repository; - - /// - public override AonprdCompendium Compendium { get; } = compendium; - #region "Внедрение зависимостей" public static void RegisterServices(IServiceCollection services) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index 72fb8af..0873bfe 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -53,11 +53,7 @@ internal partial class ClassProcessor( foreach (AttributeValues attribute in attributes) { - ClassKeyAbility classKeyAbility = new ClassKeyAbility() - { - Class = Object, - Value = attribute, - }; + ClassKeyAbility classKeyAbility = new() { Class = Object, Value = attribute }; Context.ClassKeyAbilities.Add(classKeyAbility); Object.ClassKeyAbilities.Add(classKeyAbility); diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnImporter.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnImporter.cs index dc2bce3..3f91308 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnImporter.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; -using Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder; using Pf2eDataScrapper.Importers.Repositories; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn; @@ -8,8 +7,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn; /// /// Импортёр для модуля Pf2e FoundryVTT. /// -internal class FoundryEnImporter(FoundryEnRepository repository, FoundryEnCompendium compendium) - : AbstractImporter +internal class FoundryEnImporter : AbstractImporter { /// /// Название внешней ссылки. @@ -26,13 +24,6 @@ internal class FoundryEnImporter(FoundryEnRepository repository, FoundryEnCompen /// public override string Slug => "foundry-en"; - public override FoundryEnRepository Repository { get; } = repository; - - /// - /// Загрузчик данных компендиума. - /// - public override FoundryEnCompendium Compendium { get; } = compendium; - #region "Внедрение зависимостей" public static void RegisterServices(IServiceCollection services) diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index bdb313a..2d425b1 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -1,11 +1,10 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; +using System.ComponentModel; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Importers.Implementations.Aonprd; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Importers.Implementations.FoundryEn; namespace Pf2eDataScrapper.Processors; @@ -26,28 +25,46 @@ internal sealed class ProcessorExecutor( Progress.Processors.Reset(Collection.Items.Count); - foreach (ProcessorCollectionItem item in Collection.Items) - { - Progress.Processors.Start(item.Description); - - Process(item); - - Progress.Processors.End(); - } - - Progress.Processors.Complete(); + StartItem(); } - private void Process(ProcessorCollectionItem item) + private int _currentIndex = 0; + + private void StartItem() { - Thread thread = new(() => + ProcessorCollectionItem item = Collection.Items[_currentIndex]; + + Progress.Processors.Start(item.Description); + + BackgroundWorker worker = new(); + + worker.DoWork += (_, _) => { foreach (IProcessor processor in IterateProcessors(item)) { processor.Process(); processor.Save(); } - }); + }; + + worker.RunWorkerCompleted += Continue; + + worker.RunWorkerAsync(); + } + + private void Continue(object? sender, RunWorkerCompletedEventArgs e) + { + Progress.Processors.End(); + _currentIndex++; + + if (_currentIndex < Collection.Items.Count) + { + StartItem(); + } + else + { + Progress.Processors.Complete(); + } } private IEnumerable IterateProcessors(ProcessorCollectionItem item) @@ -72,19 +89,19 @@ internal sealed class ProcessorExecutor( { ProcessorGroupEnum.AonprdPreparation or ProcessorGroupEnum.AonprdEntities - or ProcessorGroupEnum.AonprdSubclasses => typeof(AonprdImporter), + or ProcessorGroupEnum.AonprdSubclasses => typeof(AonprdCompendium), ProcessorGroupEnum.FoundryEnPreparation or ProcessorGroupEnum.FoundryEnEntities => - typeof(FoundryEnImporter), + typeof(FoundryEnCompendium), _ => throw new($"Не могу выбрать импортёр для группы {item.Group}"), }; - AbstractImporter importer = (AbstractImporter) + AonprdCompendium compendium = (AonprdCompendium) ActivatorUtilities.CreateInstance(ServiceProvider, importerType); - Progress.Local.Reset(importer.Compendium.Count(item.InnerType!)); + Progress.Local.Reset(compendium.Count(item.InnerType!)); foreach ( - (AbstractCompendiumItem compendiumItem, JObject json) in importer.Compendium.Iterate( + (AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate( item.InnerType! ) ) diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs index c848b0e..f86694d 100644 --- a/Pf2eDataScrapper/Program.cs +++ b/Pf2eDataScrapper/Program.cs @@ -17,16 +17,20 @@ internal static class Program ApplicationConfiguration.Initialize(); Application.SetHighDpiMode(HighDpiMode.SystemAware); - + /* #pragma warning disable WFO5001 Application.SetColorMode(SystemColorMode.System); #pragma warning restore WFO5001 - + */ IServiceCollection services = RegisterServices(); IServiceProvider serviceProvider = services.BuildServiceProvider(); - Application.Run(ActivatorUtilities.CreateInstance(serviceProvider)); + ImportApplication app = ActivatorUtilities.CreateInstance( + serviceProvider + ); + + Application.Run(app); } private static IServiceCollection RegisterServices() diff --git a/output/aonprd/_subclass.yaml b/output/aonprd/_subclass.yaml index 81c2506..a2c7756 100644 --- a/output/aonprd/_subclass.yaml +++ b/output/aonprd/_subclass.yaml @@ -1,6 +1,19 @@ name: _subclass -values: 326 +values: 330 schema: + [alignment]: + type: string + path: _subclass.alignment + is_null: true + values: 6 + different_values: 6 + variants: + - "LG" (x1) + - "NG" (x1) + - "CG" (x1) + - "LE" (x1) + - "NE" (x1) + - "CE" (x1) [attribute]: type: array path: _subclass.attribute @@ -41,56 +54,308 @@ schema: - "Phoenix" (x1) - "Wyrmblessed" (x1) - "Harrow" (x1) + 'category': + type: string + path: _subclass.category + is_null: false + values: 330 + different_values: 28 + variants: [omitted] + 'id': + type: string + path: _subclass.id + is_null: false + values: 330 + different_values: 330 (all different) + variants: [omitted] + [lesson_type]: + type: string + path: _subclass.lesson_type + is_null: true + values: 29 + different_values: 3 + variants: + - "Greater" (x12) + - "Basic" (x11) + - "Major" (x6) + 'markdown': + type: string + path: _subclass.markdown + is_null: false + values: 330 + different_values: 330 (all different) + variants: [omitted] + [domain]: + type: array + path: _subclass.domain + is_null: true + values: 21 + min_count: 2 + max_count: 4 + value_type: + type: string + path: _subclass.domain.[] + is_null: false + values: 64 + different_values: 33 + variants: [omitted] + [level]: + type: integer + path: _subclass.level + is_null: true + values: 16 + different_values: 3 + variants: + - 3 (x6) + - 7 (x6) + - 15 (x4) + 'name': + type: string + path: _subclass.name + is_null: false + values: 330 + different_values: 255 + variants: [omitted] + 'primary_source_category': + type: string + path: _subclass.primary_source_category + is_null: false + values: 330 + different_values: 4 + variants: + - "Rulebooks" (x297) + - "Lost Omens" (x24) + - "Adventure Paths" (x8) + - "Adventures" (x1) + 'rarity': + type: string + path: _subclass.rarity + is_null: false + values: 330 + different_values: 3 + variants: + - "common" (x321) + - "rare" (x7) + - "uncommon" (x2) + 'rarity_id': + type: integer + path: _subclass.rarity_id + is_null: false + values: 330 + different_values: 3 + variants: + - 1 (x321) + - 3 (x7) + - 2 (x2) + 'release_date': + type: string + path: _subclass.release_date + is_null: false + values: 330 + different_values: 20 + variants: + - "2024-10-30" (x55) + - "2020-07-30" (x50) + - "2019-08-01" (x50) + - "2023-11-15" (x46) + - "2024-08-01" (x45) + - "2022-07-27" (x23) + - "2025-01-30" (x10) + - "2021-09-01" (x8) + - "2021-10-13" (x8) + - "2024-05-29" (x7) + - "2025-02-26" (x7) + - "2023-08-02" (x7) + - "2024-05-15" (x4) + - "2021-07-07" (x2) + - "2022-11-16" (x2) + - "2024-08-28" (x2) + - "2021-08-05" (x1) + - "2023-04-13" (x1) + - "2021-12-22" (x1) + - "2024-07-31" (x1) 'resistance': type: object path: _subclass.resistance is_null: false - values: 326 + values: 330 schema: 'search_markdown': type: string path: _subclass.search_markdown is_null: false - values: 326 - different_values: 304 + values: 330 + different_values: 308 variants: [omitted] 'source': type: array path: _subclass.source is_null: false - values: 326 + values: 330 min_count: 1 max_count: 1 value_type: type: string path: _subclass.source.[] is_null: false - values: 326 - different_values: 22 + values: 330 + different_values: 23 variants: [omitted] - [alignment]: + 'source_markdown': type: string - path: _subclass.alignment + path: _subclass.source_markdown + is_null: false + values: 330 + different_values: 148 + variants: [omitted] + 'speed': + type: object + path: _subclass.speed + is_null: false + values: 330 + schema: + [skill_markdown]: + type: string + path: _subclass.skill_markdown is_null: true - values: 6 + values: 58 + different_values: 28 + variants: [omitted] + 'source_category': + type: array + path: _subclass.source_category + is_null: false + values: 330 + min_count: 1 + max_count: 1 + value_type: + type: string + path: _subclass.source_category.[] + is_null: false + values: 330 + different_values: 4 + variants: + - "Rulebooks" (x297) + - "Lost Omens" (x24) + - "Adventure Paths" (x8) + - "Adventures" (x1) + [spell_markdown]: + type: string + path: _subclass.spell_markdown + is_null: true + values: 85 + different_values: 77 + variants: [omitted] + 'summary_markdown': + type: string + path: _subclass.summary_markdown + is_null: false + values: 301 + different_values: 281 + variants: [omitted] + [tradition]: + type: array + path: _subclass.tradition + is_null: true + values: 52 + min_count: 1 + max_count: 1 + value_type: + type: string + path: _subclass.tradition.[] + is_null: false + values: 52 + different_values: 4 + variants: + - "Occult" (x17) + - "Primal" (x15) + - "Divine" (x14) + - "Arcane" (x6) + [tradition_markdown]: + type: string + path: _subclass.tradition_markdown + is_null: true + values: 26 + different_values: 4 + variants: + - "[Occult](/SpellLists.aspx?Tradition=3)" (x11) + - "[Primal](/SpellLists.aspx?Tradition=4)" (x9) + - "[Divine](/SpellLists.aspx?Tradition=2)" (x4) + - "[Arcane](/SpellLists.aspx?Tradition=1)" (x2) + 'type': + type: string + path: _subclass.type + is_null: false + values: 330 + different_values: 32 + variants: [omitted] + [primary_source_group]: + type: string + path: _subclass.primary_source_group + is_null: true + values: 9 different_values: 6 variants: - - "LG" (x1) - - "NG" (x1) - - "CG" (x1) - - "LE" (x1) - - "NE" (x1) - - "CE" (x1) - 'rarity_id': - type: integer - path: _subclass.rarity_id - is_null: false - values: 326 - different_values: 3 + - "Wardens of Wildwood" (x4) + - "Strength of Thousands" (x1) + - "Fists of the Ruby Phoenix" (x1) + - "Stolen Fate" (x1) + - "Blood Lords" (x1) + - "Prey for Death" (x1) + [source_group]: + type: array + path: _subclass.source_group + is_null: true + values: 9 + min_count: 1 + max_count: 1 + value_type: + type: string + path: _subclass.source_group.[] + is_null: false + values: 9 + different_values: 6 + variants: + - "Wardens of Wildwood" (x4) + - "Strength of Thousands" (x1) + - "Fists of the Ruby Phoenix" (x1) + - "Stolen Fate" (x1) + - "Blood Lords" (x1) + - "Prey for Death" (x1) + [spoilers]: + type: string + path: _subclass.spoilers + is_null: true + values: 2 + different_values: 2 variants: - - 1 (x317) - - 3 (x7) - - 2 (x2) + - "Strength of Thousands" (x1) + - "Fists of the Ruby Phoenix" (x1) + [trait]: + type: array + path: _subclass.trait + is_null: true + values: 13 + min_count: 1 + max_count: 1 + value_type: + type: string + path: _subclass.trait.[] + is_null: false + values: 13 + different_values: 4 + variants: + - "Rare" (x7) + - "Uncommon" (x2) + - "Unholy" (x2) + - "Holy" (x2) + 'url': + type: string + path: _subclass.url + is_null: false + values: 330 + different_values: 330 (all different) + variants: [omitted] [element]: type: array path: _subclass.element @@ -111,24 +376,6 @@ schema: - "Metal" (x1) - "Water" (x1) - "Wood" (x1) - 'id': - type: string - path: _subclass.id - is_null: false - values: 326 - different_values: 326 (all different) - variants: [omitted] - 'primary_source_category': - type: string - path: _subclass.primary_source_category - is_null: false - values: 326 - different_values: 4 - variants: - - "Rulebooks" (x297) - - "Lost Omens" (x24) - - "Adventure Paths" (x4) - - "Adventures" (x1) [legacy_id]: type: array path: _subclass.legacy_id @@ -143,142 +390,15 @@ schema: values: 87 different_values: 87 variants: [omitted] - 'name': - type: string - path: _subclass.name - is_null: false - values: 326 - different_values: 251 - variants: [omitted] - [lesson_type]: - type: string - path: _subclass.lesson_type - is_null: true - values: 29 - different_values: 3 - variants: - - "Greater" (x12) - - "Basic" (x11) - - "Major" (x6) - 'markdown': - type: string - path: _subclass.markdown - is_null: false - values: 326 - different_values: 326 (all different) - variants: [omitted] - [level]: - type: integer - path: _subclass.level - is_null: true - values: 16 - different_values: 3 - variants: - - 3 (x6) - - 7 (x6) - - 15 (x4) - [primary_source_group]: - type: string - path: _subclass.primary_source_group - is_null: true - values: 5 - different_values: 5 - variants: - - "Blood Lords" (x1) - - "Prey for Death" (x1) - - "Strength of Thousands" (x1) - - "Fists of the Ruby Phoenix" (x1) - - "Stolen Fate" (x1) - 'rarity': - type: string - path: _subclass.rarity - is_null: false - values: 326 - different_values: 3 - variants: - - "common" (x317) - - "rare" (x7) - - "uncommon" (x2) - 'release_date': - type: string - path: _subclass.release_date - is_null: false - values: 326 - different_values: 19 - variants: - - "2024-10-30" (x55) - - "2020-07-30" (x50) - - "2019-08-01" (x50) - - "2023-11-15" (x46) - - "2024-08-01" (x45) - - "2022-07-27" (x23) - - "2025-01-30" (x10) - - "2021-09-01" (x8) - - "2021-10-13" (x8) - - "2024-05-29" (x7) - - "2025-02-26" (x7) - - "2023-08-02" (x7) - - "2022-11-16" (x2) - - "2021-07-07" (x2) - - "2024-08-28" (x2) - - "2021-12-22" (x1) - - "2024-07-31" (x1) - - "2021-08-05" (x1) - - "2023-04-13" (x1) - 'source_category': - type: array - path: _subclass.source_category - is_null: false - values: 326 - min_count: 1 - max_count: 1 - value_type: - type: string - path: _subclass.source_category.[] - is_null: false - values: 326 - different_values: 4 - variants: - - "Rulebooks" (x297) - - "Lost Omens" (x24) - - "Adventure Paths" (x4) - - "Adventures" (x1) - 'source_markdown': - type: string - path: _subclass.source_markdown - is_null: false - values: 326 - different_values: 145 - variants: [omitted] - 'speed': - type: object - path: _subclass.speed - is_null: false - values: 326 - schema: - [domain]: - type: array - path: _subclass.domain - is_null: true - values: 21 - min_count: 2 - max_count: 4 - value_type: - type: string - path: _subclass.domain.[] - is_null: false - values: 64 - different_values: 33 - variants: [omitted] [pfs]: type: string path: _subclass.pfs is_null: true - values: 315 + values: 319 different_values: 3 variants: - "Standard" (x308) - - "Limited" (x5) + - "Limited" (x9) - "Restricted" (x2) [remaster_id]: type: array @@ -294,129 +414,6 @@ schema: values: 87 different_values: 87 variants: [omitted] - [skill_markdown]: - type: string - path: _subclass.skill_markdown - is_null: true - values: 58 - different_values: 28 - variants: [omitted] - [source_group]: - type: array - path: _subclass.source_group - is_null: true - values: 5 - min_count: 1 - max_count: 1 - value_type: - type: string - path: _subclass.source_group.[] - is_null: false - values: 5 - different_values: 5 - variants: - - "Blood Lords" (x1) - - "Prey for Death" (x1) - - "Strength of Thousands" (x1) - - "Fists of the Ruby Phoenix" (x1) - - "Stolen Fate" (x1) - [spell_markdown]: - type: string - path: _subclass.spell_markdown - is_null: true - values: 85 - different_values: 77 - variants: [omitted] - [spoilers]: - type: string - path: _subclass.spoilers - is_null: true - values: 2 - different_values: 2 - variants: - - "Strength of Thousands" (x1) - - "Fists of the Ruby Phoenix" (x1) - [summary_markdown]: - type: string - path: _subclass.summary_markdown - is_null: true - values: 297 - different_values: 277 - variants: [omitted] - [tradition]: - type: array - path: _subclass.tradition - is_null: true - values: 52 - min_count: 1 - max_count: 1 - value_type: - type: string - path: _subclass.tradition.[] - is_null: false - values: 52 - different_values: 4 - variants: - - "Occult" (x17) - - "Primal" (x15) - - "Divine" (x14) - - "Arcane" (x6) - 'category': - type: string - path: _subclass.category - is_null: false - values: 326 - different_values: 28 - variants: [omitted] - [trait]: - type: array - path: _subclass.trait - is_null: true - values: 13 - min_count: 1 - max_count: 1 - value_type: - type: string - path: _subclass.trait.[] - is_null: false - values: 13 - different_values: 4 - variants: - - "Rare" (x7) - - "Uncommon" (x2) - - "Unholy" (x2) - - "Holy" (x2) - 'type': - type: string - path: _subclass.type - is_null: false - values: 326 - different_values: 32 - variants: [omitted] - 'url': - type: string - path: _subclass.url - is_null: false - values: 326 - different_values: 326 (all different) - variants: [omitted] - 'weakness': - type: object - path: _subclass.weakness - is_null: false - values: 326 - schema: - [tradition_markdown]: - type: string - path: _subclass.tradition_markdown - is_null: true - values: 26 - different_values: 4 - variants: - - "[Occult](/SpellLists.aspx?Tradition=3)" (x11) - - "[Primal](/SpellLists.aspx?Tradition=4)" (x9) - - "[Divine](/SpellLists.aspx?Tradition=2)" (x4) - - "[Arcane](/SpellLists.aspx?Tradition=1)" (x2) [trait_markdown]: type: string path: _subclass.trait_markdown @@ -456,3 +453,9 @@ schema: - "worn shoes" (x1) - "any melee weapon in the [club](/WeaponGroups.aspx?ID=5), [hammer](/WeaponGroups.aspx?ID=8), or [axe](/WeaponGroups.aspx?ID=1) group, or any your melee [unarmed](/Traits.aspx?ID=719) [Strikes](/Rules.aspx?ID=2895) that deals bludgeoning damage" (x1) - "worn headwear or belt (typically a sash)" (x1) + 'weakness': + type: object + path: _subclass.weakness + is_null: false + values: 330 + schema: diff --git a/output/aonprd/action.yaml b/output/aonprd/action.yaml index 6045dbc..51f49a4 100644 --- a/output/aonprd/action.yaml +++ b/output/aonprd/action.yaml @@ -1,20 +1,20 @@ name: action -values: 2818 +values: 2978 schema: [actions]: type: string path: action.actions is_null: true - values: 2276 + values: 2403 different_values: 11 variants: - - "Single Action" (x765) - - "Two Actions" (x745) - - "Reaction" (x324) - - "Free Action" (x300) - - "Three Actions" (x128) + - "Single Action" (x807) + - "Two Actions" (x798) + - "Reaction" (x343) + - "Free Action" (x309) + - "Three Actions" (x131) - "Single Action to Three Actions" (x5) - - "Single Action or Two Actions" (x4) + - "Single Action or Two Actions" (x5) - "Single Action or more Actions" (x2) - "Reaction or One Action" (x1) - "Single Action to Two Actions" (x1) @@ -23,15 +23,22 @@ schema: type: string path: action.category is_null: false - values: 2818 + values: 2978 different_values: 1 (all same) - variants: "action" (x2818) + variants: "action" (x2978) + [frequency]: + type: string + path: action.frequency + is_null: true + values: 1499 + different_values: 45 + variants: [omitted] 'id': type: string path: action.id is_null: false - values: 2818 - different_values: 2818 (all different) + values: 2978 + different_values: 2978 (all different) variants: [omitted] [cost_markdown]: type: string @@ -40,39 +47,45 @@ schema: values: 14 different_values: 8 variants: [omitted] - [frequency]: + 'source_markdown': type: string - path: action.frequency - is_null: true - values: 1389 - different_values: 44 + path: action.source_markdown + is_null: false + values: 2978 + different_values: 946 variants: [omitted] + 'speed': + type: object + path: action.speed + is_null: false + values: 2978 + schema: 'trait_markdown': type: string path: action.trait_markdown is_null: false - values: 2818 - different_values: 190 + values: 2978 + different_values: 193 variants: [omitted] 'type': type: string path: action.type is_null: false - values: 2818 + values: 2978 different_values: 1 (all same) - variants: "Action" (x2818) + variants: "Action" (x2978) 'url': type: string path: action.url is_null: false - values: 2818 - different_values: 2818 (all different) + values: 2978 + different_values: 2978 (all different) variants: [omitted] 'weakness': type: object path: action.weakness is_null: false - values: 2818 + values: 2978 schema: [element]: type: array @@ -88,19 +101,27 @@ schema: values: 1 different_values: 1 (all same) variants: "Fire" (x1) - 'source_markdown': - type: string - path: action.source_markdown + 'source_category': + type: array + path: action.source_category is_null: false - values: 2818 - different_values: 903 - variants: [omitted] - 'speed': - type: object - path: action.speed - is_null: false - values: 2818 - schema: + values: 2978 + min_count: 1 + max_count: 1 + value_type: + type: string + path: action.source_category.[] + is_null: false + values: 2978 + different_values: 7 + variants: + - "Rulebooks" (x1827) + - "Adventure Paths" (x720) + - "Lost Omens" (x336) + - "Adventures" (x73) + - "Comics" (x11) + - "Blog Posts" (x9) + - "Society" (x2) [legacy_id]: type: array path: action.legacy_id @@ -119,25 +140,25 @@ schema: type: string path: action.markdown is_null: false - values: 2818 - different_values: 2792 + values: 2978 + different_values: 2944 variants: [omitted] 'name': type: string path: action.name is_null: false - values: 2818 - different_values: 780 + values: 2978 + different_values: 824 variants: [omitted] 'primary_source_category': type: string path: action.primary_source_category is_null: false - values: 2818 + values: 2978 different_values: 7 variants: - "Rulebooks" (x1827) - - "Adventure Paths" (x560) + - "Adventure Paths" (x720) - "Lost Omens" (x336) - "Adventures" (x73) - "Comics" (x11) @@ -147,29 +168,35 @@ schema: type: string path: action.rarity is_null: false - values: 2818 + values: 2978 different_values: 3 variants: - - "common" (x2808) + - "common" (x2968) - "uncommon" (x5) - "rare" (x5) 'rarity_id': type: integer path: action.rarity_id is_null: false - values: 2818 + values: 2978 different_values: 3 variants: - - 1 (x2808) + - 1 (x2968) - 2 (x5) - 3 (x5) 'release_date': type: string path: action.release_date is_null: false - values: 2818 - different_values: 70 + values: 2978 + different_values: 82 variants: [omitted] + 'resistance': + type: object + path: action.resistance + is_null: false + values: 2978 + schema: [remaster_id]: type: array path: action.remaster_id @@ -184,25 +211,12 @@ schema: values: 103 different_values: 102 variants: [omitted] - 'resistance': - type: object - path: action.resistance - is_null: false - values: 2818 - schema: - 'search_markdown': + [requirement_markdown]: type: string - path: action.search_markdown - is_null: false - values: 2818 - different_values: 2799 - variants: [omitted] - [primary_source_group]: - type: string - path: action.primary_source_group + path: action.requirement_markdown is_null: true - values: 633 - different_values: 26 + values: 532 + different_values: 459 variants: [omitted] [school]: type: string @@ -219,61 +233,47 @@ schema: - "divination" (x3) - "abjuration" (x2) - "illusion" (x2) + 'search_markdown': + type: string + path: action.search_markdown + is_null: false + values: 2978 + different_values: 2956 + variants: [omitted] 'source': type: array path: action.source is_null: false - values: 2818 + values: 2978 min_count: 1 max_count: 1 value_type: type: string path: action.source.[] is_null: false - values: 2818 - different_values: 108 + values: 2978 + different_values: 121 variants: [omitted] - [requirement_markdown]: + [primary_source_group]: type: string - path: action.requirement_markdown + path: action.primary_source_group is_null: true - values: 524 - different_values: 451 + values: 793 + different_values: 30 variants: [omitted] - 'source_category': - type: array - path: action.source_category - is_null: false - values: 2818 - min_count: 1 - max_count: 1 - value_type: - type: string - path: action.source_category.[] - is_null: false - values: 2818 - different_values: 7 - variants: - - "Rulebooks" (x1827) - - "Adventure Paths" (x560) - - "Lost Omens" (x336) - - "Adventures" (x73) - - "Comics" (x11) - - "Blog Posts" (x9) - - "Society" (x2) [source_group]: type: array path: action.source_group is_null: true - values: 633 + values: 793 min_count: 1 max_count: 1 value_type: type: string path: action.source_group.[] is_null: false - values: 633 - different_values: 26 + values: 793 + different_values: 30 variants: [omitted] [spoilers]: type: string @@ -286,27 +286,27 @@ schema: type: string path: action.summary_markdown is_null: true - values: 2602 - different_values: 2461 + values: 2750 + different_values: 2603 variants: [omitted] [trait]: type: array path: action.trait is_null: true - values: 462 + values: 465 min_count: 1 max_count: 7 value_type: type: string path: action.trait.[] is_null: false - values: 938 + values: 943 different_values: 97 variants: [omitted] [trigger_markdown]: type: string path: action.trigger_markdown is_null: true - values: 528 - different_values: 464 + values: 557 + different_values: 492 variants: [omitted] diff --git a/output/aonprd/ancestry.yaml b/output/aonprd/ancestry.yaml index ec07973..1f8b405 100644 --- a/output/aonprd/ancestry.yaml +++ b/output/aonprd/ancestry.yaml @@ -20,8 +20,8 @@ schema: - "Constitution" (x21) - "Charisma" (x19) - "Wisdom" (x17) - - "Intelligence" (x11) - "Strength" (x11) + - "Intelligence" (x11) - "Two free ability boosts" (x5) 'category': type: string @@ -69,6 +69,142 @@ schema: values: 90 different_values: 50 variants: [omitted] + 'markdown': + type: string + path: ancestry.markdown + is_null: false + values: 90 + different_values: 90 (all different) + variants: [omitted] + 'name': + type: string + path: ancestry.name + is_null: false + values: 90 + different_values: 73 + variants: [omitted] + 'primary_source_category': + type: string + path: ancestry.primary_source_category + is_null: false + values: 90 + different_values: 3 + variants: + - "Rulebooks" (x52) + - "Lost Omens" (x37) + - "Adventure Paths" (x1) + [primary_source_group]: + type: string + path: ancestry.primary_source_group + is_null: true + values: 1 + different_values: 1 (all same) + variants: "Extinction Curse" (x1) + 'rarity': + type: string + path: ancestry.rarity + is_null: false + values: 90 + different_values: 3 + variants: + - "uncommon" (x50) + - "rare" (x22) + - "common" (x18) + 'rarity_id': + type: integer + path: ancestry.rarity_id + is_null: false + values: 90 + different_values: 3 + variants: + - 2 (x50) + - 3 (x22) + - 1 (x18) + 'release_date': + type: string + path: ancestry.release_date + is_null: false + values: 90 + different_values: 15 + variants: + - "2021-02-24" (x15) + - "2023-11-15" (x12) + - "2024-08-01" (x11) + - "2020-07-30" (x10) + - "2019-08-01" (x8) + - "2024-08-28" (x7) + - "2024-05-29" (x6) + - "2021-07-07" (x6) + - "2022-11-16" (x5) + - "2019-10-16" (x3) + - "2021-10-13" (x2) + - "2023-08-02" (x2) + - "2020-03-26" (x1) + - "2022-04-27" (x1) + - "2022-07-27" (x1) + [remaster_id]: + type: array + path: ancestry.remaster_id + is_null: true + values: 23 + min_count: 1 + max_count: 1 + value_type: + type: string + path: ancestry.remaster_id.[] + is_null: false + values: 23 + different_values: 22 + variants: [omitted] + 'resistance': + type: object + path: ancestry.resistance + is_null: false + values: 90 + schema: + 'search_markdown': + type: string + path: ancestry.search_markdown + is_null: false + values: 90 + different_values: 90 (all different) + variants: [omitted] + 'size': + type: array + path: ancestry.size + is_null: false + values: 90 + min_count: 1 + max_count: 4 + value_type: + type: string + path: ancestry.size.[] + is_null: false + values: 95 + different_values: 4 + variants: + - "Medium" (x68) + - "Small" (x22) + - "Large" (x3) + - "Tiny" (x2) + 'size_id': + type: array + path: ancestry.size_id + is_null: false + values: 90 + min_count: 1 + max_count: 4 + value_type: + type: integer + path: ancestry.size_id.[] + is_null: false + values: 95 + different_values: 4 + variants: + - 3 (x68) + - 2 (x22) + - 4 (x3) + - 1 (x2) 'source': type: array path: ancestry.source @@ -89,8 +225,8 @@ schema: - "Advanced Player's Guide" (x10) - "Core Rulebook" (x8) - "Tian Xia Character Guide" (x7) - - "The Mwangi Expanse" (x6) - "Howl of the Wild" (x6) + - "The Mwangi Expanse" (x6) - "Impossible Lands" (x5) - "Character Guide" (x3) - "Rage of Elements" (x2) @@ -138,8 +274,8 @@ schema: different_values: 3 variants: - 25 (x55) - - 20 (x4) - 30 (x4) + - 20 (x4) [max]: type: integer path: ancestry.speed.max @@ -148,8 +284,8 @@ schema: different_values: 3 variants: - 25 (x55) - - 20 (x4) - 30 (x4) + - 20 (x4) 'speed_markdown': type: string path: ancestry.speed_markdown @@ -159,8 +295,69 @@ schema: variants: - "25 feet" (x55) - "" (x27) - - "20 feet" (x4) - "30 feet" (x4) + - "20 feet" (x4) + [attribute_flaw]: + type: array + path: ancestry.attribute_flaw + is_null: true + values: 45 + min_count: 1 + max_count: 1 + value_type: + type: string + path: ancestry.attribute_flaw.[] + is_null: false + values: 45 + different_values: 6 + variants: + - "Wisdom" (x11) + - "Intelligence" (x9) + - "Strength" (x9) + - "Constitution" (x8) + - "Charisma" (x7) + - "Dexterity" (x1) + [legacy_id]: + type: array + path: ancestry.legacy_id + is_null: true + values: 21 + min_count: 1 + max_count: 1 + value_type: + type: string + path: ancestry.legacy_id.[] + is_null: false + values: 21 + different_values: 21 + variants: [omitted] + [source_group]: + type: array + path: ancestry.source_group + is_null: true + values: 1 + min_count: 1 + max_count: 1 + value_type: + type: string + path: ancestry.source_group.[] + is_null: false + values: 1 + different_values: 1 (all same) + variants: "Extinction Curse" (x1) + 'weakness': + type: object + path: ancestry.weakness + is_null: false + values: 90 + schema: + [spoilers]: + type: string + path: ancestry.spoilers + is_null: true + values: 1 + different_values: 1 (all same) + variants: "Extinction Curse" (x1) 'summary_markdown': type: string path: ancestry.summary_markdown @@ -199,33 +396,6 @@ schema: - "Ancestry" (x64) - "Versatile Heritage" (x24) - "Half-Human Heritage" (x2) - 'weakness': - type: object - path: ancestry.weakness - is_null: false - values: 90 - schema: - [attribute_flaw]: - type: array - path: ancestry.attribute_flaw - is_null: true - values: 45 - min_count: 1 - max_count: 1 - value_type: - type: string - path: ancestry.attribute_flaw.[] - is_null: false - values: 45 - different_values: 7 - variants: - - "Wisdom" (x11) - - "Strength" (x9) - - "Intelligence" (x9) - - "Charisma" (x7) - - "Constitution" (x7) - - "Dexterity" (x1) - - "Consitution" (x1) 'url': type: string path: ancestry.url @@ -233,177 +403,6 @@ schema: values: 90 different_values: 90 (all different) variants: [omitted] - [legacy_id]: - type: array - path: ancestry.legacy_id - is_null: true - values: 21 - min_count: 1 - max_count: 1 - value_type: - type: string - path: ancestry.legacy_id.[] - is_null: false - values: 21 - different_values: 21 - variants: [omitted] - 'markdown': - type: string - path: ancestry.markdown - is_null: false - values: 90 - different_values: 90 (all different) - variants: [omitted] - 'name': - type: string - path: ancestry.name - is_null: false - values: 90 - different_values: 73 - variants: [omitted] - 'primary_source_category': - type: string - path: ancestry.primary_source_category - is_null: false - values: 90 - different_values: 3 - variants: - - "Rulebooks" (x52) - - "Lost Omens" (x37) - - "Adventure Paths" (x1) - 'rarity': - type: string - path: ancestry.rarity - is_null: false - values: 90 - different_values: 3 - variants: - - "uncommon" (x50) - - "rare" (x22) - - "common" (x18) - 'rarity_id': - type: integer - path: ancestry.rarity_id - is_null: false - values: 90 - different_values: 3 - variants: - - 2 (x50) - - 3 (x22) - - 1 (x18) - 'release_date': - type: string - path: ancestry.release_date - is_null: false - values: 90 - different_values: 15 - variants: - - "2021-02-24" (x15) - - "2023-11-15" (x12) - - "2024-08-01" (x11) - - "2020-07-30" (x10) - - "2019-08-01" (x8) - - "2024-08-28" (x7) - - "2021-07-07" (x6) - - "2024-05-29" (x6) - - "2022-11-16" (x5) - - "2019-10-16" (x3) - - "2021-10-13" (x2) - - "2023-08-02" (x2) - - "2020-03-26" (x1) - - "2022-04-27" (x1) - - "2022-07-27" (x1) - 'resistance': - type: object - path: ancestry.resistance - is_null: false - values: 90 - schema: - 'search_markdown': - type: string - path: ancestry.search_markdown - is_null: false - values: 90 - different_values: 90 (all different) - variants: [omitted] - [primary_source_group]: - type: string - path: ancestry.primary_source_group - is_null: true - values: 1 - different_values: 1 (all same) - variants: "Extinction Curse" (x1) - [remaster_id]: - type: array - path: ancestry.remaster_id - is_null: true - values: 23 - min_count: 1 - max_count: 1 - value_type: - type: string - path: ancestry.remaster_id.[] - is_null: false - values: 23 - different_values: 22 - variants: [omitted] - 'size': - type: array - path: ancestry.size - is_null: false - values: 90 - min_count: 1 - max_count: 4 - value_type: - type: string - path: ancestry.size.[] - is_null: false - values: 95 - different_values: 4 - variants: - - "Medium" (x68) - - "Small" (x22) - - "Large" (x3) - - "Tiny" (x2) - 'size_id': - type: array - path: ancestry.size_id - is_null: false - values: 90 - min_count: 1 - max_count: 4 - value_type: - type: integer - path: ancestry.size_id.[] - is_null: false - values: 95 - different_values: 4 - variants: - - 3 (x68) - - 2 (x22) - - 4 (x3) - - 1 (x2) - [source_group]: - type: array - path: ancestry.source_group - is_null: true - values: 1 - min_count: 1 - max_count: 1 - value_type: - type: string - path: ancestry.source_group.[] - is_null: false - values: 1 - different_values: 1 (all same) - variants: "Extinction Curse" (x1) - [spoilers]: - type: string - path: ancestry.spoilers - is_null: true - values: 1 - different_values: 1 (all same) - variants: "Extinction Curse" (x1) [vision]: type: string path: ancestry.vision diff --git a/output/aonprd/animal-companion.yaml b/output/aonprd/animal-companion.yaml index 78f1587..a530a12 100644 --- a/output/aonprd/animal-companion.yaml +++ b/output/aonprd/animal-companion.yaml @@ -1,31 +1,31 @@ name: animal-companion -values: 96 +values: 99 schema: 'category': type: string path: animal-companion.category is_null: false - values: 96 + values: 99 different_values: 1 (all same) - variants: "animal-companion" (x96) + variants: "animal-companion" (x99) 'charisma': type: integer path: animal-companion.charisma is_null: false - values: 96 + values: 99 different_values: 3 variants: - - 0 (x85) - - 1 (x7) + - 0 (x87) + - 1 (x8) - 2 (x4) 'constitution': type: integer path: animal-companion.constitution is_null: false - values: 96 + values: 99 different_values: 5 variants: - - 2 (x55) + - 2 (x58) - 1 (x25) - 3 (x14) - 0 (x1) @@ -34,26 +34,26 @@ schema: type: integer path: animal-companion.dexterity is_null: false - values: 96 + values: 99 different_values: 4 variants: - - 2 (x43) - - 3 (x30) - - 1 (x21) + - 2 (x44) + - 3 (x31) + - 1 (x22) - 0 (x2) 'source': type: array path: animal-companion.source is_null: false - values: 96 + values: 99 min_count: 1 max_count: 1 value_type: type: string path: animal-companion.source.[] is_null: false - values: 96 - different_values: 17 + values: 99 + different_values: 20 variants: - "Howl of the Wild" (x19) - "Player Core" (x16) @@ -72,35 +72,38 @@ schema: - "Secrets of Magic" (x1) - "Monsters of Myth" (x1) - "Impossible Lands" (x1) + - "Pathfinder #208: Hoof, Cinder, and Storm" (x1) + - "Pathfinder #209: Destroyer's Doom" (x1) + - "Pathfinder #201: Pactbreaker" (x1) 'source_category': type: array path: animal-companion.source_category is_null: false - values: 96 + values: 99 min_count: 1 max_count: 1 value_type: type: string path: animal-companion.source_category.[] is_null: false - values: 96 + values: 99 different_values: 3 variants: - "Rulebooks" (x65) + - "Adventure Paths" (x18) - "Lost Omens" (x16) - - "Adventure Paths" (x15) 'source_markdown': type: string path: animal-companion.source_markdown is_null: false - values: 96 - different_values: 37 + values: 99 + different_values: 40 variants: [omitted] 'speed': type: object path: animal-companion.speed is_null: false - values: 96 + values: 99 schema: [burrow]: type: integer @@ -128,14 +131,14 @@ schema: type: integer path: animal-companion.speed.land is_null: true - values: 92 + values: 95 different_values: 10 variants: + - 20 (x19) - 25 (x18) - - 20 (x18) - - 35 (x13) + - 35 (x14) - 30 (x13) - - 40 (x10) + - 40 (x11) - 10 (x8) - 15 (x6) - 50 (x2) @@ -145,13 +148,13 @@ schema: type: integer path: animal-companion.speed.max is_null: false - values: 96 + values: 99 different_values: 8 variants: - 25 (x22) - - 40 (x17) + - 40 (x19) - 30 (x17) - - 35 (x14) + - 35 (x15) - 20 (x13) - 60 (x6) - 50 (x4) @@ -160,15 +163,15 @@ schema: type: integer path: animal-companion.speed.fly is_null: true - values: 16 + values: 17 different_values: 6 variants: - 60 (x6) - 30 (x5) - 50 (x2) + - 40 (x2) - 45 (x1) - 20 (x1) - - 40 (x1) [swim]: type: integer path: animal-companion.speed.swim @@ -186,26 +189,26 @@ schema: type: string path: animal-companion.speed_markdown is_null: false - values: 96 + values: 99 different_values: 37 variants: [omitted] 'strength': type: integer path: animal-companion.strength is_null: false - values: 96 + values: 99 different_values: 4 variants: - - 2 (x49) - - 3 (x45) + - 2 (x50) + - 3 (x46) + - 1 (x2) - -5 (x1) - - 1 (x1) 'summary_markdown': type: string path: animal-companion.summary_markdown is_null: false - values: 96 - different_values: 83 + values: 99 + different_values: 86 variants: [omitted] [element]: type: array @@ -231,10 +234,10 @@ schema: type: string path: animal-companion.hp_raw is_null: false - values: 96 + values: 99 different_values: 4 variants: - - "8" (x39) + - "8" (x42) - "6" (x35) - "4" (x19) - "10" (x3) @@ -242,34 +245,34 @@ schema: type: string path: animal-companion.id is_null: false - values: 96 - different_values: 96 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'intelligence': type: integer path: animal-companion.intelligence is_null: false - values: 96 + values: 99 different_values: 3 variants: - - -4 (x87) + - -4 (x90) - -5 (x8) - -3 (x1) 'skill_markdown': type: string path: animal-companion.skill_markdown is_null: false - values: 96 + values: 99 different_values: 11 variants: - "[Stealth](/Skills.aspx?ID=15)" (x24) - "[Survival](/Skills.aspx?ID=16)" (x21) - "[Intimidation](/Skills.aspx?ID=7)" (x14) - - "[Survival](/Skills.aspx?ID=49)" (x12) + - "[Survival](/Skills.aspx?ID=49)" (x13) + - "[Stealth](/Skills.aspx?ID=48)" (x9) - "none (mindless)" (x8) - - "[Stealth](/Skills.aspx?ID=48)" (x8) + - "[Intimidation](/Skills.aspx?ID=40)" (x4) - "[Athletics](/Skills.aspx?ID=3)" (x3) - - "[Intimidation](/Skills.aspx?ID=40)" (x3) - "[Thievery](/Skills.aspx?ID=17)" (x1) - "[Acrobatics](/Skills.aspx?ID=1)" (x1) - "[Performance](/Skills.aspx?ID=45)" (x1) @@ -277,37 +280,37 @@ schema: type: string path: animal-companion.trait_markdown is_null: false - values: 96 + values: 99 different_values: 23 variants: [omitted] 'type': type: string path: animal-companion.type is_null: false - values: 96 + values: 99 different_values: 1 (all same) - variants: "Animal Companion" (x96) + variants: "Animal Companion" (x99) 'url': type: string path: animal-companion.url is_null: false - values: 96 - different_values: 96 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'weakness': type: object path: animal-companion.weakness is_null: false - values: 96 + values: 99 schema: 'wisdom': type: integer path: animal-companion.wisdom is_null: false - values: 96 + values: 99 different_values: 3 variants: - - 1 (x63) + - 1 (x66) - 2 (x24) - 0 (x9) [legacy_id]: @@ -344,106 +347,109 @@ schema: type: integer path: animal-companion.level is_null: false - values: 96 - different_values: 5 + values: 99 + different_values: 6 variants: - - 1 (x86) + - 1 (x88) - 14 (x4) - 6 (x3) - 4 (x2) - 16 (x1) + - 10 (x1) 'markdown': type: string path: animal-companion.markdown is_null: false - values: 96 - different_values: 96 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'mount': type: boolean path: animal-companion.mount is_null: false - values: 96 + values: 99 different_values: 2 variants: - False (x63) - - True (x33) + - True (x36) 'name': type: string path: animal-companion.name is_null: false - values: 96 - different_values: 80 + values: 99 + different_values: 83 variants: [omitted] 'pfs': type: string path: animal-companion.pfs is_null: false - values: 96 + values: 99 different_values: 2 variants: - "Standard" (x81) - - "Limited" (x15) + - "Limited" (x18) 'primary_source_category': type: string path: animal-companion.primary_source_category is_null: false - values: 96 + values: 99 different_values: 3 variants: - "Rulebooks" (x65) + - "Adventure Paths" (x18) - "Lost Omens" (x16) - - "Adventure Paths" (x15) 'resistance': type: object path: animal-companion.resistance is_null: false - values: 96 + values: 99 schema: 'search_markdown': type: string path: animal-companion.search_markdown is_null: false - values: 96 - different_values: 96 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] [primary_source_group]: type: string path: animal-companion.primary_source_group is_null: true - values: 15 - different_values: 4 + values: 18 + different_values: 6 variants: - "Blood Lords" (x5) - "Quest for the Frozen Flame" (x4) - "Age of Ashes" (x3) - "Extinction Curse" (x3) + - "Triumph of the Tusk" (x2) + - "Wardens of Wildwood" (x1) 'rarity': type: string path: animal-companion.rarity is_null: false - values: 96 + values: 99 different_values: 3 variants: - - "common" (x60) + - "common" (x63) - "uncommon" (x34) - "rare" (x2) 'rarity_id': type: integer path: animal-companion.rarity_id is_null: false - values: 96 + values: 99 different_values: 3 variants: - - 1 (x60) + - 1 (x63) - 2 (x34) - 3 (x2) 'release_date': type: string path: animal-companion.release_date is_null: false - values: 96 - different_values: 17 + values: 99 + different_values: 20 variants: - "2024-05-29" (x19) - "2023-11-15" (x16) @@ -462,12 +468,15 @@ schema: - "2021-09-01" (x1) - "2021-12-22" (x1) - "2022-11-16" (x1) + - "2024-11-15" (x1) + - "2024-12-15" (x1) + - "2024-04-15" (x1) 'sense_markdown': type: string path: animal-companion.sense_markdown is_null: false - values: 96 - different_values: 27 + values: 99 + different_values: 28 variants: [omitted] [remaster_id]: type: array @@ -503,56 +512,58 @@ schema: type: array path: animal-companion.size is_null: false - values: 96 + values: 99 min_count: 1 max_count: 2 value_type: type: string path: animal-companion.size.[] is_null: false - values: 112 + values: 116 different_values: 4 variants: - "Small" (x59) - - "Large" (x30) - - "Medium" (x22) + - "Large" (x33) + - "Medium" (x23) - "Huge" (x1) 'size_id': type: array path: animal-companion.size_id is_null: false - values: 96 + values: 99 min_count: 1 max_count: 2 value_type: type: integer path: animal-companion.size_id.[] is_null: false - values: 112 + values: 116 different_values: 4 variants: - 2 (x59) - - 4 (x30) - - 3 (x22) + - 4 (x33) + - 3 (x23) - 5 (x1) [source_group]: type: array path: animal-companion.source_group is_null: true - values: 15 + values: 18 min_count: 1 max_count: 1 value_type: type: string path: animal-companion.source_group.[] is_null: false - values: 15 - different_values: 4 + values: 18 + different_values: 6 variants: - "Blood Lords" (x5) - "Quest for the Frozen Flame" (x4) - "Age of Ashes" (x3) - "Extinction Curse" (x3) + - "Triumph of the Tusk" (x2) + - "Wardens of Wildwood" (x1) [spoilers]: type: string path: animal-companion.spoilers @@ -567,18 +578,18 @@ schema: type: array path: animal-companion.trait is_null: true - values: 92 + values: 96 min_count: 1 max_count: 5 value_type: type: string path: animal-companion.trait.[] is_null: false - values: 240 + values: 248 different_values: 20 variants: - - "Minion" (x91) - - "Animal" (x68) + - "Minion" (x95) + - "Animal" (x72) - "Uncommon" (x34) - "Undead" (x10) - "Mindless" (x8) diff --git a/output/aonprd/arcane-school.yaml b/output/aonprd/arcane-school.yaml index 6b72d95..efd61d1 100644 --- a/output/aonprd/arcane-school.yaml +++ b/output/aonprd/arcane-school.yaml @@ -68,7 +68,7 @@ schema: - "Red Mantis Magic School" (x1) - "School of Rooted Wisdom" (x1) - "School of Thassilonian Rune Magic" (x1) - - "School ofthe Reclamation" (x1) + - "School of the Reclamation" (x1) 'pfs': type: string path: arcane-school.pfs @@ -201,7 +201,7 @@ schema: - "[Shield](/Spells.aspx?ID=1671), [Telekinetic Projectile](/Spells.aspx?ID=1718), [Breathe Fire](/Spells.aspx?ID=1457), [Force Barrage](/Spells.aspx?ID=1536), [Mystic Armor](/Spells.aspx?ID=1611), [Mist](/Spells.aspx?ID=1606), [Resist Energy](/Spells.aspx?ID=1651), [Earthbind](/Spells.aspx?ID=1506), [Fireball](/Spells.aspx?ID=1530), [Wall of Fire](/Spells.aspx?ID=1748), [Howling Blizzard](/Spells.aspx?ID=1559), [Impaling Spike](/Spells.aspx?ID=1571), [Chain Lightning](/Spells.aspx?ID=1462), [Disintegrate](/Spells.aspx?ID=1492), [Energy Aegis](/Spells.aspx?ID=1512), [True Target](/Spells.aspx?ID=1726), [Arctic Rift](/Spells.aspx?ID=1444), [Desiccate](/Spells.aspx?ID=1484), [Falling Stars](/Spells.aspx?ID=1521), [Force Bolt](/Spells.aspx?ID=1896), [Energy Absorption](/Spells.aspx?ID=1897), [Weapon Storm](/Spells.aspx?ID=1758)" (x1) - "[Clairaudience](/Spells.aspx?ID=1465), [Clairvoyance](/Spells.aspx?ID=1466), [Disappearance](/Spells.aspx?ID=1490), [Duplicate Foe](/Spells.aspx?ID=1505), [Figment](/Spells.aspx?ID=1528), [Fleet Step](/Spells.aspx?ID=1531), [Gouging Claw](/Spells.aspx?ID=1546), [Hallucination](/Spells.aspx?ID=1551), [Illusory Disguise](/Spells.aspx?ID=1568), [Illusory Scene](/Spells.aspx?ID=1570), [Invisibility](/Spells.aspx?ID=1577), [Mislead](/Spells.aspx?ID=1605), [Mist](/Spells.aspx?ID=1606), [Paralyze](/Spells.aspx?ID=1622), [Phantasmagoria](/Spells.aspx?ID=1629), [Phantasmal Calamity](/Spells.aspx?ID=1630), [Project Image](/Spells.aspx?ID=1640), [Sure Strike](/Spells.aspx?ID=1709), [Translocate](/Spells.aspx?ID=1724), [Unrelenting Observation](/Spells.aspx?ID=1734), [Debilitating Terror](/Spells.aspx?ID=2130), [Shroud of the Mantis](/Spells.aspx?ID=2131)" (x1) - "[Detect Magic](/Spells.aspx?ID=1485), [Alarm](/Spells.aspx?ID=1439), [Dispel Magic](/Spells.aspx?ID=1493), [Safe Passage](/Spells.aspx?ID=1659), [Mountain Resilience](/Spells.aspx?ID=1610), [Control Water](/Spells.aspx?ID=1473), [Truesight](/Spells.aspx?ID=1727), [Energy Aegis](/Spells.aspx?ID=1512), [Quandary](/Spells.aspx?ID=1644), [Halcyon Mists](/Spells.aspx?ID=2303), [Call the Ten](/Spells.aspx?ID=2304)" (x1) - - "[Detect Magic](/Spells.aspx?ID=1485), [Sigil](/Spells.aspx?ID=1673), [Mystic Armor](/Spells.aspx?ID=1611), [Runic Body](/Spells.aspx?ID=1657), [Runic Weapon](/Spells.aspx?ID=1658), [Sure Strike](/Spells.aspx?ID=1709), [Darkvision](/Spells.aspx?ID=1481), [See the Unseen](/Spells.aspx?ID=1663), [Clairaudience](/Spells.aspx?ID=1465), [Veil of Privacy](/Spells.aspx?ID=1739), [Clairvoyance](/Spells.aspx?ID=1466), [Fly](/Spells.aspx?ID=1534), [Mind Probe](/Spells.aspx?ID=1601), [Sending](/Spells.aspx?ID=1665), [Scrying](/Spells.aspx?ID=1662), [Truesight](/Spells.aspx?ID=1727), [Contingency](/Spells.aspx?ID=1472), [Spell Riposte](/Spells.aspx?ID=2027), [Unrelenting Observation](/Spells.aspx?ID=1734), [Seize Soul](/Spells.aspx?ID=1664), [Foresight](/Spells.aspx?ID=1537), [Personal Runewell](/Spells.aspx?ID=2325)" (x1) + - "[Detect Magic](/Spells.aspx?ID=1485), [Sigil](/Spells.aspx?ID=1673), [Mystic Armor](/Spells.aspx?ID=1611), [Runic Body](/Spells.aspx?ID=1657), [Runic Weapon](/Spells.aspx?ID=1658), [Sure Strike](/Spells.aspx?ID=1709), [Darkvision](/Spells.aspx?ID=1481), [See the Unseen](/Spells.aspx?ID=1663), [Clairaudience](/Spells.aspx?ID=1465), [Veil of Privacy](/Spells.aspx?ID=1739), [Clairvoyance](/Spells.aspx?ID=1466), [Fly](/Spells.aspx?ID=1534), [Mind Probe](/Spells.aspx?ID=1601), [Sending](/Spells.aspx?ID=1665), [Scrying](/Spells.aspx?ID=1662), [Truesight](/Spells.aspx?ID=1727), [Contingency](/Spells.aspx?ID=1472), [Spell Riposte](/Spells.aspx?ID=2027), [Unrelenting Observation](/Spells.aspx?ID=1734), [Hidden Mind](/Spells.aspx?ID=1556), [Foresight](/Spells.aspx?ID=1537), [Personal Runewell](/Spells.aspx?ID=2325)" (x1) - "[Telekinetic Hand](/Spells.aspx?ID=1715), [Telekinetic Projectile](/Spells.aspx?ID=1718), [Fleet Step](/Spells.aspx?ID=1531), [Gust of Wind](/Spells.aspx?ID=1550), [Mending](/Spells.aspx?ID=1597), [Knock](/Spells.aspx?ID=1581), [Mental Map](/Spells.aspx?ID=2295), [Locate](/Spells.aspx?ID=1588), [Overwhelming Memory](/Spells.aspx?ID=2296), [Bridge of Vines](/Spells.aspx?ID=2294), [Shape Stone](/Spells.aspx?ID=1667), [Mind Probe](/Spells.aspx?ID=1601), [Scouting Eye](/Spells.aspx?ID=1661), [Truesight](/Spells.aspx?ID=1727), [Wall of Force](/Spells.aspx?ID=1749), [Restore Ground](/Spells.aspx?ID=2297), [Retrocognition](/Spells.aspx?ID=1652), [Pinpoint](/Spells.aspx?ID=1633), [Unrelenting Observation](/Spells.aspx?ID=1734), [Metamorphosis](/Spells.aspx?ID=1599), [Grasping Vine](/Spells.aspx?ID=2292), [Unsettling Knowledge](/Spells.aspx?ID=2293)" (x1) 'summary_markdown': type: string diff --git a/output/aonprd/archetype.yaml b/output/aonprd/archetype.yaml index 132918a..ba004b3 100644 --- a/output/aonprd/archetype.yaml +++ b/output/aonprd/archetype.yaml @@ -1,33 +1,33 @@ name: archetype -values: 264 +values: 266 schema: 'archetype': type: array path: archetype.archetype is_null: false - values: 264 + values: 266 min_count: 1 max_count: 1 value_type: type: string path: archetype.archetype.[] is_null: false - values: 264 - different_values: 208 + values: 266 + different_values: 210 variants: [omitted] 'category': type: string path: archetype.category is_null: false - values: 264 + values: 266 different_values: 1 (all same) - variants: "archetype" (x264) + variants: "archetype" (x266) 'id': type: string path: archetype.id is_null: false - values: 264 - different_values: 264 (all different) + values: 266 + different_values: 266 (all different) variants: [omitted] [image]: type: array @@ -47,15 +47,15 @@ schema: type: string path: archetype.markdown is_null: false - values: 264 - different_values: 264 (all different) + values: 266 + different_values: 266 (all different) variants: [omitted] 'name': type: string path: archetype.name is_null: false - values: 264 - different_values: 208 + values: 266 + different_values: 210 variants: [omitted] [legacy_id]: type: array @@ -75,20 +75,20 @@ schema: type: string path: archetype.primary_source_category is_null: false - values: 264 + values: 266 different_values: 5 variants: - "Rulebooks" (x181) - "Lost Omens" (x46) - - "Adventure Paths" (x33) + - "Adventure Paths" (x35) - "Adventures" (x3) - "Blog Posts" (x1) [primary_source_group]: type: string path: archetype.primary_source_group is_null: true - values: 36 - different_values: 15 + values: 38 + different_values: 17 variants: - "Extinction Curse" (x5) - "Fists of the Ruby Phoenix" (x4) @@ -105,41 +105,43 @@ schema: - "Blood Lords" (x1) - "Stolen Fate" (x1) - "Prey for Death" (x1) + - "Wardens of Wildwood" (x1) + - "Triumph of the Tusk" (x1) 'rarity': type: string path: archetype.rarity is_null: false - values: 264 + values: 266 different_values: 3 variants: - "common" (x143) - - "uncommon" (x96) + - "uncommon" (x98) - "rare" (x25) 'rarity_id': type: integer path: archetype.rarity_id is_null: false - values: 264 + values: 266 different_values: 3 variants: - 1 (x143) - - 2 (x96) + - 2 (x98) - 3 (x25) 'release_date': type: string path: archetype.release_date is_null: false - values: 264 - different_values: 41 + values: 266 + different_values: 43 variants: [omitted] [level]: type: integer path: archetype.level is_null: true - values: 251 + values: 253 different_values: 6 variants: - - 2 (x215) + - 2 (x217) - 4 (x15) - 6 (x14) - 8 (x3) @@ -149,18 +151,18 @@ schema: type: string path: archetype.pfs is_null: true - values: 254 + values: 256 different_values: 3 variants: - "Standard" (x205) - - "Limited" (x45) + - "Limited" (x47) - "Restricted" (x4) [prerequisite_markdown]: type: string path: archetype.prerequisite_markdown is_null: true - values: 217 - different_values: 176 + values: 219 + different_values: 177 variants: [omitted] [remaster_id]: type: array @@ -180,11 +182,11 @@ schema: type: string path: archetype.trait_markdown is_null: false - values: 264 + values: 266 different_values: 5 variants: - "" (x143) - - "[Uncommon](/Traits.aspx?ID=159)" (x84) + - "[Uncommon](/Traits.aspx?ID=159)" (x86) - "[Rare](/Traits.aspx?ID=137)" (x18) - "[Uncommon](/Traits.aspx?ID=721)" (x12) - "[Rare](/Traits.aspx?ID=683)" (x7) @@ -192,21 +194,21 @@ schema: type: string path: archetype.type is_null: false - values: 264 + values: 266 different_values: 1 (all same) - variants: "Archetype" (x264) + variants: "Archetype" (x266) 'url': type: string path: archetype.url is_null: false - values: 264 - different_values: 264 (all different) + values: 266 + different_values: 266 (all different) variants: [omitted] 'weakness': type: object path: archetype.weakness is_null: false - values: 264 + values: 266 schema: [requirement_markdown]: type: string @@ -219,74 +221,74 @@ schema: type: object path: archetype.resistance is_null: false - values: 264 + values: 266 schema: 'search_markdown': type: string path: archetype.search_markdown is_null: false - values: 264 - different_values: 264 (all different) + values: 266 + different_values: 266 (all different) variants: [omitted] 'source': type: array path: archetype.source is_null: false - values: 264 + values: 266 min_count: 1 max_count: 2 value_type: type: string path: archetype.source.[] is_null: false - values: 268 - different_values: 53 + values: 270 + different_values: 55 variants: [omitted] 'source_category': type: array path: archetype.source_category is_null: false - values: 264 + values: 266 min_count: 1 max_count: 2 value_type: type: string path: archetype.source_category.[] is_null: false - values: 268 + values: 270 different_values: 5 variants: - "Rulebooks" (x181) - "Lost Omens" (x48) - - "Adventure Paths" (x35) + - "Adventure Paths" (x37) - "Adventures" (x3) - "Blog Posts" (x1) 'source_markdown': type: string path: archetype.source_markdown is_null: false - values: 264 - different_values: 264 (all different) + values: 266 + different_values: 266 (all different) variants: [omitted] 'speed': type: object path: archetype.speed is_null: false - values: 264 + values: 266 schema: [source_group]: type: array path: archetype.source_group is_null: true - values: 38 + values: 40 min_count: 1 max_count: 1 value_type: type: string path: archetype.source_group.[] is_null: false - values: 38 - different_values: 15 + values: 40 + different_values: 17 variants: - "Extinction Curse" (x5) - "Strength of Thousands" (x4) @@ -303,12 +305,14 @@ schema: - "Blood Lords" (x1) - "Stolen Fate" (x1) - "Prey for Death" (x1) + - "Wardens of Wildwood" (x1) + - "Triumph of the Tusk" (x1) [spoilers]: type: string path: archetype.spoilers is_null: true - values: 35 - different_values: 15 + values: 37 + different_values: 17 variants: - "Extinction Curse" (x5) - "Fists of the Ruby Phoenix" (x4) @@ -325,26 +329,28 @@ schema: - "Blood Lords" (x1) - "Stolen Fate" (x1) - "Prey for Death" (x1) + - "Wardens of Wildwood" (x1) + - "Triumph of the Tusk" (x1) 'summary_markdown': type: string path: archetype.summary_markdown is_null: false - values: 264 - different_values: 239 + values: 266 + different_values: 241 variants: [omitted] [trait]: type: array path: archetype.trait is_null: true - values: 121 + values: 123 min_count: 1 max_count: 1 value_type: type: string path: archetype.trait.[] is_null: false - values: 121 + values: 123 different_values: 2 variants: - - "Uncommon" (x96) + - "Uncommon" (x98) - "Rare" (x25) diff --git a/output/aonprd/article.yaml b/output/aonprd/article.yaml index ea9dace..5b974b5 100644 --- a/output/aonprd/article.yaml +++ b/output/aonprd/article.yaml @@ -1,187 +1,197 @@ name: article -values: 76 +values: 99 schema: 'category': type: string path: article.category is_null: false - values: 76 + values: 99 different_values: 1 (all same) - variants: "article" (x76) + variants: "article" (x99) 'id': type: string path: article.id is_null: false - values: 76 - different_values: 76 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'markdown': type: string path: article.markdown is_null: false - values: 76 - different_values: 76 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'name': type: string path: article.name is_null: false - values: 76 - different_values: 76 (all different) + values: 99 + different_values: 98 variants: [omitted] 'primary_source_category': type: string path: article.primary_source_category is_null: false - values: 76 + values: 99 different_values: 1 (all same) - variants: "Adventure Paths" (x76) + variants: "Adventure Paths" (x99) 'primary_source_group': type: string path: article.primary_source_group is_null: false - values: 76 - different_values: 13 + values: 99 + different_values: 18 variants: - "Extinction Curse" (x11) - "Agents of Edgewatch" (x11) - "Age of Ashes" (x9) - "Strength of Thousands" (x9) - "Blood Lords" (x9) + - "Triumph of the Tusk" (x7) + - "Wardens of Wildwood" (x6) - "Abomination Vaults" (x5) - "Fists of the Ruby Phoenix" (x5) - "Sky King's Tomb" (x5) + - "Spore War" (x5) - "Quest for the Frozen Flame" (x4) - "Outlaws of Alkenstar" (x3) - "Gatewalkers" (x3) + - "Curtain Call" (x3) + - "Shades of Blood" (x2) - "Stolen Fate" (x1) - "Season of Ghosts" (x1) 'rarity': type: string path: article.rarity is_null: false - values: 76 + values: 99 different_values: 1 (all same) - variants: "common" (x76) + variants: "common" (x99) 'rarity_id': type: integer path: article.rarity_id is_null: false - values: 76 + values: 99 different_values: 1 (all same) - variants: 1 (x76) + variants: 1 (x99) 'release_date': type: string path: article.release_date is_null: false - values: 76 - different_values: 46 + values: 99 + different_values: 59 variants: [omitted] 'resistance': type: object path: article.resistance is_null: false - values: 76 + values: 99 schema: 'search_markdown': type: string path: article.search_markdown is_null: false - values: 76 - different_values: 76 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'source': type: array path: article.source is_null: false - values: 76 + values: 99 min_count: 1 max_count: 1 value_type: type: string path: article.source.[] is_null: false - values: 76 - different_values: 50 + values: 99 + different_values: 63 variants: [omitted] 'source_category': type: array path: article.source_category is_null: false - values: 76 + values: 99 min_count: 1 max_count: 1 value_type: type: string path: article.source_category.[] is_null: false - values: 76 + values: 99 different_values: 1 (all same) - variants: "Adventure Paths" (x76) + variants: "Adventure Paths" (x99) 'source_group': type: array path: article.source_group is_null: false - values: 76 + values: 99 min_count: 1 max_count: 1 value_type: type: string path: article.source_group.[] is_null: false - values: 76 - different_values: 13 + values: 99 + different_values: 18 variants: - "Extinction Curse" (x11) - "Agents of Edgewatch" (x11) - "Age of Ashes" (x9) - "Strength of Thousands" (x9) - "Blood Lords" (x9) + - "Triumph of the Tusk" (x7) + - "Wardens of Wildwood" (x6) - "Abomination Vaults" (x5) - "Fists of the Ruby Phoenix" (x5) - "Sky King's Tomb" (x5) + - "Spore War" (x5) - "Quest for the Frozen Flame" (x4) - "Outlaws of Alkenstar" (x3) - "Gatewalkers" (x3) + - "Curtain Call" (x3) + - "Shades of Blood" (x2) - "Stolen Fate" (x1) - "Season of Ghosts" (x1) 'source_markdown': type: string path: article.source_markdown is_null: false - values: 76 - different_values: 76 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'speed': type: object path: article.speed is_null: false - values: 76 + values: 99 schema: 'summary_markdown': type: string path: article.summary_markdown is_null: false - values: 76 - different_values: 76 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'type': type: string path: article.type is_null: false - values: 76 + values: 99 different_values: 1 (all same) - variants: "Setting Article" (x76) + variants: "Setting Article" (x99) 'url': type: string path: article.url is_null: false - values: 76 - different_values: 76 (all different) + values: 99 + different_values: 99 (all different) variants: [omitted] 'weakness': type: object path: article.weakness is_null: false - values: 76 + values: 99 schema: diff --git a/output/aonprd/background.yaml b/output/aonprd/background.yaml index 849061e..0e0302b 100644 --- a/output/aonprd/background.yaml +++ b/output/aonprd/background.yaml @@ -1,71 +1,71 @@ name: background -values: 484 +values: 516 schema: [attribute]: type: array path: background.attribute is_null: true - values: 464 + values: 496 min_count: 1 max_count: 2 value_type: type: string path: background.attribute.[] is_null: false - values: 919 + values: 983 different_values: 6 variants: - - "Wisdom" (x185) - - "Intelligence" (x177) - - "Charisma" (x176) - - "Constitution" (x142) - - "Dexterity" (x132) - - "Strength" (x107) + - "Wisdom" (x196) + - "Intelligence" (x190) + - "Charisma" (x187) + - "Constitution" (x150) + - "Dexterity" (x144) + - "Strength" (x116) 'category': type: string path: background.category is_null: false - values: 484 + values: 516 different_values: 1 (all same) - variants: "background" (x484) + variants: "background" (x516) [feat_markdown]: type: string path: background.feat_markdown is_null: true - values: 418 - different_values: 112 + values: 448 + different_values: 126 variants: [omitted] 'id': type: string path: background.id is_null: false - values: 484 - different_values: 484 (all different) + values: 516 + different_values: 516 (all different) variants: [omitted] 'search_markdown': type: string path: background.search_markdown is_null: false - values: 484 - different_values: 484 (all different) + values: 516 + different_values: 516 (all different) variants: [omitted] 'source_category': type: array path: background.source_category is_null: false - values: 484 + values: 516 min_count: 1 max_count: 1 value_type: type: string path: background.source_category.[] is_null: false - values: 484 + values: 516 different_values: 7 variants: - "Rulebooks" (x209) + - "Adventure Paths" (x135) - "Lost Omens" (x134) - - "Adventure Paths" (x103) - "Adventures" (x14) - "Society" (x14) - "Blog Posts" (x7) @@ -74,101 +74,101 @@ schema: type: string path: background.source_markdown is_null: false - values: 484 - different_values: 118 + values: 516 + different_values: 129 variants: [omitted] 'speed': type: object path: background.speed is_null: false - values: 484 + values: 516 schema: 'summary_markdown': type: string path: background.summary_markdown is_null: false - values: 484 - different_values: 432 + values: 516 + different_values: 464 variants: [omitted] 'trait_markdown': type: string path: background.trait_markdown is_null: false - values: 484 + values: 516 different_values: 7 variants: - "" (x328) - "[Rare](/Traits.aspx?ID=137)" (x89) - "[Uncommon](/Traits.aspx?ID=159)" (x36) - - "[Uncommon](/Traits.aspx?ID=721)" (x16) - - "[Rare](/Traits.aspx?ID=683)" (x8) + - "[Uncommon](/Traits.aspx?ID=721)" (x28) + - "[Rare](/Traits.aspx?ID=683)" (x28) - "[Legacy - Age of Ashes](/Traits.aspx?ID=270), [Rare](/Traits.aspx?ID=137)" (x4) - "[Pervasive Magic](/Traits.aspx?ID=390), [Rare](/Traits.aspx?ID=137)" (x3) 'type': type: string path: background.type is_null: false - values: 484 + values: 516 different_values: 1 (all same) - variants: "Background" (x484) + variants: "Background" (x516) 'url': type: string path: background.url is_null: false - values: 484 - different_values: 484 (all different) + values: 516 + different_values: 516 (all different) variants: [omitted] 'weakness': type: object path: background.weakness is_null: false - values: 484 + values: 516 schema: [legacy_id]: type: array path: background.legacy_id is_null: true - values: 65 + values: 64 min_count: 1 max_count: 1 value_type: type: string path: background.legacy_id.[] is_null: false - values: 65 + values: 64 different_values: 64 variants: [omitted] [skill_markdown]: type: string path: background.skill_markdown is_null: true - values: 481 - different_values: 318 + values: 513 + different_values: 348 variants: [omitted] 'markdown': type: string path: background.markdown is_null: false - values: 484 - different_values: 484 (all different) + values: 516 + different_values: 516 (all different) variants: [omitted] 'name': type: string path: background.name is_null: false - values: 484 - different_values: 420 + values: 516 + different_values: 452 variants: [omitted] 'primary_source_category': type: string path: background.primary_source_category is_null: false - values: 484 + values: 516 different_values: 7 variants: - "Rulebooks" (x209) + - "Adventure Paths" (x135) - "Lost Omens" (x134) - - "Adventure Paths" (x103) - "Adventures" (x14) - "Society" (x14) - "Blog Posts" (x7) @@ -177,43 +177,43 @@ schema: type: string path: background.rarity is_null: false - values: 484 + values: 516 different_values: 3 variants: - "common" (x328) - - "rare" (x104) - - "uncommon" (x52) + - "rare" (x124) + - "uncommon" (x64) 'rarity_id': type: integer path: background.rarity_id is_null: false - values: 484 + values: 516 different_values: 3 variants: - 1 (x328) - - 3 (x104) - - 2 (x52) + - 3 (x124) + - 2 (x64) 'release_date': type: string path: background.release_date is_null: false - values: 484 - different_values: 45 + values: 516 + different_values: 50 variants: [omitted] [pfs]: type: string path: background.pfs is_null: true - values: 369 + values: 401 different_values: 2 variants: - - "Standard" (x350) + - "Standard" (x382) - "Limited" (x19) 'resistance': type: object path: background.resistance is_null: false - values: 484 + values: 516 schema: [prerequisite_markdown]: type: string @@ -229,28 +229,9 @@ schema: type: string path: background.primary_source_group is_null: true - values: 137 - different_values: 19 - variants: - - "Pathfinder Society" (x20) - - "Age of Ashes" (x14) - - "Agents of Edgewatch" (x11) - - "Extinction Curse" (x10) - - "Abomination Vaults" (x7) - - "Kingmaker" (x7) - - "Stolen Fate" (x7) - - "Rusthenge" (x7) - - "Fists of the Ruby Phoenix" (x6) - - "Quest for the Frozen Flame" (x6) - - "Outlaws of Alkenstar" (x6) - - "Blood Lords" (x6) - - "Gatewalkers" (x6) - - "Sky King's Tomb" (x6) - - "Season of Ghosts" (x6) - - "The Fall of Plaguestone" (x5) - - "Strength of Thousands" (x5) - - "Little Trouble in Big Absalom" (x1) - - "Crown of the Kobold King" (x1) + values: 169 + different_values: 24 + variants: [omitted] [region]: type: string path: background.region @@ -286,56 +267,38 @@ schema: type: array path: background.source is_null: false - values: 484 + values: 516 min_count: 1 max_count: 1 value_type: type: string path: background.source.[] is_null: false - values: 484 - different_values: 50 + values: 516 + different_values: 55 variants: [omitted] [source_group]: type: array path: background.source_group is_null: true - values: 137 + values: 169 min_count: 1 max_count: 1 value_type: type: string path: background.source_group.[] is_null: false - values: 137 - different_values: 19 - variants: - - "Pathfinder Society" (x20) - - "Age of Ashes" (x14) - - "Agents of Edgewatch" (x11) - - "Extinction Curse" (x10) - - "Abomination Vaults" (x7) - - "Kingmaker" (x7) - - "Stolen Fate" (x7) - - "Rusthenge" (x7) - - "Fists of the Ruby Phoenix" (x6) - - "Quest for the Frozen Flame" (x6) - - "Outlaws of Alkenstar" (x6) - - "Blood Lords" (x6) - - "Gatewalkers" (x6) - - "Sky King's Tomb" (x6) - - "Season of Ghosts" (x6) - - "The Fall of Plaguestone" (x5) - - "Strength of Thousands" (x5) - - "Little Trouble in Big Absalom" (x1) - - "Crown of the Kobold King" (x1) + values: 169 + different_values: 24 + variants: [omitted] [spoilers]: type: string path: background.spoilers is_null: true - values: 67 - different_values: 14 + values: 99 + different_values: 19 variants: + - "Curtain Call" (x8) - "Kingmaker" (x7) - "Pathfinder Society" (x7) - "Stolen Fate" (x7) @@ -345,6 +308,10 @@ schema: - "Blood Lords" (x6) - "Gatewalkers" (x6) - "Season of Ghosts" (x6) + - "Wardens of Wildwood" (x6) + - "Triumph of the Tusk" (x6) + - "Spore War" (x6) + - "Shades of Blood" (x6) - "Age of Ashes" (x4) - "Fists of the Ruby Phoenix" (x2) - "Extinction Curse" (x1) @@ -354,17 +321,17 @@ schema: type: array path: background.trait is_null: true - values: 156 + values: 188 min_count: 1 max_count: 2 value_type: type: string path: background.trait.[] is_null: false - values: 163 + values: 195 different_values: 4 variants: - - "Rare" (x104) - - "Uncommon" (x52) + - "Rare" (x124) + - "Uncommon" (x64) - "Legacy" (x4) - "Pervasive Magic" (x3) diff --git a/output/aonprd/class-feature.yaml b/output/aonprd/class-feature.yaml index 2113f41..547cc7f 100644 --- a/output/aonprd/class-feature.yaml +++ b/output/aonprd/class-feature.yaml @@ -77,7 +77,7 @@ schema: path: class-feature.summary_markdown is_null: false values: 1084 - different_values: 640 + different_values: 639 variants: [omitted] 'type': type: string diff --git a/output/aonprd/creature-family.yaml b/output/aonprd/creature-family.yaml index d887682..73d1eab 100644 --- a/output/aonprd/creature-family.yaml +++ b/output/aonprd/creature-family.yaml @@ -1,91 +1,105 @@ name: creature-family -values: 516 +values: 527 schema: 'category': type: string path: creature-family.category is_null: false - values: 516 + values: 527 different_values: 1 (all same) - variants: "creature-family" (x516) + variants: "creature-family" (x527) 'creature_family': type: string path: creature-family.creature_family is_null: false - values: 516 - different_values: 409 + values: 527 + different_values: 420 variants: [omitted] 'creature_family_markdown': type: string path: creature-family.creature_family_markdown is_null: false - values: 516 - different_values: 409 + values: 527 + different_values: 420 variants: [omitted] 'id': type: string path: creature-family.id is_null: false - values: 516 - different_values: 516 (all different) + values: 527 + different_values: 527 (all different) variants: [omitted] + 'source': + type: array + path: creature-family.source + is_null: false + values: 527 + min_count: 1 + max_count: 1 + value_type: + type: string + path: creature-family.source.[] + is_null: false + values: 527 + different_values: 64 + variants: [omitted] 'source_category': type: array path: creature-family.source_category is_null: false - values: 516 + values: 527 min_count: 1 max_count: 1 value_type: type: string path: creature-family.source_category.[] is_null: false - values: 516 + values: 527 different_values: 4 variants: - "Rulebooks" (x432) - - "Adventure Paths" (x62) + - "Adventure Paths" (x73) - "Lost Omens" (x17) - "Adventures" (x5) 'source_markdown': type: string path: creature-family.source_markdown is_null: false - values: 516 - different_values: 512 + values: 527 + different_values: 522 variants: [omitted] 'speed': type: object path: creature-family.speed is_null: false - values: 516 + values: 527 schema: 'summary_markdown': type: string path: creature-family.summary_markdown is_null: false - values: 516 - different_values: 469 + values: 527 + different_values: 480 variants: [omitted] 'type': type: string path: creature-family.type is_null: false - values: 516 + values: 527 different_values: 1 (all same) - variants: "Creature Family" (x516) + variants: "Creature Family" (x527) 'url': type: string path: creature-family.url is_null: false - values: 516 - different_values: 516 (all different) + values: 527 + different_values: 527 (all different) variants: [omitted] 'weakness': type: object path: creature-family.weakness is_null: false - values: 516 + values: 527 schema: [image]: type: array @@ -105,159 +119,107 @@ schema: type: array path: creature-family.legacy_id is_null: true - values: 127 + values: 128 min_count: 1 max_count: 1 value_type: type: string path: creature-family.legacy_id.[] is_null: false - values: 127 - different_values: 127 + values: 128 + different_values: 128 variants: [omitted] 'markdown': type: string path: creature-family.markdown is_null: false - values: 516 - different_values: 516 (all different) + values: 527 + different_values: 527 (all different) variants: [omitted] 'name': type: string path: creature-family.name is_null: false - values: 516 - different_values: 409 + values: 527 + different_values: 420 variants: [omitted] 'primary_source_category': type: string path: creature-family.primary_source_category is_null: false - values: 516 + values: 527 different_values: 4 variants: - "Rulebooks" (x432) - - "Adventure Paths" (x62) + - "Adventure Paths" (x73) - "Lost Omens" (x17) - "Adventures" (x5) 'rarity': type: string path: creature-family.rarity is_null: false - values: 516 + values: 527 different_values: 1 (all same) - variants: "common" (x516) + variants: "common" (x527) 'rarity_id': type: integer path: creature-family.rarity_id is_null: false - values: 516 + values: 527 different_values: 1 (all same) - variants: 1 (x516) + variants: 1 (x527) 'release_date': type: string path: creature-family.release_date is_null: false - values: 516 - different_values: 45 + values: 527 + different_values: 52 variants: [omitted] - 'resistance': - type: object - path: creature-family.resistance - is_null: false - values: 516 - schema: [primary_source_group]: type: string path: creature-family.primary_source_group is_null: true - values: 67 - different_values: 19 - variants: - - "Strength of Thousands" (x11) - - "Extinction Curse" (x9) - - "Abomination Vaults" (x6) - - "Blood Lords" (x5) - - "Age of Ashes" (x5) - - "Kingmaker" (x4) - - "Gatewalkers" (x4) - - "Stolen Fate" (x4) - - "Quest for the Frozen Flame" (x3) - - "Sky King's Tomb" (x3) - - "Season of Ghosts" (x3) - - "Fists of the Ruby Phoenix" (x2) - - "Seven Dooms for Sandpoint" (x2) - - "Threshold of Knowledge" (x1) - - "Shadows at Sundown" (x1) - - "Rusthenge" (x1) - - "Prey for Death" (x1) - - "The Fall of Plaguestone" (x1) - - "Agents of Edgewatch" (x1) + values: 78 + different_values: 23 + variants: [omitted] [remaster_id]: type: array path: creature-family.remaster_id is_null: true - values: 127 + values: 128 min_count: 1 max_count: 1 value_type: type: string path: creature-family.remaster_id.[] is_null: false - values: 127 - different_values: 127 + values: 128 + different_values: 128 variants: [omitted] + 'resistance': + type: object + path: creature-family.resistance + is_null: false + values: 527 + schema: 'search_markdown': type: string path: creature-family.search_markdown is_null: false - values: 516 - different_values: 516 (all different) + values: 527 + different_values: 527 (all different) variants: [omitted] - 'source': - type: array - path: creature-family.source - is_null: false - values: 516 - min_count: 1 - max_count: 1 - value_type: - type: string - path: creature-family.source.[] - is_null: false - values: 516 - different_values: 57 - variants: [omitted] [source_group]: type: array path: creature-family.source_group is_null: true - values: 67 + values: 78 min_count: 1 max_count: 1 value_type: type: string path: creature-family.source_group.[] is_null: false - values: 67 - different_values: 19 - variants: - - "Strength of Thousands" (x11) - - "Extinction Curse" (x9) - - "Abomination Vaults" (x6) - - "Blood Lords" (x5) - - "Age of Ashes" (x5) - - "Kingmaker" (x4) - - "Gatewalkers" (x4) - - "Stolen Fate" (x4) - - "Quest for the Frozen Flame" (x3) - - "Sky King's Tomb" (x3) - - "Season of Ghosts" (x3) - - "Fists of the Ruby Phoenix" (x2) - - "Seven Dooms for Sandpoint" (x2) - - "Threshold of Knowledge" (x1) - - "Shadows at Sundown" (x1) - - "Rusthenge" (x1) - - "Prey for Death" (x1) - - "The Fall of Plaguestone" (x1) - - "Agents of Edgewatch" (x1) + values: 78 + different_values: 23 + variants: [omitted] diff --git a/output/aonprd/creature.yaml b/output/aonprd/creature.yaml index 387ad56..1d34b2d 100644 --- a/output/aonprd/creature.yaml +++ b/output/aonprd/creature.yaml @@ -1,54 +1,73 @@ name: creature -values: 3647 +values: 3761 schema: 'ac': type: integer path: creature.ac is_null: false - values: 3647 + values: 3761 different_values: 47 variants: [omitted] 'ac_scale_number': type: integer path: creature.ac_scale_number is_null: false - values: 3647 + values: 3761 different_values: 5 variants: - - 4 (x1973) - - 3 (x1547) - - 2 (x84) + - 4 (x2015) + - 3 (x1615) + - 2 (x86) - 5 (x38) - - 0 (x5) + - 0 (x7) + [attack_bonus_scale_number]: + type: array + path: creature.attack_bonus_scale_number + is_null: true + values: 3619 + min_count: 1 + max_count: 3 + value_type: + type: integer + path: creature.attack_bonus_scale_number.[] + is_null: false + values: 4719 + different_values: 5 + variants: + - 3 (x1983) + - 4 (x1872) + - 2 (x778) + - 5 (x79) + - 0 (x7) 'category': type: string path: creature.category is_null: false - values: 3647 + values: 3761 different_values: 1 (all same) - variants: "creature" (x3647) + variants: "creature" (x3761) 'charisma': type: integer path: creature.charisma is_null: false - values: 3647 + values: 3761 different_values: 18 variants: - - 0 (x629) - - 4 (x430) - - 2 (x408) - - 3 (x380) - - 1 (x361) - - 5 (x281) - - 6 (x210) - - -1 (x206) - - -2 (x200) - - -5 (x163) - - 7 (x111) - - 8 (x91) - - -4 (x60) - - 9 (x50) - - -3 (x43) + - 0 (x643) + - 4 (x445) + - 2 (x426) + - 3 (x389) + - 1 (x375) + - 5 (x289) + - 6 (x225) + - -1 (x210) + - -2 (x203) + - -5 (x165) + - 7 (x112) + - 8 (x95) + - -4 (x62) + - 9 (x53) + - -3 (x45) - 10 (x18) - 11 (x4) - 12 (x2) @@ -56,75 +75,82 @@ schema: type: integer path: creature.charisma_scale_number is_null: false - values: 3647 + values: 3761 different_values: 5 variants: - - 2 (x2584) - - 3 (x740) - - 4 (x300) + - 2 (x2662) + - 3 (x764) + - 4 (x310) - 5 (x18) - - 0 (x5) + - 0 (x7) 'constitution': type: integer path: creature.constitution is_null: false - values: 3647 + values: 3761 different_values: 15 variants: - - 4 (x682) - - 3 (x607) - - 2 (x593) - - 5 (x411) - - 1 (x394) - - 6 (x307) - - 0 (x303) - - 7 (x147) - - 8 (x84) - - 9 (x67) + - 4 (x700) + - 3 (x627) + - 2 (x597) + - 5 (x442) + - 1 (x398) + - 6 (x325) + - 0 (x310) + - 7 (x151) + - 8 (x87) + - 9 (x68) + - 10 (x21) - -1 (x20) - - 10 (x18) - - 11 (x5) + - 11 (x6) - -2 (x5) - 12 (x4) 'constitution_scale_number': type: integer path: creature.constitution_scale_number is_null: false - values: 3647 + values: 3761 different_values: 5 variants: - - 2 (x1816) - - 3 (x1415) - - 4 (x368) + - 2 (x1853) + - 3 (x1474) + - 4 (x384) - 5 (x43) - - 0 (x5) + - 0 (x7) + [creature_family]: + type: string + path: creature.creature_family + is_null: true + values: 2103 + different_values: 414 + variants: [omitted] 'creature_family_markdown': type: string path: creature.creature_family_markdown is_null: false - values: 3647 - different_values: 509 + values: 3761 + different_values: 520 variants: [omitted] 'dexterity': type: integer path: creature.dexterity is_null: false - values: 3647 + values: 3761 different_values: 18 variants: - - 4 (x884) - - 3 (x709) - - 2 (x516) - - 5 (x418) - - 1 (x289) - - 6 (x280) - - 0 (x147) - - 7 (x126) - - 8 (x82) + - 4 (x901) + - 3 (x727) + - 2 (x532) + - 5 (x438) + - 6 (x299) + - 1 (x293) + - 0 (x150) + - 7 (x137) + - 8 (x83) - -1 (x67) - - 9 (x48) + - 9 (x52) - -2 (x24) - - -5 (x19) + - -5 (x20) - 10 (x15) - -3 (x13) - -4 (x4) @@ -134,407 +160,87 @@ schema: type: integer path: creature.dexterity_scale_number is_null: false - values: 3647 + values: 3761 different_values: 5 variants: - - 2 (x1731) - - 3 (x1188) - - 4 (x707) - - 5 (x16) - - 0 (x5) - [element]: - type: array - path: creature.element - is_null: true - values: 366 - min_count: 1 - max_count: 6 - value_type: - type: string - path: creature.element.[] - is_null: false - values: 392 - different_values: 6 - variants: - - "Fire" (x113) - - "Earth" (x95) - - "Water" (x73) - - "Air" (x59) - - "Wood" (x31) - - "Metal" (x21) - 'fortitude_save': - type: integer - path: creature.fortitude_save - is_null: false - values: 3647 - different_values: 48 - variants: [omitted] - 'fortitude_save_scale_number': - type: integer - path: creature.fortitude_save_scale_number - is_null: false - values: 3647 - different_values: 6 - variants: - - 3 (x1555) - - 2 (x1233) - - 4 (x653) - - 1 (x150) - - 5 (x51) - - 0 (x5) - 'hp_raw': - type: string - path: creature.hp_raw - is_null: false - values: 3647 - different_values: 579 - variants: [omitted] - 'id': - type: string - path: creature.id - is_null: false - values: 3647 - different_values: 3647 (all different) - variants: [omitted] - [hardness_raw]: - type: string - path: creature.hardness_raw - is_null: true - values: 32 - different_values: 13 - variants: - - "10" (x6) - - "8" (x6) - - "9" (x3) - - "4" (x3) - - "2" (x2) - - "6" (x2) - - "3" (x2) - - "14" (x2) - - "7" (x2) - - "5" (x1) - - "15" (x1) - - "11" (x1) - - "1" (x1) - 'hp_scale_number': - type: integer - path: creature.hp_scale_number - is_null: false - values: 3647 - different_values: 4 - variants: - - 3 (x1956) - - 2 (x1190) - - 4 (x496) - - 0 (x5) - [image]: - type: array - path: creature.image - is_null: true - values: 2089 - min_count: 1 - max_count: 1 - value_type: - type: string - path: creature.image.[] - is_null: false - values: 2089 - different_values: 2048 - variants: [omitted] - [immunity_markdown]: - type: string - path: creature.immunity_markdown - is_null: true - values: 1568 - different_values: 531 - variants: [omitted] - 'intelligence': - type: integer - path: creature.intelligence - is_null: false - values: 3647 - different_values: 17 - variants: - - 0 (x624) - - 2 (x406) - - 1 (x388) - - -4 (x383) - - -5 (x366) - - 4 (x277) - - 3 (x264) - - -1 (x228) - - 5 (x176) - - -2 (x175) - - -3 (x122) - - 6 (x105) - - 7 (x66) - - 8 (x38) - - 10 (x14) - - 9 (x13) - - 11 (x2) - [creature_ability]: - type: array - path: creature.creature_ability - is_null: true - values: 3602 - min_count: 1 - max_count: 14 - value_type: - type: string - path: creature.creature_ability.[] - is_null: false - values: 14347 - different_values: 6801 - variants: [omitted] - [creature_family]: - type: string - path: creature.creature_family - is_null: true - values: 2058 - different_values: 403 - variants: [omitted] - 'intelligence_scale_number': - type: integer - path: creature.intelligence_scale_number - is_null: false - values: 3647 - different_values: 5 - variants: - - 2 (x3124) - - 3 (x362) - - 4 (x153) - - 0 (x5) - - 5 (x3) - 'language_markdown': - type: string - path: creature.language_markdown - is_null: false - values: 3647 - different_values: 1142 - variants: [omitted] - [alignment]: - type: string - path: creature.alignment - is_null: true - values: 2714 - different_values: 11 - variants: - - "N" (x816) - - "CE" (x568) - - "NE" (x544) - - "LE" (x275) - - "CN" (x181) - - "LN" (x122) - - "CG" (x80) - - "NG" (x72) - - "LG" (x47) - - "No Alignment" (x6) - - "Any" (x3) - 'source_markdown': - type: string - path: creature.source_markdown - is_null: false - values: 3647 - different_values: 2984 - variants: [omitted] - [attack_bonus_scale_number]: - type: array - path: creature.attack_bonus_scale_number - is_null: true - values: 3513 - min_count: 1 - max_count: 3 - value_type: - type: integer - path: creature.attack_bonus_scale_number.[] - is_null: false - values: 4584 - different_values: 5 - variants: - - 3 (x1925) - - 4 (x1812) - - 2 (x766) - - 5 (x76) - - 0 (x5) - 'speed': - type: object - path: creature.speed - is_null: false - values: 3647 - schema: - [burrow]: - type: integer - path: creature.speed.burrow - is_null: true - values: 193 - different_values: 11 - variants: - - 20 (x45) - - 25 (x27) - - 40 (x24) - - 10 (x23) - - 30 (x21) - - 5 (x18) - - 15 (x15) - - 35 (x7) - - 60 (x5) - - 45 (x4) - - 50 (x4) - [climb]: - type: integer - path: creature.speed.climb - is_null: true - values: 451 - different_values: 11 - variants: - - 20 (x150) - - 30 (x89) - - 25 (x87) - - 10 (x43) - - 15 (x30) - - 40 (x26) - - 35 (x12) - - 50 (x6) - - 60 (x5) - - 5 (x2) - - 45 (x1) - [fly]: - type: integer - path: creature.speed.fly - is_null: true - values: 847 - different_values: 25 - variants: [omitted] - [land]: - type: integer - path: creature.speed.land - is_null: true - values: 3437 - different_values: 16 - variants: - - 25 (x1472) - - 30 (x548) - - 20 (x458) - - 40 (x329) - - 35 (x212) - - 10 (x99) - - 15 (x98) - - 50 (x88) - - 5 (x46) - - 60 (x46) - - 45 (x23) - - 70 (x7) - - 80 (x5) - - 100 (x3) - - 0 (x2) - - 120 (x1) - [max]: - type: integer - path: creature.speed.max - is_null: true - values: 3642 - different_values: 26 - variants: [omitted] - [swim]: - type: integer - path: creature.speed.swim - is_null: true - values: 470 - different_values: 13 - variants: - - 30 (x97) - - 40 (x77) - - 25 (x75) - - 20 (x66) - - 50 (x38) - - 60 (x37) - - 35 (x26) - - 15 (x20) - - 80 (x18) - - 10 (x6) - - 100 (x6) - - 70 (x3) - - 45 (x1) - 'spell_markdown': - type: string - path: creature.spell_markdown - is_null: false - values: 3647 - different_values: 1319 - variants: [omitted] + - 2 (x1780) + - 3 (x1232) + - 4 (x722) + - 5 (x20) + - 0 (x7) 'strength': type: integer path: creature.strength is_null: false - values: 3647 + values: 3761 different_values: 18 variants: - - 4 (x572) - - 6 (x415) - - 5 (x406) - - 3 (x382) - - 2 (x371) - - 7 (x289) - - 0 (x262) + - 4 (x591) + - 6 (x434) + - 5 (x416) + - 3 (x391) + - 2 (x374) + - 7 (x307) + - 0 (x268) + - 8 (x239) - 1 (x237) - - 8 (x228) - - 9 (x140) - - -5 (x108) - - 10 (x89) + - 9 (x141) + - -5 (x120) + - 10 (x92) - -1 (x59) - -2 (x44) - -3 (x16) - - 12 (x12) + - 12 (x13) - -4 (x10) - - 11 (x7) + - 11 (x9) 'strength_scale_number': type: integer path: creature.strength_scale_number is_null: false - values: 3647 + values: 3761 different_values: 5 variants: - - 2 (x1339) - - 4 (x1039) - - 3 (x1001) - - 5 (x263) - - 0 (x5) + - 2 (x1369) + - 4 (x1078) + - 3 (x1035) + - 5 (x272) + - 0 (x7) 'strongest_save': type: array path: creature.strongest_save is_null: false - values: 3647 + values: 3761 min_count: 1 max_count: 5 value_type: type: string path: creature.strongest_save.[] is_null: false - values: 6878 + values: 7089 different_values: 5 variants: - - "fort" (x1558) - - "fortitude" (x1558) - - "ref" (x1453) - - "reflex" (x1453) - - "will" (x856) + - "fort" (x1615) + - "fortitude" (x1615) + - "ref" (x1492) + - "reflex" (x1492) + - "will" (x875) 'tradition_markdown': type: string path: creature.tradition_markdown is_null: false - values: 3647 + values: 3761 different_values: 15 variants: - - "" (x2172) - - "[Divine](/SpellLists.aspx?Tradition=2)" (x465) - - "[Occult](/SpellLists.aspx?Tradition=3)" (x384) - - "[Primal](/SpellLists.aspx?Tradition=4)" (x351) + - "" (x2234) + - "[Divine](/SpellLists.aspx?Tradition=2)" (x477) + - "[Occult](/SpellLists.aspx?Tradition=3)" (x396) + - "[Primal](/SpellLists.aspx?Tradition=4)" (x377) - "[Arcane](/SpellLists.aspx?Tradition=1)" (x237) - "[Divine](/SpellLists.aspx?Tradition=2), [Occult](/SpellLists.aspx?Tradition=3)" (x11) - "[Arcane](/SpellLists.aspx?Tradition=1), [Divine](/SpellLists.aspx?Tradition=2)" (x7) - "[Arcane](/SpellLists.aspx?Tradition=1), [Occult](/SpellLists.aspx?Tradition=3)" (x6) - - "[Occult](/SpellLists.aspx?Tradition=3), [Primal](/SpellLists.aspx?Tradition=4)" (x4) - - "[Arcane](/SpellLists.aspx?Tradition=1), [Primal](/SpellLists.aspx?Tradition=4)" (x2) + - "[Occult](/SpellLists.aspx?Tradition=3), [Primal](/SpellLists.aspx?Tradition=4)" (x5) + - "[Arcane](/SpellLists.aspx?Tradition=1), [Primal](/SpellLists.aspx?Tradition=4)" (x3) - "[Primal](/SpellLists.aspx?Tradition=4), [Occult](/SpellLists.aspx?Tradition=3)" (x2) - "[Occult](/SpellLists.aspx?Tradition=3), [Divine](/SpellLists.aspx?Tradition=2)" (x2) - "[Occult](/SpellLists.aspx?Tradition=3), [Arcane](/SpellLists.aspx?Tradition=1)" (x2) @@ -544,130 +250,61 @@ schema: type: array path: creature.trait is_null: false - values: 3647 + values: 3761 min_count: 2 max_count: 10 value_type: type: string path: creature.trait.[] is_null: false - values: 15585 - different_values: 227 + values: 16059 + different_values: 229 variants: [omitted] 'trait_markdown': type: string path: creature.trait_markdown is_null: false - values: 3647 - different_values: 1131 + values: 3761 + different_values: 1192 variants: [omitted] 'type': type: string path: creature.type is_null: false - values: 3647 + values: 3761 different_values: 1 (all same) - variants: "Creature" (x3647) + variants: "Creature" (x3761) 'url': type: string path: creature.url is_null: false - values: 3647 - different_values: 3647 (all different) + values: 3761 + different_values: 3761 (all different) variants: [omitted] 'weakest_save': type: array path: creature.weakest_save is_null: false - values: 3647 + values: 3761 min_count: 1 max_count: 5 value_type: type: string path: creature.weakest_save.[] is_null: false - values: 6413 + values: 6609 different_values: 5 variants: - - "will" (x1611) - - "ref" (x1227) - - "reflex" (x1227) - - "fort" (x1174) - - "fortitude" (x1174) - 'will_save': - type: integer - path: creature.will_save - is_null: false - values: 3647 - different_values: 45 - variants: [omitted] - 'will_save_scale_number': - type: integer - path: creature.will_save_scale_number - is_null: false - values: 3647 - different_values: 6 - variants: - - 2 (x1715) - - 3 (x1290) - - 4 (x346) - - 1 (x253) - - 5 (x38) - - 0 (x5) - 'wisdom': - type: integer - path: creature.wisdom - is_null: false - values: 3647 - different_values: 16 - variants: - - 2 (x825) - - 3 (x658) - - 4 (x511) - - 1 (x503) - - 0 (x351) - - 5 (x340) - - 6 (x237) - - 7 (x83) - - 8 (x52) - - -1 (x44) - - 9 (x18) - - 10 (x12) - - -2 (x10) - - -5 (x1) - - 11 (x1) - - 12 (x1) - 'wisdom_scale_number': - type: integer - path: creature.wisdom_scale_number - is_null: false - values: 3647 - different_values: 5 - variants: - - 2 (x2448) - - 3 (x996) - - 4 (x195) - - 0 (x5) - - 5 (x3) - [attack_bonus]: - type: array - path: creature.attack_bonus - is_null: true - values: 3513 - min_count: 1 - max_count: 6 - value_type: - type: integer - path: creature.attack_bonus.[] - is_null: false - values: 7344 - different_values: 49 - variants: [omitted] + - "will" (x1655) + - "ref" (x1273) + - "reflex" (x1273) + - "fort" (x1204) + - "fortitude" (x1204) 'weakness': type: object path: creature.weakness is_null: false - values: 3647 + values: 3761 schema: [acid]: type: integer @@ -683,18 +320,18 @@ schema: type: integer path: creature.weakness.area is_null: true - values: 128 + values: 137 different_values: 15 variants: - - 5 (x39) - - 10 (x24) - - 15 (x17) + - 5 (x40) + - 10 (x31) + - 15 (x15) - 3 (x14) - - 8 (x10) - - 20 (x8) + - 8 (x11) + - 20 (x9) - 4 (x4) + - 7 (x3) - 12 (x3) - - 7 (x2) - 6 (x2) - 9 (x1) - 2 (x1) @@ -705,9 +342,11 @@ schema: type: integer path: creature.weakness.bleed is_null: true - values: 1 - different_values: 1 (all same) - variants: 10 (x1) + values: 2 + different_values: 2 + variants: + - 10 (x1) + - 5 (x1) [bludgeoning]: type: integer path: creature.weakness.bludgeoning @@ -732,18 +371,18 @@ schema: - 10 (x4) - 15 (x3) - 20 (x2) - - 5 (x1) - 3 (x1) + - 5 (x1) - 25 (x1) [cold]: type: integer path: creature.weakness.cold is_null: true - values: 102 - different_values: 11 + values: 103 + different_values: 10 variants: - - 10 (x36) - - 15 (x26) + - 10 (x37) + - 15 (x27) - 5 (x21) - 20 (x9) - 3 (x4) @@ -751,26 +390,26 @@ schema: - 8 (x1) - 2 (x1) - 25 (x1) - - 12 (x1) - 6 (x1) [cold_iron]: type: integer path: creature.weakness.cold_iron is_null: true - values: 271 - different_values: 11 + values: 286 + different_values: 12 variants: - - 5 (x72) - - 10 (x71) - - 15 (x59) + - 5 (x77) + - 10 (x77) + - 15 (x61) + - 20 (x21) - 2 (x21) - - 20 (x20) - 3 (x19) - 4 (x4) - 6 (x2) - 18 (x1) - 8 (x1) - 1 (x1) + - 12 (x1) [electricity]: type: integer path: creature.weakness.electricity @@ -804,18 +443,19 @@ schema: type: integer path: creature.weakness.fire is_null: true - values: 192 - different_values: 9 + values: 219 + different_values: 10 variants: - - 10 (x76) - - 5 (x52) - - 15 (x31) + - 10 (x82) + - 5 (x59) + - 15 (x38) - 3 (x11) - - 20 (x9) + - 20 (x11) - 2 (x8) + - 8 (x4) - 4 (x3) - - 8 (x1) - - 7 (x1) + - 7 (x2) + - 12 (x1) [force]: type: integer path: creature.weakness.force @@ -845,14 +485,14 @@ schema: type: integer path: creature.weakness.holy is_null: true - values: 36 + values: 51 different_values: 5 variants: - - 10 (x10) - - 15 (x10) - - 5 (x9) + - 10 (x16) + - 15 (x14) + - 5 (x11) - 3 (x5) - - 20 (x2) + - 20 (x5) [lawful]: type: integer path: creature.weakness.lawful @@ -869,8 +509,8 @@ schema: type: integer path: creature.weakness.mental is_null: true - values: 11 - different_values: 6 + values: 12 + different_values: 7 variants: - 10 (x5) - 15 (x2) @@ -878,6 +518,7 @@ schema: - 5 (x1) - 20 (x1) - 2 (x1) + - 7 (x1) [orichalcum]: type: integer path: creature.weakness.orichalcum @@ -925,23 +566,25 @@ schema: type: integer path: creature.weakness.silver is_null: true - values: 50 - different_values: 4 + values: 52 + different_values: 6 variants: - 5 (x23) - 15 (x13) - 10 (x9) - 20 (x5) + - 8 (x1) + - 7 (x1) [slashing]: type: integer path: creature.weakness.slashing is_null: true - values: 82 + values: 88 different_values: 7 variants: - - 5 (x33) - - 10 (x33) - - 15 (x8) + - 10 (x34) + - 5 (x34) + - 15 (x12) - 2 (x3) - 8 (x2) - 3 (x2) @@ -962,12 +605,12 @@ schema: type: integer path: creature.weakness.spirit is_null: true - values: 5 + values: 6 different_values: 3 variants: + - 10 (x2) - 20 (x2) - 15 (x2) - - 10 (x1) [splash]: type: integer path: creature.weakness.splash @@ -1000,12 +643,12 @@ schema: type: integer path: creature.weakness.vitality is_null: true - values: 65 + values: 66 different_values: 10 variants: - 10 (x28) - 5 (x21) - - 15 (x8) + - 15 (x9) - 3 (x2) - 7 (x1) - 12 (x1) @@ -1024,94 +667,418 @@ schema: - 10 (x1) - 15 (x1) - 3 (x1) + 'will_save': + type: integer + path: creature.will_save + is_null: false + values: 3761 + different_values: 45 + variants: [omitted] + 'will_save_scale_number': + type: integer + path: creature.will_save_scale_number + is_null: false + values: 3761 + different_values: 6 + variants: + - 2 (x1758) + - 3 (x1335) + - 4 (x363) + - 1 (x260) + - 5 (x38) + - 0 (x7) + [alignment]: + type: string + path: creature.alignment + is_null: true + values: 2714 + different_values: 11 + variants: + - "N" (x816) + - "CE" (x568) + - "NE" (x544) + - "LE" (x275) + - "CN" (x181) + - "LN" (x122) + - "CG" (x80) + - "NG" (x72) + - "LG" (x47) + - "No Alignment" (x6) + - "Any" (x3) + [attack_bonus]: + type: array + path: creature.attack_bonus + is_null: true + values: 3619 + min_count: 1 + max_count: 6 + value_type: + type: integer + path: creature.attack_bonus.[] + is_null: false + values: 7550 + different_values: 49 + variants: [omitted] + [creature_ability]: + type: array + path: creature.creature_ability + is_null: true + values: 3716 + min_count: 1 + max_count: 14 + value_type: + type: string + path: creature.creature_ability.[] + is_null: false + values: 14844 + different_values: 7112 + variants: [omitted] + 'wisdom': + type: integer + path: creature.wisdom + is_null: false + values: 3761 + different_values: 16 + variants: + - 2 (x850) + - 3 (x681) + - 4 (x536) + - 1 (x504) + - 0 (x354) + - 5 (x353) + - 6 (x248) + - 7 (x88) + - 8 (x57) + - -1 (x44) + - 9 (x19) + - 10 (x14) + - -2 (x10) + - -5 (x1) + - 11 (x1) + - 12 (x1) + [element]: + type: array + path: creature.element + is_null: true + values: 381 + min_count: 1 + max_count: 6 + value_type: + type: string + path: creature.element.[] + is_null: false + values: 407 + different_values: 6 + variants: + - "Fire" (x113) + - "Earth" (x96) + - "Water" (x75) + - "Air" (x59) + - "Wood" (x43) + - "Metal" (x21) + 'fortitude_save': + type: integer + path: creature.fortitude_save + is_null: false + values: 3761 + different_values: 48 + variants: [omitted] + 'fortitude_save_scale_number': + type: integer + path: creature.fortitude_save_scale_number + is_null: false + values: 3761 + different_values: 6 + variants: + - 3 (x1594) + - 2 (x1263) + - 4 (x690) + - 1 (x154) + - 5 (x53) + - 0 (x7) + [hardness_raw]: + type: string + path: creature.hardness_raw + is_null: true + values: 32 + different_values: 13 + variants: + - "10" (x6) + - "8" (x6) + - "9" (x3) + - "4" (x3) + - "2" (x2) + - "6" (x2) + - "3" (x2) + - "14" (x2) + - "7" (x2) + - "5" (x1) + - "15" (x1) + - "11" (x1) + - "1" (x1) + 'hp_raw': + type: string + path: creature.hp_raw + is_null: false + values: 3761 + different_values: 600 + variants: [omitted] + 'hp_scale_number': + type: integer + path: creature.hp_scale_number + is_null: false + values: 3761 + different_values: 4 + variants: + - 3 (x2014) + - 2 (x1223) + - 4 (x517) + - 0 (x7) + 'id': + type: string + path: creature.id + is_null: false + values: 3761 + different_values: 3761 (all different) + variants: [omitted] + [image]: + type: array + path: creature.image + is_null: true + values: 2089 + min_count: 1 + max_count: 1 + value_type: + type: string + path: creature.image.[] + is_null: false + values: 2089 + different_values: 2048 + variants: [omitted] + 'intelligence': + type: integer + path: creature.intelligence + is_null: false + values: 3761 + different_values: 17 + variants: + - 0 (x633) + - 2 (x421) + - 1 (x404) + - -4 (x396) + - -5 (x375) + - 4 (x290) + - 3 (x274) + - -1 (x233) + - 5 (x187) + - -2 (x182) + - -3 (x124) + - 6 (x106) + - 7 (x68) + - 8 (x38) + - 9 (x14) + - 10 (x14) + - 11 (x2) + 'intelligence_scale_number': + type: integer + path: creature.intelligence_scale_number + is_null: false + values: 3761 + different_values: 5 + variants: + - 2 (x3221) + - 3 (x376) + - 4 (x154) + - 0 (x7) + - 5 (x3) + 'language_markdown': + type: string + path: creature.language_markdown + is_null: false + values: 3761 + different_values: 1185 + variants: [omitted] + 'spell_markdown': + type: string + path: creature.spell_markdown + is_null: false + values: 3761 + different_values: 1369 + variants: [omitted] + [immunity_markdown]: + type: string + path: creature.immunity_markdown + is_null: true + values: 1622 + different_values: 556 + variants: [omitted] + 'speed': + type: object + path: creature.speed + is_null: false + values: 3761 + schema: + [burrow]: + type: integer + path: creature.speed.burrow + is_null: true + values: 199 + different_values: 11 + variants: + - 20 (x46) + - 25 (x27) + - 40 (x24) + - 10 (x24) + - 30 (x22) + - 5 (x18) + - 15 (x17) + - 35 (x8) + - 60 (x5) + - 45 (x4) + - 50 (x4) + [climb]: + type: integer + path: creature.speed.climb + is_null: true + values: 468 + different_values: 11 + variants: + - 20 (x154) + - 30 (x96) + - 25 (x88) + - 10 (x44) + - 40 (x30) + - 15 (x30) + - 35 (x12) + - 50 (x6) + - 60 (x5) + - 5 (x2) + - 45 (x1) + [fly]: + type: integer + path: creature.speed.fly + is_null: true + values: 880 + different_values: 25 + variants: [omitted] + [land]: + type: integer + path: creature.speed.land + is_null: true + values: 3540 + different_values: 16 + variants: + - 25 (x1517) + - 30 (x564) + - 20 (x471) + - 40 (x344) + - 35 (x216) + - 15 (x102) + - 10 (x101) + - 50 (x89) + - 5 (x48) + - 60 (x46) + - 45 (x24) + - 70 (x7) + - 80 (x5) + - 100 (x3) + - 0 (x2) + - 120 (x1) + [max]: + type: integer + path: creature.speed.max + is_null: true + values: 3756 + different_values: 26 + variants: [omitted] + [swim]: + type: integer + path: creature.speed.swim + is_null: true + values: 481 + different_values: 14 + variants: + - 30 (x99) + - 40 (x77) + - 25 (x77) + - 20 (x72) + - 50 (x38) + - 60 (x37) + - 35 (x26) + - 15 (x20) + - 80 (x18) + - 100 (x6) + - 10 (x6) + - 70 (x3) + - 45 (x1) + - 120 (x1) + 'wisdom_scale_number': + type: integer + path: creature.wisdom_scale_number + is_null: false + values: 3761 + different_values: 5 + variants: + - 2 (x2515) + - 3 (x1028) + - 4 (x208) + - 0 (x7) + - 5 (x3) [legacy_id]: type: array path: creature.legacy_id is_null: true - values: 463 + values: 473 min_count: 1 max_count: 1 value_type: type: string path: creature.legacy_id.[] is_null: false - values: 463 - different_values: 463 + values: 473 + different_values: 473 variants: [omitted] 'level': type: integer path: creature.level is_null: false - values: 3647 - different_values: 27 + values: 3761 + different_values: 29 variants: [omitted] 'markdown': type: string path: creature.markdown is_null: false - values: 3647 - different_values: 3647 (all different) + values: 3761 + different_values: 3761 (all different) variants: [omitted] 'name': type: string path: creature.name is_null: false - values: 3647 - different_values: 3197 + values: 3761 + different_values: 3310 variants: [omitted] 'perception': type: integer path: creature.perception is_null: false - values: 3647 + values: 3761 different_values: 46 variants: [omitted] 'perception_scale_number': type: integer path: creature.perception_scale_number is_null: false - values: 3647 + values: 3761 different_values: 6 variants: - - 3 (x1967) - - 2 (x1074) - - 4 (x427) - - 1 (x142) + - 3 (x2027) + - 2 (x1094) + - 4 (x456) + - 1 (x145) - 5 (x32) - - 0 (x5) - 'primary_source_category': - type: string - path: creature.primary_source_category - is_null: false - values: 3647 - different_values: 5 - variants: - - "Rulebooks" (x2124) - - "Adventure Paths" (x1270) - - "Adventures" (x123) - - "Lost Omens" (x113) - - "Blog Posts" (x17) - [primary_source_group]: - type: string - path: creature.primary_source_group - is_null: true - values: 1393 - different_values: 27 - variants: [omitted] - [resistance_markdown]: - type: string - path: creature.resistance_markdown - is_null: true - values: 1071 - different_values: 610 - variants: [omitted] - 'search_markdown': - type: string - path: creature.search_markdown - is_null: false - values: 3647 - different_values: 3642 - variants: [omitted] + - 0 (x7) [pfs]: type: string path: creature.pfs @@ -1122,77 +1089,89 @@ schema: - "Standard" (x1758) - "Limited" (x103) - "Restricted" (x5) + 'primary_source_category': + type: string + path: creature.primary_source_category + is_null: false + values: 3761 + different_values: 5 + variants: + - "Rulebooks" (x2124) + - "Adventure Paths" (x1384) + - "Adventures" (x123) + - "Lost Omens" (x113) + - "Blog Posts" (x17) 'rarity': type: string path: creature.rarity is_null: false - values: 3647 + values: 3761 different_values: 4 variants: - - "common" (x1856) - - "uncommon" (x686) - - "unique" (x621) - - "rare" (x484) + - "common" (x1887) + - "uncommon" (x711) + - "unique" (x639) + - "rare" (x524) 'rarity_id': type: integer path: creature.rarity_id is_null: false - values: 3647 + values: 3761 different_values: 4 variants: - - 1 (x1856) - - 2 (x686) - - 4 (x621) - - 3 (x484) + - 1 (x1887) + - 2 (x711) + - 4 (x639) + - 3 (x524) 'reflex_save': type: integer path: creature.reflex_save is_null: false - values: 3647 + values: 3761 different_values: 44 variants: [omitted] 'reflex_save_scale_number': type: integer path: creature.reflex_save_scale_number is_null: false - values: 3647 + values: 3761 different_values: 6 variants: - - 3 (x1599) - - 2 (x1234) - - 4 (x518) - - 1 (x265) - - 5 (x26) - - 0 (x5) + - 3 (x1629) + - 2 (x1283) + - 4 (x543) + - 1 (x269) + - 5 (x30) + - 0 (x7) 'release_date': type: string path: creature.release_date is_null: false - values: 3647 - different_values: 77 + values: 3761 + different_values: 89 variants: [omitted] 'resistance': type: object path: creature.resistance is_null: false - values: 3647 + values: 3761 schema: [acid]: type: integer path: creature.resistance.acid is_null: true - values: 180 - different_values: 16 + values: 195 + different_values: 17 variants: - - 10 (x59) - - 5 (x47) - - 15 (x24) - - 20 (x22) + - 10 (x65) + - 5 (x49) + - 15 (x29) + - 20 (x23) - 3 (x6) - 2 (x6) - 8 (x5) - - 25 (x2) - 12 (x2) + - 25 (x2) - 24 (x1) - 26 (x1) - 4 (x1) @@ -1200,87 +1179,91 @@ schema: - 6 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [all]: type: integer path: creature.resistance.all is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [area]: type: integer path: creature.resistance.area is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [bleed]: type: integer path: creature.resistance.bleed is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [bludgeoning]: type: integer path: creature.resistance.bludgeoning is_null: true - values: 466 + values: 500 different_values: 17 variants: - - 10 (x148) - - 5 (x129) - - 15 (x81) - - 3 (x25) + - 10 (x157) + - 5 (x136) + - 15 (x91) + - 3 (x26) + - 20 (x25) - 2 (x23) - - 20 (x22) - 8 (x10) - - 7 (x6) + - 7 (x7) + - 6 (x5) - 4 (x4) - 14 (x4) - - 25 (x3) - 12 (x3) - - 6 (x3) - - 9 (x2) + - 25 (x3) + - 9 (x3) - 24 (x1) - 22 (x1) - 1 (x1) @@ -1288,121 +1271,126 @@ schema: type: integer path: creature.resistance.chaotic is_null: true - values: 122 - different_values: 13 + values: 133 + different_values: 14 variants: - - 10 (x44) - - 5 (x38) - - 15 (x16) + - 10 (x48) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [cold]: type: integer path: creature.resistance.cold is_null: true - values: 271 - different_values: 15 + values: 282 + different_values: 16 variants: - - 10 (x105) - - 5 (x75) - - 15 (x40) + - 10 (x109) + - 5 (x77) + - 15 (x44) - 20 (x15) - 2 (x10) - 3 (x9) - 8 (x5) - - 25 (x3) - 12 (x3) + - 25 (x3) - 24 (x1) - 26 (x1) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [cold_iron]: type: integer path: creature.resistance.cold_iron is_null: true - values: 119 - different_values: 13 + values: 130 + different_values: 14 variants: - - 10 (x41) - - 5 (x38) - - 15 (x16) + - 10 (x45) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [electricity]: type: integer path: creature.resistance.electricity is_null: true - values: 235 - different_values: 18 + values: 246 + different_values: 19 variants: - - 10 (x79) - - 5 (x75) - - 15 (x26) + - 10 (x83) + - 5 (x77) + - 15 (x30) - 20 (x11) - 3 (x9) - 2 (x9) - 8 (x6) - 12 (x4) - - 25 (x3) - 7 (x3) + - 25 (x3) - 4 (x2) - 1 (x2) - - 22 (x1) - 16 (x1) + - 22 (x1) - 24 (x1) - 26 (x1) - 18 (x1) - 14 (x1) + - 9 (x1) [evil]: type: integer path: creature.resistance.evil is_null: true - values: 124 - different_values: 13 + values: 135 + different_values: 14 variants: - - 10 (x44) - - 5 (x40) - - 15 (x16) + - 10 (x48) + - 5 (x42) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [fire]: type: integer path: creature.resistance.fire is_null: true - values: 319 - different_values: 15 + values: 335 + different_values: 16 variants: - - 10 (x105) - - 5 (x89) - - 15 (x54) - - 20 (x18) - - 3 (x15) + - 10 (x111) + - 5 (x92) + - 15 (x58) + - 20 (x19) + - 3 (x16) - 2 (x11) - 8 (x6) - 25 (x4) @@ -1413,6 +1401,7 @@ schema: - 6 (x2) - 24 (x1) - 14 (x1) + - 9 (x1) [force]: type: integer path: creature.resistance.force @@ -1428,81 +1417,84 @@ schema: type: integer path: creature.resistance.good is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [holy]: type: integer path: creature.resistance.holy is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [lawful]: type: integer path: creature.resistance.lawful is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [mental]: type: integer path: creature.resistance.mental is_null: true - values: 203 + values: 218 different_values: 15 variants: - - 10 (x74) - - 5 (x50) - - 15 (x38) + - 10 (x78) + - 5 (x52) + - 15 (x45) - 20 (x11) - 3 (x8) + - 8 (x6) - 2 (x6) - - 8 (x5) - 25 (x3) - 12 (x2) + - 9 (x2) - 4 (x1) - 13 (x1) - - 9 (x1) - 1 (x1) - 14 (x1) - 7 (x1) @@ -1510,32 +1502,33 @@ schema: type: integer path: creature.resistance.orichalcum is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [physical]: type: integer path: creature.resistance.physical is_null: true - values: 390 - different_values: 16 + values: 405 + different_values: 17 variants: - - 10 (x143) - - 5 (x93) - - 15 (x83) + - 10 (x148) + - 5 (x96) + - 15 (x89) - 20 (x19) - 3 (x16) - 2 (x8) @@ -1549,151 +1542,158 @@ schema: - 24 (x1) - 22 (x1) - 1 (x1) + - 9 (x1) [piercing]: type: integer path: creature.resistance.piercing is_null: true - values: 536 - different_values: 18 + values: 569 + different_values: 19 variants: - - 10 (x177) - - 5 (x159) - - 15 (x96) - - 20 (x23) - - 3 (x21) + - 10 (x186) + - 5 (x165) + - 15 (x106) + - 20 (x27) + - 3 (x22) - 2 (x13) - 8 (x12) - 7 (x8) - - 6 (x6) + - 6 (x7) - 12 (x5) - 4 (x4) - 25 (x3) + - 9 (x3) + - 14 (x3) + - 24 (x1) + - 22 (x1) + - 13 (x1) + - 1 (x1) + - 11 (x1) + [poison]: + type: integer + path: creature.resistance.poison + is_null: true + values: 267 + different_values: 17 + variants: + - 10 (x87) + - 5 (x73) + - 15 (x47) + - 2 (x14) + - 20 (x13) + - 8 (x10) + - 3 (x8) + - 12 (x2) + - 25 (x2) + - 4 (x2) + - 14 (x2) + - 6 (x2) + - 24 (x1) + - 26 (x1) + - 1 (x1) + - 7 (x1) + - 9 (x1) + [precision]: + type: integer + path: creature.resistance.precision + is_null: true + values: 148 + different_values: 14 + variants: + - 10 (x52) + - 5 (x43) + - 15 (x22) + - 3 (x6) + - 2 (x6) + - 8 (x5) + - 20 (x5) + - 12 (x2) + - 25 (x2) + - 4 (x1) + - 1 (x1) + - 14 (x1) + - 7 (x1) + - 9 (x1) + [silver]: + type: integer + path: creature.resistance.silver + is_null: true + values: 133 + different_values: 14 + variants: + - 10 (x48) + - 5 (x40) + - 15 (x20) + - 2 (x6) + - 8 (x5) + - 3 (x3) + - 12 (x2) + - 20 (x2) + - 25 (x2) + - 4 (x1) + - 1 (x1) + - 14 (x1) + - 7 (x1) + - 9 (x1) + [slashing]: + type: integer + path: creature.resistance.slashing + is_null: true + values: 528 + different_values: 19 + variants: + - 10 (x170) + - 5 (x153) + - 15 (x94) + - 3 (x28) + - 20 (x23) + - 2 (x13) + - 8 (x12) + - 7 (x9) + - 12 (x5) + - 6 (x5) + - 25 (x3) + - 4 (x3) - 14 (x3) - 9 (x2) - 24 (x1) - 22 (x1) - 13 (x1) - 1 (x1) - [poison]: - type: integer - path: creature.resistance.poison - is_null: true - values: 249 - different_values: 16 - variants: - - 10 (x79) - - 5 (x70) - - 15 (x42) - - 2 (x14) - - 20 (x13) - - 8 (x10) - - 3 (x8) - - 25 (x2) - - 12 (x2) - - 4 (x2) - - 14 (x2) - - 24 (x1) - - 26 (x1) - - 1 (x1) - - 7 (x1) - - 6 (x1) - [precision]: - type: integer - path: creature.resistance.precision - is_null: true - values: 137 - different_values: 13 - variants: - - 10 (x48) - - 5 (x41) - - 15 (x18) - - 3 (x6) - - 2 (x6) - - 8 (x5) - - 20 (x5) - - 25 (x2) - - 12 (x2) - - 4 (x1) - - 1 (x1) - - 14 (x1) - - 7 (x1) - [silver]: - type: integer - path: creature.resistance.silver - is_null: true - values: 122 - different_values: 13 - variants: - - 10 (x44) - - 5 (x38) - - 15 (x16) - - 2 (x6) - - 8 (x5) - - 3 (x3) - - 20 (x2) - - 25 (x2) - - 12 (x2) - - 4 (x1) - - 1 (x1) - - 14 (x1) - - 7 (x1) - [slashing]: - type: integer - path: creature.resistance.slashing - is_null: true - values: 506 - different_values: 18 - variants: - - 10 (x164) - - 5 (x148) - - 15 (x88) - - 3 (x28) - - 20 (x22) - - 2 (x13) - - 8 (x12) - - 7 (x7) - - 6 (x5) - - 12 (x5) - - 25 (x3) - - 4 (x3) - - 14 (x3) - - 24 (x1) - - 22 (x1) - - 9 (x1) - - 13 (x1) - - 1 (x1) + - 11 (x1) [sonic]: type: integer path: creature.resistance.sonic is_null: true - values: 152 - different_values: 15 + values: 163 + different_values: 16 variants: - - 5 (x46) - - 10 (x46) - - 15 (x28) + - 10 (x50) + - 5 (x48) + - 15 (x32) - 20 (x7) - 2 (x7) - 8 (x5) - 3 (x3) - - 25 (x2) - 12 (x2) + - 25 (x2) - 22 (x1) - 30 (x1) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [spirit]: type: integer path: creature.resistance.spirit is_null: true - values: 108 + values: 113 different_values: 11 variants: - - 10 (x40) - - 5 (x33) - - 15 (x16) + - 10 (x41) + - 5 (x34) + - 15 (x19) - 2 (x6) - 8 (x4) - 20 (x2) @@ -1706,51 +1706,53 @@ schema: type: integer path: creature.resistance.splash is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [unholy]: type: integer path: creature.resistance.unholy is_null: true - values: 121 - different_values: 13 + values: 132 + different_values: 14 variants: - - 10 (x43) - - 5 (x38) - - 15 (x16) + - 10 (x47) + - 5 (x40) + - 15 (x20) - 2 (x6) - 8 (x5) - 3 (x3) + - 12 (x2) - 20 (x2) - 25 (x2) - - 12 (x2) - 4 (x1) - 1 (x1) - 14 (x1) - 7 (x1) + - 9 (x1) [vitality]: type: integer path: creature.resistance.vitality is_null: true - values: 45 + values: 47 different_values: 9 variants: - - 5 (x12) - - 10 (x11) + - 5 (x13) + - 10 (x12) - 15 (x8) - 2 (x4) - 8 (x4) @@ -1762,82 +1764,112 @@ schema: type: integer path: creature.resistance.void is_null: true - values: 170 - different_values: 13 + values: 181 + different_values: 14 variants: - - 10 (x55) - - 5 (x47) - - 15 (x31) + - 10 (x59) + - 5 (x49) + - 15 (x35) - 2 (x10) - 8 (x6) - 20 (x6) - 3 (x5) - - 25 (x2) - 12 (x2) + - 25 (x2) - 4 (x2) - 7 (x2) - 1 (x1) - 14 (x1) + - 9 (x1) + [resistance_markdown]: + type: string + path: creature.resistance_markdown + is_null: true + values: 1121 + different_values: 635 + variants: [omitted] + 'search_markdown': + type: string + path: creature.search_markdown + is_null: false + values: 3761 + different_values: 3756 + variants: [omitted] + [sense_markdown]: + type: string + path: creature.sense_markdown + is_null: true + values: 3167 + different_values: 675 + variants: [omitted] 'size': type: array path: creature.size is_null: false - values: 3647 + values: 3761 min_count: 1 max_count: 1 value_type: type: string path: creature.size.[] is_null: false - values: 3647 + values: 3761 different_values: 6 variants: - - "Medium" (x1774) - - "Large" (x775) - - "Small" (x407) - - "Huge" (x305) - - "Gargantuan" (x242) - - "Tiny" (x144) - [remaster_id]: - type: array - path: creature.remaster_id - is_null: true - values: 463 + - "Medium" (x1827) + - "Large" (x807) + - "Small" (x414) + - "Huge" (x315) + - "Gargantuan" (x251) + - "Tiny" (x147) + 'size_id': + type: array + path: creature.size_id + is_null: false + values: 3761 min_count: 1 max_count: 1 value_type: - type: string - path: creature.remaster_id.[] + type: integer + path: creature.size_id.[] is_null: false - values: 463 - different_values: 463 - variants: [omitted] + values: 3761 + different_values: 6 + variants: + - 3 (x1827) + - 4 (x807) + - 2 (x414) + - 5 (x315) + - 6 (x251) + - 1 (x147) 'skill_markdown': type: string path: creature.skill_markdown is_null: false - values: 3647 - different_values: 3021 + values: 3761 + different_values: 3132 variants: [omitted] - 'source_category': + [source_group]: type: array - path: creature.source_category - is_null: false - values: 3647 + path: creature.source_group + is_null: true + values: 1553 min_count: 1 max_count: 2 value_type: type: string - path: creature.source_category.[] + path: creature.source_group.[] is_null: false - values: 3745 - different_values: 5 - variants: - - "Rulebooks" (x2160) - - "Adventure Paths" (x1319) - - "Adventures" (x126) - - "Lost Omens" (x123) - - "Blog Posts" (x17) + values: 1563 + different_values: 31 + variants: [omitted] + 'source_markdown': + type: string + path: creature.source_markdown + is_null: false + values: 3761 + different_values: 3091 + variants: [omitted] [school]: type: string path: creature.school @@ -1848,211 +1880,210 @@ schema: - "illusion" (x3) - "evocation" (x1) - "necromancy" (x1) - [sense_markdown]: - type: string - path: creature.sense_markdown - is_null: true - values: 3061 - different_values: 652 - variants: [omitted] - 'size_id': - type: array - path: creature.size_id - is_null: false - values: 3647 - min_count: 1 - max_count: 1 - value_type: - type: integer - path: creature.size_id.[] - is_null: false - values: 3647 - different_values: 6 - variants: - - 3 (x1774) - - 4 (x775) - - 2 (x407) - - 5 (x305) - - 6 (x242) - - 1 (x144) - [source_group]: + 'source_category': type: array - path: creature.source_group - is_null: true - values: 1439 + path: creature.source_category + is_null: false + values: 3761 min_count: 1 max_count: 2 value_type: type: string - path: creature.source_group.[] + path: creature.source_category.[] is_null: false - values: 1449 - different_values: 27 + values: 3859 + different_values: 5 + variants: + - "Rulebooks" (x2160) + - "Adventure Paths" (x1433) + - "Adventures" (x126) + - "Lost Omens" (x123) + - "Blog Posts" (x17) + [primary_source_group]: + type: string + path: creature.primary_source_group + is_null: true + values: 1507 + different_values: 31 + variants: [omitted] + [remaster_id]: + type: array + path: creature.remaster_id + is_null: true + values: 473 + min_count: 1 + max_count: 1 + value_type: + type: string + path: creature.remaster_id.[] + is_null: false + values: 473 + different_values: 473 variants: [omitted] [speed_markdown]: type: string path: creature.speed_markdown is_null: true - values: 3643 - different_values: 676 + values: 3757 + different_values: 685 variants: [omitted] - [spell_attack_bonus]: - type: array - path: creature.spell_attack_bonus - is_null: true - values: 680 - min_count: 1 - max_count: 3 - value_type: - type: integer - path: creature.spell_attack_bonus.[] - is_null: false - values: 717 - different_values: 40 - variants: [omitted] - [spell_attack_bonus_scale_number]: - type: array - path: creature.spell_attack_bonus_scale_number - is_null: true - values: 680 - min_count: 1 - max_count: 2 - value_type: - type: integer - path: creature.spell_attack_bonus_scale_number.[] - is_null: false - values: 683 - different_values: 4 - variants: - - 4 (x495) - - 3 (x169) - - 5 (x16) - - 0 (x3) - [spell_dc]: - type: array - path: creature.spell_dc - is_null: true - values: 1504 - min_count: 1 - max_count: 4 - value_type: - type: integer - path: creature.spell_dc.[] - is_null: false - values: 1770 - different_values: 39 - variants: [omitted] - [spell_dc_scale_number]: - type: array - path: creature.spell_dc_scale_number - is_null: true - values: 1504 - min_count: 1 - max_count: 2 - value_type: - type: integer - path: creature.spell_dc_scale_number.[] - is_null: false - values: 1527 - different_values: 4 - variants: - - 4 (x1081) - - 3 (x415) - - 5 (x27) - - 0 (x4) 'source': type: array path: creature.source is_null: false - values: 3647 + values: 3761 min_count: 1 max_count: 2 value_type: type: string path: creature.source.[] is_null: false - values: 3749 - different_values: 107 + values: 3863 + different_values: 119 variants: [omitted] + [spell_attack_bonus]: + type: array + path: creature.spell_attack_bonus + is_null: true + values: 707 + min_count: 1 + max_count: 3 + value_type: + type: integer + path: creature.spell_attack_bonus.[] + is_null: false + values: 746 + different_values: 40 + variants: [omitted] + [spell_attack_bonus_scale_number]: + type: array + path: creature.spell_attack_bonus_scale_number + is_null: true + values: 707 + min_count: 1 + max_count: 2 + value_type: + type: integer + path: creature.spell_attack_bonus_scale_number.[] + is_null: false + values: 710 + different_values: 4 + variants: + - 4 (x515) + - 3 (x175) + - 5 (x16) + - 0 (x4) + [spell_dc]: + type: array + path: creature.spell_dc + is_null: true + values: 1557 + min_count: 1 + max_count: 4 + value_type: + type: integer + path: creature.spell_dc.[] + is_null: false + values: 1827 + different_values: 39 + variants: [omitted] + [spell_dc_scale_number]: + type: array + path: creature.spell_dc_scale_number + is_null: true + values: 1557 + min_count: 1 + max_count: 2 + value_type: + type: integer + path: creature.spell_dc_scale_number.[] + is_null: false + values: 1580 + different_values: 4 + variants: + - 4 (x1119) + - 3 (x429) + - 5 (x27) + - 0 (x5) [spoilers]: type: string path: creature.spoilers is_null: true - values: 1218 - different_values: 26 + values: 1301 + different_values: 30 variants: [omitted] [strike_damage_average]: type: array path: creature.strike_damage_average is_null: true - values: 3514 + values: 3620 min_count: 1 max_count: 6 value_type: type: integer path: creature.strike_damage_average.[] is_null: false - values: 7241 + values: 7447 different_values: 63 variants: [omitted] [strike_damage_scale_number]: type: array path: creature.strike_damage_scale_number is_null: true - values: 3514 + values: 3620 min_count: 1 max_count: 4 value_type: type: integer path: creature.strike_damage_scale_number.[] is_null: false - values: 5016 + values: 5175 different_values: 5 variants: - - 2 (x2283) - - 3 (x1649) - - 4 (x974) + - 2 (x2349) + - 3 (x1714) + - 4 (x1000) - 5 (x105) - - 0 (x5) + - 0 (x7) [summary_markdown]: type: string path: creature.summary_markdown is_null: true - values: 2954 - different_values: 2657 + values: 3056 + different_values: 2749 variants: [omitted] [tradition]: type: array path: creature.tradition is_null: true - values: 1475 + values: 1527 min_count: 1 max_count: 3 value_type: type: string path: creature.tradition.[] is_null: false - values: 1514 + values: 1568 different_values: 4 variants: - - "Divine" (x487) - - "Occult" (x412) - - "Primal" (x360) - - "Arcane" (x255) + - "Divine" (x499) + - "Occult" (x425) + - "Primal" (x388) + - "Arcane" (x256) [vision]: type: string path: creature.vision is_null: true - values: 2803 + values: 2905 different_values: 3 variants: - - "Darkvision" (x2027) - - "Low-light vision" (x621) - - "Greater darkvision" (x155) + - "Darkvision" (x2090) + - "Low-light vision" (x652) + - "Greater darkvision" (x163) [weakness_markdown]: type: string path: creature.weakness_markdown is_null: true - values: 1151 - different_values: 429 + values: 1211 + different_values: 461 variants: [omitted] diff --git a/output/aonprd/deity-category.yaml b/output/aonprd/deity-category.yaml index c1f038f..2c5b254 100644 --- a/output/aonprd/deity-category.yaml +++ b/output/aonprd/deity-category.yaml @@ -19,8 +19,8 @@ schema: type: string path: deity-category.deity.[] is_null: false - values: 673 - different_values: 458 + values: 686 + different_values: 459 variants: [omitted] 'deity_category': type: string diff --git a/output/aonprd/deity.yaml b/output/aonprd/deity.yaml index adbc3ae..ac14540 100644 --- a/output/aonprd/deity.yaml +++ b/output/aonprd/deity.yaml @@ -1,66 +1,173 @@ name: deity -values: 673 +values: 686 schema: 'anathema': type: string path: deity.anathema is_null: false - values: 673 - different_values: 530 + values: 686 + different_values: 541 variants: [omitted] [attribute]: type: array path: deity.attribute is_null: true - values: 654 + values: 667 min_count: 2 max_count: 2 value_type: type: string path: deity.attribute.[] is_null: false - values: 1308 + values: 1334 different_values: 6 variants: - - "Wisdom" (x303) - - "Constitution" (x257) - - "Charisma" (x222) - - "Intelligence" (x204) - - "Strength" (x183) - - "Dexterity" (x139) + - "Wisdom" (x307) + - "Constitution" (x265) + - "Charisma" (x229) + - "Intelligence" (x205) + - "Strength" (x186) + - "Dexterity" (x142) 'category': type: string path: deity.category is_null: false - values: 673 + values: 686 different_values: 1 (all same) - variants: "deity" (x673) + variants: "deity" (x686) 'deity': type: array path: deity.deity is_null: false - values: 673 + values: 686 min_count: 1 max_count: 12 value_type: type: string path: deity.deity.[] is_null: false - values: 1027 - different_values: 458 + values: 1039 + different_values: 459 variants: [omitted] 'deity_category': type: string path: deity.deity_category is_null: false + values: 686 + different_values: 34 + variants: [omitted] + 'domain_alternate_markdown': + type: string + path: deity.domain_alternate_markdown + is_null: false + values: 686 + different_values: 307 + variants: [omitted] + 'domain_markdown': + type: string + path: deity.domain_markdown + is_null: false + values: 686 + different_values: 662 + variants: [omitted] + 'domain_primary_markdown': + type: string + path: deity.domain_primary_markdown + is_null: false + values: 686 + different_values: 655 + variants: [omitted] + 'edict': + type: string + path: deity.edict + is_null: false + values: 686 + different_values: 541 + variants: [omitted] + 'favored_weapon_markdown': + type: string + path: deity.favored_weapon_markdown + is_null: false + values: 686 + different_values: 189 + variants: [omitted] + [divine_font]: + type: array + path: deity.divine_font + is_null: true values: 673 + min_count: 1 + max_count: 2 + value_type: + type: string + path: deity.divine_font.[] + is_null: false + values: 852 + different_values: 2 + variants: + - "Heal" (x444) + - "Harm" (x408) + [domain]: + type: array + path: deity.domain + is_null: true + values: 673 + min_count: 3 + max_count: 12 + value_type: + type: string + path: deity.domain.[] + is_null: false + values: 3407 + different_values: 64 + variants: [omitted] + 'id': + type: string + path: deity.id + is_null: false + values: 686 + different_values: 686 (all different) + variants: [omitted] + [icon_image]: + type: string + path: deity.icon_image + is_null: true + values: 85 + different_values: 64 + variants: [omitted] + [image]: + type: array + path: deity.image + is_null: true + values: 85 + min_count: 1 + max_count: 1 + value_type: + type: string + path: deity.image.[] + is_null: false + values: 85 + different_values: 64 + variants: [omitted] + 'search_markdown': + type: string + path: deity.search_markdown + is_null: false + values: 686 + different_values: 684 + variants: [omitted] + 'skill_markdown': + type: string + path: deity.skill_markdown + is_null: false + values: 686 different_values: 34 variants: [omitted] [alignment]: type: string path: deity.alignment is_null: true - values: 254 + values: 255 different_values: 10 variants: - "NG" (x41) @@ -71,56 +178,95 @@ schema: - "CN" (x24) - "CG" (x24) - "LE" (x22) - - "NE" (x17) + - "NE" (x18) - "Any" (x2) + [area_of_concern_raw]: + type: string + path: deity.area_of_concern_raw + is_null: true + values: 599 + different_values: 559 + variants: [omitted] [deity_markdown]: type: string path: deity.deity_markdown is_null: true - values: 177 - different_values: 103 + values: 176 + different_values: 102 variants: [omitted] - 'domain_alternate_markdown': - type: string - path: deity.domain_alternate_markdown + 'source': + type: array + path: deity.source is_null: false - values: 673 - different_values: 306 + values: 686 + min_count: 1 + max_count: 1 + value_type: + type: string + path: deity.source.[] + is_null: false + values: 686 + different_values: 27 + variants: [omitted] + 'source_category': + type: array + path: deity.source_category + is_null: false + values: 686 + min_count: 1 + max_count: 1 + value_type: + type: string + path: deity.source_category.[] + is_null: false + values: 686 + different_values: 5 + variants: + - "Lost Omens" (x627) + - "Rulebooks" (x40) + - "Adventure Paths" (x9) + - "Blog Posts" (x8) + - "Adventures" (x2) + 'source_markdown': + type: string + path: deity.source_markdown + is_null: false + values: 686 + different_values: 212 variants: [omitted] - 'domain_markdown': - type: string - path: deity.domain_markdown + 'speed': + type: object + path: deity.speed is_null: false - values: 673 - different_values: 649 + values: 686 + schema: + 'spell_markdown': + type: string + path: deity.spell_markdown + is_null: false + values: 686 + different_values: 660 variants: [omitted] - 'domain_primary_markdown': + 'summary_markdown': type: string - path: deity.domain_primary_markdown + path: deity.summary_markdown is_null: false - values: 673 - different_values: 642 + values: 686 + different_values: 459 variants: [omitted] - 'edict': + 'type': type: string - path: deity.edict + path: deity.type is_null: false - values: 673 - different_values: 531 - variants: [omitted] - 'favored_weapon_markdown': + values: 686 + different_values: 1 (all same) + variants: "Deity" (x686) + 'url': type: string - path: deity.favored_weapon_markdown + path: deity.url is_null: false - values: 673 - different_values: 188 - variants: [omitted] - 'id': - type: string - path: deity.id - is_null: false - values: 673 - different_values: 673 (all different) + values: 686 + different_values: 686 (all different) variants: [omitted] [follower_alignment]: type: array @@ -145,85 +291,124 @@ schema: - "NE" (x71) - "CN" (x68) - "LE" (x57) - [icon_image]: - type: string - path: deity.icon_image - is_null: true - values: 85 - different_values: 64 - variants: [omitted] + 'weakness': + type: object + path: deity.weakness + is_null: false + values: 686 + schema: [legacy_id]: type: array path: deity.legacy_id is_null: true - values: 218 + values: 230 min_count: 1 max_count: 1 value_type: type: string path: deity.legacy_id.[] is_null: false - values: 218 - different_values: 217 + values: 230 + different_values: 230 variants: [omitted] 'markdown': type: string path: deity.markdown is_null: false - values: 673 - different_values: 673 (all different) + values: 686 + different_values: 686 (all different) variants: [omitted] 'name': type: string path: deity.name is_null: false - values: 673 - different_values: 482 + values: 686 + different_values: 483 variants: [omitted] - 'summary_markdown': + 'primary_source_category': type: string - path: deity.summary_markdown + path: deity.primary_source_category is_null: false - values: 673 - different_values: 450 - variants: [omitted] - [area_of_concern_raw]: + values: 686 + different_values: 5 + variants: + - "Lost Omens" (x627) + - "Rulebooks" (x40) + - "Adventure Paths" (x9) + - "Blog Posts" (x8) + - "Adventures" (x2) + 'rarity': type: string - path: deity.area_of_concern_raw - is_null: true - values: 586 - different_values: 546 + path: deity.rarity + is_null: false + values: 686 + different_values: 1 (all same) + variants: "common" (x686) + 'rarity_id': + type: integer + path: deity.rarity_id + is_null: false + values: 686 + different_values: 1 (all same) + variants: 1 (x686) + 'release_date': + type: string + path: deity.release_date + is_null: false + values: 686 + different_values: 22 variants: [omitted] - [divine_font]: - type: array - path: deity.divine_font + [pantheon_markdown]: + type: string + path: deity.pantheon_markdown is_null: true - values: 660 + values: 142 + different_values: 70 + variants: [omitted] + [pantheon_member_markdown]: + type: string + path: deity.pantheon_member_markdown + is_null: true + values: 34 + different_values: 32 + variants: [omitted] + [pfs]: + type: string + path: deity.pfs + is_null: true + values: 682 + different_values: 3 + variants: + - "Standard" (x449) + - "Restricted" (x226) + - "Limited" (x7) + [remaster_id]: + type: array + path: deity.remaster_id + is_null: true + values: 230 min_count: 1 - max_count: 2 + max_count: 1 value_type: type: string - path: deity.divine_font.[] + path: deity.remaster_id.[] is_null: false - values: 837 - different_values: 2 - variants: - - "Heal" (x436) - - "Harm" (x401) - [domain]: - type: array - path: deity.domain - is_null: true - values: 660 - min_count: 4 - max_count: 24 - value_type: - type: string - path: deity.domain.[] - is_null: false - values: 3516 - different_values: 64 + values: 230 + different_values: 230 variants: [omitted] + 'sanctification_raw': + type: string + path: deity.sanctification_raw + is_null: false + values: 686 + different_values: 6 + variants: + - "can choose holy" (x255) + - "can choose unholy" (x141) + - "none" (x111) + - "must choose unholy" (x82) + - "can choose holy or unholy" (x75) + - "must choose holy" (x22) [pantheon]: type: array path: deity.pantheon @@ -238,116 +423,12 @@ schema: values: 235 different_values: 39 variants: [omitted] - [pantheon_markdown]: - type: string - path: deity.pantheon_markdown - is_null: true - values: 142 - different_values: 70 - variants: [omitted] - 'type': - type: string - path: deity.type - is_null: false - values: 673 - different_values: 1 (all same) - variants: "Deity" (x673) - 'url': - type: string - path: deity.url - is_null: false - values: 673 - different_values: 673 (all different) - variants: [omitted] - 'weakness': - type: object - path: deity.weakness - is_null: false - values: 673 - schema: - [image]: - type: array - path: deity.image - is_null: true - values: 85 - min_count: 1 - max_count: 1 - value_type: - type: string - path: deity.image.[] - is_null: false - values: 85 - different_values: 64 - variants: [omitted] - 'spell_markdown': - type: string - path: deity.spell_markdown - is_null: false - values: 673 - different_values: 649 - variants: [omitted] - [pantheon_member_markdown]: - type: string - path: deity.pantheon_member_markdown - is_null: true - values: 35 - different_values: 33 - variants: [omitted] - 'release_date': - type: string - path: deity.release_date - is_null: false - values: 673 - different_values: 20 - variants: - - "2025-01-30" (x415) - - "2020-01-29" (x159) - - "2019-08-01" (x24) - - "2023-03-29" (x11) - - "2021-07-07" (x9) - - "2022-05-25" (x8) - - "2022-11-16" (x8) - - "2022-08-31" (x8) - - "2022-02-23" (x5) - - "2023-06-28" (x4) - - "2021-07-16" (x4) - - "2020-01-21" (x3) - - "2020-07-30" (x3) - - "2021-04-07" (x3) - - "2020-05-27" (x2) - - "2021-10-13" (x2) - - "2022-04-27" (x2) - - "2019-10-16" (x1) - - "2020-06-25" (x1) - - "2024-03-27" (x1) - [pfs]: - type: string - path: deity.pfs - is_null: true - values: 670 - different_values: 3 - variants: - - "Standard" (x439) - - "Restricted" (x223) - - "Limited" (x8) - 'primary_source_category': - type: string - path: deity.primary_source_category - is_null: false - values: 673 - different_values: 5 - variants: - - "Lost Omens" (x623) - - "Rulebooks" (x32) - - "Adventure Paths" (x8) - - "Blog Posts" (x8) - - "Adventures" (x2) [primary_source_group]: type: string path: deity.primary_source_group is_null: true - values: 10 - different_values: 6 + values: 11 + different_values: 7 variants: - "Quest for the Frozen Flame" (x5) - "Age of Ashes" (x1) @@ -355,114 +436,34 @@ schema: - "Night of the Gray Death" (x1) - "Strength of Thousands" (x1) - "Shadows at Sundown" (x1) - 'rarity': - type: string - path: deity.rarity - is_null: false - values: 673 - different_values: 1 (all same) - variants: "common" (x673) - 'rarity_id': - type: integer - path: deity.rarity_id - is_null: false - values: 673 - different_values: 1 (all same) - variants: 1 (x673) - [remaster_id]: - type: array - path: deity.remaster_id - is_null: true - values: 217 - min_count: 1 - max_count: 1 - value_type: - type: string - path: deity.remaster_id.[] - is_null: false - values: 217 - different_values: 217 - variants: [omitted] - 'resistance': - type: object - path: deity.resistance - is_null: false - values: 673 - schema: + - "Wardens of Wildwood" (x1) [religious_symbol]: type: string path: deity.religious_symbol is_null: true - values: 396 - different_values: 394 + values: 395 + different_values: 393 variants: [omitted] [sacred_animal]: type: string path: deity.sacred_animal is_null: true - values: 365 - different_values: 228 + values: 364 + different_values: 227 variants: [omitted] - 'search_markdown': - type: string - path: deity.search_markdown - is_null: false - values: 673 - different_values: 671 - variants: [omitted] - 'skill_markdown': - type: string - path: deity.skill_markdown - is_null: false - values: 673 - different_values: 34 - variants: [omitted] - 'source': - type: array - path: deity.source - is_null: false - values: 673 - min_count: 1 - max_count: 1 - value_type: - type: string - path: deity.source.[] - is_null: false - values: 673 - different_values: 25 - variants: [omitted] - 'source_category': - type: array - path: deity.source_category - is_null: false - values: 673 - min_count: 1 - max_count: 1 - value_type: - type: string - path: deity.source_category.[] - is_null: false - values: 673 - different_values: 5 - variants: - - "Lost Omens" (x623) - - "Rulebooks" (x32) - - "Adventure Paths" (x8) - - "Blog Posts" (x8) - - "Adventures" (x2) [source_group]: type: array path: deity.source_group is_null: true - values: 10 + values: 11 min_count: 1 max_count: 1 value_type: type: string path: deity.source_group.[] is_null: false - values: 10 - different_values: 6 + values: 11 + different_values: 7 variants: - "Quest for the Frozen Flame" (x5) - "Age of Ashes" (x1) @@ -470,31 +471,19 @@ schema: - "Night of the Gray Death" (x1) - "Strength of Thousands" (x1) - "Shadows at Sundown" (x1) - 'source_markdown': - type: string - path: deity.source_markdown - is_null: false - values: 673 - different_values: 203 - variants: [omitted] - 'speed': - type: object - path: deity.speed - is_null: false - values: 673 - schema: + - "Wardens of Wildwood" (x1) [trait]: type: array path: deity.trait is_null: true - values: 254 + values: 255 min_count: 1 max_count: 1 value_type: type: string path: deity.trait.[] is_null: false - values: 254 + values: 255 different_values: 10 variants: - "NG" (x41) @@ -505,5 +494,11 @@ schema: - "CN" (x24) - "CG" (x24) - "LE" (x22) - - "NE" (x17) + - "NE" (x18) - "Any" (x2) + 'resistance': + type: object + path: deity.resistance + is_null: false + values: 686 + schema: diff --git a/output/aonprd/deviant-ability-classification.yaml b/output/aonprd/deviant-ability-classification.yaml index d47b258..1ea1e39 100644 --- a/output/aonprd/deviant-ability-classification.yaml +++ b/output/aonprd/deviant-ability-classification.yaml @@ -1,152 +1,191 @@ name: deviant-ability-classification -values: 3 +values: 5 schema: 'category': type: string path: deviant-ability-classification.category is_null: false - values: 3 + values: 5 different_values: 1 (all same) - variants: "deviant-ability-classification" (x3) + variants: "deviant-ability-classification" (x5) 'id': type: string path: deviant-ability-classification.id is_null: false - values: 3 - different_values: 3 (all different) + values: 5 + different_values: 5 (all different) variants: - "deviant-ability-classification-1" (x1) - "deviant-ability-classification-2" (x1) - "deviant-ability-classification-3" (x1) + - "deviant-ability-classification-4" (x1) + - "deviant-ability-classification-5" (x1) 'markdown': type: string path: deviant-ability-classification.markdown is_null: false - values: 3 - different_values: 3 (all different) + values: 5 + different_values: 5 (all different) variants: [omitted] 'name': type: string path: deviant-ability-classification.name is_null: false - values: 3 - different_values: 3 (all different) + values: 5 + different_values: 5 (all different) variants: - "Dragon" (x1) - "Troll" (x1) - "Wraith" (x1) + - "Blight Soul" (x1) + - "Verdant Core" (x1) 'pfs': type: string path: deviant-ability-classification.pfs is_null: false - values: 3 - different_values: 1 (all same) - variants: "Standard" (x3) + values: 5 + different_values: 2 + variants: + - "Standard" (x3) + - "Limited" (x2) 'primary_source_category': type: string path: deviant-ability-classification.primary_source_category is_null: false - values: 3 + values: 5 + different_values: 2 + variants: + - "Rulebooks" (x3) + - "Adventure Paths" (x2) + 'primary_source_group': + type: string + path: deviant-ability-classification.primary_source_group + is_null: false + values: 2 different_values: 1 (all same) - variants: "Rulebooks" (x3) + variants: "Wardens of Wildwood" (x2) 'rarity': type: string path: deviant-ability-classification.rarity is_null: false - values: 3 + values: 5 different_values: 1 (all same) - variants: "common" (x3) + variants: "common" (x5) 'rarity_id': type: integer path: deviant-ability-classification.rarity_id is_null: false - values: 3 + values: 5 different_values: 1 (all same) - variants: 1 (x3) + variants: 1 (x5) 'release_date': type: string path: deviant-ability-classification.release_date is_null: false - values: 3 - different_values: 1 (all same) - variants: "2022-07-27" (x3) + values: 5 + different_values: 2 + variants: + - "2022-07-27" (x3) + - "2024-05-15" (x2) 'resistance': type: object path: deviant-ability-classification.resistance is_null: false - values: 3 + values: 5 schema: 'search_markdown': type: string path: deviant-ability-classification.search_markdown is_null: false - values: 3 - different_values: 3 (all different) + values: 5 + different_values: 5 (all different) variants: [omitted] 'source': type: array path: deviant-ability-classification.source is_null: false - values: 3 + values: 5 min_count: 1 max_count: 1 value_type: type: string path: deviant-ability-classification.source.[] is_null: false - values: 3 - different_values: 1 (all same) - variants: "Dark Archive" (x3) + values: 5 + different_values: 2 + variants: + - "Dark Archive" (x3) + - "Pathfinder #202: Severed at the Root" (x2) 'source_category': type: array path: deviant-ability-classification.source_category is_null: false - values: 3 + values: 5 min_count: 1 max_count: 1 value_type: type: string path: deviant-ability-classification.source_category.[] is_null: false - values: 3 + values: 5 + different_values: 2 + variants: + - "Rulebooks" (x3) + - "Adventure Paths" (x2) + 'source_group': + type: array + path: deviant-ability-classification.source_group + is_null: false + values: 2 + min_count: 1 + max_count: 1 + value_type: + type: string + path: deviant-ability-classification.source_group.[] + is_null: false + values: 2 different_values: 1 (all same) - variants: "Rulebooks" (x3) + variants: "Wardens of Wildwood" (x2) 'source_markdown': type: string path: deviant-ability-classification.source_markdown is_null: false - values: 3 - different_values: 3 (all different) + values: 5 + different_values: 5 (all different) variants: - "[Dark Archive](/Sources.aspx?ID=129) pg. 100" (x1) - "[Dark Archive](/Sources.aspx?ID=129) pg. 101" (x1) - "[Dark Archive](/Sources.aspx?ID=129) pg. 102" (x1) + - "[Pathfinder #202: Severed at the Root](/Sources.aspx?ID=242) pg. 74" (x1) + - "[Pathfinder #202: Severed at the Root](/Sources.aspx?ID=242) pg. 75" (x1) 'speed': type: object path: deviant-ability-classification.speed is_null: false - values: 3 + values: 5 schema: 'type': type: string path: deviant-ability-classification.type is_null: false - values: 3 + values: 5 different_values: 1 (all same) - variants: "Deviant Ability Classification" (x3) + variants: "Deviant Ability Classification" (x5) 'url': type: string path: deviant-ability-classification.url is_null: false - values: 3 - different_values: 3 (all different) + values: 5 + different_values: 5 (all different) variants: - "/DeviantFeats.aspx?ID=1" (x1) - "/DeviantFeats.aspx?ID=2" (x1) - "/DeviantFeats.aspx?ID=3" (x1) + - "/DeviantFeats.aspx?ID=4" (x1) + - "/DeviantFeats.aspx?ID=5" (x1) 'weakness': type: object path: deviant-ability-classification.weakness is_null: false - values: 3 + values: 5 schema: diff --git a/output/aonprd/disease.yaml b/output/aonprd/disease.yaml index 7cdd553..352ec8e 100644 --- a/output/aonprd/disease.yaml +++ b/output/aonprd/disease.yaml @@ -1,52 +1,87 @@ name: disease -values: 39 +values: 41 schema: 'category': type: string path: disease.category is_null: false - values: 39 + values: 41 different_values: 1 (all same) - variants: "disease" (x39) + variants: "disease" (x41) + 'element': + type: array + path: disease.element + is_null: false + values: 1 + min_count: 1 + max_count: 1 + value_type: + type: string + path: disease.element.[] + is_null: false + values: 1 + different_values: 1 (all same) + variants: "Earth" (x1) 'id': type: string path: disease.id is_null: false - values: 39 - different_values: 39 (all different) + values: 41 + different_values: 41 (all different) variants: [omitted] + [level]: + type: integer + path: disease.level + is_null: true + values: 40 + different_values: 13 + variants: + - 1 (x8) + - 15 (x5) + - 7 (x4) + - 11 (x4) + - 2 (x3) + - 3 (x3) + - 6 (x3) + - 0 (x2) + - 4 (x2) + - 5 (x2) + - 8 (x2) + - 10 (x1) + - 13 (x1) 'markdown': type: string path: disease.markdown is_null: false - values: 39 - different_values: 39 (all different) + values: 41 + different_values: 41 (all different) variants: [omitted] 'name': type: string path: disease.name is_null: false - values: 39 - different_values: 24 + values: 41 + different_values: 26 variants: [omitted] 'primary_source_category': type: string path: disease.primary_source_category is_null: false - values: 39 + values: 41 different_values: 4 variants: - "Rulebooks" (x35) - - "Adventure Paths" (x2) + - "Adventure Paths" (x4) - "Lost Omens" (x1) - "Adventures" (x1) [primary_source_group]: type: string path: disease.primary_source_group is_null: true - values: 3 - different_values: 3 + values: 5 + different_values: 4 variants: + - "Wardens of Wildwood" (x2) - "Extinction Curse" (x1) - "Strength of Thousands" (x1) - "Rusthenge" (x1) @@ -54,34 +89,35 @@ schema: type: string path: disease.rarity is_null: false - values: 39 + values: 41 different_values: 4 variants: - "common" (x35) - - "uncommon" (x2) + - "uncommon" (x4) - "unique" (x1) - "rare" (x1) 'rarity_id': type: integer path: disease.rarity_id is_null: false - values: 39 + values: 41 different_values: 4 variants: - 1 (x35) - - 2 (x2) + - 2 (x4) - 4 (x1) - 3 (x1) 'release_date': type: string path: disease.release_date is_null: false - values: 39 - different_values: 7 + values: 41 + different_values: 8 variants: - "2020-02-26" (x15) - "2023-11-15" (x14) - "2020-05-27" (x6) + - "2024-06-15" (x2) - "2021-09-01" (x1) - "2021-12-22" (x1) - "2023-02-22" (x1) @@ -90,32 +126,62 @@ schema: type: object path: disease.resistance is_null: false - values: 39 + values: 41 schema: 'saving_throw_markdown': type: string path: disease.saving_throw_markdown is_null: false - values: 39 + values: 41 different_values: 17 variants: - "DC 13 Fortitude" (x4) - "DC 23 Fortitude" (x4) + - "DC 28 Fortitude" (x4) - "DC 15 Fortitude" (x3) - "DC 16 Fortitude" (x3) - "DC 17 Fortitude" (x3) - "DC 22 Fortitude" (x3) - - "DC 28 Fortitude" (x3) + - "DC 32 Fortitude" (x3) - "DC 14 Fortitude" (x2) - "DC 19 Fortitude" (x2) - "DC 20 Fortitude" (x2) - "DC 25 Will" (x2) - "DC 34 Fortitude" (x2) - - "DC 32 Fortitude" (x2) - "Fortitude (DC equals that of the drug)" (x1) - "DC 29 Fortitude" (x1) - "DC 18 Fortitude" (x1) - "Fortitude; creatures with addictive exhaustion can't reduce their [sickened](/Conditions.aspx?ID=34) condition from addictive exhaustion while they have the disease" (x1) + 'search_markdown': + type: string + path: disease.search_markdown + is_null: false + values: 41 + different_values: 41 (all different) + variants: [omitted] + 'source': + type: array + path: disease.source + is_null: false + values: 41 + min_count: 1 + max_count: 1 + value_type: + type: string + path: disease.source.[] + is_null: false + values: 41 + different_values: 9 + variants: + - "Gamemastery Guide" (x15) + - "GM Core" (x14) + - "Bestiary 2" (x5) + - "Pathfinder #203 Shepherd of Decay" (x2) + - "Pathfinder #155: Lord of the Black Sands" (x1) + - "Pathfinder #170: Spoken on the Song Wind" (x1) + - "Monsters of Myth" (x1) + - "Treasure Vault" (x1) + - "Rusthenge" (x1) [school]: type: string path: disease.school @@ -125,125 +191,69 @@ schema: variants: - "necromancy" (x2) - "transmutation" (x1) - 'search_markdown': - type: string - path: disease.search_markdown - is_null: false - values: 39 - different_values: 39 (all different) - variants: [omitted] - 'source': - type: array - path: disease.source - is_null: false - values: 39 - min_count: 1 - max_count: 1 - value_type: - type: string - path: disease.source.[] - is_null: false - values: 39 - different_values: 8 - variants: - - "Gamemastery Guide" (x15) - - "GM Core" (x14) - - "Bestiary 2" (x5) - - "Pathfinder #155: Lord of the Black Sands" (x1) - - "Pathfinder #170: Spoken on the Song Wind" (x1) - - "Monsters of Myth" (x1) - - "Treasure Vault" (x1) - - "Rusthenge" (x1) 'source_category': type: array path: disease.source_category is_null: false - values: 39 + values: 41 min_count: 1 max_count: 1 value_type: type: string path: disease.source_category.[] is_null: false - values: 39 + values: 41 different_values: 4 variants: - "Rulebooks" (x35) - - "Adventure Paths" (x2) + - "Adventure Paths" (x4) - "Lost Omens" (x1) - "Adventures" (x1) - 'source_markdown': - type: string - path: disease.source_markdown - is_null: false - values: 39 - different_values: 15 - variants: - - "[Gamemastery Guide](/Sources.aspx?ID=22) pg. 118" (x9) - - "[GM Core](/Sources.aspx?ID=218) pg. 88 2.0" (x9) - - "[Gamemastery Guide](/Sources.aspx?ID=22) pg. 119" (x5) - - "[GM Core](/Sources.aspx?ID=218) pg. 89 2.0" (x5) - - "[Gamemastery Guide](/Sources.aspx?ID=22) pg. 120" (x1) - - "[Bestiary 2](/Sources.aspx?ID=32) pg. 13 2.0" (x1) - - "[Bestiary 2](/Sources.aspx?ID=32) pg. 59 2.0" (x1) - - "[Bestiary 2](/Sources.aspx?ID=32) pg. 72 2.0" (x1) - - "[Bestiary 2](/Sources.aspx?ID=32) pg. 119 2.0" (x1) - - "[Bestiary 2](/Sources.aspx?ID=32) pg. 120 2.0" (x1) - - "[Pathfinder #155: Lord of the Black Sands](/Sources.aspx?ID=33) pg. 73" (x1) - - "[Pathfinder #170: Spoken on the Song Wind](/Sources.aspx?ID=97) pg. 47" (x1) - - "[Monsters of Myth](/Sources.aspx?ID=107) pg. 65" (x1) - - "[Treasure Vault](/Sources.aspx?ID=191) pg. 45 1.1" (x1) - - "[Rusthenge](/Sources.aspx?ID=215) pg. 19" (x1) - 'speed': - type: object - path: disease.speed - is_null: false - values: 39 - schema: 'stage_markdown': type: string path: disease.stage_markdown is_null: false - values: 39 - different_values: 39 (all different) + values: 41 + different_values: 41 (all different) variants: [omitted] 'summary_markdown': type: string path: disease.summary_markdown is_null: false - values: 39 - different_values: 27 + values: 41 + different_values: 29 variants: [omitted] [trait]: type: array path: disease.trait is_null: true - values: 32 + values: 34 min_count: 1 max_count: 4 value_type: type: string path: disease.trait.[] is_null: false - values: 53 - different_values: 10 + values: 59 + different_values: 11 variants: - - "Disease" (x32) + - "Disease" (x34) - "Virulent" (x9) + - "Uncommon" (x4) + - "Primal" (x3) - "Necromancy" (x2) - - "Primal" (x2) - "Occult" (x2) - - "Uncommon" (x2) - "Unique" (x1) - "Divine" (x1) - "Rare" (x1) - "Transmutation" (x1) + - "Earth" (x1) 'trait_markdown': type: string path: disease.trait_markdown is_null: false - values: 39 - different_values: 13 + values: 41 + different_values: 15 variants: - "[Disease](/Traits.aspx?ID=46)" (x8) - "[Disease](/Traits.aspx?ID=578)" (x8) @@ -258,45 +268,28 @@ schema: - "[Disease](/Traits.aspx?ID=46), [Divine](/Traits.aspx?ID=48), [Rare](/Traits.aspx?ID=137), [Transmutation](/Traits.aspx?ID=157)" (x1) - "[Disease](/Traits.aspx?ID=578), [Primal](/Traits.aspx?ID=676)" (x1) - "[Disease](/Traits.aspx?ID=578), [Occult](/Traits.aspx?ID=662)" (x1) + - "[Disease](/Traits.aspx?ID=578), [Uncommon](/Traits.aspx?ID=721)" (x1) + - "[Disease](/Traits.aspx?ID=578), [Earth](/Traits.aspx?ID=585), [Primal](/Traits.aspx?ID=676), [Uncommon](/Traits.aspx?ID=721)" (x1) 'type': type: string path: disease.type is_null: false - values: 39 + values: 41 different_values: 1 (all same) - variants: "Disease" (x39) + variants: "Disease" (x41) 'url': type: string path: disease.url is_null: false - values: 39 - different_values: 39 (all different) + values: 41 + different_values: 41 (all different) variants: [omitted] 'weakness': type: object path: disease.weakness is_null: false - values: 39 + values: 41 schema: - [level]: - type: integer - path: disease.level - is_null: true - values: 38 - different_values: 12 - variants: - - 1 (x8) - - 15 (x5) - - 7 (x4) - - 2 (x3) - - 3 (x3) - - 6 (x3) - - 11 (x3) - - 0 (x2) - - 4 (x2) - - 5 (x2) - - 8 (x2) - - 10 (x1) [onset_raw]: type: string path: disease.onset_raw @@ -315,26 +308,58 @@ schema: type: array path: disease.source_group is_null: true - values: 3 + values: 5 min_count: 1 max_count: 1 value_type: type: string path: disease.source_group.[] is_null: false - values: 3 - different_values: 3 + values: 5 + different_values: 4 variants: + - "Wardens of Wildwood" (x2) - "Extinction Curse" (x1) - "Strength of Thousands" (x1) - "Rusthenge" (x1) + 'source_markdown': + type: string + path: disease.source_markdown + is_null: false + values: 41 + different_values: 17 + variants: + - "[Gamemastery Guide](/Sources.aspx?ID=22) pg. 118" (x9) + - "[GM Core](/Sources.aspx?ID=218) pg. 88 2.0" (x9) + - "[Gamemastery Guide](/Sources.aspx?ID=22) pg. 119" (x5) + - "[GM Core](/Sources.aspx?ID=218) pg. 89 2.0" (x5) + - "[Gamemastery Guide](/Sources.aspx?ID=22) pg. 120" (x1) + - "[Bestiary 2](/Sources.aspx?ID=32) pg. 13 2.0" (x1) + - "[Bestiary 2](/Sources.aspx?ID=32) pg. 59 2.0" (x1) + - "[Bestiary 2](/Sources.aspx?ID=32) pg. 72 2.0" (x1) + - "[Bestiary 2](/Sources.aspx?ID=32) pg. 119 2.0" (x1) + - "[Bestiary 2](/Sources.aspx?ID=32) pg. 120 2.0" (x1) + - "[Pathfinder #155: Lord of the Black Sands](/Sources.aspx?ID=33) pg. 73" (x1) + - "[Pathfinder #170: Spoken on the Song Wind](/Sources.aspx?ID=97) pg. 47" (x1) + - "[Monsters of Myth](/Sources.aspx?ID=107) pg. 65" (x1) + - "[Treasure Vault](/Sources.aspx?ID=191) pg. 45 1.1" (x1) + - "[Rusthenge](/Sources.aspx?ID=215) pg. 19" (x1) + - "[Pathfinder #203 Shepherd of Decay](/Sources.aspx?ID=243) pg. 21" (x1) + - "[Pathfinder #203 Shepherd of Decay](/Sources.aspx?ID=243) pg. 33" (x1) + 'speed': + type: object + path: disease.speed + is_null: false + values: 41 + schema: [spoilers]: type: string path: disease.spoilers is_null: true - values: 3 - different_values: 3 + values: 5 + different_values: 4 variants: + - "Wardens of Wildwood" (x2) - "Extinction Curse" (x1) - "Strength of Thousands" (x1) - "Rusthenge" (x1) diff --git a/output/aonprd/domain.yaml b/output/aonprd/domain.yaml index 3d72443..3a4c226 100644 --- a/output/aonprd/domain.yaml +++ b/output/aonprd/domain.yaml @@ -35,7 +35,7 @@ schema: path: domain.deity_markdown is_null: false values: 124 - different_values: 123 + different_values: 121 variants: [omitted] 'domain': type: array @@ -72,6 +72,132 @@ schema: values: 124 different_values: 124 (all different) variants: [omitted] + 'legacy_id': + type: array + path: domain.legacy_id + is_null: false + values: 61 + min_count: 1 + max_count: 1 + value_type: + type: string + path: domain.legacy_id.[] + is_null: false + values: 61 + different_values: 61 + variants: [omitted] + 'markdown': + type: string + path: domain.markdown + is_null: false + values: 124 + different_values: 124 (all different) + variants: [omitted] + 'name': + type: string + path: domain.name + is_null: false + values: 124 + different_values: 64 + variants: [omitted] + 'primary_source_category': + type: string + path: domain.primary_source_category + is_null: false + values: 124 + different_values: 3 + variants: + - "Rulebooks" (x77) + - "Lost Omens" (x42) + - "Adventure Paths" (x5) + [primary_source_group]: + type: string + path: domain.primary_source_group + is_null: true + values: 5 + different_values: 4 + variants: + - "Age of Ashes" (x2) + - "Agents of Edgewatch" (x1) + - "Abomination Vaults" (x1) + - "Blood Lords" (x1) + 'rarity': + type: string + path: domain.rarity + is_null: false + values: 124 + different_values: 1 (all same) + variants: "common" (x124) + 'rarity_id': + type: integer + path: domain.rarity_id + is_null: false + values: 124 + different_values: 1 (all same) + variants: 1 (x124) + [apocryphal_spell_markdown]: + type: string + path: domain.apocryphal_spell_markdown + is_null: true + values: 10 + different_values: 5 + variants: + - "[Hollow Heart](/Spells.aspx?ID=1170)" (x2) + - "[String of Fate](/Spells.aspx?ID=1175)" (x2) + - "[Cinder Gaze](/Spells.aspx?ID=1167)" (x2) + - "[Frenzied Revelry](/Spells.aspx?ID=1169)" (x2) + - "[Victory Cry](/Spells.aspx?ID=1176)" (x2) + [deity]: + type: array + path: domain.deity + is_null: true + values: 120 + min_count: 1 + max_count: 133 + value_type: + type: string + path: domain.deity.[] + is_null: false + values: 4493 + different_values: 450 + variants: [omitted] + 'release_date': + type: string + path: domain.release_date + is_null: false + values: 124 + different_values: 9 + variants: + - "2019-08-01" (x37) + - "2023-11-15" (x37) + - "2025-01-30" (x24) + - "2020-01-29" (x18) + - "2023-08-02" (x3) + - "2019-10-16" (x2) + - "2020-11-15" (x1) + - "2021-04-07" (x1) + - "2022-12-14" (x1) + 'search_markdown': + type: string + path: domain.search_markdown + is_null: false + values: 124 + different_values: 124 (all different) + variants: [omitted] + [remaster_id]: + type: array + path: domain.remaster_id + is_null: true + values: 61 + min_count: 1 + max_count: 1 + value_type: + type: string + path: domain.remaster_id.[] + is_null: false + values: 61 + different_values: 61 + variants: [omitted] 'source_category': type: array path: domain.source_category @@ -89,18 +215,6 @@ schema: - "Rulebooks" (x77) - "Lost Omens" (x44) - "Adventure Paths" (x7) - [apocryphal_spell_markdown]: - type: string - path: domain.apocryphal_spell_markdown - is_null: true - values: 10 - different_values: 5 - variants: - - "[Hollow Heart](/Spells.aspx?ID=1170)" (x2) - - "[String of Fate](/Spells.aspx?ID=1175)" (x2) - - "[Cinder Gaze](/Spells.aspx?ID=1167)" (x2) - - "[Frenzied Revelry](/Spells.aspx?ID=1169)" (x2) - - "[Victory Cry](/Spells.aspx?ID=1176)" (x2) 'source_markdown': type: string path: domain.source_markdown @@ -141,20 +255,22 @@ schema: is_null: false values: 124 schema: - [deity]: - type: array - path: domain.deity + [pfs]: + type: string + path: domain.pfs is_null: true - values: 122 - min_count: 1 - max_count: 135 - value_type: - type: string - path: domain.deity.[] - is_null: false - values: 4691 - different_values: 449 - variants: [omitted] + values: 121 + different_values: 3 + variants: + - "Standard" (x119) + - "Restricted" (x1) + - "Limited" (x1) + 'resistance': + type: object + path: domain.resistance + is_null: false + values: 124 + schema: 'source': type: array path: domain.source @@ -178,122 +294,6 @@ schema: - "Pathfinder #161: Belly of the Black Whale" (x1) - "Pathfinder #165: Eyes of Empty Death" (x1) - "Pathfinder #186: Ghost King's Rage" (x1) - [legacy_id]: - type: array - path: domain.legacy_id - is_null: true - values: 59 - min_count: 1 - max_count: 1 - value_type: - type: string - path: domain.legacy_id.[] - is_null: false - values: 59 - different_values: 59 - variants: [omitted] - 'markdown': - type: string - path: domain.markdown - is_null: false - values: 124 - different_values: 124 (all different) - variants: [omitted] - 'name': - type: string - path: domain.name - is_null: false - values: 124 - different_values: 64 - variants: [omitted] - 'primary_source_category': - type: string - path: domain.primary_source_category - is_null: false - values: 124 - different_values: 3 - variants: - - "Rulebooks" (x77) - - "Lost Omens" (x42) - - "Adventure Paths" (x5) - 'rarity': - type: string - path: domain.rarity - is_null: false - values: 124 - different_values: 1 (all same) - variants: "common" (x124) - 'rarity_id': - type: integer - path: domain.rarity_id - is_null: false - values: 124 - different_values: 1 (all same) - variants: 1 (x124) - 'resistance': - type: object - path: domain.resistance - is_null: false - values: 124 - schema: - [pfs]: - type: string - path: domain.pfs - is_null: true - values: 121 - different_values: 3 - variants: - - "Standard" (x119) - - "Restricted" (x1) - - "Limited" (x1) - [primary_source_group]: - type: string - path: domain.primary_source_group - is_null: true - values: 5 - different_values: 4 - variants: - - "Age of Ashes" (x2) - - "Agents of Edgewatch" (x1) - - "Abomination Vaults" (x1) - - "Blood Lords" (x1) - 'release_date': - type: string - path: domain.release_date - is_null: false - values: 124 - different_values: 9 - variants: - - "2019-08-01" (x37) - - "2023-11-15" (x37) - - "2025-01-30" (x24) - - "2020-01-29" (x18) - - "2023-08-02" (x3) - - "2019-10-16" (x2) - - "2020-11-15" (x1) - - "2021-04-07" (x1) - - "2022-12-14" (x1) - [remaster_id]: - type: array - path: domain.remaster_id - is_null: true - values: 59 - min_count: 1 - max_count: 1 - value_type: - type: string - path: domain.remaster_id.[] - is_null: false - values: 59 - different_values: 59 - variants: [omitted] - 'search_markdown': - type: string - path: domain.search_markdown - is_null: false - values: 124 - different_values: 124 (all different) - variants: [omitted] [source_group]: type: array path: domain.source_group diff --git a/output/aonprd/druidic-order.yaml b/output/aonprd/druidic-order.yaml index aacb572..86c3dba 100644 --- a/output/aonprd/druidic-order.yaml +++ b/output/aonprd/druidic-order.yaml @@ -1,19 +1,19 @@ name: druidic-order -values: 11 +values: 13 schema: 'category': type: string path: druidic-order.category is_null: false - values: 11 + values: 13 different_values: 1 (all same) - variants: "druidic-order" (x11) + variants: "druidic-order" (x13) 'id': type: string path: druidic-order.id is_null: false - values: 11 - different_values: 11 (all different) + values: 13 + different_values: 13 (all different) variants: - "druidic-order-1" (x1) - "druidic-order-2" (x1) @@ -26,10 +26,79 @@ schema: - "druidic-order-9" (x1) - "druidic-order-10" (x1) - "druidic-order-11" (x1) - 'legacy_id': + - "druidic-order-12" (x1) + - "druidic-order-13" (x1) + 'markdown': + type: string + path: druidic-order.markdown + is_null: false + values: 13 + different_values: 13 (all different) + variants: [omitted] + 'name': + type: string + path: druidic-order.name + is_null: false + values: 13 + different_values: 10 + variants: + - "Animal" (x2) + - "Leaf" (x2) + - "Storm" (x2) + - "Wild" (x1) + - "Flame" (x1) + - "Stone" (x1) + - "Wave" (x1) + - "Untamed" (x1) + - "Cultivation Order (Leaf Order Variant)" (x1) + - "Spore Order (Leaf Order Variant)" (x1) + 'pfs': + type: string + path: druidic-order.pfs + is_null: false + values: 13 + different_values: 2 + variants: + - "Standard" (x11) + - "Limited" (x2) + 'primary_source_category': + type: string + path: druidic-order.primary_source_category + is_null: false + values: 13 + different_values: 2 + variants: + - "Rulebooks" (x11) + - "Adventure Paths" (x2) + 'rarity': + type: string + path: druidic-order.rarity + is_null: false + values: 13 + different_values: 1 (all same) + variants: "common" (x13) + 'rarity_id': + type: integer + path: druidic-order.rarity_id + is_null: false + values: 13 + different_values: 1 (all same) + variants: 1 (x13) + 'release_date': + type: string + path: druidic-order.release_date + is_null: false + values: 13 + different_values: 4 + variants: + - "2019-08-01" (x4) + - "2023-11-15" (x4) + - "2021-09-01" (x3) + - "2024-05-15" (x2) + [legacy_id]: type: array path: druidic-order.legacy_id - is_null: false + is_null: true values: 4 min_count: 1 max_count: 1 @@ -44,71 +113,113 @@ schema: - "druidic-order-2" (x1) - "druidic-order-3" (x1) - "druidic-order-4" (x1) - 'markdown': + 'primary_source_group': type: string - path: druidic-order.markdown + path: druidic-order.primary_source_group is_null: false - values: 11 - different_values: 11 (all different) - variants: [omitted] - 'name': - type: string - path: druidic-order.name - is_null: false - values: 11 - different_values: 8 - variants: - - "Animal" (x2) - - "Leaf" (x2) - - "Storm" (x2) - - "Wild" (x1) - - "Flame" (x1) - - "Stone" (x1) - - "Wave" (x1) - - "Untamed" (x1) - 'pfs': - type: string - path: druidic-order.pfs - is_null: false - values: 11 + values: 2 different_values: 1 (all same) - variants: "Standard" (x11) - 'primary_source_category': - type: string - path: druidic-order.primary_source_category - is_null: false - values: 11 - different_values: 1 (all same) - variants: "Rulebooks" (x11) - 'rarity': - type: string - path: druidic-order.rarity - is_null: false - values: 11 - different_values: 1 (all same) - variants: "common" (x11) - 'rarity_id': - type: integer - path: druidic-order.rarity_id - is_null: false - values: 11 - different_values: 1 (all same) - variants: 1 (x11) - 'release_date': - type: string - path: druidic-order.release_date - is_null: false - values: 11 - different_values: 3 - variants: - - "2019-08-01" (x4) - - "2023-11-15" (x4) - - "2021-09-01" (x3) + variants: "Wardens of Wildwood" (x2) 'resistance': type: object path: druidic-order.resistance is_null: false - values: 11 + values: 13 + schema: + 'search_markdown': + type: string + path: druidic-order.search_markdown + is_null: false + values: 13 + different_values: 13 (all different) + variants: [omitted] + 'source_category': + type: array + path: druidic-order.source_category + is_null: false + values: 13 + min_count: 1 + max_count: 1 + value_type: + type: string + path: druidic-order.source_category.[] + is_null: false + values: 13 + different_values: 2 + variants: + - "Rulebooks" (x11) + - "Adventure Paths" (x2) + 'source_markdown': + type: string + path: druidic-order.source_markdown + is_null: false + values: 13 + different_values: 6 + variants: + - "[Secrets of Magic](/Sources.aspx?ID=96) pg. 198 2.0" (x3) + - "[Player Core](/Sources.aspx?ID=216) pg. 125 2.0" (x3) + - "[Core Rulebook](/Sources.aspx?ID=1) pg. 131 4.0" (x2) + - "[Core Rulebook](/Sources.aspx?ID=1) pg. 132 4.0" (x2) + - "[Pathfinder #202: Severed at the Root](/Sources.aspx?ID=242) pg. 72" (x2) + - "[Player Core](/Sources.aspx?ID=216) pg. 126 2.0" (x1) + 'speed': + type: object + path: druidic-order.speed + is_null: false + values: 13 + schema: + 'summary_markdown': + type: string + path: druidic-order.summary_markdown + is_null: false + values: 13 + different_values: 13 (all different) + variants: + - "You have a strong connection to beasts, and you are allied with a beast companion. You are trained in Athletics . You also gain the Animal …" (x1) + - "You revere plants and the bounty of nature, acting as both a gardener and warden for the wilderness, teaching sustainable techniques to communities, …" (x1) + - "You carry the fury of the storm within you, channeling it to terrifying effect and riding the winds through the sky. You are trained in Acrobatics . …" (x1) + - "The savage, uncontrollable call of the natural world infuses you, granting you the ability to change your shape and take on the ferocious form of a …" (x1) + - "You feel a kinship with flames and can use them for succor and destruction. You're trained in Acrobatics . You also gain the Fire Lung druid feat. …" (x1) + - "You're as enduring as stone, and you take comfort in its steadfast presence, both natural and worked. You're trained in Crafting . You also gain the …" (x1) + - "Water is the source of life, and you've learned to shape how it flows. You're trained in Medicine . You also gain the Shore Step druid feat. You …" (x1) + - "You have a strong connection to beasts, always feeling like you understood them (and they you) better than your own people. You stand for the animals …" (x1) + - "You revere the bounty of nature, acting as both a gardener and warden for the wilderness. With your leshy familiar at your side, you help areas …" (x1) + - "Whether you were born under a tempest or survived a bolt of lightning from a clear blue sky, you now carry the fury of the storm in your heart, …" (x1) + - "The uncontrollable call of the natural world courses through your body. You might have been raised by a wild animal, or you might have come to reject …" (x1) + - "You believe that nature is best tended, pruned, and maintained. Nature is inherently random and chaotic out of necessity of survival—the flower …" (x1) + - "You’re fascinated by molds and fungi , and you understand the important role they play in the continued health of ecosystems. This interest often …" (x1) + 'type': + type: string + path: druidic-order.type + is_null: false + values: 13 + different_values: 1 (all same) + variants: "Druidic Order" (x13) + 'url': + type: string + path: druidic-order.url + is_null: false + values: 13 + different_values: 13 (all different) + variants: + - "/DruidicOrders.aspx?ID=1" (x1) + - "/DruidicOrders.aspx?ID=2" (x1) + - "/DruidicOrders.aspx?ID=3" (x1) + - "/DruidicOrders.aspx?ID=4" (x1) + - "/DruidicOrders.aspx?ID=5" (x1) + - "/DruidicOrders.aspx?ID=6" (x1) + - "/DruidicOrders.aspx?ID=7" (x1) + - "/DruidicOrders.aspx?ID=8" (x1) + - "/DruidicOrders.aspx?ID=9" (x1) + - "/DruidicOrders.aspx?ID=10" (x1) + - "/DruidicOrders.aspx?ID=11" (x1) + - "/DruidicOrders.aspx?ID=12" (x1) + - "/DruidicOrders.aspx?ID=13" (x1) + 'weakness': + type: object + path: druidic-order.weakness + is_null: false + values: 13 schema: [remaster_id]: type: array @@ -128,108 +239,35 @@ schema: - "druidic-order-9" (x1) - "druidic-order-10" (x1) - "druidic-order-11" (x1) - 'search_markdown': - type: string - path: druidic-order.search_markdown - is_null: false - values: 11 - different_values: 11 (all different) - variants: [omitted] 'source': type: array path: druidic-order.source is_null: false - values: 11 + values: 13 min_count: 1 max_count: 1 value_type: type: string path: druidic-order.source.[] is_null: false - values: 11 - different_values: 3 + values: 13 + different_values: 4 variants: - "Core Rulebook" (x4) - "Player Core" (x4) - "Secrets of Magic" (x3) - 'source_category': + - "Pathfinder #202: Severed at the Root" (x2) + 'source_group': type: array - path: druidic-order.source_category + path: druidic-order.source_group is_null: false - values: 11 + values: 2 min_count: 1 max_count: 1 value_type: type: string - path: druidic-order.source_category.[] + path: druidic-order.source_group.[] is_null: false - values: 11 + values: 2 different_values: 1 (all same) - variants: "Rulebooks" (x11) - 'source_markdown': - type: string - path: druidic-order.source_markdown - is_null: false - values: 11 - different_values: 5 - variants: - - "[Secrets of Magic](/Sources.aspx?ID=96) pg. 198 2.0" (x3) - - "[Player Core](/Sources.aspx?ID=216) pg. 125 2.0" (x3) - - "[Core Rulebook](/Sources.aspx?ID=1) pg. 131 4.0" (x2) - - "[Core Rulebook](/Sources.aspx?ID=1) pg. 132 4.0" (x2) - - "[Player Core](/Sources.aspx?ID=216) pg. 126 2.0" (x1) - 'speed': - type: object - path: druidic-order.speed - is_null: false - values: 11 - schema: - 'summary_markdown': - type: string - path: druidic-order.summary_markdown - is_null: false - values: 11 - different_values: 11 (all different) - variants: - - "You have a strong connection to beasts, and you are allied with a beast companion. You are trained in Athletics . You also gain the Animal …" (x1) - - "You revere plants and the bounty of nature, acting as both a gardener and warden for the wilderness, teaching sustainable techniques to communities, …" (x1) - - "You carry the fury of the storm within you, channeling it to terrifying effect and riding the winds through the sky. You are trained in Acrobatics . …" (x1) - - "The savage, uncontrollable call of the natural world infuses you, granting you the ability to change your shape and take on the ferocious form of a …" (x1) - - "You feel a kinship with flames and can use them for succor and destruction. You're trained in Acrobatics . You also gain the Fire Lung druid feat. …" (x1) - - "You're as enduring as stone, and you take comfort in its steadfast presence, both natural and worked. You're trained in Crafting . You also gain the …" (x1) - - "Water is the source of life, and you've learned to shape how it flows. You're trained in Medicine . You also gain the Shore Step druid feat. You …" (x1) - - "You have a strong connection to beasts, always feeling like you understood them (and they you) better than your own people. You stand for the animals …" (x1) - - "You revere the bounty of nature, acting as both a gardener and warden for the wilderness. With your leshy familiar at your side, you help areas …" (x1) - - "Whether you were born under a tempest or survived a bolt of lightning from a clear blue sky, you now carry the fury of the storm in your heart, …" (x1) - - "The uncontrollable call of the natural world courses through your body. You might have been raised by a wild animal, or you might have come to reject …" (x1) - 'type': - type: string - path: druidic-order.type - is_null: false - values: 11 - different_values: 1 (all same) - variants: "Druidic Order" (x11) - 'url': - type: string - path: druidic-order.url - is_null: false - values: 11 - different_values: 11 (all different) - variants: - - "/DruidicOrders.aspx?ID=1" (x1) - - "/DruidicOrders.aspx?ID=2" (x1) - - "/DruidicOrders.aspx?ID=3" (x1) - - "/DruidicOrders.aspx?ID=4" (x1) - - "/DruidicOrders.aspx?ID=5" (x1) - - "/DruidicOrders.aspx?ID=6" (x1) - - "/DruidicOrders.aspx?ID=7" (x1) - - "/DruidicOrders.aspx?ID=8" (x1) - - "/DruidicOrders.aspx?ID=9" (x1) - - "/DruidicOrders.aspx?ID=10" (x1) - - "/DruidicOrders.aspx?ID=11" (x1) - 'weakness': - type: object - path: druidic-order.weakness - is_null: false - values: 11 - schema: + variants: "Wardens of Wildwood" (x2) diff --git a/output/aonprd/equipment-bonus.yaml b/output/aonprd/equipment-bonus.yaml index b582e19..9e743d3 100644 --- a/output/aonprd/equipment-bonus.yaml +++ b/output/aonprd/equipment-bonus.yaml @@ -1,23 +1,23 @@ name: equipment-bonus -values: 816 +values: 850 schema: 'base_item_markdown': type: string path: equipment-bonus.base_item_markdown is_null: false - values: 816 + values: 850 different_values: 31 variants: [omitted] 'bulk': type: float path: equipment-bonus.bulk is_null: false - values: 816 + values: 850 different_values: 10 variants: - - 0,1 (x370) - - 0 (x287) - - 1 (x97) + - 0,1 (x394) + - 0 (x296) + - 1 (x98) - 2 (x40) - 3 (x12) - 16 (x4) @@ -29,11 +29,11 @@ schema: type: string path: equipment-bonus.bulk_raw is_null: true - values: 529 + values: 554 different_values: 9 variants: - - "L" (x370) - - "1" (x97) + - "L" (x394) + - "1" (x98) - "2" (x40) - "3" (x12) - "16" (x4) @@ -45,51 +45,51 @@ schema: type: string path: equipment-bonus.category is_null: false - values: 816 + values: 850 different_values: 1 (all same) - variants: "item-bonus" (x816) + variants: "item-bonus" (x850) 'summary_markdown': type: string path: equipment-bonus.summary_markdown is_null: false - values: 816 - different_values: 602 + values: 850 + different_values: 632 variants: [omitted] 'trait_markdown': type: string path: equipment-bonus.trait_markdown is_null: false - values: 816 - different_values: 269 + values: 850 + different_values: 282 variants: [omitted] 'type': type: string path: equipment-bonus.type is_null: false - values: 816 + values: 850 different_values: 1 (all same) - variants: "Item" (x816) + variants: "Item" (x850) 'url': type: string path: equipment-bonus.url is_null: false - values: 816 - different_values: 460 + values: 850 + different_values: 485 variants: [omitted] 'weakness': type: object path: equipment-bonus.weakness is_null: false - values: 816 + values: 850 schema: [actions]: type: string path: equipment-bonus.actions is_null: true - values: 150 + values: 154 different_values: 5 variants: - - "Single Action" (x143) + - "Single Action" (x147) - "Free Action" (x3) - "Three Actions" (x2) - "Reaction" (x1) @@ -105,21 +105,21 @@ schema: type: integer path: equipment-bonus.duration is_null: true - values: 13 + values: 14 different_values: 6 variants: + - 86400 (x3) - 21600 (x3) - 14400 (x3) - 10800 (x2) - - 86400 (x2) - 28800 (x2) - 3600 (x1) [duration_raw]: type: string path: equipment-bonus.duration_raw is_null: true - values: 13 - different_values: 6 + values: 14 + different_values: 7 variants: - "6 hours" (x3) - "4 hours" (x3) @@ -127,6 +127,7 @@ schema: - "1 day" (x2) - "8 hours" (x2) - "1 hour" (x1) + - "24 hours" (x1) [element]: type: array path: equipment-bonus.element @@ -151,26 +152,26 @@ schema: type: string path: equipment-bonus.hands is_null: true - values: 20 + values: 31 different_values: 2 variants: - - "2" (x17) - - "1" (x3) + - "2" (x19) + - "1" (x12) 'id': type: string path: equipment-bonus.id is_null: false - values: 816 - different_values: 816 (all different) + values: 850 + different_values: 850 (all different) variants: [omitted] [item_bonus_action]: type: string path: equipment-bonus.item_bonus_action is_null: true - values: 107 + values: 110 different_values: 6 variants: - - "Perception" (x77) + - "Perception" (x80) - "Recall Knowledge" (x15) - "Decipher Writing" (x5) - "Learn a Spell" (x4) @@ -180,29 +181,29 @@ schema: type: boolean path: equipment-bonus.item_bonus_consumable is_null: false - values: 816 + values: 850 different_values: 2 variants: - - False (x666) - - True (x150) + - False (x699) + - True (x151) 'item_bonus_value': type: integer path: equipment-bonus.item_bonus_value is_null: false - values: 816 + values: 850 different_values: 5 variants: - - 2 (x346) - - 1 (x294) - - 3 (x144) + - 2 (x361) + - 1 (x303) + - 3 (x154) - 4 (x28) - -1 (x4) 'item_category': type: string path: equipment-bonus.item_category is_null: false - values: 816 - different_values: 24 + values: 850 + different_values: 25 variants: [omitted] [frequency]: type: string @@ -215,8 +216,8 @@ schema: type: string path: equipment-bonus.item_bonus_note is_null: true - values: 506 - different_values: 300 + values: 540 + different_values: 327 variants: [omitted] [item_child_id]: type: array @@ -236,49 +237,49 @@ schema: type: string path: equipment-bonus.item_subcategory is_null: true - values: 571 - different_values: 29 + values: 581 + different_values: 30 variants: [omitted] 'speed': type: object path: equipment-bonus.speed is_null: false - values: 816 + values: 850 schema: [legacy_id]: type: array path: equipment-bonus.legacy_id is_null: true - values: 163 + values: 164 min_count: 1 max_count: 1 value_type: type: string path: equipment-bonus.legacy_id.[] is_null: false - values: 163 - different_values: 67 + values: 164 + different_values: 68 variants: [omitted] 'level': type: integer path: equipment-bonus.level is_null: false - values: 816 + values: 850 different_values: 26 variants: [omitted] 'markdown': type: string path: equipment-bonus.markdown is_null: false - values: 816 - different_values: 460 + values: 850 + different_values: 485 variants: [omitted] 'name': type: string path: equipment-bonus.name is_null: false - values: 816 - different_values: 608 + values: 850 + different_values: 638 variants: [omitted] [onset_raw]: type: string @@ -294,19 +295,19 @@ schema: type: integer path: equipment-bonus.rarity_id is_null: false - values: 816 + values: 850 different_values: 4 variants: - 1 (x476) - - 2 (x199) - - 3 (x83) - - 4 (x58) + - 2 (x210) + - 3 (x99) + - 4 (x65) 'release_date': type: string path: equipment-bonus.release_date is_null: false - values: 816 - different_values: 41 + values: 850 + different_values: 49 variants: [omitted] [requirement_markdown]: type: string @@ -319,27 +320,27 @@ schema: type: object path: equipment-bonus.resistance is_null: false - values: 816 + values: 850 schema: [pfs]: type: string path: equipment-bonus.pfs is_null: true - values: 726 + values: 760 different_values: 3 variants: - "Standard" (x624) - - "Limited" (x82) + - "Limited" (x116) - "Restricted" (x20) 'primary_source_category': type: string path: equipment-bonus.primary_source_category is_null: false - values: 816 + values: 850 different_values: 6 variants: - "Rulebooks" (x558) - - "Adventure Paths" (x115) + - "Adventure Paths" (x149) - "Lost Omens" (x112) - "Adventures" (x24) - "Blog Posts" (x4) @@ -348,72 +349,54 @@ schema: type: string path: equipment-bonus.rarity is_null: false - values: 816 + values: 850 different_values: 4 variants: - "common" (x476) - - "uncommon" (x199) - - "rare" (x83) - - "unique" (x58) + - "uncommon" (x210) + - "rare" (x99) + - "unique" (x65) [price_raw]: type: string path: equipment-bonus.price_raw is_null: true - values: 725 - different_values: 146 + values: 758 + different_values: 148 variants: [omitted] [primary_source_group]: type: string path: equipment-bonus.primary_source_group is_null: true - values: 139 - different_values: 19 - variants: - - "Stolen Fate" (x42) - - "Season of Ghosts" (x24) - - "Blood Lords" (x10) - - "Kingmaker" (x8) - - "Extinction Curse" (x6) - - "Prey for Death" (x6) - - "Agents of Edgewatch" (x5) - - "Malevolence" (x5) - - "Strength of Thousands" (x5) - - "Age of Ashes" (x4) - - "Night of the Gray Death" (x4) - - "Crown of the Kobold King" (x4) - - "Gatewalkers" (x4) - - "The Fall of Plaguestone" (x3) - - "Abomination Vaults" (x3) - - "Fists of the Ruby Phoenix" (x2) - - "Quest for the Frozen Flame" (x2) - - "Troubles in Otari" (x1) - - "Shadows at Sundown" (x1) + values: 173 + different_values: 22 + variants: [omitted] [remaster_id]: type: array path: equipment-bonus.remaster_id is_null: true - values: 65 + values: 67 min_count: 1 max_count: 1 value_type: type: string path: equipment-bonus.remaster_id.[] is_null: false - values: 65 - different_values: 43 + values: 67 + different_values: 44 variants: [omitted] [saving_throw_markdown]: type: string path: equipment-bonus.saving_throw_markdown is_null: true - values: 13 - different_values: 5 + values: 14 + different_values: 6 variants: - "DC 20 Fortitude" (x4) - "DC 25 Fortitude" (x3) - "DC 15 Fortitude" (x3) - "DC 16 Fortitude" (x2) - "DC 13 Fortitude" (x1) + - "DC 43 Fortitude" (x1) [school]: type: string path: equipment-bonus.school @@ -433,46 +416,46 @@ schema: type: string path: equipment-bonus.search_markdown is_null: false - values: 816 - different_values: 631 + values: 850 + different_values: 661 variants: [omitted] 'skill_markdown': type: string path: equipment-bonus.skill_markdown is_null: false - values: 816 - different_values: 34 + values: 850 + different_values: 35 variants: [omitted] 'source': type: array path: equipment-bonus.source is_null: false - values: 816 + values: 850 min_count: 1 max_count: 2 value_type: type: string path: equipment-bonus.source.[] is_null: false - values: 843 - different_values: 64 + values: 877 + different_values: 72 variants: [omitted] 'source_category': type: array path: equipment-bonus.source_category is_null: false - values: 816 + values: 850 min_count: 1 max_count: 2 value_type: type: string path: equipment-bonus.source_category.[] is_null: false - values: 838 + values: 872 different_values: 6 variants: - "Rulebooks" (x564) - - "Adventure Paths" (x126) + - "Adventure Paths" (x160) - "Lost Omens" (x113) - "Adventures" (x24) - "Comics" (x7) @@ -481,88 +464,50 @@ schema: type: array path: equipment-bonus.source_group is_null: true - values: 150 + values: 184 min_count: 1 max_count: 1 value_type: type: string path: equipment-bonus.source_group.[] is_null: false - values: 150 - different_values: 19 - variants: - - "Stolen Fate" (x42) - - "Season of Ghosts" (x24) - - "Blood Lords" (x10) - - "Extinction Curse" (x8) - - "Agents of Edgewatch" (x8) - - "Kingmaker" (x8) - - "Strength of Thousands" (x7) - - "Age of Ashes" (x6) - - "Prey for Death" (x6) - - "Gatewalkers" (x5) - - "Malevolence" (x5) - - "Night of the Gray Death" (x4) - - "Crown of the Kobold King" (x4) - - "The Fall of Plaguestone" (x3) - - "Abomination Vaults" (x3) - - "Quest for the Frozen Flame" (x3) - - "Fists of the Ruby Phoenix" (x2) - - "Troubles in Otari" (x1) - - "Shadows at Sundown" (x1) + values: 184 + different_values: 22 + variants: [omitted] 'source_markdown': type: string path: equipment-bonus.source_markdown is_null: false - values: 816 - different_values: 303 + values: 850 + different_values: 318 variants: [omitted] [spoilers]: type: string path: equipment-bonus.spoilers is_null: true - values: 136 - different_values: 19 - variants: - - "Stolen Fate" (x42) - - "Season of Ghosts" (x24) - - "Blood Lords" (x10) - - "Kingmaker" (x8) - - "Extinction Curse" (x6) - - "Prey for Death" (x6) - - "Malevolence" (x5) - - "Strength of Thousands" (x5) - - "Age of Ashes" (x4) - - "Night of the Gray Death" (x4) - - "Crown of the Kobold King" (x4) - - "Gatewalkers" (x4) - - "The Fall of Plaguestone" (x3) - - "Abomination Vaults" (x3) - - "Agents of Edgewatch" (x2) - - "Fists of the Ruby Phoenix" (x2) - - "Quest for the Frozen Flame" (x2) - - "Troubles in Otari" (x1) - - "Shadows at Sundown" (x1) + values: 170 + different_values: 22 + variants: [omitted] [stage_markdown]: type: string path: equipment-bonus.stage_markdown is_null: true - values: 425 - different_values: 8 + values: 451 + different_values: 9 variants: [omitted] [trait]: type: array path: equipment-bonus.trait is_null: true - values: 791 + values: 825 min_count: 1 max_count: 7 value_type: type: string path: equipment-bonus.trait.[] is_null: false - values: 2843 - different_values: 86 + values: 2960 + different_values: 87 variants: [omitted] [trigger_markdown]: type: string @@ -579,13 +524,13 @@ schema: type: string path: equipment-bonus.usage_markdown is_null: true - values: 766 - different_values: 62 + values: 799 + different_values: 64 variants: [omitted] 'spell_markdown': type: string path: equipment-bonus.spell_markdown is_null: false - values: 816 + values: 850 different_values: 1 (all same) - variants: "" (x816) + variants: "" (x850) diff --git a/output/aonprd/equipment-sub.yaml b/output/aonprd/equipment-sub.yaml index 702dd7c..2effc2e 100644 --- a/output/aonprd/equipment-sub.yaml +++ b/output/aonprd/equipment-sub.yaml @@ -1,19 +1,19 @@ name: equipment-sub -values: 2879 +values: 2980 schema: 'bulk': type: float path: equipment-sub.bulk is_null: false - values: 2776 + values: 2876 different_values: 11 variants: - - 0,1 (x1267) - - 0 (x1021) - - 1 (x384) + - 0,1 (x1335) + - 0 (x1045) + - 1 (x390) - 2 (x64) - 3 (x18) - - 4 (x10) + - 4 (x12) - 16 (x5) - 8 (x3) - 6 (x2) @@ -23,33 +23,33 @@ schema: type: string path: equipment-sub.actions is_null: true - values: 796 + values: 832 different_values: 5 variants: - - "Single Action" (x648) + - "Single Action" (x683) - "Two Actions" (x89) - - "Free Action" (x41) + - "Free Action" (x42) - "Three Actions" (x15) - "Reaction" (x3) 'base_item_markdown': type: string path: equipment-sub.base_item_markdown is_null: false - values: 2776 + values: 2876 different_values: 39 variants: [omitted] [bulk_raw]: type: string path: equipment-sub.bulk_raw is_null: true - values: 1755 + values: 1831 different_values: 10 variants: - - "L" (x1267) - - "1" (x384) + - "L" (x1335) + - "1" (x390) - "2" (x64) - "3" (x18) - - "4" (x10) + - "4" (x12) - "16" (x5) - "8" (x3) - "6" (x2) @@ -59,16 +59,16 @@ schema: type: string path: equipment-sub.category is_null: false - values: 2879 + values: 2980 different_values: 2 variants: - - "equipment" (x2776) - - "category-page" (x103) + - "equipment" (x2876) + - "category-page" (x104) 'item_category': type: string path: equipment-sub.item_category is_null: false - values: 2776 + values: 2876 different_values: 27 variants: [omitted] [duration]: @@ -95,19 +95,19 @@ schema: type: array path: equipment-sub.element is_null: true - values: 225 + values: 231 min_count: 1 max_count: 1 value_type: type: string path: equipment-sub.element.[] is_null: false - values: 225 + values: 231 different_values: 6 variants: - "Fire" (x104) - "Water" (x34) - - "Wood" (x27) + - "Wood" (x33) - "Air" (x25) - "Earth" (x24) - "Metal" (x11) @@ -115,8 +115,8 @@ schema: type: string path: equipment-sub.id is_null: false - values: 2879 - different_values: 2879 (all different) + values: 2980 + different_values: 2980 (all different) variants: [omitted] [hands]: type: string @@ -131,33 +131,33 @@ schema: type: string path: equipment-sub.item_subcategory is_null: true - values: 1920 + values: 1985 different_values: 53 variants: [omitted] 'level': type: integer path: equipment-sub.level is_null: false - values: 2776 + values: 2876 different_values: 22 variants: [omitted] 'markdown': type: string path: equipment-sub.markdown is_null: false - values: 2879 - different_values: 975 + values: 2980 + different_values: 1006 variants: [omitted] 'primary_source_category': type: string path: equipment-sub.primary_source_category is_null: false - values: 2879 + values: 2980 different_values: 7 variants: - - "Rulebooks" (x2081) + - "Rulebooks" (x2082) - "Lost Omens" (x525) - - "Adventure Paths" (x217) + - "Adventure Paths" (x317) - "Comics" (x28) - "Adventures" (x17) - "Blog Posts" (x8) @@ -166,10 +166,10 @@ schema: type: string path: equipment-sub.spell_markdown is_null: false - values: 2776 + values: 2876 different_values: 14 variants: - - "" (x2747) + - "" (x2847) - "[Shocking Grasp](/Spells.aspx?ID=283)" (x4) - "[Fireball](/Spells.aspx?ID=119)" (x3) - "[Heal](/Spells.aspx?ID=148)" (x3) @@ -187,29 +187,29 @@ schema: type: string path: equipment-sub.trait_markdown is_null: false - values: 2776 - different_values: 438 + values: 2876 + different_values: 457 variants: [omitted] [legacy_id]: type: array path: equipment-sub.legacy_id is_null: true - values: 674 + values: 676 min_count: 1 max_count: 1 value_type: type: string path: equipment-sub.legacy_id.[] is_null: false - values: 674 - different_values: 212 + values: 676 + different_values: 213 variants: [omitted] 'name': type: string path: equipment-sub.name is_null: false - values: 2879 - different_values: 2878 + values: 2980 + different_values: 2979 variants: [omitted] [onset_raw]: type: string @@ -224,120 +224,102 @@ schema: type: string path: equipment-sub.primary_source_group is_null: true - values: 234 - different_values: 18 - variants: - - "Quest for the Frozen Flame" (x31) - - "Strength of Thousands" (x27) - - "Blood Lords" (x26) - - "Outlaws of Alkenstar" (x23) - - "Gatewalkers" (x17) - - "Sky King's Tomb" (x16) - - "Agents of Edgewatch" (x15) - - "Abomination Vaults" (x12) - - "Extinction Curse" (x12) - - "Prey for Death" (x12) - - "Kingmaker" (x10) - - "Age of Ashes" (x10) - - "Season of Ghosts" (x7) - - "Seven Dooms for Sandpoint" (x6) - - "Fists of the Ruby Phoenix" (x4) - - "The Slithering" (x3) - - "Night of the Gray Death" (x2) - - "Stolen Fate" (x1) + values: 334 + different_values: 22 + variants: [omitted] 'rarity_id': type: integer path: equipment-sub.rarity_id is_null: false - values: 2879 + values: 2980 different_values: 4 variants: - - 1 (x1852) - - 2 (x802) - - 3 (x219) + - 1 (x1853) + - 2 (x884) + - 3 (x237) - 4 (x6) 'release_date': type: string path: equipment-sub.release_date is_null: false - values: 2879 - different_values: 57 + values: 2980 + different_values: 65 variants: [omitted] 'resistance': type: object path: equipment-sub.resistance is_null: false - values: 2879 + values: 2980 schema: [pfs]: type: string path: equipment-sub.pfs is_null: true - values: 2691 + values: 2791 different_values: 3 variants: - "Standard" (x2379) - - "Limited" (x269) + - "Limited" (x369) - "Restricted" (x43) [price_raw]: type: string path: equipment-sub.price_raw is_null: true - values: 2752 - different_values: 389 + values: 2852 + different_values: 393 variants: [omitted] [remaster_id]: type: array path: equipment-sub.remaster_id is_null: true - values: 193 + values: 195 min_count: 1 max_count: 2 value_type: type: string path: equipment-sub.remaster_id.[] is_null: false - values: 200 - different_values: 57 + values: 202 + different_values: 58 variants: [omitted] 'search_markdown': type: string path: equipment-sub.search_markdown is_null: false - values: 2879 - different_values: 2857 + values: 2980 + different_values: 2958 variants: [omitted] 'source': type: array path: equipment-sub.source is_null: false - values: 2879 + values: 2980 min_count: 1 max_count: 2 value_type: type: string path: equipment-sub.source.[] is_null: false - values: 2991 - different_values: 83 + values: 3094 + different_values: 91 variants: [omitted] 'source_category': type: array path: equipment-sub.source_category is_null: false - values: 2879 + values: 2980 min_count: 1 max_count: 2 value_type: type: string path: equipment-sub.source_category.[] is_null: false - values: 2933 + values: 3036 different_values: 7 variants: - - "Rulebooks" (x2088) + - "Rulebooks" (x2089) - "Lost Omens" (x538) - - "Adventure Paths" (x229) + - "Adventure Paths" (x331) - "Adventures" (x36) - "Comics" (x31) - "Blog Posts" (x8) @@ -346,14 +328,14 @@ schema: type: string path: equipment-sub.source_markdown is_null: false - values: 2879 - different_values: 528 + values: 2980 + different_values: 550 variants: [omitted] 'speed': type: object path: equipment-sub.speed is_null: false - values: 2879 + values: 2980 schema: [school]: type: string @@ -374,119 +356,83 @@ schema: type: array path: equipment-sub.source_group is_null: true - values: 265 + values: 367 min_count: 1 max_count: 1 value_type: type: string path: equipment-sub.source_group.[] is_null: false - values: 265 - different_values: 19 - variants: - - "Strength of Thousands" (x36) - - "Quest for the Frozen Flame" (x31) - - "Blood Lords" (x26) - - "Outlaws of Alkenstar" (x23) - - "Gatewalkers" (x17) - - "Sky King's Tomb" (x16) - - "The Slithering" (x15) - - "Agents of Edgewatch" (x15) - - "Extinction Curse" (x14) - - "Abomination Vaults" (x12) - - "Prey for Death" (x12) - - "Age of Ashes" (x11) - - "Kingmaker" (x10) - - "Season of Ghosts" (x7) - - "The Fall of Plaguestone" (x7) - - "Seven Dooms for Sandpoint" (x6) - - "Fists of the Ruby Phoenix" (x4) - - "Night of the Gray Death" (x2) - - "Stolen Fate" (x1) + values: 367 + different_values: 23 + variants: [omitted] [spoilers]: type: string path: equipment-sub.spoilers is_null: true - values: 223 - different_values: 17 - variants: - - "Quest for the Frozen Flame" (x31) - - "Strength of Thousands" (x27) - - "Blood Lords" (x26) - - "Outlaws of Alkenstar" (x23) - - "Gatewalkers" (x17) - - "Sky King's Tomb" (x16) - - "Extinction Curse" (x12) - - "Agents of Edgewatch" (x12) - - "Prey for Death" (x12) - - "Age of Ashes" (x10) - - "Kingmaker" (x9) - - "Abomination Vaults" (x7) - - "Season of Ghosts" (x6) - - "Seven Dooms for Sandpoint" (x6) - - "Fists of the Ruby Phoenix" (x4) - - "The Slithering" (x3) - - "Night of the Gray Death" (x2) + values: 323 + different_values: 21 + variants: [omitted] [summary_markdown]: type: string path: equipment-sub.summary_markdown is_null: true - values: 2847 - different_values: 2300 + values: 2948 + different_values: 2384 variants: [omitted] [trait]: type: array path: equipment-sub.trait is_null: true - values: 2525 + values: 2625 min_count: 1 max_count: 8 value_type: type: string path: equipment-sub.trait.[] is_null: false - values: 8392 + values: 8836 different_values: 108 variants: [omitted] 'rarity': type: string path: equipment-sub.rarity is_null: false - values: 2879 + values: 2980 different_values: 4 variants: - - "common" (x1852) - - "uncommon" (x802) - - "rare" (x219) + - "common" (x1853) + - "uncommon" (x884) + - "rare" (x237) - "unique" (x6) 'type': type: string path: equipment-sub.type is_null: false - values: 2879 + values: 2980 different_values: 2 variants: - - "Item" (x2776) - - "Category Page" (x103) + - "Item" (x2876) + - "Category Page" (x104) 'url': type: string path: equipment-sub.url is_null: false - values: 2879 - different_values: 975 + values: 2980 + different_values: 1006 variants: [omitted] 'weakness': type: object path: equipment-sub.weakness is_null: false - values: 2879 + values: 2980 schema: [trigger_markdown]: type: string path: equipment-sub.trigger_markdown is_null: true - values: 37 - different_values: 16 + values: 40 + different_values: 17 variants: - "You're critically hit by a firearm attack." (x3) - "You attempt a ranged Strike with the affixed weapon before rolling" (x3) @@ -494,6 +440,7 @@ schema: - "You lose the [dying](/Conditions.aspx?ID=11) condition" (x3) - "You take physical damage" (x3) - "You make an attack with the affixed weapon, but you haven't rolled yet" (x3) + - "You are [dying](/Conditions.aspx?ID=69) at the beginning of your turn." (x3) - "Your Strike with the affixed weapon damages a creature" (x2) - "You roll for initiative" (x2) - "varies, see entry" (x2) @@ -508,6 +455,6 @@ schema: type: string path: equipment-sub.usage_markdown is_null: true - values: 2329 - different_values: 82 + values: 2426 + different_values: 83 variants: [omitted] diff --git a/output/aonprd/equipment.yaml b/output/aonprd/equipment.yaml index ef28d39..6dd7b40 100644 --- a/output/aonprd/equipment.yaml +++ b/output/aonprd/equipment.yaml @@ -1,16 +1,16 @@ name: equipment -values: 3484 +values: 3607 schema: [actions]: type: string path: equipment.actions is_null: true - values: 991 + values: 1017 different_values: 6 variants: - - "Single Action" (x692) - - "Two Actions" (x142) - - "Free Action" (x119) + - "Single Action" (x711) + - "Two Actions" (x146) + - "Free Action" (x122) - "Three Actions" (x21) - "Reaction" (x15) - "Single Action or Two Actions" (x2) @@ -18,10 +18,10 @@ schema: type: string path: equipment.alignment is_null: true - values: 32 + values: 38 different_values: 6 variants: - - "No Alignment" (x20) + - "No Alignment" (x26) - "LG" (x4) - "CN" (x3) - "CG" (x2) @@ -31,22 +31,22 @@ schema: type: string path: equipment.base_item_markdown is_null: false - values: 3484 + values: 3607 different_values: 174 variants: [omitted] 'bulk': type: float path: equipment.bulk is_null: false - values: 3484 + values: 3607 different_values: 14 variants: - - 0 (x1430) - - 0,1 (x1398) - - 1 (x436) - - 2 (x134) + - 0,1 (x1472) + - 0 (x1455) + - 1 (x451) + - 2 (x142) - 3 (x32) - - 4 (x30) + - 4 (x31) - 5 (x9) - 6 (x5) - 16 (x2) @@ -59,83 +59,63 @@ schema: type: string path: equipment.category is_null: false - values: 3484 + values: 3607 different_values: 1 (all same) - variants: "equipment" (x3484) + variants: "equipment" (x3607) 'id': type: string path: equipment.id is_null: false - values: 3484 - different_values: 3484 (all different) + values: 3607 + different_values: 3607 (all different) variants: [omitted] 'item_category': type: string path: equipment.item_category is_null: false - values: 3484 - different_values: 34 + values: 3607 + different_values: 35 variants: [omitted] [bulk_raw]: type: string path: equipment.bulk_raw is_null: true - values: 2111 + values: 2210 different_values: 31 variants: [omitted] [duration]: type: integer path: equipment.duration is_null: true - values: 187 - different_values: 20 - variants: - - 36 (x77) - - 360 (x26) - - 3600 (x15) - - 1800 (x8) - - 14400 (x7) - - 604800 (x7) - - 24 (x6) - - 28800 (x6) - - 60 (x6) - - 600 (x5) - - 21600 (x5) - - 300 (x4) - - 18 (x4) - - 432000 (x3) - - 86400 (x2) - - 50400 (x2) - - 10800 (x1) - - 12 (x1) - - 36000 (x1) - - 6 (x1) + values: 194 + different_values: 21 + variants: [omitted] [duration_raw]: type: string path: equipment.duration_raw is_null: true - values: 187 - different_values: 25 + values: 194 + different_values: 27 variants: [omitted] [element]: type: array path: equipment.element is_null: true - values: 194 + values: 201 min_count: 1 max_count: 2 value_type: type: string path: equipment.element.[] is_null: false - values: 195 + values: 202 different_values: 6 variants: - "Fire" (x81) - "Air" (x32) + - "Earth" (x30) - "Water" (x27) - - "Earth" (x27) - - "Wood" (x18) + - "Wood" (x22) - "Metal" (x10) [frequency]: type: string @@ -148,119 +128,119 @@ schema: type: string path: equipment.hands is_null: true - values: 180 + values: 248 different_values: 3 variants: - - "2" (x88) - - "1" (x87) + - "1" (x143) + - "2" (x100) - "-1" (x5) [item_child_id]: type: array path: equipment.item_child_id is_null: true - values: 872 + values: 902 min_count: 1 max_count: 24 value_type: type: string path: equipment.item_child_id.[] is_null: false - values: 2776 - different_values: 2776 + values: 2876 + different_values: 2876 variants: [omitted] 'trait_markdown': type: string path: equipment.trait_markdown is_null: false - values: 3484 - different_values: 1305 + values: 3607 + different_values: 1354 variants: [omitted] 'type': type: string path: equipment.type is_null: false - values: 3484 + values: 3607 different_values: 1 (all same) - variants: "Item" (x3484) + variants: "Item" (x3607) [item_subcategory]: type: string path: equipment.item_subcategory is_null: true - values: 2340 + values: 2413 different_values: 70 variants: [omitted] 'spell_markdown': type: string path: equipment.spell_markdown is_null: false - values: 3484 - different_values: 41 + values: 3607 + different_values: 49 variants: [omitted] 'stage_markdown': type: string path: equipment.stage_markdown is_null: false - values: 3484 - different_values: 171 + values: 3607 + different_values: 178 variants: [omitted] 'url': type: string path: equipment.url is_null: false - values: 3484 - different_values: 3484 (all different) + values: 3607 + different_values: 3607 (all different) variants: [omitted] 'weakness': type: object path: equipment.weakness is_null: false - values: 3484 + values: 3607 schema: [legacy_id]: type: array path: equipment.legacy_id is_null: true - values: 517 + values: 518 min_count: 1 max_count: 1 value_type: type: string path: equipment.legacy_id.[] is_null: false - values: 517 - different_values: 516 + values: 518 + different_values: 517 variants: [omitted] 'level': type: integer path: equipment.level is_null: false - values: 3484 + values: 3607 different_values: 29 variants: [omitted] 'markdown': type: string path: equipment.markdown is_null: false - values: 3484 - different_values: 3484 (all different) + values: 3607 + different_values: 3607 (all different) variants: [omitted] 'name': type: string path: equipment.name is_null: false - values: 3484 - different_values: 2956 + values: 3607 + different_values: 3079 variants: [omitted] 'primary_source_category': type: string path: equipment.primary_source_category is_null: false - values: 3484 + values: 3607 different_values: 7 variants: - "Rulebooks" (x2304) + - "Adventure Paths" (x615) - "Lost Omens" (x590) - - "Adventure Paths" (x492) - "Adventures" (x69) - "Comics" (x22) - "Blog Posts" (x5) @@ -269,57 +249,57 @@ schema: type: string path: equipment.pfs is_null: true - values: 3156 + values: 3279 different_values: 3 variants: - - "Standard" (x2629) - - "Limited" (x418) - - "Restricted" (x109) + - "Standard" (x2630) + - "Limited" (x541) + - "Restricted" (x108) 'rarity': type: string path: equipment.rarity is_null: false - values: 3484 + values: 3607 different_values: 4 variants: - - "common" (x1828) - - "uncommon" (x1017) - - "rare" (x457) - - "unique" (x182) + - "common" (x1829) + - "uncommon" (x1077) + - "rare" (x492) + - "unique" (x209) 'rarity_id': type: integer path: equipment.rarity_id is_null: false - values: 3484 + values: 3607 different_values: 4 variants: - - 1 (x1828) - - 2 (x1017) - - 3 (x457) - - 4 (x182) + - 1 (x1829) + - 2 (x1077) + - 3 (x492) + - 4 (x209) 'release_date': type: string path: equipment.release_date is_null: false - values: 3484 - different_values: 76 + values: 3607 + different_values: 88 variants: [omitted] 'resistance': type: object path: equipment.resistance is_null: false - values: 3484 + values: 3607 schema: [onset_raw]: type: string path: equipment.onset_raw is_null: true - values: 97 + values: 101 different_values: 8 variants: - - "1 minute" (x39) + - "1 minute" (x41) - "10 minutes" (x28) - - "1 round" (x15) + - "1 round" (x17) - "1 hour" (x7) - "30 minutes" (x4) - "5 minutes" (x2) @@ -329,49 +309,49 @@ schema: type: string path: equipment.price_raw is_null: true - values: 2199 - different_values: 266 + values: 2282 + different_values: 267 variants: [omitted] [primary_source_group]: type: string path: equipment.primary_source_group is_null: true - values: 559 - different_values: 26 + values: 682 + different_values: 30 variants: [omitted] [remaster_id]: type: array path: equipment.remaster_id is_null: true - values: 519 + values: 520 min_count: 1 max_count: 2 value_type: type: string path: equipment.remaster_id.[] is_null: false - values: 520 - different_values: 518 + values: 521 + different_values: 519 variants: [omitted] [requirement_markdown]: type: string path: equipment.requirement_markdown is_null: true - values: 99 - different_values: 74 + values: 101 + different_values: 76 variants: [omitted] [saving_throw_markdown]: type: string path: equipment.saving_throw_markdown is_null: true - values: 181 - different_values: 43 + values: 188 + different_values: 44 variants: [omitted] [school]: type: string path: equipment.school is_null: true - values: 1664 + values: 1667 different_values: 8 variants: - "transmutation" (x311) @@ -381,45 +361,45 @@ schema: - "abjuration" (x202) - "necromancy" (x185) - "enchantment" (x161) - - "illusion" (x100) + - "illusion" (x103) 'search_markdown': type: string path: equipment.search_markdown is_null: false - values: 3484 - different_values: 3458 + values: 3607 + different_values: 3581 variants: [omitted] 'source': type: array path: equipment.source is_null: false - values: 3484 + values: 3607 min_count: 1 max_count: 2 value_type: type: string path: equipment.source.[] is_null: false - values: 3697 - different_values: 116 + values: 3821 + different_values: 128 variants: [omitted] 'source_category': type: array path: equipment.source_category is_null: false - values: 3484 + values: 3607 min_count: 1 max_count: 2 value_type: type: string path: equipment.source_category.[] is_null: false - values: 3594 + values: 3718 different_values: 7 variants: - "Rulebooks" (x2356) + - "Adventure Paths" (x654) - "Lost Omens" (x604) - - "Adventure Paths" (x530) - "Adventures" (x74) - "Comics" (x23) - "Blog Posts" (x5) @@ -428,75 +408,75 @@ schema: type: string path: equipment.skill_markdown is_null: true - values: 438 - different_values: 137 + values: 460 + different_values: 148 variants: [omitted] [source_group]: type: array path: equipment.source_group is_null: true - values: 602 + values: 726 min_count: 1 max_count: 1 value_type: type: string path: equipment.source_group.[] is_null: false - values: 602 - different_values: 26 + values: 726 + different_values: 30 variants: [omitted] 'source_markdown': type: string path: equipment.source_markdown is_null: false - values: 3484 - different_values: 1050 + values: 3607 + different_values: 1093 variants: [omitted] [spoilers]: type: string path: equipment.spoilers is_null: true - values: 540 - different_values: 26 + values: 662 + different_values: 30 variants: [omitted] 'speed': type: object path: equipment.speed is_null: false - values: 3484 + values: 3607 schema: [summary_markdown]: type: string path: equipment.summary_markdown is_null: true - values: 3387 - different_values: 3190 + values: 3510 + different_values: 3313 variants: [omitted] [trait]: type: array path: equipment.trait is_null: true - values: 3169 + values: 3292 min_count: 1 max_count: 9 value_type: type: string path: equipment.trait.[] is_null: false - values: 11408 + values: 11883 different_values: 139 variants: [omitted] [trigger_markdown]: type: string path: equipment.trigger_markdown is_null: true - values: 129 - different_values: 111 + values: 133 + different_values: 115 variants: [omitted] [usage_markdown]: type: string path: equipment.usage_markdown is_null: true - values: 2798 - different_values: 190 + values: 2917 + different_values: 193 variants: [omitted] diff --git a/output/aonprd/familiar-ability.yaml b/output/aonprd/familiar-ability.yaml index 248da47..1002cdb 100644 --- a/output/aonprd/familiar-ability.yaml +++ b/output/aonprd/familiar-ability.yaml @@ -27,11 +27,12 @@ schema: path: familiar-ability.actions is_null: true values: 25 - different_values: 3 + different_values: 4 variants: - "Single Action" (x12) - "Two Actions" (x11) - - "Reaction" (x2) + - "Reaction" (x1) + - "Free Action" (x1) [frequency]: type: string path: familiar-ability.frequency diff --git a/output/aonprd/familiar-specific.yaml b/output/aonprd/familiar-specific.yaml index 6361c62..1b8e113 100644 --- a/output/aonprd/familiar-specific.yaml +++ b/output/aonprd/familiar-specific.yaml @@ -83,7 +83,7 @@ schema: path: familiar-specific.trait_markdown is_null: false values: 42 - different_values: 25 + different_values: 26 variants: [omitted] 'type': type: string @@ -204,8 +204,8 @@ schema: values: 42 different_values: 3 variants: - - "common" (x33) - - "uncommon" (x8) + - "common" (x32) + - "uncommon" (x9) - "rare" (x1) 'rarity_id': type: integer @@ -214,8 +214,8 @@ schema: values: 42 different_values: 3 variants: - - 1 (x33) - - 2 (x8) + - 1 (x32) + - 2 (x9) - 3 (x1) 'release_date': type: string @@ -353,22 +353,22 @@ schema: type: array path: familiar-specific.trait is_null: true - values: 40 + values: 41 min_count: 1 max_count: 3 value_type: type: string path: familiar-specific.trait.[] is_null: false - values: 59 + values: 61 different_values: 20 variants: - "Construct" (x10) - "Beast" (x10) - - "Uncommon" (x8) + - "Uncommon" (x9) - "Elemental" (x6) + - "Dragon" (x4) - "Undead" (x4) - - "Dragon" (x3) - "Fiend" (x2) - "Ooze" (x2) - "Incorporeal" (x2) diff --git a/output/aonprd/feat.yaml b/output/aonprd/feat.yaml index 50ccb4b..d999d17 100644 --- a/output/aonprd/feat.yaml +++ b/output/aonprd/feat.yaml @@ -1,77 +1,20 @@ name: feat -values: 7125 +values: 7184 schema: - [actions]: - type: string - path: feat.actions - is_null: true - values: 2388 - different_values: 9 - variants: - - "Single Action" (x1011) - - "Reaction" (x515) - - "Two Actions" (x487) - - "Free Action" (x220) - - "Three Actions" (x138) - - "Single Action or Two Actions" (x8) - - "Two Actions or Three Actions" (x5) - - "Single Action to Three Actions" (x3) - - "Reaction or Two Actions" (x1) 'category': type: string path: feat.category is_null: false - values: 7125 + values: 7184 different_values: 1 (all same) - variants: "feat" (x7125) - 'trait_markdown': + variants: "feat" (x7184) + 'feat_markdown': type: string - path: feat.trait_markdown + path: feat.feat_markdown is_null: false - values: 7125 - different_values: 1486 + values: 7184 + different_values: 5637 variants: [omitted] - 'type': - type: string - path: feat.type - is_null: false - values: 7125 - different_values: 1 (all same) - variants: "Feat" (x7125) - [duration]: - type: integer - path: feat.duration - is_null: true - values: 1 - different_values: 1 (all same) - variants: 60 (x1) - 'url': - type: string - path: feat.url - is_null: false - values: 7125 - different_values: 7125 (all different) - variants: [omitted] - 'weakness': - type: object - path: feat.weakness - is_null: false - values: 7125 - schema: - [archetype]: - type: array - path: feat.archetype - is_null: true - values: 2512 - min_count: 1 - max_count: 5 - value_type: - type: string - path: feat.archetype.[] - is_null: false - values: 2718 - different_values: 208 - variants: [omitted] [cost_markdown]: type: string path: feat.cost_markdown @@ -95,54 +38,47 @@ schema: type: array path: feat.element is_null: true - values: 183 + values: 186 min_count: 1 max_count: 2 value_type: type: string path: feat.element.[] is_null: false - values: 198 + values: 201 different_values: 6 variants: - "Water" (x48) - "Fire" (x45) - "Air" (x35) - "Earth" (x27) - - "Wood" (x22) + - "Wood" (x25) - "Metal" (x21) - 'feat_markdown': - type: string - path: feat.feat_markdown - is_null: false - values: 7125 - different_values: 5579 - variants: [omitted] [heighten]: type: array path: feat.heighten is_null: true - values: 82 + values: 84 min_count: 1 max_count: 3 value_type: type: string path: feat.heighten.[] is_null: false - values: 89 + values: 91 different_values: 16 variants: - "+2" (x31) - "+3" (x15) - "+4" (x8) - - "20th" (x6) + - "20th" (x7) - "10th" (x5) - "14th" (x5) - "16th" (x4) - "8th" (x3) + - "6th" (x3) - "+5" (x3) - "12th" (x2) - - "6th" (x2) - "4th" (x1) - "+1" (x1) - "3rd" (x1) @@ -152,70 +88,334 @@ schema: type: array path: feat.heighten_group is_null: true - values: 82 + values: 84 min_count: 2 max_count: 10 value_type: type: string path: feat.heighten_group.[] is_null: false - values: 444 + values: 448 different_values: 25 variants: [omitted] + 'resistance': + type: object + path: feat.resistance + is_null: false + values: 7184 + schema: + [duration]: + type: integer + path: feat.duration + is_null: true + values: 1 + different_values: 1 (all same) + variants: 60 (x1) + 'release_date': + type: string + path: feat.release_date + is_null: false + values: 7184 + different_values: 61 + variants: [omitted] [heighten_level]: type: array path: feat.heighten_level is_null: true - values: 82 + values: 84 min_count: 2 max_count: 10 value_type: type: integer path: feat.heighten_level.[] is_null: false - values: 444 + values: 448 different_values: 19 variants: - 14 (x37) - 16 (x37) - - 18 (x35) - - 20 (x35) + - 18 (x36) + - 20 (x36) - 12 (x33) - 10 (x33) - 8 (x29) - - 6 (x26) + - 6 (x27) - 1 (x21) - 19 (x21) - 13 (x19) - 7 (x18) - 15 (x16) - 17 (x16) + - 4 (x16) - 9 (x15) - 11 (x15) - - 4 (x15) - 3 (x12) - 5 (x11) 'id': type: string path: feat.id is_null: false - values: 7125 - different_values: 7125 (all different) + values: 7184 + different_values: 7184 (all different) variants: [omitted] - [frequency]: + 'level': + type: integer + path: feat.level + is_null: false + values: 7184 + different_values: 20 + variants: + - 1 (x1172) + - 4 (x766) + - 6 (x647) + - 8 (x591) + - 2 (x563) + - 10 (x478) + - 12 (x465) + - 5 (x425) + - 9 (x377) + - 14 (x357) + - 13 (x270) + - 16 (x265) + - 18 (x254) + - 20 (x223) + - 17 (x147) + - 7 (x111) + - 15 (x32) + - 3 (x28) + - 11 (x11) + - 19 (x2) + 'markdown': type: string - path: feat.frequency + path: feat.markdown + is_null: false + values: 7184 + different_values: 7184 (all different) + variants: [omitted] + 'name': + type: string + path: feat.name + is_null: false + values: 7184 + different_values: 5637 + variants: [omitted] + 'primary_source_category': + type: string + path: feat.primary_source_category + is_null: false + values: 7184 + different_values: 7 + variants: + - "Rulebooks" (x5073) + - "Lost Omens" (x1609) + - "Adventure Paths" (x455) + - "Adventures" (x32) + - "Blog Posts" (x9) + - "Comics" (x5) + - "Society" (x1) + 'rarity': + type: string + path: feat.rarity + is_null: false + values: 7184 + different_values: 3 + variants: + - "common" (x6434) + - "uncommon" (x574) + - "rare" (x176) + [primary_source_group]: + type: string + path: feat.primary_source_group is_null: true - values: 582 - different_values: 28 + values: 487 + different_values: 22 + variants: [omitted] + 'rarity_id': + type: integer + path: feat.rarity_id + is_null: false + values: 7184 + different_values: 3 + variants: + - 1 (x6434) + - 2 (x574) + - 3 (x176) + 'trait_markdown': + type: string + path: feat.trait_markdown + is_null: false + values: 7184 + different_values: 1510 + variants: [omitted] + [school]: + type: string + path: feat.school + is_null: true + values: 277 + different_values: 8 + variants: + - "transmutation" (x56) + - "evocation" (x48) + - "necromancy" (x43) + - "divination" (x29) + - "conjuration" (x29) + - "illusion" (x27) + - "abjuration" (x26) + - "enchantment" (x19) + 'search_markdown': + type: string + path: feat.search_markdown + is_null: false + values: 7184 + different_values: 7184 (all different) + variants: [omitted] + 'source': + type: array + path: feat.source + is_null: false + values: 7184 + min_count: 1 + max_count: 4 + value_type: + type: string + path: feat.source.[] + is_null: false + values: 7320 + different_values: 88 + variants: [omitted] + 'source_category': + type: array + path: feat.source_category + is_null: false + values: 7184 + min_count: 1 + max_count: 2 + value_type: + type: string + path: feat.source_category.[] + is_null: false + values: 7225 + different_values: 7 + variants: + - "Rulebooks" (x5094) + - "Lost Omens" (x1615) + - "Adventure Paths" (x466) + - "Adventures" (x32) + - "Blog Posts" (x9) + - "Comics" (x6) + - "Society" (x3) + 'source_markdown': + type: string + path: feat.source_markdown + is_null: false + values: 7184 + different_values: 1323 + variants: [omitted] + 'speed': + type: object + path: feat.speed + is_null: false + values: 7184 + schema: + [source_group]: + type: array + path: feat.source_group + is_null: true + values: 498 + min_count: 1 + max_count: 1 + value_type: + type: string + path: feat.source_group.[] + is_null: false + values: 498 + different_values: 22 + variants: [omitted] + [spoilers]: + type: string + path: feat.spoilers + is_null: true + values: 281 + different_values: 21 variants: [omitted] 'summary_markdown': type: string path: feat.summary_markdown is_null: false - values: 7125 - different_values: 6274 + values: 7184 + different_values: 6333 variants: [omitted] + 'type': + type: string + path: feat.type + is_null: false + values: 7184 + different_values: 1 (all same) + variants: "Feat" (x7184) + [actions]: + type: string + path: feat.actions + is_null: true + values: 2412 + different_values: 9 + variants: + - "Single Action" (x1016) + - "Reaction" (x522) + - "Two Actions" (x496) + - "Free Action" (x221) + - "Three Actions" (x140) + - "Single Action or Two Actions" (x8) + - "Two Actions or Three Actions" (x5) + - "Single Action to Three Actions" (x3) + - "Reaction or Two Actions" (x1) + [archetype]: + type: array + path: feat.archetype + is_null: true + values: 2524 + min_count: 1 + max_count: 5 + value_type: + type: string + path: feat.archetype.[] + is_null: false + values: 2740 + different_values: 210 + variants: [omitted] + [frequency]: + type: string + path: feat.frequency + is_null: true + values: 592 + different_values: 28 + variants: [omitted] + [prerequisite_markdown]: + type: string + path: feat.prerequisite_markdown + is_null: true + values: 4301 + different_values: 2310 + variants: [omitted] + [trigger_markdown]: + type: string + path: feat.trigger_markdown + is_null: true + values: 683 + different_values: 605 + variants: [omitted] + 'url': + type: string + path: feat.url + is_null: false + values: 7184 + different_values: 7184 (all different) + variants: [omitted] + 'weakness': + type: object + path: feat.weakness + is_null: false + values: 7184 + schema: [legacy_id]: type: array path: feat.legacy_id @@ -230,223 +430,16 @@ schema: values: 1673 different_values: 1672 variants: [omitted] - 'level': - type: integer - path: feat.level - is_null: false - values: 7125 - different_values: 20 - variants: - - 1 (x1167) - - 4 (x762) - - 6 (x640) - - 8 (x589) - - 2 (x556) - - 10 (x475) - - 12 (x459) - - 5 (x421) - - 9 (x373) - - 14 (x354) - - 13 (x265) - - 16 (x264) - - 18 (x251) - - 20 (x222) - - 17 (x144) - - 7 (x111) - - 15 (x32) - - 3 (x27) - - 11 (x11) - - 19 (x2) - 'markdown': - type: string - path: feat.markdown - is_null: false - values: 7125 - different_values: 7125 (all different) - variants: [omitted] - 'name': - type: string - path: feat.name - is_null: false - values: 7125 - different_values: 5579 - variants: [omitted] - 'rarity_id': - type: integer - path: feat.rarity_id - is_null: false - values: 7125 - different_values: 3 - variants: - - 1 (x6434) - - 2 (x523) - - 3 (x168) - 'resistance': - type: object - path: feat.resistance - is_null: false - values: 7125 - schema: - 'search_markdown': - type: string - path: feat.search_markdown - is_null: false - values: 7125 - different_values: 7125 (all different) - variants: [omitted] - 'source': - type: array - path: feat.source - is_null: false - values: 7125 - min_count: 1 - max_count: 4 - value_type: - type: string - path: feat.source.[] - is_null: false - values: 7261 - different_values: 83 - variants: [omitted] - 'source_category': - type: array - path: feat.source_category - is_null: false - values: 7125 - min_count: 1 - max_count: 2 - value_type: - type: string - path: feat.source_category.[] - is_null: false - values: 7166 - different_values: 7 - variants: - - "Rulebooks" (x5094) - - "Lost Omens" (x1615) - - "Adventure Paths" (x407) - - "Adventures" (x32) - - "Blog Posts" (x9) - - "Comics" (x6) - - "Society" (x3) [pfs]: type: string path: feat.pfs is_null: true - values: 6834 + values: 6893 different_values: 3 variants: - - "Standard" (x6403) - - "Limited" (x375) + - "Standard" (x6413) + - "Limited" (x424) - "Restricted" (x56) - 'primary_source_category': - type: string - path: feat.primary_source_category - is_null: false - values: 7125 - different_values: 7 - variants: - - "Rulebooks" (x5073) - - "Lost Omens" (x1609) - - "Adventure Paths" (x396) - - "Adventures" (x32) - - "Blog Posts" (x9) - - "Comics" (x5) - - "Society" (x1) - 'rarity': - type: string - path: feat.rarity - is_null: false - values: 7125 - different_values: 3 - variants: - - "common" (x6434) - - "uncommon" (x523) - - "rare" (x168) - 'release_date': - type: string - path: feat.release_date - is_null: false - values: 7125 - different_values: 56 - variants: [omitted] - [source_group]: - type: array - path: feat.source_group - is_null: true - values: 439 - min_count: 1 - max_count: 1 - value_type: - type: string - path: feat.source_group.[] - is_null: false - values: 439 - different_values: 18 - variants: - - "Extinction Curse" (x55) - - "Fists of the Ruby Phoenix" (x49) - - "Age of Ashes" (x41) - - "Gatewalkers" (x41) - - "Kingmaker" (x30) - - "Strength of Thousands" (x29) - - "Sky King's Tomb" (x27) - - "Season of Ghosts" (x27) - - "Agents of Edgewatch" (x22) - - "Quest for the Frozen Flame" (x22) - - "Abomination Vaults" (x21) - - "Outlaws of Alkenstar" (x20) - - "Blood Lords" (x13) - - "Night of the Gray Death" (x11) - - "Prey for Death" (x11) - - "Stolen Fate" (x10) - - "The Slithering" (x8) - - "The Fall of Plaguestone" (x2) - 'source_markdown': - type: string - path: feat.source_markdown - is_null: false - values: 7125 - different_values: 1308 - variants: [omitted] - 'speed': - type: object - path: feat.speed - is_null: false - values: 7125 - schema: - [prerequisite_markdown]: - type: string - path: feat.prerequisite_markdown - is_null: true - values: 4258 - different_values: 2285 - variants: [omitted] - [primary_source_group]: - type: string - path: feat.primary_source_group - is_null: true - values: 428 - different_values: 18 - variants: - - "Extinction Curse" (x55) - - "Fists of the Ruby Phoenix" (x49) - - "Gatewalkers" (x41) - - "Age of Ashes" (x38) - - "Strength of Thousands" (x29) - - "Kingmaker" (x29) - - "Sky King's Tomb" (x27) - - "Season of Ghosts" (x27) - - "Quest for the Frozen Flame" (x22) - - "Abomination Vaults" (x21) - - "Outlaws of Alkenstar" (x20) - - "Agents of Edgewatch" (x15) - - "Blood Lords" (x13) - - "Night of the Gray Death" (x11) - - "Prey for Death" (x11) - - "Stolen Fate" (x10) - - "The Slithering" (x8) - - "The Fall of Plaguestone" (x2) [remaster_id]: type: array path: feat.remaster_id @@ -465,66 +458,43 @@ schema: type: string path: feat.requirement_markdown is_null: true - values: 770 - different_values: 561 + values: 772 + different_values: 562 variants: [omitted] - [school]: - type: string - path: feat.school - is_null: true - values: 277 - different_values: 8 - variants: - - "transmutation" (x56) - - "evocation" (x48) - - "necromancy" (x43) - - "conjuration" (x29) - - "divination" (x29) - - "illusion" (x27) - - "abjuration" (x26) - - "enchantment" (x19) - [spoilers]: - type: string - path: feat.spoilers - is_null: true - values: 222 - different_values: 17 - variants: - - "Extinction Curse" (x34) - - "Fists of the Ruby Phoenix" (x31) - - "Age of Ashes" (x29) - - "Season of Ghosts" (x27) - - "Strength of Thousands" (x17) - - "Gatewalkers" (x17) - - "Kingmaker" (x12) - - "Blood Lords" (x10) - - "Stolen Fate" (x10) - - "Quest for the Frozen Flame" (x9) - - "Agents of Edgewatch" (x7) - - "Outlaws of Alkenstar" (x6) - - "Prey for Death" (x4) - - "Abomination Vaults" (x3) - - "Sky King's Tomb" (x3) - - "The Fall of Plaguestone" (x2) - - "The Slithering" (x1) [trait]: type: array path: feat.trait is_null: true - values: 7124 + values: 7183 min_count: 1 max_count: 14 value_type: type: string path: feat.trait.[] is_null: false - values: 13020 - different_values: 222 + values: 13191 + different_values: 225 variants: [omitted] - [trigger_markdown]: + [usage_markdown]: type: string - path: feat.trigger_markdown + path: feat.usage_markdown is_null: true - values: 671 - different_values: 594 - variants: [omitted] + values: 23 + different_values: 16 + variants: + - "imbued into a body ikon" (x3) + - "imbued into a weapon ikon" (x3) + - "imbued into a worn ikon" (x3) + - "imbued into a melee weapon ikon" (x2) + - "imbued into a one-handed weapon ikon" (x1) + - "imbued into a thrown or melee weapon ikon" (x1) + - "imbued into a melee weapon ikon or worn ikon made of [metal](/Rules.aspx?ID=3189), [stone](/Equipment.aspx?ID=2359), or a similarly hard material" (x1) + - "imbued into a ranged weapon ikon, or a melee weapon ikon with the [thrown](/Traits.aspx?ID=711) trait" (x1) + - "imbued into a melee weapon ikon that deals slashing damage" (x1) + - "imbued into a body ikon or worn ikon" (x1) + - "imbued in one or two weapon ikons that both have the [thrown](/Traits.aspx?ID=711) trait" (x1) + - "imbued into a body or worn ikon" (x1) + - "imbued into a ranged weapon ikon" (x1) + - "imbued into a worn ikon or a body ikon" (x1) + - "imbued into a weapon ikon that deals piercing or slashing damage" (x1) + - "imbued into any weapon ikon" (x1) diff --git a/output/aonprd/hazard.yaml b/output/aonprd/hazard.yaml index 1aa87a1..46cecfd 100644 --- a/output/aonprd/hazard.yaml +++ b/output/aonprd/hazard.yaml @@ -1,82 +1,335 @@ name: hazard -values: 482 +values: 499 schema: [ac]: type: integer path: hazard.ac is_null: true - values: 229 + values: 240 different_values: 33 variants: [omitted] 'category': type: string path: hazard.category is_null: false - values: 482 + values: 499 different_values: 1 (all same) - variants: "hazard" (x482) + variants: "hazard" (x499) 'complexity': type: string path: hazard.complexity is_null: false - values: 482 + values: 499 different_values: 2 variants: - - "Simple" (x270) - - "Complex" (x212) + - "Simple" (x277) + - "Complex" (x222) + 'source_markdown': + type: string + path: hazard.source_markdown + is_null: false + values: 499 + different_values: 379 + variants: [omitted] + 'speed': + type: object + path: hazard.speed + is_null: false + values: 499 + schema: + 'summary_markdown': + type: string + path: hazard.summary_markdown + is_null: false + values: 499 + different_values: 467 + variants: [omitted] + 'trait_markdown': + type: string + path: hazard.trait_markdown + is_null: false + values: 499 + different_values: 140 + variants: [omitted] + 'type': + type: string + path: hazard.type + is_null: false + values: 499 + different_values: 1 (all same) + variants: "Hazard" (x499) + 'url': + type: string + path: hazard.url + is_null: false + values: 499 + different_values: 499 (all different) + variants: [omitted] + 'weakness': + type: object + path: hazard.weakness + is_null: false + values: 499 + schema: + [acid]: + type: integer + path: hazard.weakness.acid + is_null: true + values: 2 + different_values: 2 + variants: + - 10 (x1) + - 5 (x1) + [area]: + type: integer + path: hazard.weakness.area + is_null: true + values: 3 + different_values: 3 + variants: + - 5 (x1) + - 15 (x1) + - 10 (x1) + [bludgeoning]: + type: integer + path: hazard.weakness.bludgeoning + is_null: true + values: 1 + different_values: 1 (all same) + variants: 5 (x1) + [chaotic]: + type: integer + path: hazard.weakness.chaotic + is_null: true + values: 1 + different_values: 1 (all same) + variants: 30 (x1) + [cold]: + type: integer + path: hazard.weakness.cold + is_null: true + values: 6 + different_values: 4 + variants: + - 10 (x3) + - 20 (x1) + - 15 (x1) + - 5 (x1) + [electricity]: + type: integer + path: hazard.weakness.electricity + is_null: true + values: 2 + different_values: 2 + variants: + - 20 (x1) + - 5 (x1) + [fire]: + type: integer + path: hazard.weakness.fire + is_null: true + values: 14 + different_values: 4 + variants: + - 10 (x8) + - 5 (x3) + - 20 (x2) + - 15 (x1) + [force]: + type: integer + path: hazard.weakness.force + is_null: true + values: 1 + different_values: 1 (all same) + variants: 15 (x1) + [good]: + type: integer + path: hazard.weakness.good + is_null: true + values: 1 + different_values: 1 (all same) + variants: 15 (x1) + [mental]: + type: integer + path: hazard.weakness.mental + is_null: true + values: 2 + different_values: 2 + variants: + - 30 (x1) + - 10 (x1) + [orichalcum]: + type: integer + path: hazard.weakness.orichalcum + is_null: true + values: 1 + different_values: 1 (all same) + variants: 5 (x1) + [slashing]: + type: integer + path: hazard.weakness.slashing + is_null: true + values: 2 + different_values: 1 (all same) + variants: 10 (x2) + [sonic]: + type: integer + path: hazard.weakness.sonic + is_null: true + values: 5 + different_values: 2 + variants: + - 10 (x4) + - 20 (x1) + [vitality]: + type: integer + path: hazard.weakness.vitality + is_null: true + values: 20 + different_values: 4 + variants: + - 10 (x7) + - 5 (x7) + - 15 (x4) + - 8 (x2) [element]: type: array path: hazard.element is_null: true - values: 16 + values: 17 min_count: 1 max_count: 2 value_type: type: string path: hazard.element.[] is_null: false - values: 17 + values: 18 different_values: 3 variants: - - "Fire" (x14) + - "Fire" (x15) - "Water" (x2) - "Air" (x1) 'id': type: string path: hazard.id is_null: false - values: 482 - different_values: 482 (all different) + values: 499 + different_values: 499 (all different) variants: [omitted] + [fortitude_save]: + type: integer + path: hazard.fortitude_save + is_null: true + values: 240 + different_values: 34 + variants: [omitted] + [hardness_raw]: + type: string + path: hazard.hardness_raw + is_null: true + values: 193 + different_values: 95 + variants: [omitted] + [hazard_type]: + type: string + path: hazard.hazard_type + is_null: true + values: 477 + different_values: 3 + variants: + - "Trap" (x278) + - "Environmental" (x100) + - "Haunt" (x99) + [hp_raw]: + type: string + path: hazard.hp_raw + is_null: true + values: 236 + different_values: 114 + variants: [omitted] + [immunity_markdown]: + type: string + path: hazard.immunity_markdown + is_null: true + values: 228 + different_values: 28 + variants: [omitted] + 'source': + type: array + path: hazard.source + is_null: false + values: 499 + min_count: 1 + max_count: 4 + value_type: + type: string + path: hazard.source.[] + is_null: false + values: 513 + different_values: 70 + variants: [omitted] + 'source_category': + type: array + path: hazard.source_category + is_null: false + values: 499 + min_count: 1 + max_count: 2 + value_type: + type: string + path: hazard.source_category.[] + is_null: false + values: 503 + different_values: 5 + variants: + - "Adventure Paths" (x320) + - "Rulebooks" (x136) + - "Adventures" (x38) + - "Lost Omens" (x8) + - "Blog Posts" (x1) + [legacy_id]: + type: array + path: hazard.legacy_id + is_null: true + values: 32 + min_count: 1 + max_count: 1 + value_type: + type: string + path: hazard.legacy_id.[] + is_null: false + values: 32 + different_values: 32 + variants: [omitted] 'level': type: integer path: hazard.level is_null: false - values: 482 + values: 499 different_values: 25 variants: [omitted] 'markdown': type: string path: hazard.markdown is_null: false - values: 482 - different_values: 482 (all different) + values: 499 + different_values: 499 (all different) variants: [omitted] 'name': type: string path: hazard.name is_null: false - values: 482 - different_values: 445 + values: 499 + different_values: 462 variants: [omitted] 'primary_source_category': type: string path: hazard.primary_source_category is_null: false - values: 482 + values: 499 different_values: 5 variants: - - "Adventure Paths" (x302) + - "Adventure Paths" (x319) - "Rulebooks" (x133) - "Adventures" (x38) - "Lost Omens" (x8) @@ -85,63 +338,43 @@ schema: type: string path: hazard.primary_source_group is_null: true - values: 340 - different_values: 20 - variants: - - "Blood Lords" (x53) - - "Kingmaker" (x36) - - "Extinction Curse" (x28) - - "Age of Ashes" (x27) - - "Gatewalkers" (x25) - - "Quest for the Frozen Flame" (x19) - - "Sky King's Tomb" (x17) - - "Outlaws of Alkenstar" (x16) - - "Season of Ghosts" (x16) - - "Seven Dooms for Sandpoint" (x16) - - "Crown of the Kobold King" (x15) - - "Agents of Edgewatch" (x15) - - "Strength of Thousands" (x13) - - "Fists of the Ruby Phoenix" (x11) - - "Stolen Fate" (x10) - - "Rusthenge" (x7) - - "Shadows at Sundown" (x5) - - "The Enmity Cycle" (x5) - - "Prey for Death" (x4) - - "Night of the Gray Death" (x2) + values: 357 + different_values: 21 + variants: [omitted] 'rarity': type: string path: hazard.rarity is_null: false - values: 482 + values: 499 different_values: 4 variants: - - "common" (x313) - - "uncommon" (x62) - - "unique" (x61) - - "rare" (x46) + - "common" (x319) + - "unique" (x66) + - "uncommon" (x65) + - "rare" (x49) 'rarity_id': type: integer path: hazard.rarity_id is_null: false - values: 482 + values: 499 different_values: 4 variants: - - 1 (x313) - - 2 (x62) - - 4 (x61) - - 3 (x46) + - 1 (x319) + - 4 (x66) + - 2 (x65) + - 3 (x49) 'release_date': type: string path: hazard.release_date is_null: false - values: 482 - different_values: 50 + values: 499 + different_values: 53 variants: [omitted] 'resistance': type: object path: hazard.resistance is_null: false - values: 482 + values: 499 schema: [acid]: type: integer @@ -391,6 +624,27 @@ schema: variants: - 10 (x2) - 20 (x2) + [reflex_save]: + type: integer + path: hazard.reflex_save + is_null: true + values: 237 + different_values: 35 + variants: [omitted] + [remaster_id]: + type: array + path: hazard.remaster_id + is_null: true + values: 32 + min_count: 1 + max_count: 1 + value_type: + type: string + path: hazard.remaster_id.[] + is_null: false + values: 32 + different_values: 32 + variants: [omitted] [resistance_markdown]: type: string path: hazard.resistance_markdown @@ -427,145 +681,35 @@ schema: type: string path: hazard.search_markdown is_null: false - values: 482 - different_values: 482 (all different) + values: 499 + different_values: 498 variants: [omitted] - [immunity_markdown]: - type: string - path: hazard.immunity_markdown - is_null: true - values: 218 - different_values: 28 - variants: [omitted] - 'source': - type: array - path: hazard.source - is_null: false - values: 482 - min_count: 1 - max_count: 4 - value_type: - type: string - path: hazard.source.[] - is_null: false - values: 496 - different_values: 67 - variants: [omitted] - 'source_category': - type: array - path: hazard.source_category - is_null: false - values: 482 - min_count: 1 - max_count: 2 - value_type: - type: string - path: hazard.source_category.[] - is_null: false - values: 486 - different_values: 5 - variants: - - "Adventure Paths" (x303) - - "Rulebooks" (x136) - - "Adventures" (x38) - - "Lost Omens" (x8) - - "Blog Posts" (x1) [source_group]: type: array path: hazard.source_group is_null: true - values: 341 + values: 358 min_count: 1 max_count: 2 value_type: type: string path: hazard.source_group.[] is_null: false - values: 349 - different_values: 20 - variants: - - "Blood Lords" (x56) - - "Kingmaker" (x39) - - "Extinction Curse" (x28) - - "Age of Ashes" (x27) - - "Gatewalkers" (x25) - - "Quest for the Frozen Flame" (x20) - - "Sky King's Tomb" (x18) - - "Outlaws of Alkenstar" (x16) - - "Agents of Edgewatch" (x16) - - "Season of Ghosts" (x16) - - "Seven Dooms for Sandpoint" (x16) - - "Crown of the Kobold King" (x15) - - "Strength of Thousands" (x13) - - "Fists of the Ruby Phoenix" (x11) - - "Stolen Fate" (x10) - - "Rusthenge" (x7) - - "Shadows at Sundown" (x5) - - "The Enmity Cycle" (x5) - - "Prey for Death" (x4) - - "Night of the Gray Death" (x2) - 'source_markdown': - type: string - path: hazard.source_markdown - is_null: false - values: 482 - different_values: 365 - variants: [omitted] - 'speed': - type: object - path: hazard.speed - is_null: false - values: 482 - schema: - [fortitude_save]: - type: integer - path: hazard.fortitude_save - is_null: true - values: 229 - different_values: 32 - variants: [omitted] - [hardness_raw]: - type: string - path: hazard.hardness_raw - is_null: true - values: 184 - different_values: 93 - variants: [omitted] - [hazard_type]: - type: string - path: hazard.hazard_type - is_null: true - values: 461 - different_values: 3 - variants: - - "Trap" (x268) - - "Environmental" (x97) - - "Haunt" (x96) - [hp_raw]: - type: string - path: hazard.hp_raw - is_null: true - values: 226 - different_values: 112 - variants: [omitted] - [reflex_save]: - type: integer - path: hazard.reflex_save - is_null: true - values: 226 - different_values: 34 - variants: [omitted] + values: 366 + different_values: 21 + variants: [omitted] [spoilers]: type: string path: hazard.spoilers is_null: true - values: 153 - different_values: 19 + values: 164 + different_values: 20 variants: - "Blood Lords" (x32) - "Seven Dooms for Sandpoint" (x15) - "Kingmaker" (x13) - "Age of Ashes" (x11) + - "Wardens of Wildwood" (x11) - "Strength of Thousands" (x10) - "Extinction Curse" (x10) - "Crown of the Kobold King" (x9) @@ -581,189 +725,35 @@ schema: - "Gatewalkers" (x3) - "Outlaws of Alkenstar" (x2) - "Night of the Gray Death" (x1) - 'summary_markdown': - type: string - path: hazard.summary_markdown - is_null: false - values: 482 - different_values: 451 - variants: [omitted] - 'trait_markdown': - type: string - path: hazard.trait_markdown - is_null: false - values: 482 - different_values: 131 - variants: [omitted] - 'url': - type: string - path: hazard.url - is_null: false - values: 482 - different_values: 482 (all different) - variants: [omitted] [trait]: type: array path: hazard.trait is_null: true - values: 475 + values: 492 min_count: 1 max_count: 7 value_type: type: string path: hazard.trait.[] is_null: false - values: 1268 + values: 1320 different_values: 47 variants: [omitted] - 'type': - type: string - path: hazard.type - is_null: false - values: 482 - different_values: 1 (all same) - variants: "Hazard" (x482) - 'weakness': - type: object - path: hazard.weakness - is_null: false - values: 482 - schema: - [acid]: - type: integer - path: hazard.weakness.acid - is_null: true - values: 2 - different_values: 2 - variants: - - 10 (x1) - - 5 (x1) - [area]: - type: integer - path: hazard.weakness.area - is_null: true - values: 3 - different_values: 3 - variants: - - 5 (x1) - - 15 (x1) - - 10 (x1) - [bludgeoning]: - type: integer - path: hazard.weakness.bludgeoning - is_null: true - values: 1 - different_values: 1 (all same) - variants: 5 (x1) - [chaotic]: - type: integer - path: hazard.weakness.chaotic - is_null: true - values: 1 - different_values: 1 (all same) - variants: 30 (x1) - [cold]: - type: integer - path: hazard.weakness.cold - is_null: true - values: 6 - different_values: 4 - variants: - - 10 (x3) - - 20 (x1) - - 15 (x1) - - 5 (x1) - [electricity]: - type: integer - path: hazard.weakness.electricity - is_null: true - values: 2 - different_values: 2 - variants: - - 20 (x1) - - 5 (x1) - [fire]: - type: integer - path: hazard.weakness.fire - is_null: true - values: 12 - different_values: 4 - variants: - - 10 (x7) - - 20 (x2) - - 5 (x2) - - 15 (x1) - [force]: - type: integer - path: hazard.weakness.force - is_null: true - values: 1 - different_values: 1 (all same) - variants: 15 (x1) - [good]: - type: integer - path: hazard.weakness.good - is_null: true - values: 1 - different_values: 1 (all same) - variants: 15 (x1) - [mental]: - type: integer - path: hazard.weakness.mental - is_null: true - values: 2 - different_values: 2 - variants: - - 30 (x1) - - 10 (x1) - [orichalcum]: - type: integer - path: hazard.weakness.orichalcum - is_null: true - values: 1 - different_values: 1 (all same) - variants: 5 (x1) - [slashing]: - type: integer - path: hazard.weakness.slashing - is_null: true - values: 2 - different_values: 1 (all same) - variants: 10 (x2) - [sonic]: - type: integer - path: hazard.weakness.sonic - is_null: true - values: 5 - different_values: 2 - variants: - - 10 (x4) - - 20 (x1) - [vitality]: - type: integer - path: hazard.weakness.vitality - is_null: true - values: 18 - different_values: 3 - variants: - - 10 (x7) - - 5 (x7) - - 15 (x4) [weakness_markdown]: type: string path: hazard.weakness_markdown is_null: true - values: 49 - different_values: 35 + values: 53 + different_values: 37 variants: [omitted] [will_save]: type: integer path: hazard.will_save is_null: true - values: 20 + values: 22 different_values: 14 variants: - - 15 (x3) + - 15 (x5) - 22 (x2) - 14 (x2) - 35 (x2) diff --git a/output/aonprd/instinct.yaml b/output/aonprd/instinct.yaml index 4e16687..7dd3355 100644 --- a/output/aonprd/instinct.yaml +++ b/output/aonprd/instinct.yaml @@ -1,19 +1,19 @@ name: instinct -values: 14 +values: 16 schema: 'category': type: string path: instinct.category is_null: false - values: 14 + values: 16 different_values: 1 (all same) - variants: "instinct" (x14) + variants: "instinct" (x16) 'id': type: string path: instinct.id is_null: false - values: 14 - different_values: 14 (all different) + values: 16 + different_values: 16 (all different) variants: - "instinct-1" (x1) - "instinct-2" (x1) @@ -29,113 +29,77 @@ schema: - "instinct-12" (x1) - "instinct-13" (x1) - "instinct-14" (x1) - 'search_markdown': + - "instinct-15" (x1) + - "instinct-16" (x1) + 'markdown': type: string - path: instinct.search_markdown + path: instinct.markdown is_null: false - values: 14 - different_values: 14 (all different) + values: 16 + different_values: 16 (all different) variants: [omitted] - 'source': - type: array - path: instinct.source - is_null: false - values: 14 - min_count: 1 - max_count: 1 - value_type: - type: string - path: instinct.source.[] - is_null: false - values: 14 - different_values: 5 - variants: - - "Player Core 2" (x6) - - "Core Rulebook" (x5) - - "Advanced Player's Guide" (x1) - - "Rage of Elements" (x1) - - "War of Immortals" (x1) - 'source_category': - type: array - path: instinct.source_category - is_null: false - values: 14 - min_count: 1 - max_count: 1 - value_type: - type: string - path: instinct.source_category.[] - is_null: false - values: 14 - different_values: 1 (all same) - variants: "Rulebooks" (x14) - 'source_markdown': + 'name': type: string - path: instinct.source_markdown + path: instinct.name is_null: false - values: 14 - different_values: 7 + values: 16 + different_values: 10 variants: - - "[Player Core 2](/Sources.aspx?ID=227) pg. 75 1.1" (x4) - - "[Core Rulebook](/Sources.aspx?ID=1) pg. 87 4.0" (x3) - - "[Core Rulebook](/Sources.aspx?ID=1) pg. 86 4.0" (x2) - - "[Player Core 2](/Sources.aspx?ID=227) pg. 74 1.1" (x2) - - "[Advanced Player's Guide](/Sources.aspx?ID=39) pg. 108 2.0" (x1) - - "[Rage of Elements](/Sources.aspx?ID=205) pg. 54 2.0" (x1) - - "[War of Immortals](/Sources.aspx?ID=232) pg. 60" (x1) - 'speed': - type: object - path: instinct.speed - is_null: false - values: 14 - schema: - 'summary_markdown': + - "Animal" (x2) + - "Dragon" (x2) + - "Fury" (x2) + - "Giant" (x2) + - "Spirit" (x2) + - "Superstition" (x2) + - "Elemental" (x1) + - "Bloodrager" (x1) + - "Decay" (x1) + - "Ligneous" (x1) + 'pfs': type: string - path: instinct.summary_markdown + path: instinct.pfs is_null: false - values: 14 - different_values: 12 + values: 16 + different_values: 2 variants: - - "Your rage comes from a deep and purely personal well within you. You use your rage as you choose." (x2) - - "Whether you are emotionally sensitive to the spirits around you; worship ancestors or apparitions; or are haunted by the specter of an ancestor, …" (x2) - - "The fury of a wild predator fills you when you Rage , granting you ferocious unarmed attacks. Cultures that revere vicious animals (such as apes …" (x1) - - "You summon the fury of a mighty dragon and manifest incredible abilities. Perhaps your culture reveres draconic majesty, or you gained your …" (x1) - - "Your rage gives you the raw power and size of a giant . This doesn't necessarily mean you revere giants—you might scoff at them or even aspire to …" (x1) - - "A deep distrust of magic drives you to forgo and counter the metaphysical nonsense of spellcasters. Whether you're a member of a superstitious family …" (x1) - - "This instinct option for barbarians allows you to channel elemental forces, bonding with a single element and its associated plane. Perhaps your …" (x1) - - "The fury of a wild predator fills you when you Rage, granting you ferocious unarmed attacks. Cultures that revere vicious animals (such as apes or …" (x1) - - "You summon the fury of a mighty dragon and manifest incredible abilities. Perhaps your culture reveres draconic majesty, or you gained insights by …" (x1) - - "Your rage gives you the raw power and size of a giant. This doesn’t necessarily mean you revere giants—you might scoff at them or even aspire to slay …" (x1) - - "A deep distrust of magic drives you to forgo and counter the metaphysical nonsense of spellcasters. Whether you’re a member of a superstitious family …" (x1) - - "This Instinct can be taken by Bloodragers . Drinking the potent magical blood of supernatural creatures has changed you and awakened magical power …" (x1) - 'type': + - "Standard" (x14) + - "Limited" (x2) + 'primary_source_category': type: string - path: instinct.type + path: instinct.primary_source_category is_null: false - values: 14 + values: 16 + different_values: 2 + variants: + - "Rulebooks" (x14) + - "Adventure Paths" (x2) + 'rarity': + type: string + path: instinct.rarity + is_null: false + values: 16 different_values: 1 (all same) - variants: "Barbarian Instinct" (x14) - 'url': - type: string - path: instinct.url + variants: "common" (x16) + 'rarity_id': + type: integer + path: instinct.rarity_id is_null: false - values: 14 - different_values: 14 (all different) + values: 16 + different_values: 1 (all same) + variants: 1 (x16) + 'release_date': + type: string + path: instinct.release_date + is_null: false + values: 16 + different_values: 6 variants: - - "/Instincts.aspx?ID=1" (x1) - - "/Instincts.aspx?ID=2" (x1) - - "/Instincts.aspx?ID=3" (x1) - - "/Instincts.aspx?ID=4" (x1) - - "/Instincts.aspx?ID=5" (x1) - - "/Instincts.aspx?ID=6" (x1) - - "/Instincts.aspx?ID=7" (x1) - - "/Instincts.aspx?ID=8" (x1) - - "/Instincts.aspx?ID=9" (x1) - - "/Instincts.aspx?ID=10" (x1) - - "/Instincts.aspx?ID=11" (x1) - - "/Instincts.aspx?ID=12" (x1) - - "/Instincts.aspx?ID=13" (x1) - - "/Instincts.aspx?ID=14" (x1) + - "2024-08-01" (x6) + - "2019-08-01" (x5) + - "2024-05-15" (x2) + - "2020-07-30" (x1) + - "2023-08-02" (x1) + - "2024-10-30" (x1) [legacy_id]: type: array path: instinct.legacy_id @@ -156,68 +120,121 @@ schema: - "instinct-4" (x1) - "instinct-5" (x1) - "instinct-6" (x1) - 'markdown': + 'primary_source_group': type: string - path: instinct.markdown + path: instinct.primary_source_group is_null: false - values: 14 - different_values: 14 (all different) + values: 2 + different_values: 1 (all same) + variants: "Wardens of Wildwood" (x2) + 'resistance': + type: object + path: instinct.resistance + is_null: false + values: 16 + schema: + 'search_markdown': + type: string + path: instinct.search_markdown + is_null: false + values: 16 + different_values: 16 (all different) variants: [omitted] - 'name': - type: string - path: instinct.name + 'source_category': + type: array + path: instinct.source_category is_null: false - values: 14 - different_values: 8 + values: 16 + min_count: 1 + max_count: 1 + value_type: + type: string + path: instinct.source_category.[] + is_null: false + values: 16 + different_values: 2 + variants: + - "Rulebooks" (x14) + - "Adventure Paths" (x2) + 'source_markdown': + type: string + path: instinct.source_markdown + is_null: false + values: 16 + different_values: 9 variants: - - "Animal" (x2) - - "Dragon" (x2) - - "Fury" (x2) - - "Giant" (x2) - - "Spirit" (x2) - - "Superstition" (x2) - - "Elemental" (x1) - - "Bloodrager" (x1) - 'pfs': + - "[Player Core 2](/Sources.aspx?ID=227) pg. 75 1.1" (x4) + - "[Core Rulebook](/Sources.aspx?ID=1) pg. 87 4.0" (x3) + - "[Core Rulebook](/Sources.aspx?ID=1) pg. 86 4.0" (x2) + - "[Player Core 2](/Sources.aspx?ID=227) pg. 74 1.1" (x2) + - "[Advanced Player's Guide](/Sources.aspx?ID=39) pg. 108 2.0" (x1) + - "[Rage of Elements](/Sources.aspx?ID=205) pg. 54 2.0" (x1) + - "[War of Immortals](/Sources.aspx?ID=232) pg. 60" (x1) + - "[Pathfinder #202: Severed at the Root](/Sources.aspx?ID=242) pg. 70" (x1) + - "[Pathfinder #202: Severed at the Root](/Sources.aspx?ID=242) pg. 71" (x1) + 'speed': + type: object + path: instinct.speed + is_null: false + values: 16 + schema: + 'summary_markdown': type: string - path: instinct.pfs + path: instinct.summary_markdown is_null: false - values: 14 - different_values: 1 (all same) - variants: "Standard" (x14) - 'primary_source_category': - type: string - path: instinct.primary_source_category - is_null: false - values: 14 - different_values: 1 (all same) - variants: "Rulebooks" (x14) - 'rarity': - type: string - path: instinct.rarity - is_null: false - values: 14 - different_values: 1 (all same) - variants: "common" (x14) - 'rarity_id': - type: integer - path: instinct.rarity_id - is_null: false - values: 14 - different_values: 1 (all same) - variants: 1 (x14) - 'release_date': - type: string - path: instinct.release_date - is_null: false - values: 14 - different_values: 5 + values: 16 + different_values: 14 variants: - - "2024-08-01" (x6) - - "2019-08-01" (x5) - - "2020-07-30" (x1) - - "2023-08-02" (x1) - - "2024-10-30" (x1) + - "Your rage comes from a deep and purely personal well within you. You use your rage as you choose." (x2) + - "Whether you are emotionally sensitive to the spirits around you; worship ancestors or apparitions; or are haunted by the specter of an ancestor, …" (x2) + - "The fury of a wild predator fills you when you Rage , granting you ferocious unarmed attacks. Cultures that revere vicious animals (such as apes …" (x1) + - "You summon the fury of a mighty dragon and manifest incredible abilities. Perhaps your culture reveres draconic majesty, or you gained your …" (x1) + - "Your rage gives you the raw power and size of a giant . This doesn't necessarily mean you revere giants—you might scoff at them or even aspire to …" (x1) + - "A deep distrust of magic drives you to forgo and counter the metaphysical nonsense of spellcasters. Whether you're a member of a superstitious family …" (x1) + - "This instinct option for barbarians allows you to channel elemental forces, bonding with a single element and its associated plane. Perhaps your …" (x1) + - "The fury of a wild predator fills you when you Rage, granting you ferocious unarmed attacks. Cultures that revere vicious animals (such as apes or …" (x1) + - "You summon the fury of a mighty dragon and manifest incredible abilities. Perhaps your culture reveres draconic majesty, or you gained insights by …" (x1) + - "Your rage gives you the raw power and size of a giant. This doesn’t necessarily mean you revere giants—you might scoff at them or even aspire to slay …" (x1) + - "A deep distrust of magic drives you to forgo and counter the metaphysical nonsense of spellcasters. Whether you’re a member of a superstitious family …" (x1) + - "This Instinct can be taken by Bloodragers . Drinking the potent magical blood of supernatural creatures has changed you and awakened magical power …" (x1) + - "Your rage is intrinsically tied to the decomposition of organic beings, and the new growth that decay allows. You may have meddled with ancient …" (x1) + - "Your rage is powered by the steady growth of the natural world, from the blossoming of flowers to the cultivation of vegetables and the towering, …" (x1) + 'type': + type: string + path: instinct.type + is_null: false + values: 16 + different_values: 1 (all same) + variants: "Barbarian Instinct" (x16) + 'url': + type: string + path: instinct.url + is_null: false + values: 16 + different_values: 16 (all different) + variants: + - "/Instincts.aspx?ID=1" (x1) + - "/Instincts.aspx?ID=2" (x1) + - "/Instincts.aspx?ID=3" (x1) + - "/Instincts.aspx?ID=4" (x1) + - "/Instincts.aspx?ID=5" (x1) + - "/Instincts.aspx?ID=6" (x1) + - "/Instincts.aspx?ID=7" (x1) + - "/Instincts.aspx?ID=8" (x1) + - "/Instincts.aspx?ID=9" (x1) + - "/Instincts.aspx?ID=10" (x1) + - "/Instincts.aspx?ID=11" (x1) + - "/Instincts.aspx?ID=12" (x1) + - "/Instincts.aspx?ID=13" (x1) + - "/Instincts.aspx?ID=14" (x1) + - "/Instincts.aspx?ID=15" (x1) + - "/Instincts.aspx?ID=16" (x1) + 'weakness': + type: object + path: instinct.weakness + is_null: false + values: 16 + schema: [remaster_id]: type: array path: instinct.remaster_id @@ -238,15 +255,37 @@ schema: - "instinct-11" (x1) - "instinct-12" (x1) - "instinct-13" (x1) - 'resistance': - type: object - path: instinct.resistance + 'source': + type: array + path: instinct.source is_null: false - values: 14 - schema: - 'weakness': - type: object - path: instinct.weakness + values: 16 + min_count: 1 + max_count: 1 + value_type: + type: string + path: instinct.source.[] + is_null: false + values: 16 + different_values: 6 + variants: + - "Player Core 2" (x6) + - "Core Rulebook" (x5) + - "Pathfinder #202: Severed at the Root" (x2) + - "Advanced Player's Guide" (x1) + - "Rage of Elements" (x1) + - "War of Immortals" (x1) + 'source_group': + type: array + path: instinct.source_group is_null: false - values: 14 - schema: + values: 2 + min_count: 1 + max_count: 1 + value_type: + type: string + path: instinct.source_group.[] + is_null: false + values: 2 + different_values: 1 (all same) + variants: "Wardens of Wildwood" (x2) diff --git a/output/aonprd/ritual.yaml b/output/aonprd/ritual.yaml index 72ca64a..3e6e358 100644 --- a/output/aonprd/ritual.yaml +++ b/output/aonprd/ritual.yaml @@ -1,18 +1,18 @@ name: ritual -values: 161 +values: 166 schema: 'actions': type: string path: ritual.actions is_null: false - values: 161 + values: 166 different_values: 13 variants: - - "1 day" (x96) - - "1 hour" (x29) + - "1 day" (x97) + - "1 hour" (x30) + - "4 hours" (x8) - "7 days" (x7) - - "4 hours" (x7) - - "8 hours" (x5) + - "8 hours" (x7) - "3 days" (x4) - "2 hours" (x3) - "9 days" (x2) @@ -25,32 +25,32 @@ schema: type: string path: ritual.category is_null: false - values: 161 + values: 166 different_values: 1 (all same) - variants: "ritual" (x161) + variants: "ritual" (x166) [cost_markdown]: type: string path: ritual.cost_markdown is_null: true - values: 130 - different_values: 116 + values: 133 + different_values: 119 variants: [omitted] 'source_category': type: array path: ritual.source_category is_null: false - values: 161 + values: 166 min_count: 1 max_count: 1 value_type: type: string path: ritual.source_category.[] is_null: false - values: 161 + values: 166 different_values: 6 variants: - "Rulebooks" (x116) - - "Adventure Paths" (x25) + - "Adventure Paths" (x30) - "Lost Omens" (x14) - "Adventures" (x4) - "Blog Posts" (x1) @@ -59,73 +59,53 @@ schema: type: string path: ritual.source_markdown is_null: false - values: 161 - different_values: 92 + values: 166 + different_values: 97 variants: [omitted] 'speed': type: object path: ritual.speed is_null: false - values: 161 + values: 166 schema: [area_raw]: type: string path: ritual.area_raw is_null: true - values: 27 - different_values: 20 - variants: - - "1/2-mile-radius circle centered on you" (x4) - - "40-foot-radius burst around an immobile altar, shrine, or fixture of your deity" (x2) - - "2-mile-radius circle centered on you" (x2) - - "100 feet × 100 feet, up to 20 feet high" (x2) - - "100 feet × 100 feet, up to 50 feet high" (x2) - - "circle centered on you up to 1 mile in radius" (x1) - - "An enclosed area up to 60 feet × 60 feet, up to 20 feet high" (x1) - - "1 square mile" (x1) - - "40-foot-radius burst" (x1) - - "1-mile-radius circle centered on you" (x1) - - "1-mile burst" (x1) - - "10-foot burst" (x1) - - "100 feet x 100 feet, up to 20 feet high" (x1) - - "20-foot burst" (x1) - - "up to 1 mile radius circle centered on you" (x1) - - "7-mile radius circle centered on you" (x1) - - "1-mile radius circle centered on you" (x1) - - "1-mile radius centered on you" (x1) - - "500-foot radius centered on you" (x1) - - "8-mile radius circle centered on you" (x1) + values: 29 + different_values: 21 + variants: [omitted] [area_type]: type: array path: ritual.area_type is_null: true - values: 27 + values: 29 min_count: 1 max_count: 1 value_type: type: string path: ritual.area_type.[] is_null: false - values: 27 + values: 29 different_values: 5 variants: - - "circle" (x13) + - "circle" (x14) - "burst" (x6) - "cuboid" (x6) + - "other" (x2) - "square" (x1) - - "other" (x1) [duration]: type: integer path: ritual.duration is_null: true - values: 59 + values: 61 different_values: 12 variants: - 31536000 (x22) + - 604800 (x8) - 600 (x7) - - 604800 (x7) - 86400 (x6) - - 2592000 (x4) + - 2592000 (x5) - 43200 (x3) - 259200 (x3) - 432000 (x2) @@ -137,36 +117,36 @@ schema: type: string path: ritual.duration_raw is_null: true - values: 85 + values: 88 different_values: 23 variants: [omitted] 'summary_markdown': type: string path: ritual.summary_markdown is_null: false - values: 161 - different_values: 150 + values: 166 + different_values: 154 variants: [omitted] 'trait': type: array path: ritual.trait is_null: false - values: 161 + values: 166 min_count: 1 max_count: 7 value_type: type: string path: ritual.trait.[] is_null: false - values: 389 + values: 395 different_values: 46 variants: [omitted] 'trait_markdown': type: string path: ritual.trait_markdown is_null: false - values: 161 - different_values: 81 + values: 166 + different_values: 84 variants: [omitted] [element]: type: array @@ -189,32 +169,32 @@ schema: type: array path: ritual.heighten_group is_null: false - values: 161 + values: 166 min_count: 1 max_count: 10 value_type: type: string path: ritual.heighten_group.[] is_null: false - values: 334 + values: 340 different_values: 19 variants: - "Heightened 9th rank" (x33) - - "Heightened 10th rank" (x30) + - "Heightened 10th rank" (x31) + - "5th rank" (x28) - "Heightened 6th rank" (x26) - "Heightened 8th rank" (x26) - - "5th rank" (x26) - "3rd rank" (x24) - "Heightened 7th rank" (x23) + - "6th rank" (x23) - "2nd rank" (x22) - - "6th rank" (x22) - - "4th rank" (x19) + - "4th rank" (x20) - "Heightened 5th rank" (x15) - "8th rank" (x13) - "7th rank" (x12) - "Heightened 4th rank" (x12) - "1st rank" (x11) - - "9th rank" (x9) + - "9th rank" (x10) - "Heightened 3rd rank" (x5) - "10th rank" (x3) - "Heightened 2nd rank" (x3) @@ -222,23 +202,23 @@ schema: type: array path: ritual.heighten_level is_null: false - values: 161 + values: 166 min_count: 1 max_count: 10 value_type: type: integer path: ritual.heighten_level.[] is_null: false - values: 334 + values: 340 different_values: 10 variants: - - 6 (x48) - - 9 (x42) - - 5 (x41) + - 6 (x49) + - 5 (x43) + - 9 (x43) - 8 (x39) - 7 (x35) - - 10 (x33) - - 4 (x31) + - 10 (x34) + - 4 (x32) - 3 (x29) - 2 (x25) - 1 (x11) @@ -246,61 +226,61 @@ schema: type: string path: ritual.id is_null: false - values: 161 - different_values: 161 (all different) + values: 166 + different_values: 166 (all different) variants: [omitted] 'source': type: array path: ritual.source is_null: false - values: 161 + values: 166 min_count: 1 max_count: 1 value_type: type: string path: ritual.source.[] is_null: false - values: 161 - different_values: 39 + values: 166 + different_values: 44 variants: [omitted] 'type': type: string path: ritual.type is_null: false - values: 161 + values: 166 different_values: 1 (all same) - variants: "Ritual" (x161) + variants: "Ritual" (x166) 'url': type: string path: ritual.url is_null: false - values: 161 - different_values: 161 (all different) + values: 166 + different_values: 166 (all different) variants: [omitted] 'weakness': type: object path: ritual.weakness is_null: false - values: 161 + values: 166 schema: [heighten]: type: array path: ritual.heighten is_null: true - values: 57 + values: 58 min_count: 1 max_count: 6 value_type: type: string path: ritual.heighten.[] is_null: false - values: 96 + values: 97 different_values: 9 variants: - "9th" (x18) - "+1" (x15) + - "10th" (x15) - "6th" (x15) - - "10th" (x14) - "7th" (x10) - "8th" (x10) - "5th" (x6) @@ -310,56 +290,56 @@ schema: type: array path: ritual.legacy_id is_null: true - values: 35 + values: 36 min_count: 1 max_count: 4 value_type: type: string path: ritual.legacy_id.[] is_null: false - values: 39 - different_values: 39 + values: 40 + different_values: 40 variants: [omitted] 'level': type: integer path: ritual.level is_null: false - values: 161 + values: 166 different_values: 10 variants: - - 5 (x26) + - 5 (x28) - 3 (x24) + - 6 (x23) - 2 (x22) - - 6 (x22) - - 4 (x19) + - 4 (x20) - 8 (x13) - 7 (x12) - 1 (x11) - - 9 (x9) + - 9 (x10) - 10 (x3) 'markdown': type: string path: ritual.markdown is_null: false - values: 161 - different_values: 161 (all different) + values: 166 + different_values: 166 (all different) variants: [omitted] 'name': type: string path: ritual.name is_null: false - values: 161 - different_values: 135 + values: 166 + different_values: 140 variants: [omitted] 'primary_source_category': type: string path: ritual.primary_source_category is_null: false - values: 161 + values: 166 different_values: 6 variants: - "Rulebooks" (x116) - - "Adventure Paths" (x25) + - "Adventure Paths" (x30) - "Lost Omens" (x14) - "Adventures" (x4) - "Blog Posts" (x1) @@ -377,19 +357,20 @@ schema: type: object path: ritual.resistance is_null: false - values: 161 + values: 166 schema: [primary_source_group]: type: string path: ritual.primary_source_group is_null: true - values: 29 - different_values: 15 + values: 34 + different_values: 19 variants: - "Season of Ghosts" (x9) - "Strength of Thousands" (x5) - "Extinction Curse" (x2) - "Blood Lords" (x2) + - "Wardens of Wildwood" (x2) - "Agents of Edgewatch" (x1) - "Abomination Vaults" (x1) - "Malevolence" (x1) @@ -401,56 +382,59 @@ schema: - "Stolen Fate" (x1) - "The Enmity Cycle" (x1) - "Sky King's Tomb" (x1) + - "Curtain Call" (x1) + - "Spore War" (x1) + - "Triumph of the Tusk" (x1) 'rarity': type: string path: ritual.rarity is_null: false - values: 161 + values: 166 different_values: 3 variants: - "uncommon" (x101) - - "rare" (x57) - - "unique" (x3) + - "rare" (x60) + - "unique" (x5) 'rarity_id': type: integer path: ritual.rarity_id is_null: false - values: 161 + values: 166 different_values: 3 variants: - 2 (x101) - - 3 (x57) - - 4 (x3) + - 3 (x60) + - 4 (x5) 'release_date': type: string path: ritual.release_date is_null: false - values: 161 - different_values: 33 + values: 166 + different_values: 38 variants: [omitted] [pfs]: type: string path: ritual.pfs is_null: true - values: 100 + values: 105 different_values: 3 variants: - "Standard" (x73) - "Limited" (x16) - - "Restricted" (x11) + - "Restricted" (x16) 'primary_check_markdown': type: string path: ritual.primary_check_markdown is_null: false - values: 161 - different_values: 96 + values: 166 + different_values: 101 variants: [omitted] 'search_markdown': type: string path: ritual.search_markdown is_null: false - values: 161 - different_values: 161 (all different) + values: 166 + different_values: 166 (all different) variants: [omitted] [range]: type: integer @@ -496,15 +480,15 @@ schema: type: array path: ritual.remaster_id is_null: true - values: 34 + values: 35 min_count: 1 max_count: 1 value_type: type: string path: ritual.remaster_id.[] is_null: false - values: 34 - different_values: 33 + values: 35 + different_values: 34 variants: [omitted] [school]: type: string @@ -525,34 +509,35 @@ schema: type: string path: ritual.secondary_casters_raw is_null: true - values: 141 - different_values: 21 + values: 146 + different_values: 22 variants: [omitted] [secondary_check_markdown]: type: string path: ritual.secondary_check_markdown is_null: true - values: 141 - different_values: 111 + values: 146 + different_values: 115 variants: [omitted] [source_group]: type: array path: ritual.source_group is_null: true - values: 29 + values: 34 min_count: 1 max_count: 1 value_type: type: string path: ritual.source_group.[] is_null: false - values: 29 - different_values: 15 + values: 34 + different_values: 19 variants: - "Season of Ghosts" (x9) - "Strength of Thousands" (x5) - "Extinction Curse" (x2) - "Blood Lords" (x2) + - "Wardens of Wildwood" (x2) - "Agents of Edgewatch" (x1) - "Abomination Vaults" (x1) - "Malevolence" (x1) @@ -564,17 +549,21 @@ schema: - "Stolen Fate" (x1) - "The Enmity Cycle" (x1) - "Sky King's Tomb" (x1) + - "Curtain Call" (x1) + - "Spore War" (x1) + - "Triumph of the Tusk" (x1) [spoilers]: type: string path: ritual.spoilers is_null: true - values: 29 - different_values: 15 + values: 34 + different_values: 19 variants: - "Season of Ghosts" (x9) - "Strength of Thousands" (x5) - "Extinction Curse" (x2) - "Blood Lords" (x2) + - "Wardens of Wildwood" (x2) - "Agents of Edgewatch" (x1) - "Abomination Vaults" (x1) - "Malevolence" (x1) @@ -586,6 +575,9 @@ schema: - "Stolen Fate" (x1) - "The Enmity Cycle" (x1) - "Sky King's Tomb" (x1) + - "Curtain Call" (x1) + - "Spore War" (x1) + - "Triumph of the Tusk" (x1) [target_markdown]: type: string path: ritual.target_markdown diff --git a/output/aonprd/rules.yaml b/output/aonprd/rules.yaml index 2a32b6c..a5c33ea 100644 --- a/output/aonprd/rules.yaml +++ b/output/aonprd/rules.yaml @@ -1,49 +1,49 @@ name: rules -values: 3385 +values: 3386 schema: 'category': type: string path: rules.category is_null: false - values: 3385 + values: 3386 different_values: 1 (all same) - variants: "rules" (x3385) + variants: "rules" (x3386) 'id': type: string path: rules.id is_null: false - values: 3385 - different_values: 3385 (all different) + values: 3386 + different_values: 3386 (all different) variants: [omitted] [breadcrumbs]: type: array path: rules.breadcrumbs is_null: true - values: 3280 + values: 3281 min_count: 1 max_count: 6 value_type: type: string path: rules.breadcrumbs.[] is_null: false - values: 8207 + values: 8210 different_values: 618 variants: [omitted] 'source_category': type: array path: rules.source_category is_null: false - values: 3385 + values: 3386 min_count: 1 max_count: 1 value_type: type: string path: rules.source_category.[] is_null: false - values: 3385 + values: 3386 different_values: 4 variants: - - "Rulebooks" (x3127) + - "Rulebooks" (x3128) - "Adventure Paths" (x207) - "Lost Omens" (x47) - "Adventures" (x4) @@ -51,78 +51,78 @@ schema: type: string path: rules.source_markdown is_null: false - values: 3385 - different_values: 913 + values: 3386 + different_values: 914 variants: [omitted] 'speed': type: object path: rules.speed is_null: false - values: 3385 + values: 3386 schema: 'summary_markdown': type: string path: rules.summary_markdown is_null: false - values: 3385 - different_values: 2804 + values: 3386 + different_values: 2805 variants: [omitted] 'type': type: string path: rules.type is_null: false - values: 3385 + values: 3386 different_values: 1 (all same) - variants: "Rules" (x3385) + variants: "Rules" (x3386) 'url': type: string path: rules.url is_null: false - values: 3385 - different_values: 3385 (all different) + values: 3386 + different_values: 3386 (all different) variants: [omitted] 'weakness': type: object path: rules.weakness is_null: false - values: 3385 + values: 3386 schema: [legacy_id]: type: array path: rules.legacy_id is_null: true - values: 1309 + values: 1310 min_count: 1 max_count: 1 value_type: type: string path: rules.legacy_id.[] is_null: false - values: 1309 - different_values: 1179 + values: 1310 + different_values: 1180 variants: [omitted] 'markdown': type: string path: rules.markdown is_null: false - values: 3385 - different_values: 3385 (all different) + values: 3386 + different_values: 3386 (all different) variants: [omitted] 'name': type: string path: rules.name is_null: false - values: 3385 + values: 3386 different_values: 2023 variants: [omitted] 'primary_source_category': type: string path: rules.primary_source_category is_null: false - values: 3385 + values: 3386 different_values: 4 variants: - - "Rulebooks" (x3127) + - "Rulebooks" (x3128) - "Adventure Paths" (x207) - "Lost Omens" (x47) - "Adventures" (x4) @@ -130,28 +130,28 @@ schema: type: string path: rules.rarity is_null: false - values: 3385 + values: 3386 different_values: 1 (all same) - variants: "common" (x3385) + variants: "common" (x3386) 'rarity_id': type: integer path: rules.rarity_id is_null: false - values: 3385 + values: 3386 different_values: 1 (all same) - variants: 1 (x3385) + variants: 1 (x3386) 'release_date': type: string path: rules.release_date is_null: false - values: 3385 + values: 3386 different_values: 29 variants: [omitted] 'resistance': type: object path: rules.resistance is_null: false - values: 3385 + values: 3386 schema: [primary_source_group]: type: string @@ -172,35 +172,35 @@ schema: type: array path: rules.remaster_id is_null: true - values: 1179 + values: 1180 min_count: 1 max_count: 1 value_type: type: string path: rules.remaster_id.[] is_null: false - values: 1179 - different_values: 1179 + values: 1180 + different_values: 1180 variants: [omitted] 'search_markdown': type: string path: rules.search_markdown is_null: false - values: 3385 - different_values: 3385 (all different) + values: 3386 + different_values: 3386 (all different) variants: [omitted] 'source': type: array path: rules.source is_null: false - values: 3385 + values: 3386 min_count: 1 max_count: 1 value_type: type: string path: rules.source.[] is_null: false - values: 3385 + values: 3386 different_values: 36 variants: [omitted] [source_group]: diff --git a/output/aonprd/sidebar.yaml b/output/aonprd/sidebar.yaml index cc75653..96f3777 100644 --- a/output/aonprd/sidebar.yaml +++ b/output/aonprd/sidebar.yaml @@ -1,35 +1,35 @@ name: sidebar -values: 618 +values: 617 schema: 'category': type: string path: sidebar.category is_null: false - values: 618 + values: 617 different_values: 1 (all same) - variants: "sidebar" (x618) + variants: "sidebar" (x617) 'id': type: string path: sidebar.id is_null: false - values: 618 - different_values: 618 (all different) + values: 617 + different_values: 617 (all different) variants: [omitted] 'source_category': type: array path: sidebar.source_category is_null: false - values: 618 + values: 617 min_count: 1 max_count: 1 value_type: type: string path: sidebar.source_category.[] is_null: false - values: 618 + values: 617 different_values: 4 variants: - - "Rulebooks" (x511) + - "Rulebooks" (x510) - "Lost Omens" (x85) - "Adventure Paths" (x21) - "Adventures" (x1) @@ -37,41 +37,41 @@ schema: type: string path: sidebar.source_markdown is_null: false - values: 618 - different_values: 405 + values: 617 + different_values: 404 variants: [omitted] 'speed': type: object path: sidebar.speed is_null: false - values: 618 + values: 617 schema: 'summary_markdown': type: string path: sidebar.summary_markdown is_null: false - values: 618 + values: 617 different_values: 553 variants: [omitted] 'type': type: string path: sidebar.type is_null: false - values: 618 + values: 617 different_values: 1 (all same) - variants: "Sidebar" (x618) + variants: "Sidebar" (x617) 'url': type: string path: sidebar.url is_null: false - values: 618 - different_values: 440 + values: 617 + different_values: 439 variants: [omitted] 'weakness': type: object path: sidebar.weakness is_null: false - values: 618 + values: 617 schema: [legacy_id]: type: array @@ -91,17 +91,17 @@ schema: type: string path: sidebar.markdown is_null: false - values: 618 - different_values: 618 (all different) + values: 617 + different_values: 617 (all different) variants: [omitted] 'primary_source_category': type: string path: sidebar.primary_source_category is_null: false - values: 618 + values: 617 different_values: 4 variants: - - "Rulebooks" (x511) + - "Rulebooks" (x510) - "Lost Omens" (x85) - "Adventure Paths" (x21) - "Adventures" (x1) @@ -109,27 +109,27 @@ schema: type: string path: sidebar.release_date is_null: false - values: 618 + values: 617 different_values: 25 variants: [omitted] 'resistance': type: object path: sidebar.resistance is_null: false - values: 618 + values: 617 schema: 'search_markdown': type: string path: sidebar.search_markdown is_null: false - values: 618 - different_values: 616 + values: 617 + different_values: 615 variants: [omitted] [name]: type: string path: sidebar.name is_null: true - values: 617 + values: 616 different_values: 433 variants: [omitted] [primary_source_group]: @@ -164,14 +164,14 @@ schema: type: array path: sidebar.source is_null: false - values: 618 + values: 617 min_count: 1 max_count: 1 value_type: type: string path: sidebar.source.[] is_null: false - values: 618 + values: 617 different_values: 30 variants: [omitted] [source_group]: diff --git a/output/aonprd/source.yaml b/output/aonprd/source.yaml index bea4d7b..28d0c4e 100644 --- a/output/aonprd/source.yaml +++ b/output/aonprd/source.yaml @@ -1,42 +1,42 @@ name: source -values: 200 +values: 220 schema: 'category': type: string path: source.category is_null: false - values: 200 + values: 220 different_values: 1 (all same) - variants: "source" (x200) + variants: "source" (x220) 'id': type: string path: source.id is_null: false - values: 200 - different_values: 200 (all different) + values: 220 + different_values: 220 (all different) variants: [omitted] 'markdown': type: string path: source.markdown is_null: false - values: 200 - different_values: 200 (all different) + values: 220 + different_values: 220 (all different) variants: [omitted] 'name': type: string path: source.name is_null: false - values: 200 - different_values: 200 (all different) + values: 220 + different_values: 220 (all different) variants: [omitted] 'primary_source_category': type: string path: source.primary_source_category is_null: false - values: 200 + values: 220 different_values: 7 variants: - - "Adventure Paths" (x73) + - "Adventure Paths" (x93) - "Adventures" (x42) - "Blog Posts" (x26) - "Rulebooks" (x22) @@ -47,65 +47,65 @@ schema: type: string path: source.rarity is_null: false - values: 200 + values: 220 different_values: 1 (all same) - variants: "common" (x200) + variants: "common" (x220) 'rarity_id': type: integer path: source.rarity_id is_null: false - values: 200 + values: 220 different_values: 1 (all same) - variants: 1 (x200) + variants: 1 (x220) 'release_date': type: string path: source.release_date is_null: false - values: 200 - different_values: 118 + values: 220 + different_values: 133 variants: [omitted] 'resistance': type: object path: source.resistance is_null: false - values: 200 + values: 220 schema: 'search_markdown': type: string path: source.search_markdown is_null: false - values: 200 - different_values: 175 + values: 220 + different_values: 195 variants: [omitted] 'source': type: array path: source.source is_null: false - values: 200 + values: 220 min_count: 1 max_count: 1 value_type: type: string path: source.source.[] is_null: false - values: 200 - different_values: 200 (all different) + values: 220 + different_values: 220 (all different) variants: [omitted] 'source_category': type: array path: source.source_category is_null: false - values: 200 + values: 220 min_count: 1 max_count: 1 value_type: type: string path: source.source_category.[] is_null: false - values: 200 + values: 220 different_values: 7 variants: - - "Adventure Paths" (x73) + - "Adventure Paths" (x93) - "Adventures" (x42) - "Blog Posts" (x26) - "Rulebooks" (x22) @@ -116,53 +116,53 @@ schema: type: string path: source.source_markdown is_null: false - values: 200 - different_values: 200 (all different) + values: 220 + different_values: 220 (all different) variants: [omitted] 'speed': type: object path: source.speed is_null: false - values: 200 + values: 220 schema: 'type': type: string path: source.type is_null: false - values: 200 + values: 220 different_values: 1 (all same) - variants: "Source" (x200) + variants: "Source" (x220) 'url': type: string path: source.url is_null: false - values: 200 - different_values: 200 (all different) + values: 220 + different_values: 220 (all different) variants: [omitted] 'weakness': type: object path: source.weakness is_null: false - values: 200 + values: 220 schema: [primary_source_group]: type: string path: source.primary_source_group is_null: true - values: 117 - different_values: 30 + values: 137 + different_values: 35 variants: [omitted] [source_group]: type: array path: source.source_group is_null: true - values: 117 + values: 137 min_count: 1 max_count: 1 value_type: type: string path: source.source_group.[] is_null: false - values: 117 - different_values: 30 + values: 137 + different_values: 35 variants: [omitted] diff --git a/output/aonprd/spell.yaml b/output/aonprd/spell.yaml index 21f1017..c5d6f57 100644 --- a/output/aonprd/spell.yaml +++ b/output/aonprd/spell.yaml @@ -1,23 +1,23 @@ name: spell -values: 2274 +values: 2296 schema: [actions]: type: string path: spell.actions is_null: true - values: 2273 + values: 2294 different_values: 18 variants: - - "Two Actions" (x1349) - - "Single Action" (x336) - - "Three Actions" (x225) + - "Two Actions" (x1367) + - "Single Action" (x337) + - "Three Actions" (x226) - "Reaction" (x124) - "1 minute" (x90) - "10 minutes" (x68) - "Single Action to Three Actions" (x33) - "Free Action" (x17) + - "Two Actions or Three Actions" (x7) - "1 hour" (x6) - - "Two Actions or Three Actions" (x6) - "Single Action to Two Actions" (x5) - "Single Action or Two Actions" (x5) - "Two Actions to 2 rounds" (x3) @@ -30,44 +30,44 @@ schema: type: string path: spell.bloodline_markdown is_null: false - values: 2274 + values: 2296 different_values: 68 variants: [omitted] 'category': type: string path: spell.category is_null: false - values: 2274 + values: 2296 different_values: 1 (all same) - variants: "spell" (x2274) + variants: "spell" (x2296) [area_raw]: type: string path: spell.area_raw is_null: true - values: 523 - different_values: 143 + values: 528 + different_values: 147 variants: [omitted] [area_type]: type: array path: spell.area_type is_null: true - values: 523 + values: 528 min_count: 1 max_count: 3 value_type: type: string path: spell.area_type.[] is_null: false - values: 543 + values: 548 different_values: 11 variants: - - "burst" (x227) + - "burst" (x229) - "emanation" (x150) - - "cone" (x64) + - "cone" (x66) - "line" (x27) - "square" (x25) - "cylinder" (x21) - - "cube" (x16) + - "cube" (x17) - "varies" (x6) - "cuboid" (x4) - "circle" (x2) @@ -76,91 +76,91 @@ schema: type: string path: spell.rarity is_null: false - values: 2274 + values: 2296 different_values: 3 variants: - - "common" (x1193) - - "uncommon" (x968) - - "rare" (x113) + - "common" (x1189) + - "uncommon" (x984) + - "rare" (x123) 'rarity_id': type: integer path: spell.rarity_id is_null: false - values: 2274 + values: 2296 different_values: 3 variants: - - 1 (x1193) - - 2 (x968) - - 3 (x113) + - 1 (x1189) + - 2 (x984) + - 3 (x123) 'resistance': type: object path: spell.resistance is_null: false - values: 2274 + values: 2296 schema: 'search_markdown': type: string path: spell.search_markdown is_null: false - values: 2274 - different_values: 2274 (all different) + values: 2296 + different_values: 2296 (all different) variants: [omitted] 'source': type: array path: spell.source is_null: false - values: 2274 + values: 2296 min_count: 1 max_count: 2 value_type: type: string path: spell.source.[] is_null: false - values: 2315 - different_values: 73 + values: 2335 + different_values: 80 variants: [omitted] 'source_category': type: array path: spell.source_category is_null: false - values: 2274 + values: 2296 min_count: 1 max_count: 2 value_type: type: string path: spell.source_category.[] is_null: false - values: 2297 + values: 2317 different_values: 5 variants: - - "Rulebooks" (x1792) - - "Lost Omens" (x302) - - "Adventure Paths" (x182) + - "Rulebooks" (x1791) + - "Lost Omens" (x301) + - "Adventure Paths" (x204) - "Adventures" (x13) - "Comics" (x8) 'source_markdown': type: string path: spell.source_markdown is_null: false - values: 2274 - different_values: 545 + values: 2296 + different_values: 554 variants: [omitted] 'speed': type: object path: spell.speed is_null: false - values: 2274 + values: 2296 schema: 'spell_type': type: string path: spell.spell_type is_null: false - values: 2274 + values: 2296 different_values: 3 variants: - - "Spell" (x1410) - - "Focus" (x703) - - "Cantrip" (x161) + - "Spell" (x1428) + - "Focus" (x706) + - "Cantrip" (x162) [cost_markdown]: type: string path: spell.cost_markdown @@ -181,50 +181,50 @@ schema: type: string path: spell.deity_markdown is_null: true - values: 513 - different_values: 286 + values: 514 + different_values: 287 variants: [omitted] 'domain_markdown': type: string path: spell.domain_markdown is_null: false - values: 2274 + values: 2296 different_values: 123 variants: [omitted] 'heighten_level': type: array path: spell.heighten_level is_null: false - values: 2274 + values: 2296 min_count: 1 max_count: 11 value_type: type: integer path: spell.heighten_level.[] is_null: false - values: 7718 + values: 7792 different_values: 10 variants: - - 5 (x866) - - 4 (x861) - - 9 (x851) - - 7 (x838) - - 6 (x831) - - 8 (x799) - - 10 (x750) - - 3 (x707) - - 1 (x684) - - 2 (x531) + - 5 (x873) + - 4 (x867) + - 9 (x865) + - 7 (x849) + - 6 (x839) + - 8 (x809) + - 10 (x759) + - 3 (x715) + - 1 (x686) + - 2 (x530) [duration]: type: integer path: spell.duration is_null: true - values: 1203 + values: 1212 different_values: 15 variants: - - 60 (x769) - - 600 (x146) - - 3600 (x99) + - 60 (x774) + - 600 (x147) + - 3600 (x102) - 6 (x74) - 28800 (x30) - 86400 (x28) @@ -241,21 +241,21 @@ schema: type: string path: spell.duration_raw is_null: true - values: 1535 - different_values: 67 + values: 1546 + different_values: 71 variants: [omitted] [element]: type: array path: spell.element is_null: true - values: 476 + values: 480 min_count: 1 max_count: 4 value_type: type: string path: spell.element.[] is_null: false - values: 499 + values: 503 different_values: 7 variants: - "Fire" (x100) @@ -263,89 +263,89 @@ schema: - "Air" (x81) - "Universal" (x81) - "Water" (x78) - - "Wood" (x48) + - "Wood" (x52) - "Metal" (x28) 'heighten_group': type: array path: spell.heighten_group is_null: false - values: 2274 + values: 2296 min_count: 1 max_count: 11 value_type: type: string path: spell.heighten_group.[] is_null: false - values: 7718 + values: 7792 different_values: 19 variants: - - "Heightened 9th rank" (x789) - - "Heightened 7th rank" (x736) - - "Heightened 8th rank" (x729) - - "Heightened 10th rank" (x709) - - "Heightened 6th rank" (x699) - - "1st rank" (x684) - - "Heightened 5th rank" (x632) - - "Heightened 4th rank" (x497) - - "Heightened 3rd rank" (x406) - - "4th rank" (x364) - - "3rd rank" (x301) + - "Heightened 9th rank" (x801) + - "Heightened 7th rank" (x745) + - "Heightened 8th rank" (x737) + - "Heightened 10th rank" (x718) + - "Heightened 6th rank" (x704) + - "1st rank" (x686) + - "Heightened 5th rank" (x637) + - "Heightened 4th rank" (x500) + - "Heightened 3rd rank" (x408) + - "4th rank" (x367) + - "3rd rank" (x307) - "2nd rank" (x284) - - "Heightened 2nd rank" (x247) - - "5th rank" (x234) - - "6th rank" (x132) - - "7th rank" (x102) - - "8th rank" (x70) - - "9th rank" (x62) + - "Heightened 2nd rank" (x246) + - "5th rank" (x236) + - "6th rank" (x135) + - "7th rank" (x104) + - "8th rank" (x72) + - "9th rank" (x64) - "10th rank" (x41) 'id': type: string path: spell.id is_null: false - values: 2274 - different_values: 2274 (all different) + values: 2296 + different_values: 2296 (all different) variants: [omitted] 'level': type: integer path: spell.level is_null: false - values: 2274 + values: 2296 different_values: 10 variants: - - 1 (x684) - - 4 (x364) - - 3 (x301) + - 1 (x686) + - 4 (x367) + - 3 (x307) - 2 (x284) - - 5 (x234) - - 6 (x132) - - 7 (x102) - - 8 (x70) - - 9 (x62) + - 5 (x236) + - 6 (x135) + - 7 (x104) + - 8 (x72) + - 9 (x64) - 10 (x41) 'markdown': type: string path: spell.markdown is_null: false - values: 2274 - different_values: 2274 (all different) + values: 2296 + different_values: 2296 (all different) variants: [omitted] 'name': type: string path: spell.name is_null: false - values: 2274 - different_values: 1717 + values: 2296 + different_values: 1737 variants: [omitted] 'primary_source_category': type: string path: spell.primary_source_category is_null: false - values: 2274 + values: 2296 different_values: 5 variants: - "Rulebooks" (x1785) - "Lost Omens" (x295) - - "Adventure Paths" (x173) + - "Adventure Paths" (x195) - "Adventures" (x13) - "Comics" (x8) [domain]: @@ -366,26 +366,26 @@ schema: type: array path: spell.heighten is_null: true - values: 1341 + values: 1356 min_count: 1 max_count: 9 value_type: type: string path: spell.heighten.[] is_null: false - values: 1800 + values: 1825 different_values: 13 variants: - - "+1" (x536) - - "+2" (x215) - - "6th" (x159) - - "5th" (x142) - - "4th" (x135) - - "9th" (x126) - - "7th" (x122) - - "8th" (x116) - - "3rd" (x108) - - "10th" (x68) + - "+1" (x543) + - "+2" (x216) + - "6th" (x161) + - "5th" (x143) + - "4th" (x137) + - "9th" (x130) + - "7th" (x125) + - "8th" (x117) + - "3rd" (x110) + - "10th" (x70) - "2nd" (x47) - "+3" (x22) - "+4" (x4) @@ -407,46 +407,29 @@ schema: type: string path: spell.primary_source_group is_null: true - values: 186 - different_values: 17 - variants: - - "Blood Lords" (x39) - - "Extinction Curse" (x25) - - "Strength of Thousands" (x25) - - "Agents of Edgewatch" (x13) - - "Age of Ashes" (x12) - - "Kingmaker" (x10) - - "Season of Ghosts" (x10) - - "Abomination Vaults" (x9) - - "Quest for the Frozen Flame" (x9) - - "Prey for Death" (x9) - - "Sky King's Tomb" (x8) - - "Fists of the Ruby Phoenix" (x6) - - "Gatewalkers" (x4) - - "Stolen Fate" (x3) - - "Threshold of Knowledge" (x2) - - "Malevolence" (x1) - - "The Enmity Cycle" (x1) + values: 208 + different_values: 21 + variants: [omitted] 'release_date': type: string path: spell.release_date is_null: false - values: 2274 - different_values: 60 + values: 2296 + different_values: 67 variants: [omitted] [remaster_id]: type: array path: spell.remaster_id is_null: true - values: 710 + values: 713 min_count: 1 max_count: 2 value_type: type: string path: spell.remaster_id.[] is_null: false - values: 714 - different_values: 703 + values: 717 + different_values: 706 variants: [omitted] [requirement_markdown]: type: string @@ -459,80 +442,46 @@ schema: type: array path: spell.source_group is_null: true - values: 195 + values: 217 min_count: 1 max_count: 1 value_type: type: string path: spell.source_group.[] is_null: false - values: 195 - different_values: 17 - variants: - - "Blood Lords" (x39) - - "Extinction Curse" (x27) - - "Strength of Thousands" (x25) - - "Age of Ashes" (x16) - - "Agents of Edgewatch" (x15) - - "Abomination Vaults" (x10) - - "Kingmaker" (x10) - - "Season of Ghosts" (x10) - - "Quest for the Frozen Flame" (x9) - - "Prey for Death" (x9) - - "Sky King's Tomb" (x8) - - "Fists of the Ruby Phoenix" (x6) - - "Gatewalkers" (x4) - - "Stolen Fate" (x3) - - "Threshold of Knowledge" (x2) - - "Malevolence" (x1) - - "The Enmity Cycle" (x1) + values: 217 + different_values: 21 + variants: [omitted] [spoilers]: type: string path: spell.spoilers is_null: true - values: 180 - different_values: 17 - variants: - - "Blood Lords" (x39) - - "Extinction Curse" (x25) - - "Strength of Thousands" (x25) - - "Age of Ashes" (x11) - - "Agents of Edgewatch" (x11) - - "Kingmaker" (x10) - - "Season of Ghosts" (x10) - - "Abomination Vaults" (x9) - - "Quest for the Frozen Flame" (x9) - - "Prey for Death" (x9) - - "Sky King's Tomb" (x8) - - "Fists of the Ruby Phoenix" (x6) - - "Stolen Fate" (x3) - - "Threshold of Knowledge" (x2) - - "Malevolence" (x1) - - "Gatewalkers" (x1) - - "The Enmity Cycle" (x1) + values: 202 + different_values: 21 + variants: [omitted] 'summary_markdown': type: string path: spell.summary_markdown is_null: false - values: 2274 - different_values: 1928 + values: 2296 + different_values: 1950 variants: [omitted] [component]: type: array path: spell.component is_null: true - values: 1353 + values: 1356 min_count: 1 max_count: 4 value_type: type: string path: spell.component.[] is_null: false - values: 2587 + values: 2593 different_values: 12 variants: - - "verbal" (x1139) - - "somatic" (x1132) + - "verbal" (x1142) + - "somatic" (x1135) - "material" (x241) - "1 minute" (x37) - "10 minutes" (x28) @@ -547,58 +496,58 @@ schema: type: array path: spell.deity is_null: true - values: 513 + values: 514 min_count: 1 max_count: 30 value_type: type: string path: spell.deity.[] is_null: false - values: 2688 - different_values: 446 + values: 2686 + different_values: 447 variants: [omitted] 'tradition_markdown': type: string path: spell.tradition_markdown is_null: false - values: 2274 + values: 2296 different_values: 27 variants: [omitted] 'trait': type: array path: spell.trait is_null: false - values: 2274 + values: 2296 min_count: 1 max_count: 10 value_type: type: string path: spell.trait.[] is_null: false - values: 8602 + values: 8713 different_values: 109 variants: [omitted] [legacy_id]: type: array path: spell.legacy_id is_null: true - values: 701 + values: 704 min_count: 1 max_count: 4 value_type: type: string path: spell.legacy_id.[] is_null: false - values: 709 - different_values: 709 + values: 712 + different_values: 712 variants: [omitted] 'type': type: string path: spell.type is_null: false - values: 2274 + values: 2296 different_values: 1 (all same) - variants: "Spell" (x2274) + variants: "Spell" (x2296) [patron_theme_markdown]: type: string path: spell.patron_theme_markdown @@ -610,20 +559,20 @@ schema: type: array path: spell.tradition is_null: true - values: 1503 + values: 1525 min_count: 1 max_count: 5 value_type: type: string path: spell.tradition.[] is_null: false - values: 3317 + values: 3364 different_values: 5 variants: - - "Arcane" (x1027) - - "Occult" (x854) - - "Primal" (x773) - - "Divine" (x576) + - "Arcane" (x1039) + - "Occult" (x866) + - "Primal" (x788) + - "Divine" (x584) - "Elemental" (x87) [trigger_markdown]: type: string @@ -636,51 +585,51 @@ schema: type: string path: spell.url is_null: false - values: 2274 - different_values: 2274 (all different) + values: 2296 + different_values: 2296 (all different) variants: [omitted] [pfs]: type: string path: spell.pfs is_null: true - values: 2200 + values: 2222 different_values: 3 variants: - "Standard" (x2040) - - "Limited" (x151) + - "Limited" (x173) - "Restricted" (x9) [range]: type: integer path: spell.range is_null: true - values: 1553 - different_values: 25 + values: 1572 + different_values: 28 variants: [omitted] [range_raw]: type: string path: spell.range_raw is_null: true - values: 1571 - different_values: 44 + values: 1590 + different_values: 47 variants: [omitted] [saving_throw_markdown]: type: string path: spell.saving_throw_markdown is_null: true - values: 813 - different_values: 27 + values: 826 + different_values: 28 variants: [omitted] [school]: type: string path: spell.school is_null: true - values: 1355 + values: 1356 different_values: 8 variants: - "evocation" (x252) - "necromancy" (x193) - "transmutation" (x165) - - "illusion" (x162) + - "illusion" (x163) - "enchantment" (x161) - "divination" (x158) - "conjuration" (x140) @@ -689,15 +638,15 @@ schema: type: string path: spell.trait_markdown is_null: false - values: 2274 - different_values: 1265 + values: 2296 + different_values: 1285 variants: [omitted] [target_markdown]: type: string path: spell.target_markdown is_null: true - values: 1147 - different_values: 324 + values: 1159 + different_values: 330 variants: [omitted] [warden_spell_tier]: type: string @@ -714,5 +663,5 @@ schema: type: object path: spell.weakness is_null: false - values: 2274 + values: 2296 schema: diff --git a/output/aonprd/trait.yaml b/output/aonprd/trait.yaml index ecdc70a..22daca6 100644 --- a/output/aonprd/trait.yaml +++ b/output/aonprd/trait.yaml @@ -1,83 +1,83 @@ name: trait -values: 810 +values: 813 schema: 'category': type: string path: trait.category is_null: false - values: 810 + values: 813 different_values: 1 (all same) - variants: "trait" (x810) + variants: "trait" (x813) 'id': type: string path: trait.id is_null: false - values: 810 - different_values: 810 (all different) + values: 813 + different_values: 813 (all different) variants: [omitted] 'source_category': type: array path: trait.source_category is_null: false - values: 810 + values: 813 min_count: 1 max_count: 1 value_type: type: string path: trait.source_category.[] is_null: false - values: 810 + values: 813 different_values: 3 variants: - "Rulebooks" (x721) - - "Adventure Paths" (x55) + - "Adventure Paths" (x58) - "Lost Omens" (x34) 'source_markdown': type: string path: trait.source_markdown is_null: false - values: 810 - different_values: 236 + values: 813 + different_values: 239 variants: [omitted] 'speed': type: object path: trait.speed is_null: false - values: 810 + values: 813 schema: 'summary_markdown': type: string path: trait.summary_markdown is_null: false - values: 810 + values: 813 different_values: 682 variants: [omitted] 'trait_markdown': type: string path: trait.trait_markdown is_null: false - values: 810 - different_values: 540 + values: 813 + different_values: 543 variants: [omitted] 'type': type: string path: trait.type is_null: false - values: 810 + values: 813 different_values: 1 (all same) - variants: "Trait" (x810) + variants: "Trait" (x813) 'url': type: string path: trait.url is_null: false - values: 810 - different_values: 810 (all different) + values: 813 + different_values: 813 (all different) variants: [omitted] 'weakness': type: object path: trait.weakness is_null: false - values: 810 + values: 813 schema: [legacy_id]: type: array @@ -97,59 +97,59 @@ schema: type: string path: trait.markdown is_null: false - values: 810 - different_values: 810 (all different) + values: 813 + different_values: 813 (all different) variants: [omitted] 'name': type: string path: trait.name is_null: false - values: 810 - different_values: 540 + values: 813 + different_values: 543 variants: [omitted] 'primary_source_category': type: string path: trait.primary_source_category is_null: false - values: 810 + values: 813 different_values: 3 variants: - "Rulebooks" (x721) - - "Adventure Paths" (x55) + - "Adventure Paths" (x58) - "Lost Omens" (x34) 'rarity': type: string path: trait.rarity is_null: false - values: 810 + values: 813 different_values: 1 (all same) - variants: "common" (x810) + variants: "common" (x813) 'rarity_id': type: integer path: trait.rarity_id is_null: false - values: 810 + values: 813 different_values: 1 (all same) - variants: 1 (x810) + variants: 1 (x813) 'release_date': type: string path: trait.release_date is_null: false - values: 810 - different_values: 37 + values: 813 + different_values: 40 variants: [omitted] 'resistance': type: object path: trait.resistance is_null: false - values: 810 + values: 813 schema: [primary_source_group]: type: string path: trait.primary_source_group is_null: true - values: 55 - different_values: 10 + values: 58 + different_values: 13 variants: - "Kingmaker" (x31) - "Age of Ashes" (x4) @@ -161,6 +161,9 @@ schema: - "Stolen Fate" (x2) - "Abomination Vaults" (x1) - "Sky King's Tomb" (x1) + - "Wardens of Wildwood" (x1) + - "Curtain Call" (x1) + - "Spore War" (x1) [remaster_id]: type: array path: trait.remaster_id @@ -179,36 +182,36 @@ schema: type: string path: trait.search_markdown is_null: false - values: 810 - different_values: 809 + values: 813 + different_values: 812 variants: [omitted] 'source': type: array path: trait.source is_null: false - values: 810 + values: 813 min_count: 1 max_count: 1 value_type: type: string path: trait.source.[] is_null: false - values: 810 - different_values: 49 + values: 813 + different_values: 52 variants: [omitted] [source_group]: type: array path: trait.source_group is_null: true - values: 55 + values: 58 min_count: 1 max_count: 1 value_type: type: string path: trait.source_group.[] is_null: false - values: 55 - different_values: 10 + values: 58 + different_values: 13 variants: - "Kingmaker" (x31) - "Age of Ashes" (x4) @@ -220,17 +223,20 @@ schema: - "Stolen Fate" (x2) - "Abomination Vaults" (x1) - "Sky King's Tomb" (x1) + - "Wardens of Wildwood" (x1) + - "Curtain Call" (x1) + - "Spore War" (x1) [trait]: type: array path: trait.trait is_null: true - values: 808 + values: 811 min_count: 1 max_count: 2 value_type: type: string path: trait.trait.[] is_null: false - values: 828 - different_values: 539 + values: 831 + different_values: 542 variants: [omitted] diff --git a/output/aonprd/weapon.yaml b/output/aonprd/weapon.yaml index d7600df..3714536 100644 --- a/output/aonprd/weapon.yaml +++ b/output/aonprd/weapon.yaml @@ -1,15 +1,15 @@ name: weapon -values: 450 +values: 451 schema: 'bulk': type: float path: weapon.bulk is_null: false - values: 450 + values: 451 different_values: 5 variants: - 0,1 (x169) - - 1 (x155) + - 1 (x156) - 2 (x103) - 0 (x19) - 3 (x4) @@ -17,44 +17,44 @@ schema: type: string path: weapon.summary_markdown is_null: false - values: 450 - different_values: 360 + values: 451 + different_values: 361 variants: [omitted] 'trait_markdown': type: string path: weapon.trait_markdown is_null: false - values: 450 - different_values: 299 + values: 451 + different_values: 300 variants: [omitted] 'type': type: string path: weapon.type is_null: false - values: 450 + values: 451 different_values: 1 (all same) - variants: "Item" (x450) + variants: "Item" (x451) 'url': type: string path: weapon.url is_null: false - values: 450 - different_values: 450 (all different) + values: 451 + different_values: 451 (all different) variants: [omitted] 'weakness': type: object path: weapon.weakness is_null: false - values: 450 + values: 451 schema: 'weapon_category': type: string path: weapon.weapon_category is_null: false - values: 450 + values: 451 different_values: 5 variants: - - "Martial" (x282) + - "Martial" (x283) - "Simple" (x75) - "Advanced" (x68) - "Ammunition" (x23) @@ -63,7 +63,7 @@ schema: type: string path: weapon.weapon_group is_null: false - values: 450 + values: 451 different_values: 17 variants: - "Sword" (x66) @@ -79,7 +79,7 @@ schema: - "Dart" (x20) - "Brawling" (x15) - "Pick" (x11) - - "Hammer" (x10) + - "Hammer" (x11) - "Crossbow" (x9) - "Shield" (x6) - "Bomb" (x2) @@ -87,7 +87,7 @@ schema: type: string path: weapon.weapon_group_markdown is_null: false - values: 450 + values: 451 different_values: 17 variants: - "[Sword](/WeaponGroups.aspx?ID=15)" (x66) @@ -103,7 +103,7 @@ schema: - "[Dart](/WeaponGroups.aspx?ID=6)" (x20) - "[Brawling](/WeaponGroups.aspx?ID=4)" (x15) - "[Pick](/WeaponGroups.aspx?ID=10)" (x11) - - "[Hammer](/WeaponGroups.aspx?ID=8)" (x10) + - "[Hammer](/WeaponGroups.aspx?ID=8)" (x11) - "[Crossbow](/WeaponGroups.aspx?ID=17)" (x9) - "[Shield](/WeaponGroups.aspx?ID=12)" (x6) - "[Bomb](/WeaponGroups.aspx?ID=2)" (x2) @@ -111,103 +111,103 @@ schema: type: string path: weapon.weapon_type is_null: false - values: 450 + values: 451 different_values: 2 variants: - - "Melee" (x307) + - "Melee" (x308) - "Ranged" (x143) [bulk_raw]: type: string path: weapon.bulk_raw is_null: true - values: 431 + values: 432 different_values: 4 variants: - "L" (x169) - - "1" (x155) + - "1" (x156) - "2" (x103) - "3" (x4) 'category': type: string path: weapon.category is_null: false - values: 450 + values: 451 different_values: 1 (all same) - variants: "weapon" (x450) + variants: "weapon" (x451) 'deity_markdown': type: string path: weapon.deity_markdown is_null: false - values: 450 - different_values: 95 + values: 451 + different_values: 96 variants: [omitted] 'id': type: string path: weapon.id is_null: false - values: 450 - different_values: 450 (all different) + values: 451 + different_values: 451 (all different) variants: [omitted] 'item_category': type: string path: weapon.item_category is_null: false - values: 450 + values: 451 different_values: 1 (all same) - variants: "Weapons" (x450) + variants: "Weapons" (x451) 'item_subcategory': type: string path: weapon.item_subcategory is_null: false - values: 450 + values: 451 different_values: 1 (all same) - variants: "Base Weapons" (x450) + variants: "Base Weapons" (x451) [damage]: type: string path: weapon.damage is_null: true - values: 426 + values: 427 different_values: 21 variants: [omitted] [damage_type]: type: array path: weapon.damage_type is_null: true - values: 424 + values: 425 min_count: 1 max_count: 3 value_type: type: string path: weapon.damage_type.[] is_null: false - values: 521 + values: 522 different_values: 3 variants: - "Piercing" (x229) - "Slashing" (x174) - - "Bludgeoning" (x118) + - "Bludgeoning" (x119) [deity]: type: array path: weapon.deity is_null: true - values: 170 + values: 171 min_count: 1 max_count: 32 value_type: type: string path: weapon.deity.[] is_null: false - values: 904 - different_values: 446 + values: 905 + different_values: 447 variants: [omitted] [hands]: type: string path: weapon.hands is_null: true - values: 426 + values: 427 different_values: 5 variants: - - "1" (x282) + - "1" (x283) - "2" (x121) - "1+" (x20) - "1 or 2" (x2) @@ -216,14 +216,14 @@ schema: type: string path: weapon.source_markdown is_null: false - values: 450 - different_values: 114 + values: 451 + different_values: 115 variants: [omitted] 'speed': type: object path: weapon.speed is_null: false - values: 450 + values: 451 schema: [legacy_id]: type: array @@ -243,8 +243,8 @@ schema: type: string path: weapon.spoilers is_null: true - values: 27 - different_values: 8 + values: 28 + different_values: 9 variants: - "Extinction Curse" (x7) - "Abomination Vaults" (x6) @@ -254,14 +254,15 @@ schema: - "Agents of Edgewatch" (x1) - "Crown of the Kobold King" (x1) - "Kingmaker" (x1) + - "Triumph of the Tusk" (x1) 'level': type: integer path: weapon.level is_null: false - values: 450 + values: 451 different_values: 4 variants: - - 0 (x409) + - 0 (x410) - 1 (x39) - 4 (x1) - 8 (x1) @@ -269,60 +270,60 @@ schema: type: string path: weapon.markdown is_null: false - values: 450 - different_values: 450 (all different) + values: 451 + different_values: 451 (all different) variants: [omitted] 'name': type: string path: weapon.name is_null: false - values: 450 - different_values: 306 + values: 451 + different_values: 307 variants: [omitted] 'primary_source_category': type: string path: weapon.primary_source_category is_null: false - values: 450 + values: 451 different_values: 4 variants: - "Rulebooks" (x302) - "Lost Omens" (x117) - - "Adventure Paths" (x29) + - "Adventure Paths" (x30) - "Adventures" (x2) 'rarity': type: string path: weapon.rarity is_null: false - values: 450 + values: 451 different_values: 3 variants: - - "uncommon" (x243) + - "uncommon" (x244) - "common" (x205) - "rare" (x2) 'rarity_id': type: integer path: weapon.rarity_id is_null: false - values: 450 + values: 451 different_values: 3 variants: - - 2 (x243) + - 2 (x244) - 1 (x205) - 3 (x2) 'release_date': type: string path: weapon.release_date is_null: false - values: 450 - different_values: 24 + values: 451 + different_values: 25 variants: [omitted] [primary_source_group]: type: string path: weapon.primary_source_group is_null: true - values: 31 - different_values: 8 + values: 32 + different_values: 9 variants: - "Extinction Curse" (x7) - "Abomination Vaults" (x7) @@ -332,6 +333,7 @@ schema: - "The Fall of Plaguestone" (x1) - "Crown of the Kobold King" (x1) - "Kingmaker" (x1) + - "Triumph of the Tusk" (x1) [reload_raw]: type: string path: weapon.reload_raw @@ -361,7 +363,7 @@ schema: type: object path: weapon.resistance is_null: false - values: 450 + values: 451 schema: [range]: type: integer @@ -413,70 +415,70 @@ schema: type: string path: weapon.search_markdown is_null: false - values: 450 - different_values: 449 + values: 451 + different_values: 450 variants: [omitted] 'source_category': type: array path: weapon.source_category is_null: false - values: 450 + values: 451 min_count: 1 max_count: 3 value_type: type: string path: weapon.source_category.[] is_null: false - values: 535 + values: 536 different_values: 4 variants: - "Rulebooks" (x358) - "Lost Omens" (x135) - - "Adventure Paths" (x40) + - "Adventure Paths" (x41) - "Adventures" (x2) [pfs]: type: string path: weapon.pfs is_null: true - values: 441 + values: 442 different_values: 2 variants: - - "Standard" (x416) + - "Standard" (x417) - "Limited" (x25) [price_raw]: type: string path: weapon.price_raw is_null: true - values: 439 + values: 440 different_values: 41 variants: [omitted] 'source': type: array path: weapon.source is_null: false - values: 450 + values: 451 min_count: 1 max_count: 3 value_type: type: string path: weapon.source.[] is_null: false - values: 561 - different_values: 33 + values: 562 + different_values: 34 variants: [omitted] [source_group]: type: array path: weapon.source_group is_null: true - values: 42 + values: 43 min_count: 1 max_count: 1 value_type: type: string path: weapon.source_group.[] is_null: false - values: 42 - different_values: 8 + values: 43 + different_values: 9 variants: - "Fists of the Ruby Phoenix" (x12) - "Abomination Vaults" (x10) @@ -486,17 +488,18 @@ schema: - "Kingmaker" (x2) - "The Fall of Plaguestone" (x1) - "Crown of the Kobold King" (x1) + - "Triumph of the Tusk" (x1) [trait]: type: array path: weapon.trait is_null: true - values: 428 + values: 429 min_count: 1 max_count: 8 value_type: type: string path: weapon.trait.[] is_null: false - values: 1462 + values: 1467 different_values: 75 variants: [omitted] From 6426ecfe4042be6bec9ad999d0b5e25e092e9928 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Wed, 14 May 2025 01:28:48 +0400 Subject: [PATCH 07/33] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B4=D0=BE=D0=BB?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/ProcessorAttribute.cs | 5 + Pf2eDataScrapper/App/ProcessorCollection.cs | 7 + Pf2eDataScrapper/App/ProcessorGroupEnum.cs | 13 ++ .../ProcessorCollectionControl.Designer.cs | 60 +++++++++ .../Controls/ProcessorCollectionControl.cs | 87 +++++++++++++ .../Controls/ProcessorCollectionControl.resx | 120 ++++++++++++++++++ Pf2eDataScrapper/Forms/ProgressForm.cs | 3 +- Pf2eDataScrapper/Forms/StartForm.Designer.cs | 22 ++-- Pf2eDataScrapper/Forms/StartForm.cs | 30 +++-- Pf2eDataScrapper/Helpers/ProcessProgress.cs | 4 +- .../Aonprd/AonprdRepository.cs | 1 + .../Common/RepositoryUpdateProcessor.cs | 1 + .../Processors/Common/SchemaBuildProcessor.cs | 2 +- .../Processors/Common/SkillProcessor.cs | 2 +- .../Processors/Common/SourceProcessor.cs | 2 +- .../Subclasses/ApparitionProcessor.cs | 2 +- .../Processors/Types/ActionProcessor.cs | 2 +- .../Processors/Types/AncestryProcessor.cs | 2 +- .../Processors/Types/ArchetypeProcessor.cs | 2 +- .../Processors/Types/BackgroundProcessor.cs | 2 +- .../Aonprd/Processors/Types/ClassProcessor.cs | 2 +- .../Aonprd/Processors/Types/FeatProcessor.cs | 2 +- .../Processors/Types/LanguageProcessor.cs | 2 +- .../Processors/Types/RitualProcessor.cs | 2 +- .../Processors/Types/Rules/RuleProcessor.cs | 2 +- .../Types/Rules/RulesOrderProcessor.cs | 2 +- .../Types/Rules/RulesTocProcessor.cs | 2 +- .../Aonprd/Processors/Types/SpellProcessor.cs | 2 +- .../Aonprd/Processors/Types/TraitProcessor.cs | 2 +- .../Entities}/ActionProcessor.cs | 5 +- .../Entities}/ConditionProcessor.cs | 5 +- .../FoundryEnAbstractProcessor.cs | 2 +- .../Preparation/CompendiumItemsProcessor.cs | 1 + .../Preparation/RepositoryUpdateProcessor.cs | 1 + .../Preparation/SchemaBuildProcessor.cs | 5 +- .../AbstractImporterRepository.cs | 3 +- .../Importers/Repositories/GitRepository.cs | 1 + Pf2eDataScrapper/Pf2eDataScrapper.csproj | 3 + .../Processors/ProcessorExecutor.cs | 1 + .../AbstractSchemaBuildProcessor.cs | 1 + 40 files changed, 362 insertions(+), 53 deletions(-) create mode 100644 Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.Designer.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.resx rename Pf2eDataScrapper/Importers/Implementations/FoundryEn/{DataBuilder/Processors => Processors/Entities}/ActionProcessor.cs (84%) rename Pf2eDataScrapper/Importers/Implementations/FoundryEn/{DataBuilder/Processors => Processors/Entities}/ConditionProcessor.cs (84%) rename Pf2eDataScrapper/Importers/Implementations/FoundryEn/{DataBuilder => Processors}/FoundryEnAbstractProcessor.cs (98%) diff --git a/Pf2eDataScrapper/App/ProcessorAttribute.cs b/Pf2eDataScrapper/App/ProcessorAttribute.cs index e1b0d7b..0f3a04f 100644 --- a/Pf2eDataScrapper/App/ProcessorAttribute.cs +++ b/Pf2eDataScrapper/App/ProcessorAttribute.cs @@ -26,6 +26,11 @@ public class ProcessorAttribute(string name) : Attribute /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. /// public string? InnerType { get; init; } = null; + + /// + /// Процессор включён по умолчанию. + /// + public bool Active { get; init; } = true; } /// diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index 103a045..65c2f41 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -20,6 +20,13 @@ internal class ProcessorCollection /// public List Items { get; private set; } = []; + /// + /// Обеспечение доступа к элементам обработчиков. + /// + /// + /// + public ProcessorCollectionItem this[string name] => Items.First(item => item.Name == name); + /// /// Конструктор класса . /// diff --git a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs index 85b918f..a0c36e5 100644 --- a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs +++ b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Reflection; namespace Pf2eDataScrapper.App; @@ -22,3 +23,15 @@ public enum ProcessorGroupEnum [Description("Foundry EN: Основные сущности")] FoundryEnEntities, } + +public static class ProcessorGroupEnumExtension +{ + public static string GetDescription(this ProcessorGroupEnum group) + { + return group + .GetType() + .GetField(group.ToString()) + ?.GetCustomAttribute() + ?.Description ?? group.ToString(); + } +} diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.Designer.cs new file mode 100644 index 0000000..bc10be6 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.Designer.cs @@ -0,0 +1,60 @@ +namespace Pf2eDataScrapper.Forms.Controls; + +partial class ProcessorCollectionControl +{ + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + RootFlowLayoutPanel = new FlowLayoutPanel(); + SuspendLayout(); + // + // RootFlowLayoutPanel + // + RootFlowLayoutPanel.AutoScroll = true; + RootFlowLayoutPanel.BackColor = SystemColors.Window; + RootFlowLayoutPanel.BorderStyle = BorderStyle.FixedSingle; + RootFlowLayoutPanel.Dock = DockStyle.Fill; + RootFlowLayoutPanel.FlowDirection = FlowDirection.TopDown; + RootFlowLayoutPanel.Location = new Point(0, 0); + RootFlowLayoutPanel.Name = "RootFlowLayoutPanel"; + RootFlowLayoutPanel.Size = new Size(432, 406); + RootFlowLayoutPanel.TabIndex = 0; + RootFlowLayoutPanel.WrapContents = false; + // + // ProcessorCollectionControl + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + Controls.Add(RootFlowLayoutPanel); + Name = "ProcessorCollectionControl"; + Size = new Size(432, 406); + ResumeLayout(false); + } + + #endregion + + private FlowLayoutPanel RootFlowLayoutPanel; +} diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs new file mode 100644 index 0000000..e54bddd --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs @@ -0,0 +1,87 @@ +using System.ComponentModel; +using Pf2eDataScrapper.App; + +namespace Pf2eDataScrapper.Forms.Controls; + +internal partial class ProcessorCollectionControl : UserControl +{ + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ProcessorCollection? Collection + { + get => _collection; + set + { + _collection = value; + UpdateContent(); + } + } + private ProcessorCollection? _collection = null; + + public ProcessorCollectionControl() + { + InitializeComponent(); + } + + private Dictionary GroupCheckBoxes { get; } = []; + + private Dictionary GroupFlowLayoutPanels { get; } = []; + + private Dictionary ItemCheckBoxes { get; } = []; + + private void UpdateContent() + { + RootFlowLayoutPanel.Controls.Clear(); + + if (Collection == null) + { + return; + } + + foreach (ProcessorGroupEnum group in Enum.GetValues()) + { + GroupCheckBoxes[group] = new() + { + Text = group.GetDescription(), + //AutoSize = false, + Dock = DockStyle.Fill, + Checked = true, + CheckAlign = ContentAlignment.TopLeft, + }; + + GroupCheckBoxes[group].CheckedChanged += GroupCheckBox_CheckedChanged; + + GroupFlowLayoutPanels[group] = new() + { + AutoSize = true, + Dock = DockStyle.Fill, + FlowDirection = FlowDirection.LeftToRight, + WrapContents = true, + BorderStyle = BorderStyle.FixedSingle, + }; + + RootFlowLayoutPanel.Controls.Add(GroupCheckBoxes[group]); + RootFlowLayoutPanel.Controls.Add(GroupFlowLayoutPanels[group]); + } + + foreach (ProcessorCollectionItem item in Collection.AllItems) + { + ItemCheckBoxes[item.Name] = new() + { + Text = item.Description, + AutoSize = true, + // Dock = DockStyle.Fill, + // MaximumSize = new Size(Width, 20), + // Width = 200, + Checked = true, + CheckAlign = ContentAlignment.TopLeft, + }; + + GroupFlowLayoutPanels[item.Group].Controls.Add(ItemCheckBoxes[item.Name]); + } + } + + private void GroupCheckBox_CheckedChanged(object? sender, EventArgs e) + { + // + } +} diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.resx b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/ProgressForm.cs b/Pf2eDataScrapper/Forms/ProgressForm.cs index fb3d7ad..f262e04 100644 --- a/Pf2eDataScrapper/Forms/ProgressForm.cs +++ b/Pf2eDataScrapper/Forms/ProgressForm.cs @@ -1,4 +1,5 @@ -using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Forms; diff --git a/Pf2eDataScrapper/Forms/StartForm.Designer.cs b/Pf2eDataScrapper/Forms/StartForm.Designer.cs index c439d08..06e6f1b 100644 --- a/Pf2eDataScrapper/Forms/StartForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/StartForm.Designer.cs @@ -32,7 +32,7 @@ partial class StartForm PropertiesGroupBox = new GroupBox(); SettingsPropertyGrid = new PropertyGrid(); ProcessorsGroupBox = new GroupBox(); - ProcessorsListView = new ListView(); + ProcessorCollectionControl = new Pf2eDataScrapper.Forms.Controls.ProcessorCollectionControl(); SelectAllButton = new Button(); DeselectAllButton = new Button(); StartButton = new Button(); @@ -87,7 +87,7 @@ partial class StartForm // ProcessorsGroupBox // ProcessorsGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - ProcessorsGroupBox.Controls.Add(ProcessorsListView); + ProcessorsGroupBox.Controls.Add(ProcessorCollectionControl); ProcessorsGroupBox.Controls.Add(SelectAllButton); ProcessorsGroupBox.Controls.Add(DeselectAllButton); ProcessorsGroupBox.Location = new Point(3, 3); @@ -97,17 +97,13 @@ partial class StartForm ProcessorsGroupBox.TabStop = false; ProcessorsGroupBox.Text = "Обработчики"; // - // ProcessorsListView + // ProcessorCollectionControl // - ProcessorsListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - ProcessorsListView.CheckBoxes = true; - ProcessorsListView.Location = new Point(6, 48); - ProcessorsListView.MultiSelect = false; - ProcessorsListView.Name = "ProcessorsListView"; - ProcessorsListView.Size = new Size(606, 548); - ProcessorsListView.TabIndex = 2; - ProcessorsListView.UseCompatibleStateImageBehavior = false; - ProcessorsListView.View = View.SmallIcon; + ProcessorCollectionControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + ProcessorCollectionControl.Location = new Point(6, 48); + ProcessorCollectionControl.Name = "ProcessorCollectionControl"; + ProcessorCollectionControl.Size = new Size(609, 548); + ProcessorCollectionControl.TabIndex = 2; // // SelectAllButton // @@ -166,5 +162,5 @@ partial class StartForm private GroupBox ProcessorsGroupBox; private Button SelectAllButton; private Button DeselectAllButton; - private ListView ProcessorsListView; + private Controls.ProcessorCollectionControl ProcessorCollectionControl; } \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/StartForm.cs b/Pf2eDataScrapper/Forms/StartForm.cs index feadfe4..a782517 100644 --- a/Pf2eDataScrapper/Forms/StartForm.cs +++ b/Pf2eDataScrapper/Forms/StartForm.cs @@ -17,9 +17,10 @@ internal partial class StartForm : Form private void StartForm_Load(object sender, EventArgs e) { - PopulateListView(); + ProcessorCollectionControl.Collection = ProcessorCollection; } + /* private void PopulateListView() { Dictionary> items = []; @@ -39,14 +40,10 @@ internal partial class StartForm : Form continue; } - string name = - group - .GetType() - .GetField(group.ToString()) - ?.GetCustomAttribute() - ?.Description ?? group.ToString(); - - ListViewGroup listViewGroup = new(name); + ListViewGroup listViewGroup = new(group.GetDescription()) + { + TaskLink = "Выбрать/снять все", + }; ProcessorsListView.Groups.Add(listViewGroup); foreach (ProcessorCollectionItem item in list) @@ -55,13 +52,26 @@ internal partial class StartForm : Form { Group = listViewGroup, Text = item.Description, - Selected = true, + Checked = true, + ToolTipText = item.Name, }; ProcessorsListView.Items.Add(listViewItem); } } } + private void ProcessorsListView_GroupTaskLinkClick(object sender, ListViewGroupEventArgs e) + { + ListViewGroup group = ProcessorsListView.Groups[e.GroupIndex]; + + bool allSelected = group.Items.Cast().All(item => item.Checked); + + foreach (ListViewItem item in group.Items.Cast()) + { + item.Checked = !allSelected; + } + } + */ #region "Кнопка старта" private void StartButton_Click(object sender, EventArgs e) diff --git a/Pf2eDataScrapper/Helpers/ProcessProgress.cs b/Pf2eDataScrapper/Helpers/ProcessProgress.cs index 5e2f7cb..ec0e608 100644 --- a/Pf2eDataScrapper/Helpers/ProcessProgress.cs +++ b/Pf2eDataScrapper/Helpers/ProcessProgress.cs @@ -1,4 +1,6 @@ -namespace Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.Helpers; internal class ProcessProgress { diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs index 4fd6785..9e1a631 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Repositories; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs index 3117e5d..9e7d9c1 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs @@ -1,4 +1,5 @@ using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs index 1b1820d..82738ae 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs @@ -1,6 +1,6 @@ using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; -using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.SchemaBuilder; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs index 869aefc..09815a2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs @@ -10,7 +10,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; [Processor( "aonprd:skill", InnerType = "skill", - Description = "Создание цели внешних ссылок", + Description = "Навыки", Group = ProcessorGroupEnum.AonprdPreparation )] internal class SkillProcessor(ProcessorInput input, Pf2eDbContext context) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs index 80d7b26..c369af6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs @@ -9,7 +9,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; [Processor( "aonprd:source", InnerType = "source", - Description = "Создание цели внешних ссылок", + Description = "Источники", Group = ProcessorGroupEnum.AonprdPreparation )] [ProcessorDependencies(["aonprd:repository"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs index 4be5018..ad7985a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs @@ -15,7 +15,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasse [Processor( "aonprd:apparition", InnerType = "apparition", - Description = "Создание цели внешних ссылок", + Description = "Apparitions", Group = ProcessorGroupEnum.AonprdSubclasses )] [ProcessorDependencies( diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs index ba385ac..56174e3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs @@ -11,7 +11,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:action", InnerType = "action", - Description = "Создание цели внешних ссылок", + Description = "Действия", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs index 526f4a3..a3aa582 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs @@ -13,7 +13,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:ancestry", InnerType = "ancestry", - Description = "Создание цели внешних ссылок", + Description = "Родословные", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:language"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs index 17d848e..a97727a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs @@ -12,7 +12,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:archetype", InnerType = "archetype", - Description = "Создание цели внешних ссылок", + Description = "Архетипы", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs index b720049..1b9b7cf 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs @@ -14,7 +14,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:background", InnerType = "background", - Description = "Создание цели внешних ссылок", + Description = "Предыстории", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index 0873bfe..2d3a05f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -18,7 +18,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:class", InnerType = "class", - Description = "Создание цели внешних ссылок", + Description = "Классы", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs index b6ef9d6..05a517f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs @@ -13,7 +13,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:feat", InnerType = "feat", - Description = "Создание цели внешних ссылок", + Description = "Черты", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:archetype"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs index 8ddf42f..7d2960a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs @@ -12,7 +12,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:language", InnerType = "language", - Description = "Создание цели внешних ссылок", + Description = "Языки", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs index 0a45e36..5d96c0e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs @@ -16,7 +16,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:ritual", InnerType = "ritual", - Description = "Создание цели внешних ссылок", + Description = "Ритуалы", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs index aeb25fe..5d6737e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs @@ -16,7 +16,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rul [Processor( "aonprd:rules", InnerType = "rules", - Description = "Создание цели внешних ссылок", + Description = "Правила", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs index 200bae7..6c17394 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs @@ -15,7 +15,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rul [Processor( "aonprd:rules-order", - Description = "Создание цели внешних ссылок", + Description = "Формирование порядка правил", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:rules"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs index c062fa2..052f55d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs @@ -12,7 +12,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rul [Processor( "aonprd:rules-toc", - Description = "Создание цели внешних ссылок", + Description = "Загрузка содержания правил", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:source"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs index 3a427b2..d69b536 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs @@ -15,7 +15,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:spell", InnerType = "spell", - Description = "Создание цели внешних ссылок", + Description = "Заклинания", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs index 9809b7f..718a5f8 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -12,7 +12,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [Processor( "aonprd:trait", InnerType = "trait", - Description = "Создание цели внешних ссылок", + Description = "Признаки", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:external"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs similarity index 84% rename from Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ActionProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs index 0438961..f5e860c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs @@ -1,17 +1,16 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors; +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities; [Processor( "foundry-en:action", InnerType = "action", - Description = "Создание цели внешних ссылок", + Description = "Действия", Group = ProcessorGroupEnum.FoundryEnEntities )] [ProcessorDependencies(["foundry-en:compendium"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ConditionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs similarity index 84% rename from Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ConditionProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs index ce0accd..89a3157 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/Processors/ConditionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs @@ -1,18 +1,17 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors; +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities; [Processor( "foundry-en:condition", InnerType = "Item.condition", - Description = "Создание цели внешних ссылок", + Description = "Состояния", Group = ProcessorGroupEnum.FoundryEnEntities )] [ProcessorDependencies(["foundry-en:compendium"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/FoundryEnAbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs similarity index 98% rename from Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/FoundryEnAbstractProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs index 95d8c2a..829fc9d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/DataBuilder/FoundryEnAbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs @@ -13,7 +13,7 @@ using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder; +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors; internal abstract class FoundryEnAbstractProcessor( DatabaseHelper databaseHelper, diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs index 8afe640..77e661f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs index ccfb96f..1fcdbfa 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs @@ -1,4 +1,5 @@ using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs index 34b2cfc..5f55eb5 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs @@ -1,6 +1,5 @@ using Pf2eDataScrapper.App; -using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; -using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.SchemaBuilder; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation; @@ -8,7 +7,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar [Processor( "foundry-en:schema", Description = "Построение схемы данных", - Group = ProcessorGroupEnum.AonprdPreparation + Group = ProcessorGroupEnum.FoundryEnPreparation )] [ProcessorDependencies(["foundry-en:compendium"])] internal class SchemaBuildProcessor( diff --git a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs index c88d524..9d09d4a 100644 --- a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs @@ -1,4 +1,5 @@ -using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Repositories; diff --git a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs index ab73ea2..db78e45 100644 --- a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Repositories; diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj index 504d388..bb9ae8f 100644 --- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj +++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj @@ -33,4 +33,7 @@ Always + + + diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index 2d425b1..1de918c 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Importers.Implementations.Aonprd; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; diff --git a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs index 8e449d4..bce0c7b 100644 --- a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Processors; From 619ac7138ce8979ae55eef3e14e4461290166a7f Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Wed, 14 May 2025 09:11:06 +0400 Subject: [PATCH 08/33] =?UTF-8?q?=D0=9C=D0=B8=D0=BD=D0=B8-=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs index e54bddd..02d879b 100644 --- a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs @@ -57,6 +57,7 @@ internal partial class ProcessorCollectionControl : UserControl FlowDirection = FlowDirection.LeftToRight, WrapContents = true, BorderStyle = BorderStyle.FixedSingle, + Margin = new Padding(10, 0, 10, 15), }; RootFlowLayoutPanel.Controls.Add(GroupCheckBoxes[group]); From e6f53fc0747c826b0c5aca342a2638f9244ff258 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Wed, 14 May 2025 13:58:57 +0400 Subject: [PATCH 09/33] =?UTF-8?q?=D0=91=D0=BE=D0=BB=D1=8C=D1=88=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=B0=D1=87=D0=BA=D0=B0=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 2 +- Pf2eDataScrapper/App/ProcessorCollection.cs | 9 +- Pf2eDataScrapper/ApplicationSettings.cs | 14 +- .../Forms/Controls/CoreBindingNavigator.cs | 22 + .../DatabaseViewer/DatabaseQueryPerformer.cs | 130 ++++ .../DatabaseViewerControl.Designer.cs | 148 +++++ .../DatabaseViewer/DatabaseViewerControl.cs | 390 +++++++++++ .../DatabaseViewer/DatabaseViewerControl.resx | 132 ++++ .../EntityTreeViewerControl.Designer.cs | 60 ++ .../EntityTreeViewerControl.cs | 394 +++++++++++ .../EntityTreeViewerControl.resx | 120 ++++ .../EntityTreeViewerControlRow.cs | 606 +++++++++++++++++ .../EntityTreeViewer/StructureBuilder.cs | 627 ++++++++++++++++++ .../Controls/ProcessorCollectionControl.cs | 113 +++- .../Forms/EntityViewForm.Designer.cs | 244 +++++++ Pf2eDataScrapper/Forms/EntityViewForm.cs | 99 +++ Pf2eDataScrapper/Forms/EntityViewForm.resx | 126 ++++ .../Forms/ProgressForm.Designer.cs | 38 +- Pf2eDataScrapper/Forms/ProgressForm.cs | 28 +- Pf2eDataScrapper/Forms/StartForm.Designer.cs | 84 ++- Pf2eDataScrapper/Forms/StartForm.cs | 34 +- Pf2eDataScrapper/Forms/StartForm.resx | 3 + Pf2eDataScrapper/Helpers/DatabaseHelper.cs | 2 +- Pf2eDataScrapper/Helpers/EntityView.cs | 138 ++++ Pf2eDataScrapper/Helpers/ILogger.cs | 22 + Pf2eDataScrapper/Helpers/ProcessProgress.cs | 12 +- Pf2eDataScrapper/ImportApplication.cs | 59 +- .../Implementations/Aonprd/AonprdImporter.cs | 2 + .../Aonprd/AonprdRepository.cs | 4 +- .../Aonprd/Compendium/AonprdCompendium.cs | 48 +- .../Aonprd/Helpers/AonprdBuilderGlobals.cs | 7 +- .../Aonprd/Helpers/AonprdEnumResolver.cs | 134 ---- .../Helpers/AonprdProcessorInputExtension.cs | 92 ++- .../Aonprd/Helpers/StringHelper.cs | 7 +- .../Processors/AbstractEntityProcessor.cs | 70 +- .../Processors/Common/ExternalProcessor.cs | 8 +- .../Processors/Common/SkillProcessor.cs | 1 + .../Processors/Common/SourceProcessor.cs | 2 +- .../Preparation/CompendiumLoadProcessor.cs | 54 ++ .../RepositoryUpdateProcessor.cs | 16 +- .../SchemaBuildProcessor.cs | 5 +- .../Aonprd/Processors/Types/TraitProcessor.cs | 2 +- .../FoundryEn/FoundryEnCompendium.cs | 5 +- .../Processors/Entities/ActionProcessor.cs | 3 +- .../Processors/Entities/ConditionProcessor.cs | 3 +- .../Preparation/CompendiumFolderProcessor.cs | 3 +- .../Preparation/CompendiumItemsProcessor.cs | 3 +- .../Preparation/RepositoryUpdateProcessor.cs | 3 +- .../Preparation/SchemaBuildProcessor.cs | 3 +- Pf2eDataScrapper/Pf2eDataScrapper.csproj | 24 +- .../Processors/ProcessorExecutor.cs | 58 +- Pf2eDataScrapper/Processors/ProcessorInput.cs | 34 +- ...signer.cs => 20250514084949_1.Designer.cs} | 2 +- ...0250510202833_1.cs => 20250514084949_1.cs} | 0 54 files changed, 3901 insertions(+), 348 deletions(-) create mode 100644 Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.resx create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.resx create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs create mode 100644 Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs create mode 100644 Pf2eDataScrapper/Forms/EntityViewForm.cs create mode 100644 Pf2eDataScrapper/Forms/EntityViewForm.resx create mode 100644 Pf2eDataScrapper/Helpers/EntityView.cs create mode 100644 Pf2eDataScrapper/Helpers/ILogger.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdEnumResolver.cs create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/CompendiumLoadProcessor.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/{Common => Preparation}/RepositoryUpdateProcessor.cs (59%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/{Common => Preparation}/SchemaBuildProcessor.cs (96%) rename Pf2eModel/Migrations/{20250510202833_1.Designer.cs => 20250514084949_1.Designer.cs} (99%) rename Pf2eModel/Migrations/{20250510202833_1.cs => 20250514084949_1.cs} (100%) diff --git a/.editorconfig b/.editorconfig index 9a561d9..c259c56 100644 --- a/.editorconfig +++ b/.editorconfig @@ -121,7 +121,7 @@ csharp_style_conditional_delegate_call = true # Предпочтения модификатора csharp_prefer_static_anonymous_function = true csharp_prefer_static_local_function = true -csharp_preferred_modifier_order = public, private, protected, file, static, extern, internal, virtual, abstract, override, sealed, readonly, new, unsafe, required, volatile, async:suggestion +csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, virtual, abstract, override, sealed, readonly, new, unsafe, required, volatile, async:suggestion csharp_style_prefer_readonly_struct = true csharp_style_prefer_readonly_struct_member = true diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index 65c2f41..e2b7edf 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -1,5 +1,4 @@ -using System.ComponentModel; -using System.Reflection; +using System.Reflection; using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.App; @@ -77,6 +76,7 @@ internal class ProcessorCollection Dependencies = dependenciesAttribute?.Dependencies ?? [], IsCollectionProcessor = isCollection, InnerType = processorAttribute.InnerType, + Active = processorAttribute.Active, }; } @@ -202,6 +202,11 @@ internal class ProcessorCollectionItem /// public ICollection Dependencies { get; init; } = []; + /// + /// Процессор включён по умолчанию. + /// + public bool Active { get; init; } = true; + /// /// Приоритет выполнения обработчика. /// diff --git a/Pf2eDataScrapper/ApplicationSettings.cs b/Pf2eDataScrapper/ApplicationSettings.cs index 4dc914d..6e8da60 100644 --- a/Pf2eDataScrapper/ApplicationSettings.cs +++ b/Pf2eDataScrapper/ApplicationSettings.cs @@ -66,19 +66,7 @@ internal class ApplicationSettings #region Основные настройки - /// - /// Флаг, указывающий, что все старые записи необходимо удалить. - /// - [Browsable(true)] - [DisplayName("Удаление всех записей перед запуском.")] - [Description("Редактирование через .env-файл.")] - public bool TruncateAllTables { get; set; } = true; - - /// - /// Список обработчиков, которые должны быть выполнены. - /// Пустой список означает, что должны быть выполнены все обработчики. - /// - public ICollection ProcessorNames { get; } = []; + // #endregion } diff --git a/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs b/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs new file mode 100644 index 0000000..dc1f2e2 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs @@ -0,0 +1,22 @@ +namespace Pf2eViewer; + +/// +/// Элемент управления прокрутки страниц +/// +internal sealed class CoreBindingNavigator : BindingNavigator +{ + public CoreBindingNavigator() + { + AddStandardItems(); + + if (DeleteItem != null) + { + DeleteItem.Visible = false; + } + + if (AddNewItem != null) + { + AddNewItem.Visible = false; + } + } +} diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs new file mode 100644 index 0000000..3623ce1 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs @@ -0,0 +1,130 @@ +using System.Collections.Concurrent; +using Pf2eModel; +using Pf2eModel.Model.Base; +using Pf2eModel.Model.Enum; + +namespace Pf2eViewer.DatabaseViewer; + +/// +/// Класс, выполняющий страничные запросы к базе данных. +/// +internal class DatabaseQueryPerformer +{ + /// + /// Тип сущностей, которую необходимо получить. + /// + public required EntityType Type; + + /// + /// Номер страницы, которую необходимо получить. + /// + public required int Page; + + /// + /// Количество элементов на странице. + /// + public required int PerPage; + + /// + /// Поисковая строка, по которой будет производиться фильтрация. + /// + public required string Search = ""; + + /// + /// Поток, в котором будет выполняться запрос к базе данных. + /// + private readonly Thread _queryThread; + + /// + /// Конструктор класса, который инициализирует поток для выполнения запроса к базе данных. + /// + public DatabaseQueryPerformer() + { + _queryThread = new(PerformQuery); + } + + /// + /// Запускает поток для выполнения запроса к базе данных. + /// + public void Query() + { + _queryThread.Start(); + } + + /// + /// Прерывает выполнение потока, если он ещё не завершён. + /// + public void Abort() + { + _queryThread.Interrupt(); + } + + /// + /// Выполняет запрос к базе данных и обрабатывает результаты. + /// + private void PerformQuery() + { + try + { + Pf2eDbContext dbContext = new(); + + IQueryable query = + Search == "" + ? dbContext.Entities.Where(e => e.Type == Type) + : + // ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall + dbContext.Entities.Where(e => + e.Type == Type && Pf2eDbContext.LocaleSearch(e.Name, Search) + ); + + int count = query.Count(); + + List entities = query + .OrderBy(e => e.Id) + .Skip((Page - 1) * PerPage) + .Take(PerPage) + .ToList(); + + DatabaseQueryResult result = new(count, entities); + + OnQueryCompleted?.Invoke(this, result); + } + catch (ThreadInterruptedException) + { + // Ignore + } + } + + /// + /// Делегат, который будет вызван по завершении выполнения запроса к базе данных. + /// + /// Объект класса, выполневшего запрос. + /// Результат запроса. + public delegate void DatabaseQueryPerformerDelegate( + DatabaseQueryPerformer sender, + DatabaseQueryResult result + ); + + /// + /// Событие, которое будет вызвано по завершении выполнения запроса к базе данных. + /// + public event DatabaseQueryPerformerDelegate? OnQueryCompleted; +} + +/// +/// Класс, который содержит результат выполнения запроса к базе данных. +/// +/// Общее количество найденных сущностей. +/// Набор найденных сущностей. +internal class DatabaseQueryResult(int count, ICollection entities) +{ + /// + /// Общее количество найденных сущностей. + /// + public int Count { get; } = count; + + /// + /// Набор найденных сущностей. + /// + public ConcurrentBag Entities { get; } = new(entities); +} diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs new file mode 100644 index 0000000..51bca07 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs @@ -0,0 +1,148 @@ +namespace Pf2eViewer.DatabaseViewer; + +partial class DatabaseViewerControl +{ + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + BindingNavigator = new CoreBindingNavigator(); + SearchTextBox = new TextBox(); + DataGridView = new DataGridView(); + DbBindingSource = new BindingSource(components); + PagesBindingSource = new BindingSource(components); + Id = new DataGridViewTextBoxColumn(); + EntityName = new DataGridViewTextBoxColumn(); + Rarity = new DataGridViewTextBoxColumn(); + (BindingNavigator).BeginInit(); + ((System.ComponentModel.ISupportInitialize)DataGridView).BeginInit(); + ((System.ComponentModel.ISupportInitialize)DbBindingSource).BeginInit(); + ((System.ComponentModel.ISupportInitialize)PagesBindingSource).BeginInit(); + SuspendLayout(); + // + // BindingNavigator + // + BindingNavigator.AddNewItem = null; + BindingNavigator.DeleteItem = null; + BindingNavigator.ImeMode = ImeMode.NoControl; + BindingNavigator.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow; + BindingNavigator.Location = new Point(0, 0); + BindingNavigator.Name = "BindingNavigator"; + BindingNavigator.Size = new Size(345, 25); + BindingNavigator.Stretch = true; + BindingNavigator.TabIndex = 1; + BindingNavigator.Text = "coreBindingNavigator1"; + BindingNavigator.RefreshItems += BindingNavigator_RefreshItems; + // + // SearchTextBox + // + SearchTextBox.Dock = DockStyle.Top; + SearchTextBox.Location = new Point(0, 25); + SearchTextBox.Name = "SearchTextBox"; + SearchTextBox.PlaceholderText = "Поиск"; + SearchTextBox.Size = new Size(345, 23); + SearchTextBox.TabIndex = 3; + SearchTextBox.TextChanged += SearchTextBox_TextChanged; + SearchTextBox.Leave += SearchTextBox_Leave; + // + // DataGridView + // + DataGridView.AllowUserToAddRows = false; + DataGridView.AllowUserToDeleteRows = false; + DataGridView.AutoGenerateColumns = false; + DataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader; + DataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders; + DataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; + DataGridView.Columns.AddRange(new DataGridViewColumn[] { Id, EntityName, Rarity }); + DataGridView.DataSource = DbBindingSource; + DataGridView.Dock = DockStyle.Fill; + DataGridView.Location = new Point(0, 48); + DataGridView.MultiSelect = false; + DataGridView.Name = "DataGridView"; + DataGridView.ReadOnly = true; + DataGridView.RowHeadersVisible = false; + DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + DataGridView.Size = new Size(345, 345); + DataGridView.TabIndex = 4; + DataGridView.RowStateChanged += DataGridView_RowStateChanged; + // + // Id + // + Id.DataPropertyName = "Id"; + Id.HeaderText = "Id"; + Id.Name = "Id"; + Id.ReadOnly = true; + Id.Resizable = DataGridViewTriState.False; + Id.SortMode = DataGridViewColumnSortMode.NotSortable; + Id.Width = 5; + // + // EntityName + // + EntityName.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; + EntityName.DataPropertyName = "Name"; + EntityName.HeaderText = "Name"; + EntityName.Name = "EntityName"; + EntityName.ReadOnly = true; + EntityName.Resizable = DataGridViewTriState.False; + EntityName.SortMode = DataGridViewColumnSortMode.NotSortable; + // + // Rarity + // + Rarity.DataPropertyName = "Rarity"; + Rarity.HeaderText = "Rarity"; + Rarity.Name = "Rarity"; + Rarity.ReadOnly = true; + Rarity.Resizable = DataGridViewTriState.False; + Rarity.SortMode = DataGridViewColumnSortMode.NotSortable; + Rarity.Width = 5; + // + // DatabaseViewerControl + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + Controls.Add(DataGridView); + Controls.Add(SearchTextBox); + Controls.Add(BindingNavigator); + Name = "DatabaseViewerControl"; + Size = new Size(345, 393); + (BindingNavigator).EndInit(); + ((System.ComponentModel.ISupportInitialize)DataGridView).EndInit(); + ((System.ComponentModel.ISupportInitialize)DbBindingSource).EndInit(); + ((System.ComponentModel.ISupportInitialize)PagesBindingSource).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + private CoreBindingNavigator BindingNavigator; + private TextBox SearchTextBox; + private DataGridView DataGridView; + private BindingSource DbBindingSource; + private BindingSource PagesBindingSource; + private DataGridViewTextBoxColumn Id; + private DataGridViewTextBoxColumn EntityName; + private DataGridViewTextBoxColumn Rarity; +} diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs new file mode 100644 index 0000000..d056a03 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs @@ -0,0 +1,390 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Pf2eModel; +using Pf2eModel.Model.Base; +using Pf2eModel.Model.Common.I18n; +using Pf2eModel.Model.Enum; +using Timer = System.Windows.Forms.Timer; + +namespace Pf2eViewer.DatabaseViewer; + +/// +/// Класс элемента управления, который отображает сущности в виде таблицы. +/// +public partial class DatabaseViewerControl : UserControl +{ + #region "Поля и свойства" + + /// + /// Контекст базы данных, который используется для получения сущностей. + /// + public readonly Pf2eDbContext Context = new(); + + /// + /// Количество элементов на странице. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int PerPage + { + get => _perPage; + set + { + _perPage = value; + UpdateView(); + } + } + private int _perPage = 30; + + /// + /// Номер страницы, которую необходимо отобразить. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int Page + { + get => _page; + set + { + _page = value; + UpdateView(); + } + } + private int _page = 1; + + /// + /// Тип сущности, которую необходимо отобразить. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public EntityType Type + { + get => _type; + set + { + _page = 1; + _type = value; + UpdateView(); + } + } + private EntityType _type; + + /// + /// Количество элементов, которые были получены из базы данных. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int Count { get; private set; } + + /// + /// Номер последней страницы, которая может быть отображена. + /// + public int MaxPage => Convert.ToInt32(Math.Ceiling(1.0 * Count / PerPage)); + + /// + /// Выбранная в элементе управления сущность. + /// + [AllowNull] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public EntityView CurrentEntity { get; private set; } + + /// + /// Флаг, указывающий, что элемент управления занят выполнением запроса к базе данных. + /// + private bool _isBusy = true; + + /// + /// Объект, который выполняет запрос к базе данных. + /// + private DatabaseQueryPerformer? _performer; + + /// + /// Таймер, который используется для задержки обработки события изменения текста в поле поиска. + /// + private Timer? _typingTimer; + + /// + /// Строка, которая хранит последнее значение, введенное в поле поиска. + /// + private string _lastSearch = ""; + + #endregion + + /// + /// Конструктор класса, который инициализирует элемент управления и связывает его с источником данных. + /// + public DatabaseViewerControl() + { + InitializeComponent(); + + BindingNavigator.BindingSource = DbBindingSource; + //DataGridView.AutoGenerateColumns = true; + DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + + _isBusy = false; + } + + #region "События элемента управления" + + /// + /// Обработчик события, который вызывается при изменении текущей страницы в элементе управления. + /// + /// Источник события. + /// Объект, содержащий данные события. + private void BindingNavigator_RefreshItems(object sender, EventArgs e) + { + if (_isBusy) + { + return; + } + + Page = (int)(BindingNavigator?.BindingSource?.Current ?? 1); + UpdateView(); + } + + /// + /// Обработчик события, который вызывается при изменении выделенной строки в элементе управления DataGridView. + /// + /// Источник события. + /// Объект, содержащий данные события. + private void DataGridView_RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e) + { + if (_isBusy) + { + return; + } + + UpdateEntity(); + } + + /// + /// Обработчик события, который вызывается когда поле поиска теряет фокус. + /// + /// Источник события. + /// Объект, содержащий данные события. + private void SearchTextBox_Leave(object sender, EventArgs e) + { + if (_isBusy || SearchTextBox.Text == _lastSearch) + { + return; + } + + UpdateView(); + } + + /// + /// Обработчик события, который вызывается при изменении текста в поле поиска. + /// + /// Источник события. + /// Объект, содержащий данные события. + private void SearchTextBox_TextChanged(object sender, EventArgs e) + { + if (_isBusy) + { + return; + } + + if (_typingTimer == null) + { + _typingTimer = new(); + _typingTimer.Interval = 3000; + _typingTimer.Tick += HandleTypingTimerTimeout; + } + _typingTimer.Stop(); // Resets the timer + _typingTimer.Tag = (sender as TextBox)!.Text; // This should be done with EventArgs + _typingTimer.Start(); + } + + /// + /// Обработчик события, который вызывается при истечении времени таймера ввода текста. + /// + /// Источник события. + /// Объект, содержащий данные события. + private void HandleTypingTimerTimeout(object? sender, EventArgs e) + { + if (_isBusy || sender is not Timer timer) + { + return; + } + + UpdateView(); + timer.Stop(); + } + + #endregion + + #region "Обновление данных" + + /// + /// Устанавливает статус занятости элемента управления. + /// + /// Новый статус занятости. + private void SetBusyStatus(bool busy) + { + if (InvokeRequired) + { + Invoke(SetBusyStatus, busy); + return; + } + + _isBusy = busy; + + BindingNavigator.Enabled = !busy; + DataGridView.Enabled = !busy; + SearchTextBox.Enabled = !busy; + + DataGridView.ColumnHeadersHeightSizeMode = !busy + ? DataGridViewColumnHeadersHeightSizeMode.AutoSize + : DataGridViewColumnHeadersHeightSizeMode.DisableResizing; + } + + /// + /// Обновляет данные в элементе управления. + /// + /// Проигнорировать флаг занятости. + public void UpdateView(bool force = false) + { + if (_isBusy && !force) + { + return; + } + + SetBusyStatus(true); + + _performer?.Abort(); + + _performer = new() + { + Type = Type, + Page = Page, + PerPage = PerPage, + Search = SearchTextBox.Text, + }; + + _performer.OnQueryCompleted += HandleQueryCompleted; + + _performer.Query(); + } + + /// + /// Обработчик события, который вызывается по завершении выполнения запроса к базе данных. + /// + /// Источник события. + /// Результат выполнения события + private void HandleQueryCompleted(DatabaseQueryPerformer sender, DatabaseQueryResult result) + { + if (sender != _performer) + { + return; + } + + Count = result.Count; + _lastSearch = SearchTextBox.Text; + + InvokeControl( + BindingNavigator, + b => + { + PagesBindingSource.DataSource = Enumerable.Range(1, MaxPage).ToList(); + b.BindingSource = PagesBindingSource; + } + ); + + InvokeControl( + DataGridView, + view => + { + view.DataSource = result + .Entities.Select(e => new EntityGridRow(e)) + .OrderBy(r => r.Id) + .ToList(); + + SetBusyStatus(false); + + if (view.Rows.Count > 0) + { + view.Rows[0].Selected = true; + UpdateEntity(); + } + } + ); + } + + /// + /// Вызывает действие на указанном элементе управления в нужном потоке , если это необходимо. + /// + /// + /// + /// + private void InvokeControl(T control, Action action) + where T : Control + { + if (control.InvokeRequired) + { + control.Invoke(action, control); + } + else + { + action(control); + } + } + + /// + /// Обновить сущность, которая была выбрана в элементе управления DataGridView. + /// + private void UpdateEntity() + { + if (DataGridView.SelectedRows.Count == 0) + { + return; + } + + EntityGridRow? data = (EntityGridRow?)DataGridView.SelectedRows[0].DataBoundItem; + + try + { + CurrentEntity = data is not null ? new(Context, Type, data.Id) : null; + } + catch (Exception) + { + // ignored + } + + EntityChange?.Invoke(CurrentEntity); + } + + #endregion + + #region Output events + + /// + /// Делегат, который будет вызван при изменении выбранной сущности. + /// + /// Новая выбранная сущность. + public delegate void EntityChangeHandler(EntityView newEntity); + + /// + /// Событие, которое вызывается при изменении выбранной сущности. + /// + public event EntityChangeHandler? EntityChange; + + #endregion + + /// + /// Класс, который представляет строку в таблице сущностей. + /// + /// Сущность, связанная со строкой + internal class EntityGridRow(Entity entity) + { + /// + /// Идентификатор сущности. + /// + public int Id { get; set; } = entity.Id; + + /// + /// Название сущности. + /// + public string Name { get; set; } = entity.Name[LocaleType.English]; + + /// + /// Редкость сущности. + /// + public EntityRarity Rarity { get; set; } = entity.Rarity; + } +} diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.resx b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.resx new file mode 100644 index 0000000..aea47c3 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + True + + + True + + + 17, 17 + + + 163, 17 + + \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs new file mode 100644 index 0000000..a0d535f --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs @@ -0,0 +1,60 @@ +namespace Pf2eViewer.EntityTreeViewer; + +sealed partial class EntityTreeViewerControl +{ + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + ContentPanel = new PictureBox(); + ((System.ComponentModel.ISupportInitialize)ContentPanel).BeginInit(); + SuspendLayout(); + // + // ContentPanel + // + ContentPanel.Location = new Point(0, 0); + ContentPanel.Name = "ContentPanel"; + ContentPanel.Size = new Size(400, 500); + ContentPanel.TabIndex = 0; + ContentPanel.TabStop = false; + ContentPanel.MouseMove += ContentPanel_MouseMove; + // + // EntityTreeViewerControl + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + AutoScroll = true; + Controls.Add(ContentPanel); + Name = "EntityTreeViewerControl"; + Size = new Size(400, 500); + Resize += EntityTreeViewerControl_Resize; + ((System.ComponentModel.ISupportInitialize)ContentPanel).EndInit(); + ResumeLayout(false); + } + + #endregion + + public PictureBox ContentPanel; +} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs new file mode 100644 index 0000000..daef077 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs @@ -0,0 +1,394 @@ +using System.ComponentModel; + +namespace Pf2eViewer.EntityTreeViewer; + +/// +/// Элемент управления для отображения структуры сущности. +/// +public sealed partial class EntityTreeViewerControl : UserControl +{ + #region "Поля элемента управления" + + /// + /// Высота строки поля структуры. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [Category("Entity Render")] + [Description("The height of each item in the tree view.")] + public int ItemHeight + { + get => _itemHeight; + set + { + _itemHeight = value; + UpdateView(); + } + } + private int _itemHeight = 20; + + /// + /// Сущность, которая будет отображаться в элементе управления. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public EntityView? EntityView + { + get => _entityView; + set + { + _entityView = value; + UpdateData(); + } + } + private EntityView? _entityView; + + /// + /// Ширина отступа глубины. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [Category("Entity Render")] + [Description("The size of tab.")] + public int DepthSize + { + get => _depthSize; + set + { + _depthSize = value; + UpdateView(); + } + } + private int _depthSize = 20; + + /// + /// Размер шрифта типа поля сущности. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [Category("Entity Render")] + [Description("The size of tab.")] + public float TypeFontSize + { + get => _typeFontSize; + set + { + _typeFontSize = value; + UpdateView(); + } + } + private float _typeFontSize = 9.5f; + + /// + /// Размер кнопки плюс/минус. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [Category("Entity Render")] + [Description("The size of plus/minus button.")] + public int ButtonSize + { + get => _buttonSize; + set + { + _buttonSize = value; + UpdateView(); + } + } + private int _buttonSize = 20; + + /// + /// Ширина имени поля сущности. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public int NameWidth { get; private set; } = 200; + + /// + /// Цвет фона строки поля при наведении на элемент управления. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [Category("Entity Render")] + [Description("Color on item hover.")] + public Color HoverColor + { + get => _hoverColor; + set + { + _hoverColor = value; + UpdateView(); + } + } + private Color _hoverColor = SystemColors.ControlDark; + + /// + /// Изображение, используемое для отображения содержимого элемента управления. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public Bitmap ContentImage { get; set; } + + #endregion + + #region "Вычисляемые поля" + + /// + /// Ширина области содержимого элемента управления. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public int ContentWidth => Width - SystemInformation.VerticalScrollBarWidth - 2; + + #endregion + + #region "Приватные поля" + + + /// + /// Коллекция полей структуры. + /// + private readonly List _rows = []; + + /// + /// Коллекция строк полей структуры, которые будут отображаться в элементе управления. + /// + private List _viewerRows = []; + + /// + /// Флаг, указывающий, что элемент управления в данный момент перерисовывается. + /// + private bool _rendering; + + #endregion + + /// + /// Конструктор класса . + /// + public EntityTreeViewerControl() + { + InitializeComponent(); + ContentImage = new(ContentWidth, Height); + DoubleBuffered = true; + VerticalScroll.Visible = true; + HorizontalScroll.Visible = false; + + ContentPanel.MouseWheel += ContentPanel_MouseWheel; + } + + #region "Методы обновления" + + /// + /// Обновление данных в элементе управления. + /// + public void UpdateData() + { + if (EntityView?.Entity is null) + { + return; + } + + _rows.Clear(); + ContentPanel.Controls.Clear(); + + StructureBuilder builder = new(); + + _rows.Add(builder.Create("Entity", EntityView.Entity)); + + if (EntityView.SubEntity is not null) + { + string typeName = EntityView.SubEntity.GetType().Name; + + _rows.Add(builder.Create(typeName, EntityView.SubEntity)); + } + + _viewerRows = _rows + .SelectMany(r => r.GetRows(row => new EntityTreeViewerControlRow(this, row))) + .ToList(); + + UpdateRenders(); + } + + /// + /// Обновление отрисовок в элементе управления. + /// + public void UpdateRenders() + { + int maxNameWidth = -1; + + foreach (EntityTreeViewerControlRow row in _viewerRows) + { + int nameWidth = row.NameWidth + row.Item.Depth * DepthSize; + maxNameWidth = Math.Max(maxNameWidth, nameWidth); + } + + NameWidth = maxNameWidth + 3; + + // Debug.WriteLine($@"Height Inner {ContentWidth} / Outer {Width}"); + // Debug.WriteLine($@"Name {NameWidth} / Full {ContentWidth}"); + + foreach (EntityTreeViewerControlRow row in _viewerRows) + { + row.UpdateCache(); + } + + UpdateView(); + } + + /// + /// Обновление отображения в элементе управления. + /// + public void UpdateView() + { + _viewerRows.ForEach(row => row.UpdateButton()); + + List rows = _viewerRows.Where(r => r.Item.Visible).ToList(); + if (rows.Count == 0) + { + return; + } + + BuildLadder(rows); + + _rendering = true; + ContentImage = new(ContentWidth, rows.Count * ItemHeight); + Graphics g = Graphics.FromImage(ContentImage); + + int viewRow = 0; + foreach (EntityTreeViewerControlRow row in rows) + { + row.ViewIndex = viewRow++; + row.Invalidate(g); + } + + g.Save(); + + ContentPanel.Image = ContentImage; + ContentPanel.Width = ContentImage.Width; + ContentPanel.Height = ContentImage.Height; + + _rendering = false; + } + + #endregion + + #region "Вспомогательные методы" + + /// + /// Построение лестницы для строк полей структуры. + /// + /// Набор строк полей структуры. + private void BuildLadder(List rows) + { + List lastLadder = []; + + for (int i = rows.Count - 1; i >= 0; i--) + { + EntityTreeViewerControlRow row = rows[i]; + List ladder; + + if (row.Depth == 0) + { + ladder = []; + } + else if (row.Depth < lastLadder.Count) + { + ladder = + [ + .. lastLadder[..(row.Depth - 1)], + lastLadder[row.Depth - 1] == LadderType.Empty + ? LadderType.Last + : LadderType.Through, + ]; + } + else if (row.Depth == lastLadder.Count) + { + ladder = lastLadder; + } + else + { + ladder = + [ + .. lastLadder, + .. Enumerable.Repeat(LadderType.Empty, row.Depth - lastLadder.Count - 1), + LadderType.Last, + ]; + } + + row.Ladder = ladder; + + lastLadder = ladder + .Select(l => (l == LadderType.Last) ? LadderType.Through : l) + .ToList(); + } + } + + #endregion + + #region "События элемента управления" + + /// + /// Обработчик события изменения размера элемента управления. + /// + /// Источник события. + /// Объект события. + private void EntityTreeViewerControl_Resize(object sender, EventArgs e) + { + if (_rendering) + { + return; + } + + ContentPanel.Width = Width; + + UpdateRenders(); + } + + /// + /// Обработчик события прокрутки элемента управления. + /// + /// Источник события. + /// Объект события. + private void ContentPanel_MouseWheel(object? sender, MouseEventArgs e) + { + /* + int initValue = VerticalScroll.Value; + int delta = -e.Delta / 120; + + VerticalScroll.Value = Math.Clamp( + initValue + delta, + VerticalScrollBar.Minimum, + VerticalScrollBar.Maximum - VerticalScrollBar.LargeChange + 1 + ); + + if (VerticalScrollBar.Value != initValue) + { + ContentPanel.Invalidate(); + } + */ + } + + /// + /// Обработчик события движения мыши над элементом управления. + /// + /// Источник события. + /// Объект события. + private void ContentPanel_MouseMove(object sender, MouseEventArgs e) + { + PointF p = new(e.X, e.Y); + + foreach (EntityTreeViewerControlRow row in _viewerRows) + { + bool contained = row.Target.Contains(p); + + if (contained != row.Hovered) + { + Rectangle r = new(); + row.RealTarget.Intersect(r); + ContentPanel.Invalidate(r); + } + + row.Hovered = contained; + } + } + + #endregion +} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.resx b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs new file mode 100644 index 0000000..1baecfb --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs @@ -0,0 +1,606 @@ +using FontAwesome.Sharp; +using Font = System.Drawing.Font; +using FontStyle = System.Drawing.FontStyle; +using Image = System.Drawing.Image; + +namespace Pf2eViewer.EntityTreeViewer; + +/// +/// Элемент управления для отображения строки поля сущности. +/// +internal class EntityTreeViewerControlRow +{ + /// + /// Элемент управления, которому принадлежит эта строка. + /// + public readonly EntityTreeViewerControl ParentControl; + + /// + /// Отображаемая строка поля сущности. + /// + public readonly StructureField Item; + + #region "Поля строки" + + /// + /// Кэш изображения строки. + /// + public Image Cache + { + get + { + if (_cache == null) + { + UpdateCache(); + } + return _cache!; + } + set => _cache = value; + } + private Image? _cache; + + /// + /// Кэш изображения строки при наведении. + /// + public Image HoveredCache + { + get + { + if (_hoveredCache == null) + { + UpdateCache(); + } + return _hoveredCache!; + } + set => _hoveredCache = value; + } + private Image? _hoveredCache; + + /// + /// Список типов лестницы. + /// + public List Ladder + { + get => _ladder; + set + { + _ladder = value; + _cache = null; + } + } + private List _ladder = []; + + /// + /// Проверяет, находится ли курсор над строкой. + /// + public bool Hovered + { + get => _hovered; + set + { + if (value != _hovered) + { + _hovered = value; + Invalidate(); + } + } + } + private bool _hovered = false; + + /// + /// Индекс строки в представлении. + /// + public int ViewIndex { get; set; } // = -1; + + /// + /// Кнопка раскрытия/сворачивания. + /// + public IconButton? Button { get; set; } = null; + + #endregion + + #region "Поля для рендеринга" + + /// + public Font Font + { + get => ParentControl.Font; + set => ParentControl.Font = value; + } + + /// + public Color ForeColor + { + get => ParentControl.ForeColor; + set => ParentControl.ForeColor = value; + } + + /// + public Color BackColor + { + get => ParentControl.BackColor; + set => ParentControl.BackColor = value; + } + + /// + public Color HoverColor + { + get => ParentControl.HoverColor; + set => ParentControl.HoverColor = value; + } + + /// + public int ItemHeight + { + get => ParentControl.ItemHeight; + set => ParentControl.ItemHeight = value; + } + + /// + public int DepthSize + { + get => ParentControl.DepthSize; + set => ParentControl.DepthSize = value; + } + + /// + public int ButtonSize + { + get => ParentControl.ButtonSize; + set => ParentControl.ButtonSize = value; + } + + /// + public float TypeFontSize + { + get => ParentControl.TypeFontSize; + set => ParentControl.TypeFontSize = value; + } + + #endregion + + #region "Вычисляемые поля" + + /// + /// Ширина строки. + /// + public int Width => ParentControl.ContentWidth; + + /// + /// Высота строки. + /// + public int Height => ItemHeight; + + /// + /// Флаг наличия кнопки. + /// + public bool HasButton => Item.FieldType == StructureFieldType.Complex; + + /// + /// Глубина отображения строки. + /// + public int Depth => Item.Depth; + + /// + /// Ширина имени в строке. + /// + public int NameWidth + { + get + { + Graphics g = Graphics.FromImage(Cache); + return Convert.ToInt32(g.MeasureString(Item.Name, Font).Width + DepthSize); + } + } + + /// + /// Строковое представление типа поля. + /// + public string TypeText + { + get + { + string result = Item.GetTypeString(); + if (result != "") + { + result = $@"({result})"; + } + return result; + } + } + + /// + /// Шрифт для отображения типа поля. + /// + public Font TypeFont => new(Font.FontFamily, TypeFontSize); + + /// + /// Строковое представление значения поля. + /// + public string ValueText => Item.GetValueString(); + + /// + /// Шрифт для отображения значения поля. + /// + public Font ValueFont => + new( + Font.FontFamily, + Font.Size, + Item.FieldType == StructureFieldType.Complex ? FontStyle.Italic : FontStyle.Regular + ); + + /// + /// Прямоугольник, занимаемый строкой в представлении. + /// + public RectangleF Target => new(0, ViewIndex * Height, Width, Height); + + /// + /// Прямоугольник, занимаемый строкой в родительском представлении. + /// + public RectangleF RealTarget => + new(0, ViewIndex * Height - ParentControl.VerticalScroll.Value, Width, Height); + + #endregion + + /// + /// Список данных для рендеринга строк. + /// + private readonly List _renderData = []; + + #region "Основные методы" + + /// + /// Создание элемента управления строки поля сущности. + /// + /// Родительский элемент управления. + /// Строка поля структуры объекта. + public EntityTreeViewerControlRow(EntityTreeViewerControl parent, StructureField item) + { + ParentControl = parent; + Item = item; + ApplyItem(); + UpdateCache(); + + UpdateButton(); + } + + /// + /// Применение свойств к элементу управления. + /// + private void ApplyItem() + { + _renderData.Clear(); + + _renderData.Add(new(Item.Name, Font, ForeColor)); + _renderData.Add(new(TypeText, TypeFont, GetValueColor())); + _renderData.Add( + new(ValueText.Replace("\r", "").Replace('\n', ' '), ValueFont, GetValueColor()) + ); + + if (HasButton) + { + Button = new() + { + Width = ButtonSize, + Height = ButtonSize, + IconSize = ButtonSize, + IconChar = IconChar.MinusSquare, + ImageAlign = ContentAlignment.MiddleCenter, + TextAlign = ContentAlignment.MiddleCenter, + Visible = false, + }; + + Button.Click += Button_Click; + + ParentControl.ContentPanel.Controls.Add(Button); + } + } + + /// + /// Обновление кэша изображения строки. + /// + public void UpdateCache() + { + Cache = RenderCache(BackColor); + HoveredCache = RenderCache(HoverColor); + } + + /// + /// Создание кэша изображения строки. + /// + /// Цвет заднего фона. + /// Кеш изображения строки. + private Image RenderCache(Color backColor) + { + Image cache = new Bitmap(Width, Height); + Graphics g = Graphics.FromImage(cache); + + g.FillRectangle(new SolidBrush(backColor), 0, 0, Width, Height); + + RenderLadder(g); + RenderContent(g); + + g.Save(); + return cache; + } + + /// + /// Запрос на перерисовку строки. + /// + /// Графический объект. + public void Invalidate(Graphics? g = null) + { + g ??= Graphics.FromImage(ParentControl.ContentImage); + + g.DrawImage(Hovered ? HoveredCache : Cache, Target); + + UpdateButton(); + } + + #endregion + + #region "Кнопка" + + /// + /// Обновление кнопки раскрытия/сворачивания. + /// + public void UpdateButton() + { + if (Button is null) + { + return; + } + + int cx = Convert.ToInt32((0.5 + Depth) * DepthSize - 0.5 * ButtonSize); + int cy = Convert.ToInt32((0.5 + ViewIndex) * ItemHeight - 0.5 * ButtonSize); + + Button.Left = cx; + Button.Top = cy; + + Button!.IconChar = ((StructureFieldComplex)Item).Collapsed + ? IconChar.PlusSquare + : IconChar.MinusSquare; + Button.Visible = Item.Visible; + } + + /// + /// Событие нажатия кнопки раскрытия/сворачивания. + /// + /// Источник события. + /// Событие нажатие кнопки + private void Button_Click(object? sender, EventArgs e) + { + if (Item is StructureFieldComplex complex) + { + complex.Collapsed = !complex.Collapsed; + ParentControl.UpdateView(); + } + } + + #endregion + + #region "Отрисовка" + + /// + /// Отрисовка лестницы. + /// + /// Графический объект. + private void RenderLadder(Graphics g) + { + int n = Depth; + int h = ItemHeight; + int w = DepthSize; + + if (_ladder.Count < Depth) + { + return; + } + + Pen pen = new(Color.FromArgb(127, ForeColor.R, ForeColor.G, ForeColor.B), 1); + pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; + + float cy = 0.5f * h; + + for (int i = 0; i < n; i++) + { + float cx = (i + 1.5f) * w; + + switch (_ladder[i]) + { + case LadderType.Last: + g.DrawLine(pen, cx, 0, cx, cy); + break; + case LadderType.Through: + g.DrawLine(pen, cx, 0, cx, h); + break; + case LadderType.Empty: + default: + break; + } + + if (i == n - 1) + { + g.DrawLine(pen, cx, cy, (i + 2) * w, cy); + } + } + } + + /// + /// Отрисовка содержимого строки. + /// + /// Графический объект. + private void RenderContent(Graphics g) + { + float[] xs = + [ + DepthSize * (Item.Depth + 1), + ParentControl.NameWidth, + ParentControl.NameWidth + g.MeasureString(TypeText, TypeFont).Width + 3, + Width, + ]; + + Pen p = new(Color.Aqua, 1); + + for (int i = 0; i < 3; i++) + { + StringRenderData item = _renderData[i]; + + item.Left = xs[i]; + item.Width = xs[i + 1] - xs[i]; + item.Height = ItemHeight; + + // g.DrawRectangle(p, item.Rect); + item.Draw(g); + } + } + + #endregion + + #region Функции-помощники в отрисовке + + /// + /// Смешивание цвета с цветом текста. + /// + /// Цвет оттенка. + /// Степень смешения. + /// Смешенный цвет. + private Color BlendColor(Color color, double rate = 0.5) + { + return BlendColor(ForeColor, color, rate); + } + + /// + /// Смешивание цветов. + /// + /// Первый смешиваемый цвет. + /// Второй смешиваемый цвет. + /// Степень смешения. + /// Смешенный цвет. + private Color BlendColor(Color color1, Color color2, double rate) + { + return Color.FromArgb( + Mix(color1.A, color2.A), + Mix(color1.R, color2.R), + Mix(color1.G, color2.G), + Mix(color1.B, color2.B) + ); + + int Mix(int a, int b) => Convert.ToInt32(Math.Round(a * rate + b * (1 - rate))); + } + + /// + /// Получение цвета текста по типу значения поля. + /// + /// Цвет текста. + public Color GetValueColor() + { + return Item switch + { + StructureFieldPrimitive primitive => Type.GetTypeCode(primitive.Type) switch + { + TypeCode.Boolean => BlendColor(Color.DarkGoldenrod), + TypeCode.Int32 => BlendColor(Color.Blue), + TypeCode.Double => BlendColor(Color.Aqua), + TypeCode.String => BlendColor(Color.Green), + _ => ForeColor, + }, + StructureFieldEnum => BlendColor(Color.Red), + StructureFieldNull => BlendColor(BackColor), + _ => ForeColor, + }; + } + + #endregion + + /// + /// Класс для хранения данных рендеринга строки. + /// + /// Текст отображаемой строки. + /// Шрифт отображаемой строки. + /// Цвет отображаемой строки. + private class StringRenderData(string text, Font font, Color color) + { + /// + /// Текст отображаемой строки. + /// + public string Text { get; } = text; + + /// + /// Шрифт отображаемой строки. + /// + public Font Font { get; } = font; + + /// + /// Цвет отображаемой строки. + /// + public Color Color { get; } = color; + + /// + /// Левый край отображаемой строки. + /// + public float Left { get; set; } + + /// + /// Ширина отображаемой строки. + /// + public float Width { get; set; } + + /// + /// Высота отображаемой строки. + /// + public float Height { get; set; } + + /// + /// Прямоугольник, занимаемый строкой. + /// + public RectangleF Rect => new(Left, 0, Width, Height); + + /// + /// Уместить строку в занимаемый прямоугольник. + /// + /// Элемент графики. + /// Занимаемый прямоугольник + /// Строка, на которую будет заканчиваться значнеие при переполнении. + /// + private string Fit(Graphics g, RectangleF rect, string filler = "…") + { + if (Text.Length == 0 || g.MeasureString(Text, Font).Width <= rect.Width) + { + return Text; + } + + string txt = Text.Length > 100 ? Text[..100] : Text; + + int i = txt.Length; + + while (g.MeasureString(txt + filler, Font).Width > rect.Width) + { + txt = txt[..--i]; + if (i == 0) + break; + } + + return txt + filler; + } + + /// + /// Отрисовка строки. + /// + /// Элемент графики. + public void Draw(Graphics g) + { + g.DrawString( + Fit(g, Rect), + Font, + new SolidBrush(Color), + Rect, + new StringFormat + { + Alignment = StringAlignment.Near, + LineAlignment = StringAlignment.Center, + FormatFlags = StringFormatFlags.NoWrap, + } + ); + } + } +} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs new file mode 100644 index 0000000..0dfea73 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs @@ -0,0 +1,627 @@ +using System.Collections; +using System.Reflection; +using LinqToDB.Extensions; +using Pf2eModel.Model.Common.I18n; + +namespace Pf2eViewer.EntityTreeViewer; + +/// +/// Класс для построения структуры данных класса. +/// +internal class StructureBuilder +{ + /// + /// Список обработанных объектов. + /// + private readonly List _processedObjects = []; + + /// + /// Создание поля структуры. + /// + /// Название поля. + /// Значение поля. + /// Глубина, на котором поле отображается. + /// Поле структуры. + public StructureFieldComplex Create(string name, object value, int depth = 0) + { + _ = RegisterProcessed(value); + + StructureFieldComplex field = new(this, value, name, depth); + + CompleteProcessed(value, field); + + return field; + } + + /// + /// Регистрация обработанного объекта. + /// + /// Регистрируемый объект. + /// `true`, если объект был добавлен; `false`, если объект был добавлен ранее. + public bool RegisterProcessed(object value) + { + if (_processedObjects.Any(p => p.Value == value)) + { + return false; + } + + StructureProcessedObject processed = new(value); + + _processedObjects.Add(processed); + + return true; + } + + /// + /// Дополнить обработанный объект. + /// + /// Значение. + /// Поле объекта. + public void CompleteProcessed(object value, StructureFieldComplex field) + { + StructureProcessedObject? q = _processedObjects.FirstOrDefault(p => p.Value == value); + + if (q == null) + { + return; + } + + q.Field = field; + } + + /// + /// Выбор поля структуры по значению. + /// + /// Название поля. + /// Значение поля. + /// Глубина, на котором поле отображается. + /// + public StructureField Resolve(string name, object? value, int depth) + { + switch (GetFieldType(value)) + { + case StructureFieldType.Null: + return new StructureFieldNull(this, name, depth); + + case StructureFieldType.Primitive: + return new StructureFieldPrimitive(this, value!, name, depth); + + case StructureFieldType.Enum: + return new StructureFieldEnum(this, value!, name, depth); + + case StructureFieldType.Reference: + break; + + case StructureFieldType.Complex: + default: + + StructureProcessedObject? processed = _processedObjects.FirstOrDefault(p => + p.Value == value + ); + + if (processed is null) + { + return Create(name, value!, depth); + } + + StructureFieldReference reference = new StructureFieldReference( + this, + name, + null, + depth + ); + processed.AddReference(reference); + return reference; + } + + throw new("Impossible"); + } + + /// + /// Получить тип поля структуры по значению. + /// + /// Значение поля. + /// Тип поля структуры. + public static StructureFieldType GetFieldType(object? value) + { + if (value is null) + { + return StructureFieldType.Null; + } + + Type type = value.GetType(); + + if (type.IsEnum) + { + return StructureFieldType.Enum; + } + + if (type.IsPrimitive || type == typeof(string)) + { + return StructureFieldType.Primitive; + } + + return StructureFieldType.Complex; + } +} + +/// +/// Класс для хранения обработанного объекта. +/// +/// Объект. +internal class StructureProcessedObject(object value) +{ + public readonly object Value = value; + + private StructureFieldComplex? _field = null; + + public StructureFieldComplex? Field + { + get => _field; + set + { + _field = value; + foreach (StructureFieldReference reference in _references) + { + reference.Reference = Field; + } + } + } + + private readonly List _references = []; + + public void AddReference(StructureFieldReference reference) + { + _references.Add(reference); + + if (_field is not null) + { + reference.Reference = Field; + } + } +} + +/// +/// Тип поля структуры. +/// +internal enum StructureFieldType +{ + /// + /// Пустое поле. + /// + Null, + + /// + /// Простое поле. + /// + Primitive, + + /// + /// Поле с перечислением. + /// + Enum, + + /// + /// Поле с объектом. + /// + Complex, + + /// + /// Поле с ссылкой на объект. + /// + Reference, +} + +/// +/// Тип лестницы глубины. +/// +internal enum LadderType +{ + /// + /// Пустой элемент: [ ]. + /// + Empty, + + /// + /// Последний элемент: [└]. + /// + Last, + + /// + /// Проходной элемент: [├] или [│]. + /// + Through, +} + +/// +/// Базовый класс для полей структуры. +/// +/// Класс построения структуры данных. +/// Название поля. +/// Глубина поля. +internal abstract class StructureField(StructureBuilder builder, string name, int depth) +{ + /// + /// Название поля. + /// + public readonly string Name = name; + + /// + /// Тип поля структуры. + /// + public abstract StructureFieldType FieldType { get; } + + /// + /// Класс построения структуры данных. + /// + public readonly StructureBuilder Builder = builder; + + /// + /// Флаг, указывающий, отображается ли поле. + /// + public virtual bool Visible { get; set; } = true; + + /// + /// Глубина поля структуры. + /// + public readonly int Depth = depth; + + /// + /// Получить строку, представляющую тип поля. + /// + /// Строка, представляющая тип поля. + public abstract string GetTypeString(); + + /// + /// Получить строку, представляющую значение поля. + /// + /// Строка, представляющая значение поля. + public abstract string GetValueString(); + + /// + /// Получить набор строк, используя результат функция. + /// + /// Тип результирующей строки. + /// Функция, оборажения поля в результирующую строку. + /// Коллекция, содержащая набор строк. + public virtual ICollection GetRows(Func func) + { + StructureField[] q = [this]; + return q.Select(func).ToList(); + } +} + +/// +/// Пустое поле структуры. +/// +/// Класс построения структуры данных. +/// Название поля. +/// Глубина поля. +internal class StructureFieldNull(StructureBuilder builder, string name, int depth) + : StructureField(builder, name, depth) +{ + /// + public override StructureFieldType FieldType => StructureFieldType.Null; + + /// + public override string GetTypeString() => "null"; + + /// + public override string GetValueString() => ""; +} + +/// +/// Простое поле структуры. +/// +/// Класс построения структуры данных. +/// Значение поля. +/// Название поля. +/// Глубина поля. +internal class StructureFieldPrimitive( + StructureBuilder builder, + object value, + string name, + int depth +) : StructureField(builder, name, depth) +{ + /// + public override StructureFieldType FieldType => StructureFieldType.Primitive; + + /// + /// Значение поля. + /// + public readonly object Value = value; + + /// + /// Тип значения поля. + /// + public readonly Type Type = value.GetType(); + + /// + public override string GetTypeString() + { + return Type.GetTypeCode(Type) switch + { + TypeCode.Boolean => "bool", + TypeCode.Int32 => "int", + TypeCode.String => "string", + TypeCode.Double => "double", + _ => Type.Name, + }; + } + + /// + public override string GetValueString() + { + if (Type == typeof(string)) + { + return "\"" + ((string)Value).Replace("\"", "\\\"") + "\""; + } + + return Value.ToString() ?? string.Empty; + } +} + +/// +/// Поле структуры с перечислением. +/// +/// Класс построения структуры данных. +/// Значение поля. +/// Название поля. +/// Глубина поля. +internal class StructureFieldEnum(StructureBuilder builder, object value, string name, int depth) + : StructureField(builder, name, depth) +{ + /// + public override StructureFieldType FieldType => StructureFieldType.Enum; + + /// + /// Значение поля. + /// + public readonly object Value = value; + + /// + /// Тип значения поля. + /// + public readonly Type Type = value.GetType(); + + /// + public override string GetTypeString() => "enum"; + + /// + public override string GetValueString() => $"{Type.Name}.{Value}"; +} + +/// +/// Поле стркутуры с объектом. +/// +internal class StructureFieldComplex : StructureField +{ + /// + public override StructureFieldType FieldType => StructureFieldType.Complex; + + /// + /// Набор полей объекта. + /// + public readonly List Fields = []; + + /// + /// Тип значения поля. + /// + public readonly Type Type; + + /// + /// Флаг, указывающий, отображается ли поля объекта. + /// + public bool Collapsed + { + get => _collapsed; + set + { + _collapsed = value; + UpdateFieldVisibility(); + } + } + private bool _collapsed = false; + + /// + public override bool Visible + { + get => _visible; + set + { + _visible = value; + UpdateFieldVisibility(); + } + } + private bool _visible = true; + + /// + /// Конструктор класса поля структуры с объектом. + /// + /// Класс построения структуры данных. + /// Значение поля. + /// Название поля. + /// Глубина поля. + public StructureFieldComplex(StructureBuilder builder, object value, string name, int depth) + : base(builder, name, depth) + { + Type = value.GetType(); + + SetFields((dynamic)value); + } + + /// + /// Обновить видимость полей структуры. + /// + private void UpdateFieldVisibility() + { + foreach (StructureField field in Fields) + { + field.Visible = Visible && !Collapsed; + } + } + + /// + /// Установить поля структуры `LocaleString`. + /// + /// Объект строки локализации. + private void SetFields(LocaleString localeString) + { + SetFieldsFromRecords( + localeString + .Select(field => new FieldRecord(field.Locale.ToString(), field.Value)) + .ToList() + ); + } + + /// + /// Установить поля структуры `IEnumerable`. + /// + /// Объект-перечисление. + private void SetFields(IEnumerable enumerable) + { + int i = 0; + + List records = []; + + foreach (object? item in enumerable) + { + records.Add(new($"[{i++}]", item)); + } + + SetFieldsFromRecords(records); + } + + /// + /// Установить поля структуры общего объекта. + /// + /// Объект. + private void SetFields(object value) + { + Type type = value.GetType(); + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + + SetFieldsFromRecords( + properties + .Where(property => + property.GetAttribute() is null + ) + .Select(property => new FieldRecord(property.Name, property.GetValue(value, null))) + .ToList() + ); + } + + /// + /// Установить поля структуры из списка записей. + /// + /// + private void SetFieldsFromRecords(List records) + { + foreach ( + FieldRecord record in records.Where(record => + record.FieldType == StructureFieldType.Complex + ) + ) + { + record.Allowed = Builder.RegisterProcessed(record.Value!); + } + + foreach (FieldRecord record in records) + { + Fields.Add( + record is { FieldType: StructureFieldType.Complex, Allowed: true } + ? Builder.Create(record.Name, record.Value!, Depth + 1) + : Builder.Resolve(record.Name, record.Value!, Depth + 1) + ); + } + } + + /// + /// Запись поля структуры. + /// + /// Название поля. + /// Значение поля. + private class FieldRecord(string name, object? value) + { + /// + /// Название поля. + /// + public readonly string Name = name; + + /// + /// Значение поля. + /// + public readonly object? Value = value; + + /// + /// Тип поля структуры. + /// + public StructureFieldType FieldType => StructureBuilder.GetFieldType(Value); + + /// + /// Флаг, указывающий, использовать ли поле. + /// + public bool Allowed = true; + } + + /// + public override string GetTypeString() + { + if (Type.IsGenericEnumerableType()) + { + Type elementType = Type.GetGenericArguments().Single(); + + return $"{elementType.Name}[]"; + } + else + { + return Type.Name; + } + } + + /// + public override string GetValueString() + { + if (Type.IsGenericEnumerableType()) + { + return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items"); + } + return ""; + } + + /// + public override ICollection GetRows(Func func) + { + return [.. base.GetRows(func), .. Fields.SelectMany(f => f.GetRows(func)).ToList()]; + } +} + +/// +/// Поле структуры с ссылкой на объект. +/// +/// Класс построения структуры данных. +/// Название поля. +/// Ссылка на объект. +/// Глубина поля. +internal class StructureFieldReference( + StructureBuilder builder, + string name, + StructureFieldComplex? reference, + int depth +) : StructureField(builder, name, depth) +{ + /// + /// Ссылка на объект. + /// + public StructureFieldComplex? Reference = reference; + + /// + public override StructureFieldType FieldType => StructureFieldType.Reference; + + /// + public override string GetTypeString() => Reference?.GetTypeString() ?? "???"; + + /// + public override string GetValueString() => ""; +} diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs index 02d879b..37f32d7 100644 --- a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs @@ -6,16 +6,16 @@ namespace Pf2eDataScrapper.Forms.Controls; internal partial class ProcessorCollectionControl : UserControl { [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public ProcessorCollection? Collection + public ProcessorCollection Collection { get => _collection; set { _collection = value; - UpdateContent(); + SetContent(); } } - private ProcessorCollection? _collection = null; + private ProcessorCollection _collection = null!; public ProcessorCollectionControl() { @@ -28,27 +28,24 @@ internal partial class ProcessorCollectionControl : UserControl private Dictionary ItemCheckBoxes { get; } = []; - private void UpdateContent() + private void SetContent() { RootFlowLayoutPanel.Controls.Clear(); - if (Collection == null) - { - return; - } - foreach (ProcessorGroupEnum group in Enum.GetValues()) { GroupCheckBoxes[group] = new() { Text = group.GetDescription(), - //AutoSize = false, + Tag = group, Dock = DockStyle.Fill, + ThreeState = true, Checked = true, + AutoCheck = false, CheckAlign = ContentAlignment.TopLeft, }; - GroupCheckBoxes[group].CheckedChanged += GroupCheckBox_CheckedChanged; + GroupCheckBoxes[group].Click += GroupCheckBox_Click; GroupFlowLayoutPanels[group] = new() { @@ -69,20 +66,108 @@ internal partial class ProcessorCollectionControl : UserControl ItemCheckBoxes[item.Name] = new() { Text = item.Description, + Tag = item, AutoSize = true, // Dock = DockStyle.Fill, // MaximumSize = new Size(Width, 20), // Width = 200, - Checked = true, + Checked = item.Active, CheckAlign = ContentAlignment.TopLeft, }; + ItemCheckBoxes[item.Name].CheckedChanged += ItemCheckBox_CheckedChanged; + GroupFlowLayoutPanels[item.Group].Controls.Add(ItemCheckBoxes[item.Name]); } + + UpdateContent(); } - private void GroupCheckBox_CheckedChanged(object? sender, EventArgs e) + private void UpdateContent() { - // + Dictionary checkStates = []; + List checkedItems = []; + + foreach ((string key, CheckBox checkBox) in ItemCheckBoxes) + { + ProcessorCollectionItem item = Collection![key]; + + if (checkStates.TryGetValue(item.Group, out CheckState state)) + { + /* + * | Checked | Unchecked | Indeterminate + * -------+---------------+---------------+--------------- + * true | Checked | Indeterminate | Indeterminate + * false | Indeterminate | Unchecked | Indeterminate + */ + + checkStates[item.Group] = + checkBox.Checked && state == CheckState.Checked ? CheckState.Checked + : !checkBox.Checked && state == CheckState.Unchecked ? CheckState.Unchecked + : CheckState.Indeterminate; + } + else + { + checkStates[item.Group] = checkBox.Checked + ? CheckState.Checked + : CheckState.Unchecked; + } + + if (checkBox.Checked) + { + checkedItems.Add(key); + } + } + + foreach ((ProcessorGroupEnum groupEnum, CheckBox checkBox) in GroupCheckBoxes) + { + checkBox.CheckState = checkStates.GetValueOrDefault( + groupEnum, + CheckState.Indeterminate + ); + } + + Collection.FilterProcessors(checkedItems); + } + + private void GroupCheckBox_Click(object? sender, EventArgs e) + { + if (sender is null) + { + return; + } + + CheckBox groupCheckBox = (CheckBox)sender; + ProcessorGroupEnum groupEnum = GroupCheckBoxes + .First(pair => pair.Value == groupCheckBox) + .Key; + + CheckState nextState = groupCheckBox.CheckState switch + { + CheckState.Unchecked => CheckState.Checked, + CheckState.Checked => CheckState.Unchecked, + CheckState.Indeterminate => CheckState.Checked, + _ => throw new ArgumentOutOfRangeException( + $"Неизвестное состояние {groupCheckBox.CheckState}" + ), + }; + + groupCheckBox.CheckState = nextState; + + foreach ( + (string _, CheckBox checkBox) in ItemCheckBoxes.Where(pair => + ((ProcessorCollectionItem?)pair.Value.Tag)?.Group == groupEnum + ) + ) + { + checkBox.Checked = nextState == CheckState.Checked; + } + + UpdateContent(); + } + + private void ItemCheckBox_CheckedChanged(object? sender, EventArgs e) + { + UpdateContent(); } } diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs new file mode 100644 index 0000000..a063fba --- /dev/null +++ b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs @@ -0,0 +1,244 @@ +using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; +using Pf2eViewer.DatabaseViewer; + +namespace Pf2eViewer; + +partial class EntityViewForm +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + RootSplitContainer = new SplitContainer(); + InnerSplitContainer = new SplitContainer(); + TypesListBox = new ListBox(); + ResultSplitContainer = new SplitContainer(); + DescriptionTextBox = new TextBox(); + DatabaseViewer = new DatabaseViewerControl(); + EntityTreeViewerControl = new Pf2eViewer.EntityTreeViewer.EntityTreeViewerControl(); + MainMenuStrip = new MenuStrip(); + FileToolStripMenuItem = new ToolStripMenuItem(); + ExitToolStripMenuItem = new ToolStripMenuItem(); + WindowsToolStripMenuItem = new ToolStripMenuItem(); + ImportSettingsWindowsToolStripMenuItem = new ToolStripMenuItem(); + EntityViewWindowToolStripMenuItem = new ToolStripMenuItem(); + ((System.ComponentModel.ISupportInitialize)RootSplitContainer).BeginInit(); + RootSplitContainer.Panel1.SuspendLayout(); + RootSplitContainer.Panel2.SuspendLayout(); + RootSplitContainer.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)InnerSplitContainer).BeginInit(); + InnerSplitContainer.Panel1.SuspendLayout(); + InnerSplitContainer.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)ResultSplitContainer).BeginInit(); + ResultSplitContainer.Panel2.SuspendLayout(); + ResultSplitContainer.SuspendLayout(); + MainMenuStrip.SuspendLayout(); + SuspendLayout(); + // + // RootSplitContainer + // + RootSplitContainer.Dock = DockStyle.Fill; + RootSplitContainer.Location = new Point(0, 24); + RootSplitContainer.Name = "RootSplitContainer"; + // + // RootSplitContainer.Panel1 + // + RootSplitContainer.Panel1.Controls.Add(InnerSplitContainer); + RootSplitContainer.Panel1MinSize = 200; + // + // RootSplitContainer.Panel2 + // + RootSplitContainer.Panel2.Controls.Add(ResultSplitContainer); + RootSplitContainer.Panel2MinSize = 400; + RootSplitContainer.Size = new Size(1384, 837); + RootSplitContainer.SplitterDistance = 322; + RootSplitContainer.TabIndex = 0; + // + // InnerSplitContainer + // + InnerSplitContainer.Dock = DockStyle.Fill; + InnerSplitContainer.Location = new Point(0, 0); + InnerSplitContainer.Name = "InnerSplitContainer"; + InnerSplitContainer.Orientation = Orientation.Horizontal; + // + // InnerSplitContainer.Panel1 + // + InnerSplitContainer.Panel1.Controls.Add(TypesListBox); + // + // InnerSplitContainer.Panel2 + // + InnerSplitContainer.Panel2.Controls.Add(DatabaseViewer); + InnerSplitContainer.Size = new Size(322, 861); + InnerSplitContainer.SplitterDistance = 126; + InnerSplitContainer.TabIndex = 1; + // + // TypesListBox + // + TypesListBox.Dock = DockStyle.Fill; + TypesListBox.FormattingEnabled = true; + TypesListBox.Location = new Point(0, 0); + TypesListBox.Name = "TypesListBox"; + TypesListBox.Size = new Size(322, 122); + TypesListBox.TabIndex = 0; + TypesListBox.SelectedIndexChanged += TypesListBox_SelectedIndexChanged; + // + // ResultSplitContainer + // + ResultSplitContainer.Dock = DockStyle.Fill; + ResultSplitContainer.Location = new Point(0, 0); + ResultSplitContainer.Name = "ResultSplitContainer"; + // + // ResultSplitContainer.Panel1 + // + ResultSplitContainer.Panel1.Controls.Add(EntityTreeViewerControl); + // + // ResultSplitContainer.Panel2 + // + ResultSplitContainer.Panel2.Controls.Add(DescriptionTextBox); + ResultSplitContainer.Size = new Size(1058, 837); + ResultSplitContainer.SplitterDistance = 582; + ResultSplitContainer.TabIndex = 1; + // + // DescriptionTextBox + // + DescriptionTextBox.Dock = DockStyle.Fill; + DescriptionTextBox.Font = new Font("Consolas", 9F); + DescriptionTextBox.Location = new Point(0, 0); + DescriptionTextBox.Multiline = true; + DescriptionTextBox.Name = "DescriptionTextBox"; + DescriptionTextBox.ReadOnly = true; + DescriptionTextBox.ScrollBars = ScrollBars.Vertical; + DescriptionTextBox.Size = new Size(472, 837); + DescriptionTextBox.TabIndex = 0; + // + // DatabaseViewer + // + DatabaseViewer.Dock = DockStyle.Fill; + DatabaseViewer.Location = new Point(0, 0); + DatabaseViewer.Name = "DatabaseViewer"; + DatabaseViewer.Size = new Size(322, 731); + DatabaseViewer.TabIndex = 1; + // + // EntityTreeViewerControl + // + EntityTreeViewerControl.AutoScroll = true; + EntityTreeViewerControl.ButtonSize = 15; + EntityTreeViewerControl.DepthSize = 15; + EntityTreeViewerControl.Dock = DockStyle.Fill; + EntityTreeViewerControl.Font = new Font("Consolas", 9F); + EntityTreeViewerControl.HoverColor = SystemColors.ControlDark; + EntityTreeViewerControl.ItemHeight = 20; + EntityTreeViewerControl.Location = new Point(0, 0); + EntityTreeViewerControl.Name = "EntityTreeViewerControl"; + EntityTreeViewerControl.Size = new Size(582, 861); + EntityTreeViewerControl.TabIndex = 0; + EntityTreeViewerControl.TypeFontSize = 7F; + // + // MainMenuStrip + // + MainMenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem }); + MainMenuStrip.Location = new Point(0, 0); + MainMenuStrip.Name = "MainMenuStrip"; + MainMenuStrip.Size = new Size(1384, 24); + MainMenuStrip.TabIndex = 1; + MainMenuStrip.Text = "menuStrip1"; + // + // FileToolStripMenuItem + // + FileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ExitToolStripMenuItem }); + FileToolStripMenuItem.Name = "FileToolStripMenuItem"; + FileToolStripMenuItem.Size = new Size(48, 20); + FileToolStripMenuItem.Text = "Файл"; + // + // ExitToolStripMenuItem + // + ExitToolStripMenuItem.Name = "ExitToolStripMenuItem"; + ExitToolStripMenuItem.Size = new Size(180, 22); + ExitToolStripMenuItem.Text = "Выход"; + ExitToolStripMenuItem.Click += ExitToolStripMenuItem_Click; + // + // WindowsToolStripMenuItem + // + WindowsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ImportSettingsWindowsToolStripMenuItem, EntityViewWindowToolStripMenuItem }); + WindowsToolStripMenuItem.Name = "WindowsToolStripMenuItem"; + WindowsToolStripMenuItem.Size = new Size(47, 20); + WindowsToolStripMenuItem.Text = "Окна"; + // + // ImportSettingsWindowsToolStripMenuItem + // + ImportSettingsWindowsToolStripMenuItem.Name = "ImportSettingsWindowsToolStripMenuItem"; + ImportSettingsWindowsToolStripMenuItem.Size = new Size(228, 22); + ImportSettingsWindowsToolStripMenuItem.Text = "Настройка импорта данных"; + ImportSettingsWindowsToolStripMenuItem.Click += ImportSettingsWindowsToolStripMenuItem_Click; + // + // EntityViewWindowToolStripMenuItem + // + EntityViewWindowToolStripMenuItem.Enabled = false; + EntityViewWindowToolStripMenuItem.Name = "EntityViewWindowToolStripMenuItem"; + EntityViewWindowToolStripMenuItem.Size = new Size(228, 22); + EntityViewWindowToolStripMenuItem.Text = "Просмотр сущностей"; + // + // EntityViewForm + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(1384, 861); + Controls.Add(RootSplitContainer); + Controls.Add(MainMenuStrip); + Name = "EntityViewForm"; + Text = "DataBaseViewer"; + RootSplitContainer.Panel1.ResumeLayout(false); + RootSplitContainer.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)RootSplitContainer).EndInit(); + RootSplitContainer.ResumeLayout(false); + InnerSplitContainer.Panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)InnerSplitContainer).EndInit(); + InnerSplitContainer.ResumeLayout(false); + ResultSplitContainer.Panel2.ResumeLayout(false); + ResultSplitContainer.Panel2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)ResultSplitContainer).EndInit(); + ResultSplitContainer.ResumeLayout(false); + MainMenuStrip.ResumeLayout(false); + MainMenuStrip.PerformLayout(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private SplitContainer RootSplitContainer; + private SplitContainer InnerSplitContainer; + private ListBox TypesListBox; + private SplitContainer ResultSplitContainer; + private TextBox DescriptionTextBox; + private EntityTreeViewer.EntityTreeViewerControl EntityTreeViewerControl; + private DatabaseViewerControl DatabaseViewer; + private MenuStrip MainMenuStrip; + private ToolStripMenuItem FileToolStripMenuItem; + private ToolStripMenuItem ExitToolStripMenuItem; + private ToolStripMenuItem WindowsToolStripMenuItem; + private ToolStripMenuItem ImportSettingsWindowsToolStripMenuItem; + private ToolStripMenuItem EntityViewWindowToolStripMenuItem; +} diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.cs b/Pf2eDataScrapper/Forms/EntityViewForm.cs new file mode 100644 index 0000000..8aa18e1 --- /dev/null +++ b/Pf2eDataScrapper/Forms/EntityViewForm.cs @@ -0,0 +1,99 @@ +using System.ComponentModel; +using Pf2eDataScrapper; +using Pf2eModel; +using Pf2eModel.Model.Enum; + +namespace Pf2eViewer; + +/// +/// Основная форма приложения. +/// +internal partial class EntityViewForm : Form +{ + public Pf2eDbContext Context { get; } + + /// + /// Элемент управления для отображения дерева сущностей. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public EntityView? CurrentView + { + get => EntityTreeViewerControl.EntityView; + set + { + EntityTreeViewerControl.EntityView = value; + Application.DoEvents(); + } + } + + /// + /// Конструктор класса . + /// + public EntityViewForm(Pf2eDbContext context) + { + Context = context; + + InitializeComponent(); + + DatabaseViewer.EntityChange += DatabaseViewer_EntityChange; + + Reset(); + } + + /// + /// Сброс состояния формы. + /// + public void Reset() + { + TypesListBox.DataSource = Context + .Entities.Select(e => e.Type) + .Distinct() + .OrderBy(t => t.ToString()) + .ToList(); + + if (TypesListBox.Items.Count > 0) + { + TypesListBox.SelectedIndex = 0; + } + } + + /// + /// Обработчик события изменения выбранного элемента в списке типов сущностей. + /// + /// Источник события. + /// Данные события. + private void TypesListBox_SelectedIndexChanged(object sender, EventArgs _) + { + if (TypesListBox.SelectedItem is null) + { + return; + } + + CurrentView = null; + + DatabaseViewer.Type = (EntityType)TypesListBox.SelectedItem; + } + + /// + /// Обработчик события изменения выбранной сущности в дереве сущностей. + /// + /// Новая выбранная сущность. + private void DatabaseViewer_EntityChange(EntityView newEntity) + { + CurrentView = newEntity; + + DescriptionTextBox.Text = newEntity.Entity!.Description; + } + + private void ExitToolStripMenuItem_Click(object sender, EventArgs e) + { + Close(); + } + + private void ImportSettingsWindowsToolStripMenuItem_Click(object sender, EventArgs e) + { + ApplicationStateChange?.Invoke(this, ApplicationStage.InitialSettings); + } + + public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange; +} diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.resx b/Pf2eDataScrapper/Forms/EntityViewForm.resx new file mode 100644 index 0000000..51c499e --- /dev/null +++ b/Pf2eDataScrapper/Forms/EntityViewForm.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 51 + + \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs b/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs index ec7a110..1d2a967 100644 --- a/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs @@ -33,6 +33,9 @@ partial class ProgressForm LocalLabel = new Label(); LocalProgressBar = new ProgressBar(); MessageListView = new ListView(); + TimeColumnHeader = new ColumnHeader(); + TypeColumnHeader = new ColumnHeader(); + MessageColumnHeader = new ColumnHeader(); SuspendLayout(); // // ProcessorsProgressBar @@ -40,7 +43,7 @@ partial class ProgressForm ProcessorsProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; ProcessorsProgressBar.Location = new Point(12, 27); ProcessorsProgressBar.Name = "ProcessorsProgressBar"; - ProcessorsProgressBar.Size = new Size(439, 23); + ProcessorsProgressBar.Size = new Size(537, 23); ProcessorsProgressBar.TabIndex = 0; // // ProcessorsLabel @@ -48,7 +51,7 @@ partial class ProgressForm ProcessorsLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; ProcessorsLabel.Location = new Point(12, 9); ProcessorsLabel.Name = "ProcessorsLabel"; - ProcessorsLabel.Size = new Size(439, 15); + ProcessorsLabel.Size = new Size(537, 15); ProcessorsLabel.TabIndex = 1; ProcessorsLabel.Text = "ProcessorsLabel"; // @@ -57,7 +60,7 @@ partial class ProgressForm LocalLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; LocalLabel.Location = new Point(12, 59); LocalLabel.Name = "LocalLabel"; - LocalLabel.Size = new Size(439, 15); + LocalLabel.Size = new Size(537, 15); LocalLabel.TabIndex = 2; LocalLabel.Text = "LocalLabel"; // @@ -66,24 +69,42 @@ partial class ProgressForm LocalProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; LocalProgressBar.Location = new Point(12, 77); LocalProgressBar.Name = "LocalProgressBar"; - LocalProgressBar.Size = new Size(439, 23); + LocalProgressBar.Size = new Size(537, 23); LocalProgressBar.TabIndex = 3; // // MessageListView // MessageListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + MessageListView.Columns.AddRange(new ColumnHeader[] { TimeColumnHeader, TypeColumnHeader, MessageColumnHeader }); + MessageListView.GridLines = true; + MessageListView.HeaderStyle = ColumnHeaderStyle.Nonclickable; MessageListView.Location = new Point(12, 114); MessageListView.Name = "MessageListView"; - MessageListView.Size = new Size(439, 316); + MessageListView.Size = new Size(537, 450); MessageListView.TabIndex = 4; MessageListView.UseCompatibleStateImageBehavior = false; - MessageListView.View = View.List; + MessageListView.View = View.Details; + // + // TimeColumnHeader + // + TimeColumnHeader.Text = "Время"; + TimeColumnHeader.Width = 80; + // + // TypeColumnHeader + // + TypeColumnHeader.Text = "Тип"; + TypeColumnHeader.Width = 80; + // + // MessageColumnHeader + // + MessageColumnHeader.Text = "Сообщение"; + MessageColumnHeader.Width = 2000; // // ProgressForm // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(463, 442); + ClientSize = new Size(561, 576); Controls.Add(MessageListView); Controls.Add(LocalProgressBar); Controls.Add(LocalLabel); @@ -101,4 +122,7 @@ partial class ProgressForm private Label LocalLabel; private ProgressBar LocalProgressBar; private ListView MessageListView; + private ColumnHeader TimeColumnHeader; + private ColumnHeader TypeColumnHeader; + private ColumnHeader MessageColumnHeader; } \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/ProgressForm.cs b/Pf2eDataScrapper/Forms/ProgressForm.cs index f262e04..18f6a89 100644 --- a/Pf2eDataScrapper/Forms/ProgressForm.cs +++ b/Pf2eDataScrapper/Forms/ProgressForm.cs @@ -15,6 +15,14 @@ internal partial class ProgressForm : Form Progress.ProgressUpdate += Progress_ProgressUpdate; Progress.ProgressMessage += Progress_ProgressMessage; + + MessageListView + .GetType() + .GetProperty( + "DoubleBuffered", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic + )! + .SetValue(MessageListView, true, null); } private void Progress_ProgressUpdate(ProcessProgress sender) @@ -36,15 +44,29 @@ internal partial class ProgressForm : Form Application.DoEvents(); } - private void Progress_ProgressMessage(ProcessProgress sender, string message) + private void Progress_ProgressMessage(ProcessProgress sender, LogLevel level, string message) { if (InvokeRequired) { - Invoke(Progress_ProgressMessage, sender, message); + Invoke(Progress_ProgressMessage, sender, level, message); return; } - MessageListView.Items.Add(message); + ListViewItem item = new([DateTime.Now.ToString("HH:mm:ss.fff"), level.ToString(), message]) + { + BackColor = level switch + { + LogLevel.Important => Color.Salmon, + LogLevel.Warning => Color.Khaki, + LogLevel.Information => Color.White, + LogLevel.Progress => Color.LightGray, + _ => Color.White, + }, + }; + + MessageListView.Items.Add(item); + //MessageListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); + //MessageListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); Application.DoEvents(); } diff --git a/Pf2eDataScrapper/Forms/StartForm.Designer.cs b/Pf2eDataScrapper/Forms/StartForm.Designer.cs index 06e6f1b..297306d 100644 --- a/Pf2eDataScrapper/Forms/StartForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/StartForm.Designer.cs @@ -36,18 +36,26 @@ partial class StartForm SelectAllButton = new Button(); DeselectAllButton = new Button(); StartButton = new Button(); + MainMenuStrip = new MenuStrip(); + FileToolStripMenuItem = new ToolStripMenuItem(); + ExitToolStripMenuItem = new ToolStripMenuItem(); + WindowsToolStripMenuItem = new ToolStripMenuItem(); + ImportSettingsWindowsToolStripMenuItem = new ToolStripMenuItem(); + EntityViewWindowToolStripMenuItem = new ToolStripMenuItem(); + StartToolStripMenuItem = new ToolStripMenuItem(); ((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit(); MainSplitContainer.Panel1.SuspendLayout(); MainSplitContainer.Panel2.SuspendLayout(); MainSplitContainer.SuspendLayout(); PropertiesGroupBox.SuspendLayout(); ProcessorsGroupBox.SuspendLayout(); + MainMenuStrip.SuspendLayout(); SuspendLayout(); // // MainSplitContainer // MainSplitContainer.Dock = DockStyle.Fill; - MainSplitContainer.Location = new Point(0, 0); + MainSplitContainer.Location = new Point(0, 24); MainSplitContainer.Name = "MainSplitContainer"; // // MainSplitContainer.Panel1 @@ -60,7 +68,7 @@ partial class StartForm MainSplitContainer.Panel2.Controls.Add(ProcessorsGroupBox); MainSplitContainer.Panel2.Controls.Add(StartButton); MainSplitContainer.Panel2MinSize = 300; - MainSplitContainer.Size = new Size(931, 677); + MainSplitContainer.Size = new Size(931, 653); MainSplitContainer.SplitterDistance = 300; MainSplitContainer.TabIndex = 0; // @@ -70,7 +78,7 @@ partial class StartForm PropertiesGroupBox.Controls.Add(SettingsPropertyGrid); PropertiesGroupBox.Location = new Point(3, 3); PropertiesGroupBox.Name = "PropertiesGroupBox"; - PropertiesGroupBox.Size = new Size(294, 671); + PropertiesGroupBox.Size = new Size(294, 647); PropertiesGroupBox.TabIndex = 0; PropertiesGroupBox.TabStop = false; PropertiesGroupBox.Text = "Свойства"; @@ -81,7 +89,7 @@ partial class StartForm SettingsPropertyGrid.Dock = DockStyle.Fill; SettingsPropertyGrid.Location = new Point(3, 19); SettingsPropertyGrid.Name = "SettingsPropertyGrid"; - SettingsPropertyGrid.Size = new Size(288, 649); + SettingsPropertyGrid.Size = new Size(288, 625); SettingsPropertyGrid.TabIndex = 0; // // ProcessorsGroupBox @@ -92,7 +100,7 @@ partial class StartForm ProcessorsGroupBox.Controls.Add(DeselectAllButton); ProcessorsGroupBox.Location = new Point(3, 3); ProcessorsGroupBox.Name = "ProcessorsGroupBox"; - ProcessorsGroupBox.Size = new Size(621, 602); + ProcessorsGroupBox.Size = new Size(621, 578); ProcessorsGroupBox.TabIndex = 1; ProcessorsGroupBox.TabStop = false; ProcessorsGroupBox.Text = "Обработчики"; @@ -102,7 +110,7 @@ partial class StartForm ProcessorCollectionControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; ProcessorCollectionControl.Location = new Point(6, 48); ProcessorCollectionControl.Name = "ProcessorCollectionControl"; - ProcessorCollectionControl.Size = new Size(609, 548); + ProcessorCollectionControl.Size = new Size(609, 524); ProcessorCollectionControl.TabIndex = 2; // // SelectAllButton @@ -127,7 +135,7 @@ partial class StartForm // StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold); - StartButton.Location = new Point(3, 611); + StartButton.Location = new Point(3, 587); StartButton.Name = "StartButton"; StartButton.Size = new Size(621, 63); StartButton.TabIndex = 0; @@ -135,12 +143,64 @@ partial class StartForm StartButton.UseVisualStyleBackColor = true; StartButton.Click += StartButton_Click; // + // MainMenuStrip + // + MainMenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem, StartToolStripMenuItem }); + MainMenuStrip.Location = new Point(0, 0); + MainMenuStrip.Name = "MainMenuStrip"; + MainMenuStrip.Size = new Size(931, 24); + MainMenuStrip.TabIndex = 1; + MainMenuStrip.Text = "menuStrip1"; + // + // FileToolStripMenuItem + // + FileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ExitToolStripMenuItem }); + FileToolStripMenuItem.Name = "FileToolStripMenuItem"; + FileToolStripMenuItem.Size = new Size(48, 20); + FileToolStripMenuItem.Text = "Файл"; + // + // ExitToolStripMenuItem + // + ExitToolStripMenuItem.Name = "ExitToolStripMenuItem"; + ExitToolStripMenuItem.Size = new Size(109, 22); + ExitToolStripMenuItem.Text = "Выход"; + ExitToolStripMenuItem.Click += ExitToolStripMenuItem_Click; + // + // WindowsToolStripMenuItem + // + WindowsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ImportSettingsWindowsToolStripMenuItem, EntityViewWindowToolStripMenuItem }); + WindowsToolStripMenuItem.Name = "WindowsToolStripMenuItem"; + WindowsToolStripMenuItem.Size = new Size(47, 20); + WindowsToolStripMenuItem.Text = "Окна"; + // + // ImportSettingsWindowsToolStripMenuItem + // + ImportSettingsWindowsToolStripMenuItem.Enabled = false; + ImportSettingsWindowsToolStripMenuItem.Name = "ImportSettingsWindowsToolStripMenuItem"; + ImportSettingsWindowsToolStripMenuItem.Size = new Size(228, 22); + ImportSettingsWindowsToolStripMenuItem.Text = "Настройка импорта данных"; + // + // EntityViewWindowToolStripMenuItem + // + EntityViewWindowToolStripMenuItem.Name = "EntityViewWindowToolStripMenuItem"; + EntityViewWindowToolStripMenuItem.Size = new Size(228, 22); + EntityViewWindowToolStripMenuItem.Text = "Просмотр сущностей"; + EntityViewWindowToolStripMenuItem.Click += EntityViewWindowToolStripMenuItem_Click; + // + // StartToolStripMenuItem + // + StartToolStripMenuItem.Name = "StartToolStripMenuItem"; + StartToolStripMenuItem.Size = new Size(108, 20); + StartToolStripMenuItem.Text = "Запуск импорта"; + StartToolStripMenuItem.Click += StartToolStripMenuItem_Click; + // // StartForm // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(931, 677); Controls.Add(MainSplitContainer); + Controls.Add(MainMenuStrip); Name = "StartForm"; Text = "Настройки импорта данных"; Load += StartForm_Load; @@ -150,7 +210,10 @@ partial class StartForm MainSplitContainer.ResumeLayout(false); PropertiesGroupBox.ResumeLayout(false); ProcessorsGroupBox.ResumeLayout(false); + MainMenuStrip.ResumeLayout(false); + MainMenuStrip.PerformLayout(); ResumeLayout(false); + PerformLayout(); } #endregion @@ -163,4 +226,11 @@ partial class StartForm private Button SelectAllButton; private Button DeselectAllButton; private Controls.ProcessorCollectionControl ProcessorCollectionControl; + private MenuStrip MainMenuStrip; + private ToolStripMenuItem FileToolStripMenuItem; + private ToolStripMenuItem ExitToolStripMenuItem; + private ToolStripMenuItem WindowsToolStripMenuItem; + private ToolStripMenuItem ImportSettingsWindowsToolStripMenuItem; + private ToolStripMenuItem EntityViewWindowToolStripMenuItem; + private ToolStripMenuItem StartToolStripMenuItem; } \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/StartForm.cs b/Pf2eDataScrapper/Forms/StartForm.cs index a782517..899bbda 100644 --- a/Pf2eDataScrapper/Forms/StartForm.cs +++ b/Pf2eDataScrapper/Forms/StartForm.cs @@ -1,16 +1,16 @@ -using System.ComponentModel; -using System.Reflection; -using Pf2eDataScrapper.App; +using Pf2eDataScrapper.App; namespace Pf2eDataScrapper.Forms; internal partial class StartForm : Form { private ProcessorCollection ProcessorCollection { get; } + public ApplicationSettings Settings { get; } - public StartForm(ProcessorCollection processorCollection) + public StartForm(ProcessorCollection processorCollection, ApplicationSettings settings) { ProcessorCollection = processorCollection; + Settings = settings; InitializeComponent(); } @@ -18,6 +18,8 @@ internal partial class StartForm : Form private void StartForm_Load(object sender, EventArgs e) { ProcessorCollectionControl.Collection = ProcessorCollection; + + SettingsPropertyGrid.SelectedObject = Settings; } /* @@ -76,12 +78,30 @@ internal partial class StartForm : Form private void StartButton_Click(object sender, EventArgs e) { - StartButtonPressed?.Invoke(); + ApplicationStateChange?.Invoke(this, ApplicationStage.ImportProcess); } - public delegate void StartButtonPressedDelegate(); + #endregion - public event StartButtonPressedDelegate? StartButtonPressed; + #region "Основное меню" + + + private void ExitToolStripMenuItem_Click(object sender, EventArgs e) + { + Close(); + } + + private void EntityViewWindowToolStripMenuItem_Click(object sender, EventArgs e) + { + ApplicationStateChange?.Invoke(this, ApplicationStage.Viewer); + } + + private void StartToolStripMenuItem_Click(object sender, EventArgs e) + { + ApplicationStateChange?.Invoke(this, ApplicationStage.ImportProcess); + } + + public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange; #endregion } diff --git a/Pf2eDataScrapper/Forms/StartForm.resx b/Pf2eDataScrapper/Forms/StartForm.resx index 8b2ff64..fc37e2c 100644 --- a/Pf2eDataScrapper/Forms/StartForm.resx +++ b/Pf2eDataScrapper/Forms/StartForm.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/Pf2eDataScrapper/Helpers/DatabaseHelper.cs b/Pf2eDataScrapper/Helpers/DatabaseHelper.cs index 85bebfa..bcf30e0 100644 --- a/Pf2eDataScrapper/Helpers/DatabaseHelper.cs +++ b/Pf2eDataScrapper/Helpers/DatabaseHelper.cs @@ -29,7 +29,7 @@ public class DatabaseHelper /// public int NextEntityId => ++_id; - private DatabaseHelper(Pf2eDbContext context, SlugHelper slugHelper) + public DatabaseHelper(Pf2eDbContext context, SlugHelper slugHelper) { Context = context; Slugger = slugHelper; diff --git a/Pf2eDataScrapper/Helpers/EntityView.cs b/Pf2eDataScrapper/Helpers/EntityView.cs new file mode 100644 index 0000000..cd440b2 --- /dev/null +++ b/Pf2eDataScrapper/Helpers/EntityView.cs @@ -0,0 +1,138 @@ +using Microsoft.EntityFrameworkCore; +using Pf2eModel; +using Pf2eModel.Model; +using Pf2eModel.Model.Base; +using Pf2eModel.Model.Enum; +using Pf2eModel.Model.Structure; + +namespace Pf2eViewer; + +/// +/// Класс, обрабатывающий запросы к сущностям и подключающий необходимые данные. +/// +public class EntityView +{ + /// + /// Корневая сущность. + /// + public Entity? Entity { get; } + + /// + /// Основная сущность. + /// + public IEntity? SubEntity { get; } + + /// + /// Конструктор класса, который получает сущность из базы данных и заполняет её данными. + /// + /// Контекст базы данных. + /// Тип запрашиваемой сущности. + /// Идентификатор запрашиваемой сущности. + public EntityView(Pf2eDbContext context, EntityType type, int id) + { + Getter? getter = _getters.FirstOrDefault(g => g.Type == type); + + Entity fullEntity = + 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"); + + IEntity? subEntity = + (getter != null) + ? getter.Get(id, context) + : (IEntity?)context.Find(EntityOfTypeAttribute.Resolve(fullEntity), id); + + Entity = fullEntity; + SubEntity = subEntity; + } + + /// + /// Словарь, содержащий методы получения сущностей из базы данных. + /// + private readonly ICollection _getters = + [ + new Getter( + EntityType.Ancestry, + context => context.Ancestries.Include(a => a.AncestryBoosts) + ), + new Getter( + EntityType.Archetype, + context => + context + .Archetypes.Include(a => a.DedicationFeat) + .ThenInclude(f => f.Entity) + .Include(a => a.Feats) + .ThenInclude(f => f.Entity) + ), + new Getter( + EntityType.Background, + context => + context + .Backgrounds.Include(b => b.BackgroundBoosts) + .Include(b => b.BackgroundSkills) + .ThenInclude(t => t.Training) + .Include(b => b.BackgroundItems) + ), + new Getter( + EntityType.Class, + context => + context + .Classes.Include(c => c.ClassKeyAbilities) + .Include(c => c.ClassTrainings) + .ThenInclude(t => t.Training) + .Include(c => c.ClassProgressions) + ), + new Getter( + EntityType.Feat, + context => + context.Feats.Include(f => f.Archetypes).ThenInclude(a => a.Entity).AsQueryable() + ), + new Getter( + EntityType.Spell, + context => context.Spells.Include(s => s.SpellAreas).Include(s => s.SpellDefenses) + ), + new Getter( + EntityType.Ritual, + context => + context + .Rituals.Include(r => r.Area) + .Include(r => r.Checks) + .ThenInclude(c => c.Training) + .AsQueryable() + ), + ]; + + /// + /// Абстрактный класс, который определяет метод получения сущности из базы данных. + /// + /// Связанный тип сущности. + private abstract class Getter(EntityType type) + { + public readonly EntityType Type = type; + + public abstract IEntity? Get(int id, Pf2eDbContext context); + } + + /// + /// Класс, определяющий метод получения сущности из базы данных. + /// + /// Тип данных основной сущности. + /// Связанный тип сущности. + /// Метод получения сущности из базы данных. + private class Getter(EntityType type, Func> method) + : Getter(type) + where T : IEntity + { + public override IEntity? Get(int id, Pf2eDbContext context) + { + return method.Invoke(context).FirstOrDefault(c => c.Id == id); + } + } +} diff --git a/Pf2eDataScrapper/Helpers/ILogger.cs b/Pf2eDataScrapper/Helpers/ILogger.cs new file mode 100644 index 0000000..adb1550 --- /dev/null +++ b/Pf2eDataScrapper/Helpers/ILogger.cs @@ -0,0 +1,22 @@ +namespace Pf2eDataScrapper.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, +} diff --git a/Pf2eDataScrapper/Helpers/ProcessProgress.cs b/Pf2eDataScrapper/Helpers/ProcessProgress.cs index ec0e608..168fbb9 100644 --- a/Pf2eDataScrapper/Helpers/ProcessProgress.cs +++ b/Pf2eDataScrapper/Helpers/ProcessProgress.cs @@ -2,7 +2,7 @@ namespace Pf2eDataScrapper.Helpers; -internal class ProcessProgress +internal class ProcessProgress : AbstractLogger { /// /// Прогресс обработки обработчиков . @@ -31,15 +31,19 @@ internal class ProcessProgress ProgressUpdate?.Invoke(this); } - public void Message(string message) + public override void Message(LogLevel level, string message) { - ProgressMessage?.Invoke(this, message); + ProgressMessage?.Invoke(this, level, message); } public delegate void ProgressUpdateDelegate(ProcessProgress sender); public event ProgressUpdateDelegate? ProgressUpdate; - public delegate void ProgressMessageDelegate(ProcessProgress sender, string message); + public delegate void ProgressMessageDelegate( + ProcessProgress sender, + LogLevel level, + string message + ); public event ProgressMessageDelegate? ProgressMessage; /// diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index aaccccc..23d7dbc 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -4,6 +4,7 @@ using Pf2eDataScrapper.Forms; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; +using Pf2eViewer; using Slugify; namespace Pf2eDataScrapper; @@ -50,10 +51,26 @@ internal sealed class ImportApplication : ApplicationContext private ProgressForm? _progressForm; + private EntityViewForm? _entityViewForm; + #endregion #region "Жизненный цикл" + private Form? CurrentForm { get; set; } = null; + + public delegate void ApplicationStateChangeDelegate(object sender, ApplicationStage nextStage); + + public void ApplicationStateChange(object sender, ApplicationStage nextStage) + { + if (sender != CurrentForm) + { + return; + } + + CurrentStage = nextStage; + } + private void StartStage(ApplicationStage stage) { switch (stage) @@ -66,15 +83,14 @@ internal sealed class ImportApplication : ApplicationContext case ApplicationStage.InitialSettings: _startForm = ActivatorUtilities.CreateInstance(ServiceProvider); _startForm.StartPosition = FormStartPosition.CenterParent; - _startForm.StartButtonPressed += () => - { - CurrentStage = ApplicationStage.ProcessorsWorking; - }; + _startForm.ApplicationStateChange += ApplicationStateChange; _startForm.FormClosing += HandleFormClosing; _startForm.Show(); + + CurrentForm = _startForm; break; - case ApplicationStage.ProcessorsWorking: + case ApplicationStage.ImportProcess: ProcessorExecutor executor = ActivatorUtilities.CreateInstance( ServiceProvider ); @@ -84,10 +100,21 @@ internal sealed class ImportApplication : ApplicationContext _progressForm.FormClosing += HandleFormClosing; _progressForm.Show(); + + CurrentForm = _progressForm; executor.Start(); break; case ApplicationStage.Viewer: + _entityViewForm = ActivatorUtilities.CreateInstance( + ServiceProvider + ); + _entityViewForm.StartPosition = FormStartPosition.CenterParent; + _entityViewForm.ApplicationStateChange += ApplicationStateChange; + _entityViewForm.FormClosing += HandleFormClosing; + + _entityViewForm.Show(); + CurrentForm = _entityViewForm; break; default: @@ -99,6 +126,7 @@ internal sealed class ImportApplication : ApplicationContext private void CloseStage(ApplicationStage stage) { + CurrentForm = null; switch (stage) { case ApplicationStage.BootUp: @@ -111,10 +139,13 @@ internal sealed class ImportApplication : ApplicationContext _startForm = null; break; - case ApplicationStage.ProcessorsWorking: + case ApplicationStage.ImportProcess: break; case ApplicationStage.Viewer: + _entityViewForm!.Hide(); + _entityViewForm!.Close(); + _entityViewForm = null; break; default: @@ -126,16 +157,13 @@ internal sealed class ImportApplication : ApplicationContext private void HandleFormClosing(object? sender, FormClosingEventArgs e) { - switch (sender) + if (sender != CurrentForm) { - case StartForm: - e.Cancel = CurrentStage != ApplicationStage.InitialSettings; - if (!e.Cancel) - { - ExitThread(); - } - return; + e.Cancel = true; + return; } + + ExitThread(); } #endregion @@ -177,6 +205,7 @@ internal enum ApplicationStage { BootUp, InitialSettings, - ProcessorsWorking, + ImportProcess, Viewer, + Rules, } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs index 84ea587..e6b7095 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs @@ -33,6 +33,8 @@ internal class AonprdImporter : AbstractImporter services.AddSingleton(); services.AddSingleton(); services.AddTransient(); + + AonprdProcessorInputExtension.RegisterServices(services); } #endregion diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs index 9e1a631..7569127 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs @@ -43,11 +43,11 @@ internal class AonprdRepository(AonprdImporter importer) : AbstractImporterRepos if (CheckRevision()) { progress.Local.Complete(); - progress.Message($"Данные Aonprd актуальны, текущая ревизия {Revision}."); + progress.Information($"Данные Aonprd актуальны, текущая ревизия {Revision}."); return; } - progress.Message( + progress.Information( $"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..." ); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs index 3980c48..24adc29 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Concurrent; +using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; @@ -9,33 +10,10 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo { public override AonprdRepository Repository { get; } = repository; - /// - /// Загрузка данных компендиума. - /// - public void Load() - { - using StreamReader indexFile = new( - Path.Combine(Repository.RepositoryDirPath, "index.json") - ); - JObject indexJson = JObject.Parse(indexFile.ReadToEnd()); - - foreach ((string file, JToken? value) in indexJson) - { - JArray items = (JArray)value!; - - foreach (JToken item in items) - { - AonprdCompendiumItem compendiumItem = new(item.ToString(), file); - - Items.Add(compendiumItem); - } - } - } - /// /// Коллекция всех записей компендиума. /// - public List Items { get; } = []; + public ConcurrentBag Items { get; } = []; /// /// Возвращает количество элементов в компендиуме. @@ -50,20 +28,6 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo /// Количество элементов. public override int Count(string type) => Items.Count(i => i.Type == type); - /// - /// Возвращает список всех файлов. - /// - /// Список искомых файлов. - private IEnumerable Files() => Items.Select(t => t.File).Distinct(); - - /// - /// Возвращает список файлов, в которых есть сущности указанного типа данных. - /// - /// Тип данных. - /// Список искомых файлов. - private IEnumerable Files(string type) => - Items.Where(t => t.Type == type).Select(t => t.File).Distinct(); - /// /// Получение итератора сущностей. Если указан тип - то только указанного типа. /// @@ -71,7 +35,11 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo /// Итератор сущностей. public override IEnumerable> Iterate(string? type = null) { - foreach (string file in type is null ? Files() : Files(type)) + IEnumerable files = type is null + ? Items.Select(t => t.File).Distinct() + : Items.Where(t => t.Type == type).Select(t => t.File).Distinct(); + + foreach (string file in files) { Dictionary fileReferences = Items .Where(i => i.File == file) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs index f065e5b..9fe0ceb 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs @@ -16,10 +16,5 @@ internal class AonprdBuilderGlobals /// /// Конструктор класса . /// - private AonprdBuilderGlobals() { } - - /// - /// Экземпляр класса . - /// - public static readonly AonprdBuilderGlobals Instance = new(); + public AonprdBuilderGlobals() { } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdEnumResolver.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdEnumResolver.cs deleted file mode 100644 index 1f43999..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdEnumResolver.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Pf2eModel.Model.Enum; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; - -/// -/// Класс, помогающий разрешить строковые значения с Архивов Нефиса в перечисления. -/// -public static class AonprdEnumResolver -{ - /// - /// Коллекция классов, разрешающих строковые значения в перечисления. - /// - private static readonly ICollection Solvers = - [ - new Solver( - 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 }, - } - ), - new Solver( - new() - { - { "Low-Light VisionEnum", VisionEnum.LowLightVision }, - { "Darkvision", VisionEnum.DarkVision }, - } - ), - new Solver( - new() - { - { "Consitution", AttributeValues.Constitution }, - { "Other", AttributeValues.Free }, - { "other", AttributeValues.Free }, - } - ), - new Solver( - 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 }, - { "Nethys Note: No cast time was printed", ActionEnum.Unknown }, - } - ), - ]; - - /// - /// Разрешить строковое значение с Архивов Нефиса в перечисление. - /// - /// Тип искомого перечисления. - /// Обрабатываемое строковоее значение. - /// Значение искомого перечисления. - /// - /// Исключение, если для указанного типа перечисления не указан класс, разрешающий строковые значения. - /// - /// - /// Исключение, если указанное значение не получилось преобразовать искомое значение. - /// - public static T Resolve(string value) - where T : Enum - { - Type type = typeof(T); - - Solver? solver = Solvers.FirstOrDefault(s => s.Type == type); - - if (solver is null || solver.GetType() != typeof(Solver)) - { - throw new EnumResolverEnumUnknownException(type); - } - - Solver tSolver = (Solver)solver; - - return tSolver.Data.TryGetValue(value, out T? data) - ? data - : throw new EnumResolverValueUnknownException(type, value); - } - - /// - /// Класс, разрешающий строковые значения в перечисления. - /// - private abstract class Solver - { - public abstract Type Type { get; } - } - - /// - /// Абстрактный общий класс, разрешающий строковые значения в перечисления. - /// - private class Solver(Dictionary data) : Solver - where T : Enum - { - public override Type Type { get; } = typeof(T); - public Dictionary Data { get; } = data; - } - - /// - /// Исключение, указывающее, что для указанного типа перечисления не указан класс, разрешающий строковые значения. - /// - /// Тип искомого перечисления. - public class EnumResolverEnumUnknownException(Type t) - : Exception($"Can't resolve type '{t.FullName}': Solver not defined"); - - /// - /// Исключение, указывающее, что указанное значение не получилось преобразовать искомое значение. - /// - /// Тип искомого перечисления. - /// Искомое значение. - public class EnumResolverValueUnknownException(Type t, string value) - : Exception($"Can't resolve type '{t.FullName}': Value '{value}' is not matched"); -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs index ed5f530..b29847a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs @@ -1,25 +1,15 @@ -using Pf2eDataScrapper.Processors; +using Microsoft.Extensions.DependencyInjection; +using Pf2eDataScrapper.Processors; +using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; internal static class AonprdProcessorInputExtension { - public static T Enum(this ProcessorInput input, string value) - where T : Enum - { - try - { - return AonprdEnumResolver.Resolve(value); - } - catch - { - return input.Enum(value); - } - } - /// /// Обработать строку, содержащую информацию о количестве действий. /// + /// /// Входная строка с данными. /// Структура, хранящая информацию о количетсве действий. public static ActionStruct ResolveActionString(this ProcessorInput _, string input) @@ -129,4 +119,78 @@ internal static class AonprdProcessorInputExtension /// public bool TwoRounds { get; set; } } + + public class SourceCategorySolver : EnumSolver + { + public override Dictionary 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 + { + public override Dictionary Data { get; } = + new() + { + { "Low-Light VisionEnum", VisionEnum.LowLightVision }, + { "Darkvision", VisionEnum.DarkVision }, + }; + } + + public class AttributeValuesSolver : EnumSolver + { + public override Dictionary Data { get; } = + new() + { + { "Consitution", AttributeValues.Constitution }, + { "Other", AttributeValues.Free }, + { "other", AttributeValues.Free }, + }; + } + + public class ActionEnumSolver : EnumSolver + { + public override Dictionary 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 }, + { "Nethys Note: No cast time was printed", ActionEnum.Unknown }, + }; + } + + public static void RegisterServices(IServiceCollection serviceCollection) + { + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs index 7682c74..12e25b0 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs @@ -11,7 +11,7 @@ internal partial class StringHelper /// /// Конструктор класса /// - private StringHelper() { } + public StringHelper() { } /// /// Разбиение строки по нестабильным строковым разделителям (например, ", ", " or " и т.д.). @@ -67,11 +67,6 @@ internal partial class StringHelper return match.Captures.Count > 0 ? match.Captures[0].Value : ""; } - /// - /// Экземпляр класса . - /// - public static StringHelper Instance { get; } = new(); - /// /// Регулярное выражение для извлечения источника из строки с Markdown-ссылкой. /// diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index 442eec0..5bfbe31 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -1,4 +1,6 @@ -using System.Security.Cryptography; +using System.Collections.Concurrent; +using System.Linq; +using System.Security.Cryptography; using System.Text; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; @@ -62,12 +64,7 @@ internal abstract class AbstractEntityProcessor( /// public override void Process() { - if (Input.GetOptional("remaster_id") is not null) - { - LegacyEntityHandler handler = new(this); - LegacyHandlers.Add(handler); - throw new LegacyItemException(handler.LegacyName); - } + ProcessRemaster(); AddEntity(); AddSource(); @@ -199,7 +196,8 @@ internal abstract class AbstractEntityProcessor( { if (!traits.Any(t => t.Entity.Name.Contains(name))) { - throw new Exception($"Trait '{name}' is not found!!!"); + //TODO + //throw new Exception($"Trait '{name}' is not found!!!"); } } @@ -225,6 +223,11 @@ internal abstract class AbstractEntityProcessor( return; } + if (legacyIdArray.Count > 1) + { + throw new("Несколько отметок ремастер-сущности на устаревшие!"); + } + string[] legacyIds = legacyIdArray.ToArray().Select(t => t.ToString()).ToArray(); if (legacyIds.Length == 0) @@ -250,6 +253,40 @@ internal abstract class AbstractEntityProcessor( } } + protected virtual void ProcessRemaster() + { + JArray? remasterIdArray = Input.GetOptional("remaster_id"); + + if (remasterIdArray is null) + { + return; + } + + if (remasterIdArray.Count > 1) + { + throw new("Несколько отметок ремастер-сущности на устаревшие!"); + } + + string[] remasterIds = remasterIdArray.ToArray().Select(t => t.ToString()).ToArray(); + + foreach (string remasterId in remasterIds) + { + LegacyEntityHandler? handler = null; + //LegacyHandlers.FirstOrDefault(h => + // h.RemasterEntities.Contains(remasterId) + //); + + if (handler is null) + { + handler = new(this); + LegacyHandlers.Add(handler); + } + else { } + } + + throw new LegacyItemException(Input.GetRequired("name")); + } + /// /// Получение списка источников сущности. /// @@ -322,20 +359,5 @@ internal abstract class AbstractEntityProcessor( return isLowercase ? hash.ToLower() : hash; } - protected static readonly List LegacyHandlers = []; + protected static readonly ConcurrentBag LegacyHandlers = []; } - -/// -/// Исключение, показывающее, что обработка сущности пропускается как устаревшая. -/// -/// Название сущности. -public class LegacyItemException(string name) - : Exception($"Item '{name}' is linked with newer version, skipping.") { } - -/// -/// Исключение, показывающее, что обработка сущности пропускается. -/// -/// Название сущности. -/// Причина пропуска. -public class SkipItemException(string name, string reason) - : Exception($"Item '{name}' is skipped, reason: {reason}.") { } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs index c5f99cf..7b8f8ee 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs @@ -1,4 +1,5 @@ using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Base.Targets; @@ -11,9 +12,11 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; Group = ProcessorGroupEnum.AonprdPreparation )] [ProcessorDependencies(["aonprd:repository"])] -internal class ExternalProcessor(Pf2eDbContext context) : AbstractProcessor +internal class ExternalProcessor(Pf2eDbContext context, AonprdBuilderGlobals globals) + : AbstractProcessor { public Pf2eDbContext Context { get; } = context; + public AonprdBuilderGlobals Globals { get; } = globals; public ExternalTarget AonprdTarget = null!; @@ -31,7 +34,6 @@ internal class ExternalProcessor(Pf2eDbContext context) : AbstractProcessor { Context.ExternalTargets.Add(AonprdTarget); - // TODO - // Globals.AonprdTarget = AonprdTarget; + Globals.AonprdTarget = AonprdTarget; } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs index 09815a2..9c84231 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs @@ -13,6 +13,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; Description = "Навыки", Group = ProcessorGroupEnum.AonprdPreparation )] +[ProcessorDependencies(["aonprd:compendium"])] internal class SkillProcessor(ProcessorInput input, Pf2eDbContext context) : AbstractInputProcessor(input) { diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs index c369af6..c20c2c0 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs @@ -12,7 +12,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; Description = "Источники", Group = ProcessorGroupEnum.AonprdPreparation )] -[ProcessorDependencies(["aonprd:repository"])] +[ProcessorDependencies(["aonprd:compendium"])] internal class SourceProcessor(ProcessorInput input, Pf2eDbContext context) : AbstractInputProcessor(input) { diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/CompendiumLoadProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/CompendiumLoadProcessor.cs new file mode 100644 index 0000000..09e1885 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/CompendiumLoadProcessor.cs @@ -0,0 +1,54 @@ +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; +using Pf2eDataScrapper.Processors; + +namespace Pf2eDataScrapper.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() { } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/RepositoryUpdateProcessor.cs similarity index 59% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/RepositoryUpdateProcessor.cs index 9e7d9c1..ce5d764 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/RepositoryUpdateProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/RepositoryUpdateProcessor.cs @@ -1,23 +1,18 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Processors; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation; [Processor( "aonprd:repository", Description = "Обновление репозитория", Group = ProcessorGroupEnum.AonprdPreparation )] -internal class RepositoryUpdateProcessor( - AonprdRepository repository, - AonprdCompendium compendium, - ProcessProgress progress -) : IProcessor +internal class RepositoryUpdateProcessor(AonprdRepository repository, ProcessProgress progress) + : IProcessor { public AonprdRepository Repository { get; } = repository; - public AonprdCompendium Compendium { get; } = compendium; public ProcessProgress Progress { get; } = progress; public void Process() @@ -25,8 +20,5 @@ internal class RepositoryUpdateProcessor( Repository.Update(Progress); } - public void Save() - { - Compendium.Load(); - } + public void Save() { } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs similarity index 96% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs index 82738ae..33ef7d2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs @@ -3,12 +3,13 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.SchemaBuilder; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation; [Processor( "aonprd:schema", Description = "Построение схемы данных", - Group = ProcessorGroupEnum.AonprdPreparation + Group = ProcessorGroupEnum.AonprdPreparation, + Active = false )] [ProcessorDependencies(["aonprd:repository"])] internal class SchemaBuildProcessor( diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs index 718a5f8..39a03c1 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -15,7 +15,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; Description = "Признаки", Group = ProcessorGroupEnum.AonprdEntities )] -[ProcessorDependencies(["aonprd:external"])] +[ProcessorDependencies(["aonprd:external", "aonprd:source"])] internal class TraitProcessor( ProcessorInput input, Pf2eDbContext context, diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs index bf99a45..6341c0c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Concurrent; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn; @@ -25,7 +26,7 @@ internal sealed class FoundryEnCompendium( /// /// Коллекция всех записей компендиума. /// - public List Items { get; } = []; + public ConcurrentBag Items { get; } = []; /// /// Возвращает количество элементов в компендиуме. diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs index f5e860c..b07acf0 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs @@ -11,7 +11,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entiti "foundry-en:action", InnerType = "action", Description = "Действия", - Group = ProcessorGroupEnum.FoundryEnEntities + Group = ProcessorGroupEnum.FoundryEnEntities, + Active = false )] [ProcessorDependencies(["foundry-en:compendium"])] internal class ActionProcessor( diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs index 89a3157..c967d8d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs @@ -12,7 +12,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entiti "foundry-en:condition", InnerType = "Item.condition", Description = "Состояния", - Group = ProcessorGroupEnum.FoundryEnEntities + Group = ProcessorGroupEnum.FoundryEnEntities, + Active = false )] [ProcessorDependencies(["foundry-en:compendium"])] internal class ConditionProcessor( diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs index a53b9a0..da6b757 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs @@ -8,7 +8,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar [Processor( "foundry-en:compendium-folders", Description = "Загрузка каталогов компендиума", - Group = ProcessorGroupEnum.FoundryEnPreparation + Group = ProcessorGroupEnum.FoundryEnPreparation, + Active = false )] [ProcessorDependencies(["foundry-en:repository"])] internal class CompendiumFolderProcessor( diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs index 77e661f..7fd6260 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs @@ -9,7 +9,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar [Processor( "foundry-en:compendium", Description = "Загрузка компендиума", - Group = ProcessorGroupEnum.FoundryEnPreparation + Group = ProcessorGroupEnum.FoundryEnPreparation, + Active = false )] [ProcessorDependencies(["foundry-en:compendium-folders"])] internal class CompendiumItemsProcessor( diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs index 1fcdbfa..2d25747 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/RepositoryUpdateProcessor.cs @@ -7,7 +7,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar [Processor( "foundry-en:repository", Description = "Обновление репозитория", - Group = ProcessorGroupEnum.FoundryEnPreparation + Group = ProcessorGroupEnum.FoundryEnPreparation, + Active = false )] internal class RepositoryUpdateProcessor(FoundryEnRepository repository, ProcessProgress progress) : IProcessor diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs index 5f55eb5..905ea16 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs @@ -7,7 +7,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar [Processor( "foundry-en:schema", Description = "Построение схемы данных", - Group = ProcessorGroupEnum.FoundryEnPreparation + Group = ProcessorGroupEnum.FoundryEnPreparation, + Active = false )] [ProcessorDependencies(["foundry-en:compendium"])] internal class SchemaBuildProcessor( diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj index bb9ae8f..a299921 100644 --- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj +++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj @@ -6,14 +6,19 @@ true enable + + + + + - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -34,6 +39,17 @@ - + + Component + + + UserControl + + + UserControl + + + Form + diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index 1de918c..831d350 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -4,21 +4,27 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Importers.Implementations.FoundryEn; +using Pf2eModel; namespace Pf2eDataScrapper.Processors; internal sealed class ProcessorExecutor( IServiceProvider serviceProvider, ProcessorCollection collection, - ProcessProgress progress + ProcessProgress progress, + Pf2eDbContext context, + AonprdCompendium aonprdCompendium, + FoundryEnCompendium foundryEnCompendium ) { public IServiceProvider ServiceProvider { get; } = serviceProvider; public ProcessorCollection Collection { get; } = collection; public ProcessProgress Progress { get; } = progress; + public Pf2eDbContext Context { get; } = context; + public AonprdCompendium AonprdCompendium { get; } = aonprdCompendium; + public FoundryEnCompendium FoundryEnCompendium { get; } = foundryEnCompendium; public void Start() { @@ -36,6 +42,7 @@ internal sealed class ProcessorExecutor( ProcessorCollectionItem item = Collection.Items[_currentIndex]; Progress.Processors.Start(item.Description); + Progress.Progress(Progress.Processors.ToString()); BackgroundWorker worker = new(); @@ -43,9 +50,17 @@ internal sealed class ProcessorExecutor( { foreach (IProcessor processor in IterateProcessors(item)) { - processor.Process(); - processor.Save(); + try + { + processor.Process(); + processor.Save(); + } + catch (SkipItemException exception) + { + Progress.Information(exception.Message); + } } + Context.SaveChanges(); }; worker.RunWorkerCompleted += Continue; @@ -86,19 +101,16 @@ internal sealed class ProcessorExecutor( ); } - Type importerType = item.Group switch + AbstractCompendium compendium = item.Group switch { ProcessorGroupEnum.AonprdPreparation or ProcessorGroupEnum.AonprdEntities - or ProcessorGroupEnum.AonprdSubclasses => typeof(AonprdCompendium), + or ProcessorGroupEnum.AonprdSubclasses => AonprdCompendium, ProcessorGroupEnum.FoundryEnPreparation or ProcessorGroupEnum.FoundryEnEntities => - typeof(FoundryEnCompendium), + FoundryEnCompendium, _ => throw new($"Не могу выбрать импортёр для группы {item.Group}"), }; - AonprdCompendium compendium = (AonprdCompendium) - ActivatorUtilities.CreateInstance(ServiceProvider, importerType); - Progress.Local.Reset(compendium.Count(item.InnerType!)); foreach ( @@ -109,12 +121,14 @@ internal sealed class ProcessorExecutor( { Progress.Local.Start(compendiumItem.Id); + ProcessorInput processorInput = ActivatorUtilities.CreateInstance( + ServiceProvider, + compendiumItem.Id, + json + ); + yield return (IProcessor) - ActivatorUtilities.CreateInstance( - ServiceProvider, - item.Type, - new ProcessorInput(compendiumItem.Id, json) - ); + ActivatorUtilities.CreateInstance(ServiceProvider, item.Type, processorInput); Progress.Local.End(); } @@ -122,3 +136,17 @@ internal sealed class ProcessorExecutor( Progress.Local.Complete(); } } + +/// +/// Исключение, показывающее, что обработка сущности пропускается как устаревшая. +/// +/// Название сущности. +public class LegacyItemException(string name) : SkipItemException(name, "Устаревший элемент") { } + +/// +/// Исключение, показывающее, что обработка сущности пропускается. +/// +/// Название сущности. +/// Причина пропуска. +public class SkipItemException(string name, string reason) + : Exception($"Пропускаю элемент '{name}', указана причина: {reason}.") { } diff --git a/Pf2eDataScrapper/Processors/ProcessorInput.cs b/Pf2eDataScrapper/Processors/ProcessorInput.cs index c6acd01..d1351da 100644 --- a/Pf2eDataScrapper/Processors/ProcessorInput.cs +++ b/Pf2eDataScrapper/Processors/ProcessorInput.cs @@ -9,7 +9,7 @@ namespace Pf2eDataScrapper.Processors; /// /// Название объекта, ассоциированного с вводом. /// JSON-объект, который будет использоваться для доступа к данным. -internal class ProcessorInput(string name, JObject input) +internal class ProcessorInput(string name, JObject input, IEnumerable enumSolvers) { /// /// Название объекта, ассоциированного с вводом. @@ -21,6 +21,11 @@ internal class ProcessorInput(string name, JObject input) /// public JObject Input { get; } = input; + /// + /// Интерфейсы, разрешающие значения перечислений. + /// + public IEnumerable EnumSolvers { get; } = enumSolvers; + /// /// Значение из JSON по заданному пути, которое обязательно должно существовать. /// @@ -141,6 +146,20 @@ internal class ProcessorInput(string name, JObject input) public virtual T Enum(string value) where T : Enum { + IEnumerable> solvers = EnumSolvers + .Where(solver => solver.GetType().IsAssignableTo(typeof(EnumSolver))) + .Cast>(); + + foreach (EnumSolver solver in solvers) + { + T? result = solver.SolveEnum(value); + + if (result is not null) + { + return result; + } + } + foreach (System.Reflection.FieldInfo field in typeof(T).GetFields()) { List variants = [field.Name]; @@ -170,3 +189,16 @@ internal class ProcessorInput(string name, JObject input) public class InvalidEnumStringException(Type type, string value) : Exception($"Invalid value '{value}' for enum type {type.FullName}.") { } } + +internal interface IEnumSolver { } + +public abstract class EnumSolver : IEnumSolver + where T : Enum +{ + public abstract Dictionary Data { get; } + + public T? SolveEnum(string input) + { + return Data.GetValueOrDefault(input); + } +} diff --git a/Pf2eModel/Migrations/20250510202833_1.Designer.cs b/Pf2eModel/Migrations/20250514084949_1.Designer.cs similarity index 99% rename from Pf2eModel/Migrations/20250510202833_1.Designer.cs rename to Pf2eModel/Migrations/20250514084949_1.Designer.cs index 396cb60..66ebb3f 100644 --- a/Pf2eModel/Migrations/20250510202833_1.Designer.cs +++ b/Pf2eModel/Migrations/20250514084949_1.Designer.cs @@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum; namespace Pf2eModel.Migrations { [DbContext(typeof(Pf2eDbContext))] - [Migration("20250510202833_1")] + [Migration("20250514084949_1")] partial class _1 { /// diff --git a/Pf2eModel/Migrations/20250510202833_1.cs b/Pf2eModel/Migrations/20250514084949_1.cs similarity index 100% rename from Pf2eModel/Migrations/20250510202833_1.cs rename to Pf2eModel/Migrations/20250514084949_1.cs From 995029ce8d01ab216954cba9b79dc13b21def035 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Wed, 14 May 2025 15:44:32 +0400 Subject: [PATCH 10/33] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BE=D1=81?= =?UTF-8?q?=D0=BC=D1=8B=D1=81=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B0=D1=82?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=B1=D1=83=D1=82=D0=BE=D0=B2=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B1=D0=B5=D1=80=D0=B0=D1=8E=D1=89=D0=B8=D1=85=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B8=20=D1=83=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=88=D1=8B=D1=85=20=D1=8D=D0=BB=D0=B5=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/ProcessorAttribute.cs | 39 +++- Pf2eDataScrapper/App/ProcessorCollection.cs | 29 ++- Pf2eDataScrapper/App/ProcessorGroupEnum.cs | 6 + .../Processors/AbstractEntityProcessor.cs | 23 +- .../Processors/Common/LegacyProcessor.cs | 125 ++++++++++ .../Processors/Common/SkillProcessor.cs | 3 +- .../Processors/Common/SourceProcessor.cs | 3 +- .../Subclasses/ApparitionProcessor.cs | 3 +- .../Processors/Types/ActionProcessor.cs | 3 +- .../Processors/Types/AncestryProcessor.cs | 3 +- .../Processors/Types/ArchetypeProcessor.cs | 3 +- .../Processors/Types/BackgroundProcessor.cs | 3 +- .../Aonprd/Processors/Types/ClassProcessor.cs | 3 +- .../Aonprd/Processors/Types/FeatProcessor.cs | 3 +- .../Processors/Types/LanguageProcessor.cs | 3 +- .../Processors/Types/RitualProcessor.cs | 3 +- .../Processors/Types/Rules/RuleProcessor.cs | 6 +- .../Aonprd/Processors/Types/SpellProcessor.cs | 3 +- .../Aonprd/Processors/Types/TraitProcessor.cs | 5 +- .../Processors/Entities/ActionProcessor.cs | 3 +- .../Processors/Entities/ConditionProcessor.cs | 3 +- .../Processors/FoundryEnAbstractProcessor.cs | 1 + .../Processors/ProcessorExecutor.cs | 26 ++- Pf2eDataScrapper/Processors/ProcessorInput.cs | 16 +- ...signer.cs => 20250514102154_1.Designer.cs} | 214 +++++++++++++++++- ...0250514084949_1.cs => 20250514102154_1.cs} | 137 ++++++++++- .../Migrations/Pf2eDbContextModelSnapshot.cs | 212 +++++++++++++++++ Pf2eModel/Model/Base/Entity.cs | 6 + Pf2eModel/Model/Base/Legacy/LegacyEntity.cs | 42 ++++ .../Model/Base/Legacy/LegacyEntityExternal.cs | 58 +++++ .../Model/Base/Legacy/LegacyEntitySource.cs | 59 +++++ Pf2eModel/Model/Base/Source/Source.cs | 6 + .../Model/Base/Targets/EntityExternal.cs | 4 +- .../Model/Base/Targets/ExternalTarget.cs | 6 + Pf2eModel/Pf2eDbContext.cs | 7 + Pf2eModel/Pf2eDbContextModel.cs | 35 +++ 36 files changed, 1036 insertions(+), 68 deletions(-) create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs rename Pf2eModel/Migrations/{20250514084949_1.Designer.cs => 20250514102154_1.Designer.cs} (92%) rename Pf2eModel/Migrations/{20250514084949_1.cs => 20250514102154_1.cs} (91%) create mode 100644 Pf2eModel/Model/Base/Legacy/LegacyEntity.cs create mode 100644 Pf2eModel/Model/Base/Legacy/LegacyEntityExternal.cs create mode 100644 Pf2eModel/Model/Base/Legacy/LegacyEntitySource.cs diff --git a/Pf2eDataScrapper/App/ProcessorAttribute.cs b/Pf2eDataScrapper/App/ProcessorAttribute.cs index 0f3a04f..aa02c8d 100644 --- a/Pf2eDataScrapper/App/ProcessorAttribute.cs +++ b/Pf2eDataScrapper/App/ProcessorAttribute.cs @@ -22,15 +22,44 @@ public class ProcessorAttribute(string name) : Attribute /// public ProcessorGroupEnum Group { get; init; } = ProcessorGroupEnum.General; - /// - /// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика. - /// - public string? InnerType { get; init; } = null; - /// /// Процессор включён по умолчанию. /// public bool Active { get; init; } = true; + + /// + /// Разрешить выполнять процессов в параллельном режиме + /// + public bool Parallel { get; init; } = true; + + /// + /// Обработчик набора элементов + /// + public virtual bool Collection { get; } = false; +} + +/// +/// Аттрибут обработчика набор элементов. +/// +/// Название обработчика. +[AttributeUsage(AttributeTargets.Class)] +public class ProcessorCollectionAttribute(string name) : ProcessorAttribute(name) +{ + /// + /// Название типов, который обрабатывает этот оброботчик. + /// null - обработать все типы. + /// + public required string? InnerType { get; init; } + + /// + /// Тип используемого компендиума. + /// + public required ProcessorCompendiumEnum CompendiumType { get; init; } + + /// + /// Обработчик набора элементов. + /// + public override bool Collection { get; } = true; } /// diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index e2b7edf..bd7b61c 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -24,7 +24,7 @@ internal class ProcessorCollection /// /// /// - public ProcessorCollectionItem this[string name] => Items.First(item => item.Name == name); + public ProcessorCollectionItem this[string name] => AllItems.First(item => item.Name == name); /// /// Конструктор класса . @@ -65,7 +65,19 @@ internal class ProcessorCollection ProcessorDependenciesAttribute? dependenciesAttribute = type.GetCustomAttribute(); - bool isCollection = processorAttribute.InnerType is not null; + bool isCollection = processorAttribute.Collection; + + string? innerType = null; + ProcessorCompendiumEnum? compendium = null; + + if (processorAttribute.GetType() == typeof(ProcessorCollectionAttribute)) + { + ProcessorCollectionAttribute processorCollectionAttribute = + (ProcessorCollectionAttribute)processorAttribute; + + innerType = processorCollectionAttribute.InnerType; + compendium = processorCollectionAttribute.CompendiumType; + } return new() { @@ -74,8 +86,9 @@ internal class ProcessorCollection Description = processorAttribute.Description, Group = processorAttribute.Group, Dependencies = dependenciesAttribute?.Dependencies ?? [], - IsCollectionProcessor = isCollection, - InnerType = processorAttribute.InnerType, + IsCollection = isCollection, + InnerType = innerType, + Compendium = compendium, Active = processorAttribute.Active, }; } @@ -216,10 +229,16 @@ internal class ProcessorCollectionItem /// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов. /// Внутренний тип элементов указывается в . /// - public required bool IsCollectionProcessor { get; init; } + public required bool IsCollection { get; init; } /// /// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов. /// public string? InnerType { get; init; } + + /// + /// Название типов, который обрабатывает этот оброботчик. + /// null - обработать все типы. + /// + public ProcessorCompendiumEnum? Compendium { get; init; } } diff --git a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs index a0c36e5..fcf7fc6 100644 --- a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs +++ b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs @@ -24,6 +24,12 @@ public enum ProcessorGroupEnum FoundryEnEntities, } +public enum ProcessorCompendiumEnum +{ + Aonprd, + FoundryEn, +} + public static class ProcessorGroupEnumExtension { public static string GetDescription(this ProcessorGroupEnum group) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index 5bfbe31..19f47c3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -13,6 +13,7 @@ using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model; using Pf2eModel.Model.Base; +using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; @@ -160,8 +161,9 @@ internal abstract class AbstractEntityProcessor( { Entity = Entity, Target = Globals.AonprdTarget, + ExternalId = Input.GetRequired("id"), Suffix = Input.GetRequired("url"), - Md5 = Md5(Input.Input.ToString()), + Md5 = Input.Md5(), }; Context.EntityExternals.Add(external); @@ -223,11 +225,6 @@ internal abstract class AbstractEntityProcessor( return; } - if (legacyIdArray.Count > 1) - { - throw new("Несколько отметок ремастер-сущности на устаревшие!"); - } - string[] legacyIds = legacyIdArray.ToArray().Select(t => t.ToString()).ToArray(); if (legacyIds.Length == 0) @@ -345,19 +342,5 @@ internal abstract class AbstractEntityProcessor( return JsonConvert.SerializeObject(data, new JsonSerializerSettings()); } - /// - /// Создание контрольной суммы объекта. - /// - /// Строковое представление вычисляемого объекта. - /// Использовать шестнадцатеричные цифры в нижнем регистре. - /// Контрольная сумма объекта. - public static string Md5(string input, bool isLowercase = false) - { - using MD5 md5 = MD5.Create(); - byte[] byteHash = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); - string hash = BitConverter.ToString(byteHash).Replace("-", ""); - return isLowercase ? hash.ToLower() : hash; - } - protected static readonly ConcurrentBag LegacyHandlers = []; } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs new file mode 100644 index 0000000..f0c311d --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs @@ -0,0 +1,125 @@ +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using Pf2eDataScrapper.Processors; +using Pf2eModel; +using Pf2eModel.Model.Base.Legacy; +using Pf2eModel.Model.Base.Source; +using Pf2eModel.Model.Common.I18n; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; + +[ProcessorCollection( + "aonprd:legacy", + InnerType = null, + CompendiumType = ProcessorCompendiumEnum.Aonprd, + Description = "Создание ссылок на устаревшие сущности", + Group = ProcessorGroupEnum.AonprdPreparation +)] +[ProcessorDependencies(["aonprd:source", "aonprd:external"])] +internal class LegacyProcessor( + ProcessorInput input, + Pf2eDbContext context, + AonprdBuilderGlobals globals, + StringHelper stringHelper +) : AbstractInputProcessor(input) +{ + public Pf2eDbContext Context { get; } = context; + public AonprdBuilderGlobals Globals { get; } = globals; + public StringHelper StringHelper { get; } = stringHelper; + + private LegacyEntity LegacyEntity { get; set; } = null!; + + public override void Process() + { + JArray? remasterIdArray = Input.GetOptional("remaster_id"); + + if (Input.GetOptional("remaster_id") is null) + { + throw new SilentSkipItemException(); + } + + LegacyEntity = new() + { + Name = { [LocaleType.English] = Input.GetOptional("name", "sidebar-29") }, + }; + + AddSource(); + AddExternal(); + } + + /// + /// Генерация источников сущности. + /// + protected virtual void AddSource() + { + foreach (LegacyEntitySource entitySource in GetSources()) + { + LegacyEntity.Sources.Add(entitySource); + } + } + + /// + /// Получение списка источников сущности. + /// + /// + public List GetSources() + { + string[] sourceNames = Input + .GetRequired("source") + .Select(token => token.ToString()) + .ToArray(); + + if (sourceNames.Length == 0) + { + throw new( + $"Source for entity with id \"{LegacyEntity.Name[LocaleType.English]}\" " + + $"(Aonprd id \"{Input.GetRequired("id")}\") is not found" + ); + } + + List entitySources = []; + + foreach (string sourceName in sourceNames) + { + Source source = + Context.Sources.FirstOrDefault(s => s.Name == sourceName) + ?? throw new($"Source \"{sourceName}\" did not found"); + + LegacyEntitySource entitySource = new() + { + LegacyEntity = LegacyEntity, + Source = source, + Suffix = StringHelper.ResolveSource(Input.GetRequired("source_markdown")), + IsRemaster = Input.GetOptional("legacy_id") is not null, + }; + + entitySources.Add(entitySource); + } + + return entitySources; + } + + /// + /// Генерация внешних ссылок сущности. + /// + protected virtual void AddExternal() + { + LegacyEntityExternal external = new() + { + LegacyEntity = LegacyEntity, + Target = Globals.AonprdTarget, + ExternalId = Input.GetRequired("id"), + Suffix = Input.GetRequired("url"), + Md5 = Input.Md5(), + }; + + Context.LegacyEntityExternals.Add(external); + } + + public override void Save() + { + Context.LegacyEntities.Add(LegacyEntity); + } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs index 9c84231..03c9666 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs @@ -7,8 +7,9 @@ using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; -[Processor( +[ProcessorCollection( "aonprd:skill", + CompendiumType = ProcessorCompendiumEnum.Aonprd, InnerType = "skill", Description = "Навыки", Group = ProcessorGroupEnum.AonprdPreparation diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs index c20c2c0..570cf42 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs @@ -6,9 +6,10 @@ using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; -[Processor( +[ProcessorCollection( "aonprd:source", InnerType = "source", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Источники", Group = ProcessorGroupEnum.AonprdPreparation )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs index ad7985a..47d2cc9 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs @@ -12,9 +12,10 @@ using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses; -[Processor( +[ProcessorCollection( "aonprd:apparition", InnerType = "apparition", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Apparitions", Group = ProcessorGroupEnum.AonprdSubclasses )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs index 56174e3..2cbc2fc 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs @@ -8,9 +8,10 @@ using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:action", InnerType = "action", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Действия", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs index a3aa582..3631619 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs @@ -10,9 +10,10 @@ using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:ancestry", InnerType = "ancestry", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Родословные", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs index a97727a..e8d9b62 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs @@ -9,9 +9,10 @@ using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:archetype", InnerType = "archetype", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Архетипы", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs index 1b9b7cf..2d857d6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs @@ -11,9 +11,10 @@ using Pf2eModel.Model.Structure.BackgroundRelation; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:background", InnerType = "background", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Предыстории", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index 2d3a05f..869bcd2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -15,9 +15,10 @@ using static System.Text.RegularExpressions.Regex; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:class", InnerType = "class", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Классы", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs index 05a517f..e1e01da 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs @@ -10,9 +10,10 @@ using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:feat", InnerType = "feat", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Черты", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs index 7d2960a..9ded0f6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs @@ -9,9 +9,10 @@ using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:language", InnerType = "language", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Языки", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs index 5d96c0e..1a73ccd 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs @@ -13,9 +13,10 @@ using Pf2eModel.Model.Structure.RitualRelation; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:ritual", InnerType = "ritual", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Ритуалы", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs index 5d6737e..6534154 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs @@ -5,6 +5,7 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; +using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; @@ -13,10 +14,11 @@ using Rule = Pf2eModel.Model.Structure.Rule; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules; -[Processor( +[ProcessorCollection( "aonprd:rules", InnerType = "rules", - Description = "Правила", + CompendiumType = ProcessorCompendiumEnum.Aonprd, + Description = "Создание цели внешних ссылок", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs index d69b536..9a4aaf9 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs @@ -12,9 +12,10 @@ using Pf2eModel.Model.Structure.SpellRelation; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:spell", InnerType = "spell", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Заклинания", Group = ProcessorGroupEnum.AonprdEntities )] diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs index 39a03c1..80e55db 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -9,13 +9,14 @@ using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; -[Processor( +[ProcessorCollection( "aonprd:trait", InnerType = "trait", + CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Признаки", Group = ProcessorGroupEnum.AonprdEntities )] -[ProcessorDependencies(["aonprd:external", "aonprd:source"])] +[ProcessorDependencies(["aonprd:legacy"])] internal class TraitProcessor( ProcessorInput input, Pf2eDbContext context, diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs index b07acf0..93777b2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ActionProcessor.cs @@ -7,9 +7,10 @@ using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities; -[Processor( +[ProcessorCollection( "foundry-en:action", InnerType = "action", + CompendiumType = ProcessorCompendiumEnum.FoundryEn, Description = "Действия", Group = ProcessorGroupEnum.FoundryEnEntities, Active = false diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs index c967d8d..1c34cbd 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Entities/ConditionProcessor.cs @@ -8,9 +8,10 @@ using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities; -[Processor( +[ProcessorCollection( "foundry-en:condition", InnerType = "Item.condition", + CompendiumType = ProcessorCompendiumEnum.FoundryEn, Description = "Состояния", Group = ProcessorGroupEnum.FoundryEnEntities, Active = false diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs index 829fc9d..5874ed4 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs @@ -7,6 +7,7 @@ using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model; using Pf2eModel.Model.Base; +using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index 831d350..9d74fa0 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -59,6 +59,10 @@ internal sealed class ProcessorExecutor( { Progress.Information(exception.Message); } + catch (SilentSkipItemException) + { + // + } } Context.SaveChanges(); }; @@ -85,7 +89,7 @@ internal sealed class ProcessorExecutor( private IEnumerable IterateProcessors(ProcessorCollectionItem item) { - if (!item.IsCollectionProcessor) + if (!item.IsCollection) { Progress.Local.Reset(1); Progress.Local.Start(item.Description); @@ -101,21 +105,20 @@ internal sealed class ProcessorExecutor( ); } - AbstractCompendium compendium = item.Group switch + AbstractCompendium compendium = item.Compendium switch { - ProcessorGroupEnum.AonprdPreparation - or ProcessorGroupEnum.AonprdEntities - or ProcessorGroupEnum.AonprdSubclasses => AonprdCompendium, - ProcessorGroupEnum.FoundryEnPreparation or ProcessorGroupEnum.FoundryEnEntities => - FoundryEnCompendium, + ProcessorCompendiumEnum.Aonprd => AonprdCompendium, + ProcessorCompendiumEnum.FoundryEn => FoundryEnCompendium, _ => throw new($"Не могу выбрать импортёр для группы {item.Group}"), }; - Progress.Local.Reset(compendium.Count(item.InnerType!)); + Progress.Local.Reset( + item.InnerType is null ? compendium.Count() : compendium.Count(item.InnerType) + ); foreach ( (AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate( - item.InnerType! + item.InnerType ) ) { @@ -150,3 +153,8 @@ public class LegacyItemException(string name) : SkipItemException(name, "Уст /// Причина пропуска. public class SkipItemException(string name, string reason) : Exception($"Пропускаю элемент '{name}', указана причина: {reason}.") { } + +/// +/// Исключение, показывающее, что обработка сущности пропускается без вывода сообщения. +/// +public class SilentSkipItemException : Exception { } diff --git a/Pf2eDataScrapper/Processors/ProcessorInput.cs b/Pf2eDataScrapper/Processors/ProcessorInput.cs index d1351da..e0663b1 100644 --- a/Pf2eDataScrapper/Processors/ProcessorInput.cs +++ b/Pf2eDataScrapper/Processors/ProcessorInput.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using System.Security.Cryptography; +using System.Text; +using Newtonsoft.Json.Linq; using NpgsqlTypes; using Pf2eModel.Model.Common.I18n; @@ -181,6 +183,18 @@ internal class ProcessorInput(string name, JObject input, IEnumerable + /// Создание контрольной суммы объекта. + /// + /// Использовать шестнадцатеричные цифры в нижнем регистре. + /// Контрольная сумма объекта. + public string Md5(bool isLowercase = false) + { + byte[] byteHash = MD5.HashData(Encoding.UTF8.GetBytes(Input.ToString())); + string hash = Convert.ToHexString(byteHash).Replace("-", ""); + return isLowercase ? hash.ToLower() : hash; + } + /// /// Исключение при неверном значении для перечисленния. /// diff --git a/Pf2eModel/Migrations/20250514084949_1.Designer.cs b/Pf2eModel/Migrations/20250514102154_1.Designer.cs similarity index 92% rename from Pf2eModel/Migrations/20250514084949_1.Designer.cs rename to Pf2eModel/Migrations/20250514102154_1.Designer.cs index 66ebb3f..fc193fc 100644 --- a/Pf2eModel/Migrations/20250514084949_1.Designer.cs +++ b/Pf2eModel/Migrations/20250514102154_1.Designer.cs @@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum; namespace Pf2eModel.Migrations { [DbContext(typeof(Pf2eDbContext))] - [Migration("20250514084949_1")] + [Migration("20250514102154_1")] partial class _1 { /// @@ -86,6 +86,25 @@ namespace Pf2eModel.Migrations b.ToTable("entity_keyword", (string)null); }); + modelBuilder.Entity("EntityLegacyEntity", b => + { + b.Property("LegacyEntitiesId") + .HasColumnType("integer") + .HasColumnName("legacy_entities_id"); + + b.Property("RemasterEntitiesId") + .HasColumnType("integer") + .HasColumnName("remaster_entities_id"); + + b.HasKey("LegacyEntitiesId", "RemasterEntitiesId") + .HasName("pk_entity_legacy_entity"); + + b.HasIndex("RemasterEntitiesId") + .HasDatabaseName("ix_entity_legacy_entity_remaster_entities_id"); + + b.ToTable("entity_legacy_entity", (string)null); + }); + modelBuilder.Entity("EntityTrait", b => { b.Property("EntitiesId") @@ -240,6 +259,123 @@ namespace Pf2eModel.Migrations b.ToTable("keyword_aliases", (string)null); }); + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasColumnOrder(1); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("name") + .HasColumnOrder(2) + .HasComment("Отображаемое имя"); + + b.HasKey("Id") + .HasName("pk_legacy_entities"); + + b.ToTable("legacy_entities", (string)null); + }); + + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b => + { + b.Property("LegacyEntityId") + .HasColumnType("integer") + .HasColumnName("legacy_entity_id") + .HasColumnOrder(1) + .HasComment("Идентификатор сущности"); + + b.Property("TargetId") + .HasColumnType("integer") + .HasColumnName("target_id") + .HasColumnOrder(2) + .HasComment("Идентификатор цели"); + + b.Property("ExternalId") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("external_id") + .HasColumnOrder(3); + + b.Property("Md5") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("md5") + .HasColumnOrder(5) + .HasComment("Контрольная сумма объекта"); + + b.Property("Suffix") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("suffix") + .HasColumnOrder(4) + .HasComment("Часть URL до объекта. Для источников Foundry - полный UUID объекта"); + + b.HasKey("LegacyEntityId", "TargetId") + .HasName("pk_legacy_entity_externals"); + + b.HasIndex("TargetId") + .HasDatabaseName("ix_legacy_entity_externals_target_id"); + + b.ToTable("legacy_entity_externals", (string)null); + }); + + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasColumnOrder(0) + .HasComment("Идентификатор"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IsRemaster") + .HasColumnType("boolean") + .HasColumnName("is_remaster") + .HasColumnOrder(4) + .HasComment("Флаг, указывающий, что объект является частью ремастера"); + + b.Property("LegacyEntityId") + .HasColumnType("integer") + .HasColumnName("legacy_entity_id") + .HasColumnOrder(1) + .HasComment("Идентификатор устаревшей сущности"); + + b.Property("SourceId") + .HasColumnType("integer") + .HasColumnName("source_id") + .HasColumnOrder(2) + .HasComment("Идентификатор источника"); + + b.Property("Suffix") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("suffix") + .HasColumnOrder(3) + .HasComment("Номер страницы, эррата и т.д."); + + b.HasKey("Id") + .HasName("pk_legacy_entity_sources"); + + b.HasIndex("LegacyEntityId") + .HasDatabaseName("ix_legacy_entity_sources_legacy_entity_id"); + + b.HasIndex("SourceId") + .HasDatabaseName("ix_legacy_entity_sources_source_id"); + + b.ToTable("legacy_entity_sources", (string)null); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b => { b.Property("Id") @@ -340,6 +476,12 @@ namespace Pf2eModel.Migrations .HasColumnOrder(2) .HasComment("Идентификатор цели"); + b.Property("ExternalId") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("external_id") + .HasColumnOrder(3); + b.Property("Md5") .IsRequired() .HasMaxLength(32) @@ -1842,6 +1984,23 @@ namespace Pf2eModel.Migrations .HasConstraintName("fk_entity_keyword_keywords_keywords_id"); }); + modelBuilder.Entity("EntityLegacyEntity", b => + { + b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", null) + .WithMany() + .HasForeignKey("LegacyEntitiesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_entity_legacy_entity_legacy_entities_legacy_entities_id"); + + b.HasOne("Pf2eModel.Model.Base.Entity", null) + .WithMany() + .HasForeignKey("RemasterEntitiesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_entity_legacy_entity_entities_remaster_entities_id"); + }); + modelBuilder.Entity("EntityTrait", b => { b.HasOne("Pf2eModel.Model.Base.Entity", null) @@ -1871,6 +2030,48 @@ namespace Pf2eModel.Migrations b.Navigation("Keyword"); }); + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b => + { + b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity") + .WithMany("Externals") + .HasForeignKey("LegacyEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_entity_externals_legacy_entities_legacy_entity_id"); + + b.HasOne("Pf2eModel.Model.Base.Targets.ExternalTarget", "Target") + .WithMany("LegacyEntityExternals") + .HasForeignKey("TargetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_entity_externals_external_targets_target_id"); + + b.Navigation("LegacyEntity"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b => + { + b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity") + .WithMany("Sources") + .HasForeignKey("LegacyEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_entity_sources_legacy_entities_legacy_entity_id"); + + b.HasOne("Pf2eModel.Model.Base.Source.Source", "Source") + .WithMany("LegacyEntitySources") + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_entity_sources_sources_source_id"); + + b.Navigation("LegacyEntity"); + + b.Navigation("Source"); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b => { b.HasOne("Pf2eModel.Model.Base.Entity", "Entity") @@ -2509,14 +2710,25 @@ namespace Pf2eModel.Migrations b.Navigation("Aliases"); }); + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b => + { + b.Navigation("Externals"); + + b.Navigation("Sources"); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Source.Source", b => { b.Navigation("EntitySources"); + + b.Navigation("LegacyEntitySources"); }); modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b => { b.Navigation("EntityExternals"); + + b.Navigation("LegacyEntityExternals"); }); modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b => diff --git a/Pf2eModel/Migrations/20250514084949_1.cs b/Pf2eModel/Migrations/20250514102154_1.cs similarity index 91% rename from Pf2eModel/Migrations/20250514084949_1.cs rename to Pf2eModel/Migrations/20250514102154_1.cs index dfcb326..fefb752 100644 --- a/Pf2eModel/Migrations/20250514084949_1.cs +++ b/Pf2eModel/Migrations/20250514102154_1.cs @@ -101,6 +101,19 @@ namespace Pf2eModel.Migrations table.PrimaryKey("pk_keywords", x => x.id); }); + migrationBuilder.CreateTable( + name: "legacy_entities", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "jsonb", nullable: false, comment: "Отображаемое имя") + }, + constraints: table => + { + table.PrimaryKey("pk_legacy_entities", x => x.id); + }); + migrationBuilder.CreateTable( name: "sources", columns: table => new @@ -399,6 +412,7 @@ namespace Pf2eModel.Migrations { entity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор сущности"), target_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор цели"), + external_id = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), suffix = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Часть URL до объекта. Для источников Foundry - полный UUID объекта"), md5 = table.Column(type: "character varying(32)", maxLength: 32, nullable: false, comment: "Контрольная сумма объекта") }, @@ -463,6 +477,57 @@ namespace Pf2eModel.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "entity_legacy_entity", + columns: table => new + { + legacy_entities_id = table.Column(type: "integer", nullable: false), + remaster_entities_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_entity_legacy_entity", x => new { x.legacy_entities_id, x.remaster_entities_id }); + table.ForeignKey( + name: "fk_entity_legacy_entity_entities_remaster_entities_id", + column: x => x.remaster_entities_id, + principalTable: "entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_entity_legacy_entity_legacy_entities_legacy_entities_id", + column: x => x.legacy_entities_id, + principalTable: "legacy_entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "legacy_entity_externals", + columns: table => new + { + legacy_entity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор сущности"), + target_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор цели"), + external_id = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + suffix = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Часть URL до объекта. Для источников Foundry - полный UUID объекта"), + md5 = table.Column(type: "character varying(32)", maxLength: 32, nullable: false, comment: "Контрольная сумма объекта") + }, + constraints: table => + { + table.PrimaryKey("pk_legacy_entity_externals", x => new { x.legacy_entity_id, x.target_id }); + table.ForeignKey( + name: "fk_legacy_entity_externals_external_targets_target_id", + column: x => x.target_id, + principalTable: "external_targets", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_legacy_entity_externals_legacy_entities_legacy_entity_id", + column: x => x.legacy_entity_id, + principalTable: "legacy_entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "entity_sources", columns: table => new @@ -491,6 +556,34 @@ namespace Pf2eModel.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "legacy_entity_sources", + columns: table => new + { + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + legacy_entity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор устаревшей сущности"), + source_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор источника"), + suffix = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Номер страницы, эррата и т.д."), + is_remaster = table.Column(type: "boolean", nullable: false, comment: "Флаг, указывающий, что объект является частью ремастера") + }, + constraints: table => + { + table.PrimaryKey("pk_legacy_entity_sources", x => x.id); + table.ForeignKey( + name: "fk_legacy_entity_sources_legacy_entities_legacy_entity_id", + column: x => x.legacy_entity_id, + principalTable: "legacy_entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_legacy_entity_sources_sources_source_id", + column: x => x.source_id, + principalTable: "sources", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "ancestry_boosts", columns: table => new @@ -1260,6 +1353,11 @@ namespace Pf2eModel.Migrations table: "entity_keyword", column: "keywords_id"); + migrationBuilder.CreateIndex( + name: "ix_entity_legacy_entity_remaster_entities_id", + table: "entity_legacy_entity", + column: "remaster_entities_id"); + migrationBuilder.CreateIndex( name: "ix_entity_sources_entity_id", table: "entity_sources", @@ -1295,6 +1393,21 @@ namespace Pf2eModel.Migrations table: "languages", column: "entity_id"); + migrationBuilder.CreateIndex( + name: "ix_legacy_entity_externals_target_id", + table: "legacy_entity_externals", + column: "target_id"); + + migrationBuilder.CreateIndex( + name: "ix_legacy_entity_sources_legacy_entity_id", + table: "legacy_entity_sources", + column: "legacy_entity_id"); + + migrationBuilder.CreateIndex( + name: "ix_legacy_entity_sources_source_id", + table: "legacy_entity_sources", + column: "source_id"); + migrationBuilder.CreateIndex( name: "ix_ritual_checks_ritual_id", table: "ritual_checks", @@ -1411,6 +1524,9 @@ namespace Pf2eModel.Migrations migrationBuilder.DropTable( name: "entity_keyword"); + migrationBuilder.DropTable( + name: "entity_legacy_entity"); + migrationBuilder.DropTable( name: "entity_sources"); @@ -1429,6 +1545,12 @@ namespace Pf2eModel.Migrations migrationBuilder.DropTable( name: "keyword_aliases"); + migrationBuilder.DropTable( + name: "legacy_entity_externals"); + + migrationBuilder.DropTable( + name: "legacy_entity_sources"); + migrationBuilder.DropTable( name: "ritual_areas"); @@ -1465,12 +1587,6 @@ namespace Pf2eModel.Migrations migrationBuilder.DropTable( name: "deities"); - migrationBuilder.DropTable( - name: "external_targets"); - - migrationBuilder.DropTable( - name: "sources"); - migrationBuilder.DropTable( name: "traits"); @@ -1480,6 +1596,15 @@ namespace Pf2eModel.Migrations migrationBuilder.DropTable( name: "keywords"); + migrationBuilder.DropTable( + name: "external_targets"); + + migrationBuilder.DropTable( + name: "legacy_entities"); + + migrationBuilder.DropTable( + name: "sources"); + migrationBuilder.DropTable( name: "rituals"); diff --git a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs index 1dcf56e..77fe09f 100644 --- a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs +++ b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs @@ -83,6 +83,25 @@ namespace Pf2eModel.Migrations b.ToTable("entity_keyword", (string)null); }); + modelBuilder.Entity("EntityLegacyEntity", b => + { + b.Property("LegacyEntitiesId") + .HasColumnType("integer") + .HasColumnName("legacy_entities_id"); + + b.Property("RemasterEntitiesId") + .HasColumnType("integer") + .HasColumnName("remaster_entities_id"); + + b.HasKey("LegacyEntitiesId", "RemasterEntitiesId") + .HasName("pk_entity_legacy_entity"); + + b.HasIndex("RemasterEntitiesId") + .HasDatabaseName("ix_entity_legacy_entity_remaster_entities_id"); + + b.ToTable("entity_legacy_entity", (string)null); + }); + modelBuilder.Entity("EntityTrait", b => { b.Property("EntitiesId") @@ -237,6 +256,123 @@ namespace Pf2eModel.Migrations b.ToTable("keyword_aliases", (string)null); }); + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasColumnOrder(1); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("name") + .HasColumnOrder(2) + .HasComment("Отображаемое имя"); + + b.HasKey("Id") + .HasName("pk_legacy_entities"); + + b.ToTable("legacy_entities", (string)null); + }); + + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b => + { + b.Property("LegacyEntityId") + .HasColumnType("integer") + .HasColumnName("legacy_entity_id") + .HasColumnOrder(1) + .HasComment("Идентификатор сущности"); + + b.Property("TargetId") + .HasColumnType("integer") + .HasColumnName("target_id") + .HasColumnOrder(2) + .HasComment("Идентификатор цели"); + + b.Property("ExternalId") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("external_id") + .HasColumnOrder(3); + + b.Property("Md5") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("md5") + .HasColumnOrder(5) + .HasComment("Контрольная сумма объекта"); + + b.Property("Suffix") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("suffix") + .HasColumnOrder(4) + .HasComment("Часть URL до объекта. Для источников Foundry - полный UUID объекта"); + + b.HasKey("LegacyEntityId", "TargetId") + .HasName("pk_legacy_entity_externals"); + + b.HasIndex("TargetId") + .HasDatabaseName("ix_legacy_entity_externals_target_id"); + + b.ToTable("legacy_entity_externals", (string)null); + }); + + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasColumnOrder(0) + .HasComment("Идентификатор"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IsRemaster") + .HasColumnType("boolean") + .HasColumnName("is_remaster") + .HasColumnOrder(4) + .HasComment("Флаг, указывающий, что объект является частью ремастера"); + + b.Property("LegacyEntityId") + .HasColumnType("integer") + .HasColumnName("legacy_entity_id") + .HasColumnOrder(1) + .HasComment("Идентификатор устаревшей сущности"); + + b.Property("SourceId") + .HasColumnType("integer") + .HasColumnName("source_id") + .HasColumnOrder(2) + .HasComment("Идентификатор источника"); + + b.Property("Suffix") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("suffix") + .HasColumnOrder(3) + .HasComment("Номер страницы, эррата и т.д."); + + b.HasKey("Id") + .HasName("pk_legacy_entity_sources"); + + b.HasIndex("LegacyEntityId") + .HasDatabaseName("ix_legacy_entity_sources_legacy_entity_id"); + + b.HasIndex("SourceId") + .HasDatabaseName("ix_legacy_entity_sources_source_id"); + + b.ToTable("legacy_entity_sources", (string)null); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b => { b.Property("Id") @@ -337,6 +473,12 @@ namespace Pf2eModel.Migrations .HasColumnOrder(2) .HasComment("Идентификатор цели"); + b.Property("ExternalId") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("external_id") + .HasColumnOrder(3); + b.Property("Md5") .IsRequired() .HasMaxLength(32) @@ -1839,6 +1981,23 @@ namespace Pf2eModel.Migrations .HasConstraintName("fk_entity_keyword_keywords_keywords_id"); }); + modelBuilder.Entity("EntityLegacyEntity", b => + { + b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", null) + .WithMany() + .HasForeignKey("LegacyEntitiesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_entity_legacy_entity_legacy_entities_legacy_entities_id"); + + b.HasOne("Pf2eModel.Model.Base.Entity", null) + .WithMany() + .HasForeignKey("RemasterEntitiesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_entity_legacy_entity_entities_remaster_entities_id"); + }); + modelBuilder.Entity("EntityTrait", b => { b.HasOne("Pf2eModel.Model.Base.Entity", null) @@ -1868,6 +2027,48 @@ namespace Pf2eModel.Migrations b.Navigation("Keyword"); }); + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b => + { + b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity") + .WithMany("Externals") + .HasForeignKey("LegacyEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_entity_externals_legacy_entities_legacy_entity_id"); + + b.HasOne("Pf2eModel.Model.Base.Targets.ExternalTarget", "Target") + .WithMany("LegacyEntityExternals") + .HasForeignKey("TargetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_entity_externals_external_targets_target_id"); + + b.Navigation("LegacyEntity"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b => + { + b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity") + .WithMany("Sources") + .HasForeignKey("LegacyEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_entity_sources_legacy_entities_legacy_entity_id"); + + b.HasOne("Pf2eModel.Model.Base.Source.Source", "Source") + .WithMany("LegacyEntitySources") + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_entity_sources_sources_source_id"); + + b.Navigation("LegacyEntity"); + + b.Navigation("Source"); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b => { b.HasOne("Pf2eModel.Model.Base.Entity", "Entity") @@ -2506,14 +2707,25 @@ namespace Pf2eModel.Migrations b.Navigation("Aliases"); }); + modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b => + { + b.Navigation("Externals"); + + b.Navigation("Sources"); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Source.Source", b => { b.Navigation("EntitySources"); + + b.Navigation("LegacyEntitySources"); }); modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b => { b.Navigation("EntityExternals"); + + b.Navigation("LegacyEntityExternals"); }); modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b => diff --git a/Pf2eModel/Model/Base/Entity.cs b/Pf2eModel/Model/Base/Entity.cs index 429e1c8..89cff39 100644 --- a/Pf2eModel/Model/Base/Entity.cs +++ b/Pf2eModel/Model/Base/Entity.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Base.Keywords; +using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; @@ -99,4 +100,9 @@ public class Entity /// Коллекция внешних ссылок, связанных с сущностью. /// public ICollection Externals { get; set; } = []; + + /// + /// Коллекция устаревших сущностей. + /// + public IEnumerable LegacyEntities { get; set; } = []; } diff --git a/Pf2eModel/Model/Base/Legacy/LegacyEntity.cs b/Pf2eModel/Model/Base/Legacy/LegacyEntity.cs new file mode 100644 index 0000000..af1251a --- /dev/null +++ b/Pf2eModel/Model/Base/Legacy/LegacyEntity.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; +using Pf2eModel.Model.Common.I18n; + +namespace Pf2eModel.Model.Base.Legacy; + +/// +/// Информация об устаревшей сущности +/// +public class LegacyEntity +{ + /// + /// Идентификатор сущности. + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Column(Order = 1)] + public int Id { get; set; } + + /// + /// Отображаемое имя сущности. + /// + [Column(Order = 2)] + [Comment("Отображаемое имя")] + public LocaleString Name { get; set; } = []; + + /// + /// Коллекция обновлённых сущностей. + /// + public ICollection RemasterEntities { get; set; } = []; + + /// + /// Коллекция источников, связанных с сущностью. + /// + public ICollection Sources { get; set; } = []; + + /// + /// Коллекция внешних ссылок, связанных с сущностью. + /// + public ICollection Externals { get; set; } = []; +} diff --git a/Pf2eModel/Model/Base/Legacy/LegacyEntityExternal.cs b/Pf2eModel/Model/Base/Legacy/LegacyEntityExternal.cs new file mode 100644 index 0000000..962635d --- /dev/null +++ b/Pf2eModel/Model/Base/Legacy/LegacyEntityExternal.cs @@ -0,0 +1,58 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; +using Pf2eModel.Model.Base.Targets; + +namespace Pf2eModel.Model.Base.Legacy; + +/// +/// Внешняя ссылка сущности. +/// +[PrimaryKey(nameof(LegacyEntityId), nameof(TargetId))] +public class LegacyEntityExternal +{ + /// + /// Идентификатор сущности. + /// + [Column(Order = 1)] + [Comment("Идентификатор сущности")] + public int LegacyEntityId { get; set; } + + /// + /// Идентификатор цели. + /// + [Column(Order = 2)] + [Comment("Идентификатор цели")] + public int TargetId { get; set; } + + [Column(Order = 3)] + [MaxLength(255)] + public string? ExternalId { get; set; } = null; + + /// + /// Конечная часть URL внешней ссылки. + /// Для источников Foundry - полный UUID объекта. + /// + [Column(Order = 4)] + [MaxLength(255)] + [Comment("Часть URL до объекта. Для источников Foundry - полный UUID объекта")] + public string Suffix { get; set; } = ""; + + /// + /// Контрольная сумма объекта. + /// + [Column(Order = 5)] + [MaxLength(32)] + [Comment("Контрольная сумма объекта")] + public string Md5 { get; set; } = null!; + + /// + /// Сущность, которая ссылается на внешнюю ссылку. + /// + public LegacyEntity LegacyEntity { get; set; } = null!; + + /// + /// Цель внешней ссылки. + /// + public ExternalTarget Target { get; set; } = null!; +} diff --git a/Pf2eModel/Model/Base/Legacy/LegacyEntitySource.cs b/Pf2eModel/Model/Base/Legacy/LegacyEntitySource.cs new file mode 100644 index 0000000..a3d959d --- /dev/null +++ b/Pf2eModel/Model/Base/Legacy/LegacyEntitySource.cs @@ -0,0 +1,59 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Pf2eModel.Model.Base.Legacy; + +/// +/// Связь между сущностью и источником. +/// +public class LegacyEntitySource +{ + /// + /// Идентификатор связи. + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Column(Order = 0)] + [Comment("Идентификатор")] + public int Id { get; set; } + + /// + /// Идентификатор устаревшей сущности. + /// + [Column(Order = 1)] + [Comment("Идентификатор устаревшей сущности")] + public int LegacyEntityId { get; set; } + + /// + /// Идентификатор источника. + /// + [Column(Order = 2)] + [Comment("Идентификатор источника")] + public int SourceId { get; set; } + + /// + /// Дополнительная информация о источнике, например, номер страницы, номер тома и т.д. + /// + [Column(Order = 3)] + [MaxLength(255)] + [Comment("Номер страницы, эррата и т.д.")] + public string Suffix { get; set; } = null!; + + /// + /// Флаг, указывающий, что объект является частью ремастера. + /// + [Column(Order = 4)] + [Comment("Флаг, указывающий, что объект является частью ремастера")] + public bool IsRemaster { get; set; } + + /// + /// Сущность, к которой относится источник. + /// + public LegacyEntity LegacyEntity { get; set; } = null!; + + /// + /// Источник, к которому относится сущность. + /// + public Source.Source Source { get; set; } = null!; +} diff --git a/Pf2eModel/Model/Base/Source/Source.cs b/Pf2eModel/Model/Base/Source/Source.cs index 7f098f1..6a88179 100644 --- a/Pf2eModel/Model/Base/Source/Source.cs +++ b/Pf2eModel/Model/Base/Source/Source.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; +using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Base.Source; @@ -45,4 +46,9 @@ public class Source /// Связанные сущности. /// public ICollection EntitySources { get; set; } = []; + + /// + /// Связанные сущности. + /// + public ICollection LegacyEntitySources { get; set; } = []; } diff --git a/Pf2eModel/Model/Base/Targets/EntityExternal.cs b/Pf2eModel/Model/Base/Targets/EntityExternal.cs index a662e50..56f4951 100644 --- a/Pf2eModel/Model/Base/Targets/EntityExternal.cs +++ b/Pf2eModel/Model/Base/Targets/EntityExternal.cs @@ -24,11 +24,9 @@ public class EntityExternal [Comment("Идентификатор цели")] public int TargetId { get; set; } - /* [Column(Order = 3)] [MaxLength(255)] - public string ExternalId { get; set; } = ""; - */ + public string? ExternalId { get; set; } = null; /// /// Конечная часть URL внешней ссылки. diff --git a/Pf2eModel/Model/Base/Targets/ExternalTarget.cs b/Pf2eModel/Model/Base/Targets/ExternalTarget.cs index 44b74bb..6602b5b 100644 --- a/Pf2eModel/Model/Base/Targets/ExternalTarget.cs +++ b/Pf2eModel/Model/Base/Targets/ExternalTarget.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; +using Pf2eModel.Model.Base.Legacy; namespace Pf2eModel.Model.Base.Targets; @@ -46,4 +47,9 @@ public class ExternalTarget /// Коллекция сущностей, которые ссылаются на эту цель. /// public ICollection EntityExternals { get; set; } = []; + + /// + /// Коллекция устаревших сущностей, которые ссылаются на эту цель. + /// + public ICollection LegacyEntityExternals { get; set; } = []; } diff --git a/Pf2eModel/Pf2eDbContext.cs b/Pf2eModel/Pf2eDbContext.cs index 4bcf998..2ecf8c7 100644 --- a/Pf2eModel/Pf2eDbContext.cs +++ b/Pf2eModel/Pf2eDbContext.cs @@ -3,6 +3,7 @@ using dotenv.net.Utilities; using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Base; using Pf2eModel.Model.Base.Keywords; +using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common; @@ -97,6 +98,12 @@ public partial class Pf2eDbContext : DbContext public virtual DbSet Languages { get; set; } + public virtual DbSet LegacyEntities { get; set; } + + public virtual DbSet LegacyEntityExternals { get; set; } + + public virtual DbSet LegacyEntitySources { get; set; } + public virtual DbSet Rituals { get; set; } public virtual DbSet RitualAreas { get; set; } diff --git a/Pf2eModel/Pf2eDbContextModel.cs b/Pf2eModel/Pf2eDbContextModel.cs index 56a8917..94afdf3 100644 --- a/Pf2eModel/Pf2eDbContextModel.cs +++ b/Pf2eModel/Pf2eDbContextModel.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Pf2eModel.Model; using Pf2eModel.Model.Base; using Pf2eModel.Model.Base.Keywords; +using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common; @@ -144,6 +145,28 @@ public partial class Pf2eDbContext .OnDelete(DeleteBehavior.Cascade); }); + // LegacyEntity + modelBuilder.Entity(entity => + { + BindLocaleString(entity, l => l.Name); + + entity.HasMany(l => l.RemasterEntities).WithMany(e => e.LegacyEntities); + + // LegacyEntitySource + entity + .HasMany(e => e.Sources) + .WithOne(e => e.LegacyEntity) + .HasForeignKey(e => e.LegacyEntityId) + .OnDelete(DeleteBehavior.Cascade); + + // LegacyEntityExternal + entity + .HasMany(e => e.Externals) + .WithOne(e => e.LegacyEntity) + .HasForeignKey(e => e.LegacyEntityId) + .OnDelete(DeleteBehavior.Cascade); + }); + // Source modelBuilder.Entity(entity => { @@ -155,6 +178,12 @@ public partial class Pf2eDbContext .HasForeignKey(e => e.SourceId) .OnDelete(DeleteBehavior.Cascade); + entity + .HasMany(s => s.LegacyEntitySources) + .WithOne(e => e.Source) + .HasForeignKey(e => e.SourceId) + .OnDelete(DeleteBehavior.Cascade); + // + EntitySource }); modelBuilder.Entity(entity => { }); @@ -167,6 +196,12 @@ public partial class Pf2eDbContext .WithOne(e => e.Target) .HasForeignKey(e => e.TargetId) .OnDelete(DeleteBehavior.Cascade); + + entity + .HasMany(t => t.LegacyEntityExternals) + .WithOne(e => e.Target) + .HasForeignKey(e => e.TargetId) + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { }); From 2b61669e7ac9d1ba410df9a60edef7ae8d86db54 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Thu, 15 May 2025 01:37:27 +0400 Subject: [PATCH 11/33] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/.env.example | 3 + Pf2eDataScrapper/App/ProcessorAttribute.cs | 2 +- Pf2eDataScrapper/App/ProcessorCollection.cs | 6 + Pf2eDataScrapper/ApplicationSettings.cs | 10 + .../EntityTreeViewerControl.cs | 1 + .../EntityTreeViewerControlRow.cs | 1 + .../EntityTreeViewer/StructureBuilder.cs | 2 +- .../Controls/ProcessorCollectionControl.cs | 2 +- Pf2eDataScrapper/Helpers/ProcessProgress.cs | 1 - Pf2eDataScrapper/ImportApplication.cs | 5 + .../Implementations/Aonprd/AonprdImporter.cs | 1 - .../Aonprd/Helpers/AonprdBuilderGlobals.cs | 13 +- .../Aonprd/Helpers/LegacyEntityHandler.cs | 105 --------- .../Processors/AbstractEntityProcessor.cs | 210 +++++------------- .../Processors/AbstractSubclassProcessor.cs | 5 +- .../Processors/Common/ExternalProcessor.cs | 6 +- .../Processors/Common/LegacyProcessor.cs | 48 ++-- .../Processors/Common/SourceProcessor.cs | 9 +- .../Subclasses/ApparitionProcessor.cs | 3 +- .../Processors/Types/ActionProcessor.cs | 5 +- .../Processors/Types/AncestryProcessor.cs | 5 +- .../Processors/Types/ArchetypeProcessor.cs | 5 +- .../Processors/Types/BackgroundProcessor.cs | 5 +- .../Aonprd/Processors/Types/ClassProcessor.cs | 5 +- .../Aonprd/Processors/Types/FeatProcessor.cs | 5 +- .../Processors/Types/LanguageProcessor.cs | 5 +- .../Processors/Types/RitualProcessor.cs | 5 +- .../Processors/Types/Rules/RuleProcessor.cs | 8 +- .../Aonprd/Processors/Types/SpellProcessor.cs | 5 +- .../Aonprd/Processors/Types/TraitProcessor.cs | 16 +- .../Processors/ProcessorExecutor.cs | 49 ++-- Pf2eModel/EfCleanHelper.cs | 4 +- ...signer.cs => 20250514120355_1.Designer.cs} | 2 +- ...0250514102154_1.cs => 20250514120355_1.cs} | 0 Pf2eModel/Model/Base/Entity.cs | 2 +- Pf2eModel/Pf2eDbContext.cs | 1 + 36 files changed, 200 insertions(+), 360 deletions(-) delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs rename Pf2eModel/Migrations/{20250514102154_1.Designer.cs => 20250514120355_1.Designer.cs} (99%) rename Pf2eModel/Migrations/{20250514102154_1.cs => 20250514120355_1.cs} (100%) diff --git a/Pf2eDataScrapper/.env.example b/Pf2eDataScrapper/.env.example index d7a97be..77f1d48 100644 --- a/Pf2eDataScrapper/.env.example +++ b/Pf2eDataScrapper/.env.example @@ -6,3 +6,6 @@ OUTPUT_DIR=D:\path\to\output\directory # Строка подключения к БД DATABASE_CONNECTION=Host=localhost;Port=5433;Database=usersdb2;Username=postgres;Password=password + +# Максимальное количество потоков. +MAX_THREADS=60 \ No newline at end of file diff --git a/Pf2eDataScrapper/App/ProcessorAttribute.cs b/Pf2eDataScrapper/App/ProcessorAttribute.cs index aa02c8d..3497ce6 100644 --- a/Pf2eDataScrapper/App/ProcessorAttribute.cs +++ b/Pf2eDataScrapper/App/ProcessorAttribute.cs @@ -30,7 +30,7 @@ public class ProcessorAttribute(string name) : Attribute /// /// Разрешить выполнять процессов в параллельном режиме /// - public bool Parallel { get; init; } = true; + public bool Parallel { get; init; } = false; /// /// Обработчик набора элементов diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index bd7b61c..74d435c 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -90,6 +90,7 @@ internal class ProcessorCollection InnerType = innerType, Compendium = compendium, Active = processorAttribute.Active, + Parallel = processorAttribute.Parallel, }; } @@ -241,4 +242,9 @@ internal class ProcessorCollectionItem /// null - обработать все типы. /// public ProcessorCompendiumEnum? Compendium { get; init; } + + /// + /// Разрешить выполнять процессов в параллельном режиме + /// + public bool Parallel { get; init; } = true; } diff --git a/Pf2eDataScrapper/ApplicationSettings.cs b/Pf2eDataScrapper/ApplicationSettings.cs index 6e8da60..f5b4938 100644 --- a/Pf2eDataScrapper/ApplicationSettings.cs +++ b/Pf2eDataScrapper/ApplicationSettings.cs @@ -19,6 +19,7 @@ internal class ApplicationSettings DataDirPath = EnvReader.GetStringValue("DATA_DIR"); OutputDirPath = EnvReader.GetStringValue("OUTPUT_DIR"); DatabaseConnection = EnvReader.GetStringValue("DATABASE_CONNECTION"); + MaxThreads = Convert.ToInt32(EnvReader.GetStringValue("MAX_THREADS")); } #region Настройки из .env @@ -62,6 +63,15 @@ internal class ApplicationSettings [Description("Редактирование через .env-файл.")] public string DatabaseConnection { get; } + /// + /// Максимальное количество потоков. + /// + [Browsable(true)] + [ReadOnly(true)] + [DisplayName("Максимальное количество потоков.")] + [Description("Редактирование через .env-файл.")] + public int MaxThreads { get; } + #endregion #region Основные настройки diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs index daef077..e41f95c 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; namespace Pf2eViewer.EntityTreeViewer; diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs index 1baecfb..601ea84 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs @@ -1,4 +1,5 @@ using FontAwesome.Sharp; +using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; using Font = System.Drawing.Font; using FontStyle = System.Drawing.FontStyle; using Image = System.Drawing.Image; diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs index 0dfea73..d1681c9 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs @@ -3,7 +3,7 @@ using System.Reflection; using LinqToDB.Extensions; using Pf2eModel.Model.Common.I18n; -namespace Pf2eViewer.EntityTreeViewer; +namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; /// /// Класс для построения структуры данных класса. diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs index 37f32d7..07b2a5c 100644 --- a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs @@ -54,7 +54,7 @@ internal partial class ProcessorCollectionControl : UserControl FlowDirection = FlowDirection.LeftToRight, WrapContents = true, BorderStyle = BorderStyle.FixedSingle, - Margin = new Padding(10, 0, 10, 15), + Margin = new(10, 0, 10, 15), }; RootFlowLayoutPanel.Controls.Add(GroupCheckBoxes[group]); diff --git a/Pf2eDataScrapper/Helpers/ProcessProgress.cs b/Pf2eDataScrapper/Helpers/ProcessProgress.cs index 168fbb9..bb3d6b7 100644 --- a/Pf2eDataScrapper/Helpers/ProcessProgress.cs +++ b/Pf2eDataScrapper/Helpers/ProcessProgress.cs @@ -125,7 +125,6 @@ internal class ProcessProgress : AbstractLogger /// public void End() { - _text = ""; _current++; Update?.Invoke(); } diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index 23d7dbc..3400960 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -177,7 +177,12 @@ internal sealed class ImportApplication : ApplicationContext public static void RegisterServices(IServiceCollection services) { // Контекст базы данных + services.AddEntityFrameworkNpgsql(); services.AddDbContext(); + //services.AddDbContextPool(options => + //{ + // options.Us + //}); // Основная программа services.AddSingleton(); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs index e6b7095..2bbf2a3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs @@ -30,7 +30,6 @@ internal class AonprdImporter : AbstractImporter services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddTransient(); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs index 9fe0ceb..f8891ca 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs @@ -4,14 +4,23 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; /// /// Класс-помощник, хранящий необходимые глобальные данные. -/// Реализует шаблон Singleton. /// internal class AonprdBuilderGlobals { /// /// Цель внешних ссылок Архивов Нефисов. /// - public ExternalTarget AonprdTarget = null!; + public ExternalTarget AonprdTarget { get; set; } = null!; + + /// + /// Словарь идентификаторов источников. + /// + public Dictionary SourceIds { get; set; } = []; + + /// + /// Словарь идентификаторов признаков. + /// + public Dictionary TraitIds { get; set; } = []; /// /// Конструктор класса . diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs deleted file mode 100644 index 24b378e..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/LegacyEntityHandler.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; -using Pf2eModel.Model.Base; -using Pf2eModel.Model.Base.Source; -using Pf2eModel.Model.Common.I18n; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; - -/// -/// Обработчик сущностей, помеченных как устаревшие. -/// -internal class LegacyEntityHandler -{ - /// - /// Идентификатор устаревшей сущности. - /// - public string LegacyId { get; private set; } - - /// - /// Название устаревшей сущности. - /// - public string LegacyName { get; private set; } = ""; - - /// - /// Коллекция источников устревшей сущности. - /// - public List LegacySources { get; private set; } = []; - - /// - /// Флаг, указывающий, что сведения об устаревшей сущности готовы к добавлению в обновлённые. - /// - public bool LegacyPrepared { get; private set; } = false; - - /// - /// Список обновлённых сущностей, что отметели устаревшую сущность как предшественника. - /// - public List RemasterEntities { get; } = []; - - /// - /// Конструктор класса , если сущность сама отмечена как устаревшая. - /// - /// Обработчик устаревшей сущности. - public LegacyEntityHandler(AbstractEntityProcessor processor) - { - LegacyId = processor.Input.GetRequired("id"); - Prepare(processor); - } - - /// - /// Конструктор класса , если обновлённая сущность указывает на ещё недобавленную устаревшую сущность. - /// - /// Идентификатор устаревшей сущности. - /// Обновлённая сущность. - public LegacyEntityHandler(string legacyId, Entity remasterEntity) - { - LegacyId = legacyId; - LegacyPrepared = false; - RemasterEntities.Add(remasterEntity); - } - - /// - /// Подготовить данные об устравшей сущности. - /// - /// Обработчик устаревшей сущности. - public void Prepare(AbstractEntityProcessor processor) - { - if (LegacyPrepared) - { - return; - } - - LegacyName = processor.Input.GetRequired("name"); - LegacySources = processor.GetSources(); - LegacyPrepared = true; - - foreach (Entity remasterEntity in RemasterEntities) - { - Apply(remasterEntity); - } - } - - /// - /// Применить данные устаревшей сущности к обновлённой. - /// - /// Обновлённая сущность. - public void Apply(Entity remasterEntity) - { - if (!remasterEntity.Name.Contains(LegacyName)) - { - remasterEntity.Name[LocaleType.LegacyEnglish] = LegacyName; - } - - foreach (EntitySource legacySource in LegacySources) - { - EntitySource entitySource = new() - { - Entity = remasterEntity, - Source = legacySource.Source, - Suffix = legacySource.Suffix, - IsRemaster = false, - }; - - remasterEntity.Sources.Add(entitySource); - } - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index 19f47c3..1395ede 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -1,8 +1,4 @@ -using System.Collections.Concurrent; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; @@ -31,13 +27,12 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; /// internal abstract class AbstractEntityProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals ) : AbstractInputProcessor(input) { - public Pf2eDbContext Context { get; } = context; + public Pf2eDbContext Context { get; } = new(); public StringHelper StringHelper { get; } = stringHelper; public DatabaseHelper Helper { get; } = helper; public AonprdBuilderGlobals Globals { get; } = globals; @@ -60,19 +55,22 @@ internal abstract class AbstractEntityProcessor( /// /// Добавление основной сущности в контекст базы данных. /// - public abstract void Add(); + protected abstract void Add(); /// public override void Process() { - ProcessRemaster(); + if (Input.GetOptional("remaster_id") is not null) + { + throw new LegacyItemException(Input.GetOptional("name", "")); + } AddEntity(); AddSource(); AddExternal(); AddTraits(); - ProcessLegacy(); + AddLegacy(); } /// @@ -116,6 +114,23 @@ internal abstract class AbstractEntityProcessor( protected virtual string GetDescription() => Input.GetRequired("markdown") + AddedDescription(AddedDescriptionPaths()); + /// + /// Генерация JSON-строки из объектов, располагаюхися по путям, указанным в . + /// + /// Коллекция путей, из которых необходимо достатить объекты. + /// JSON-строка. + protected string GenerateJsonString(params ICollection paths) + { + Dictionary data = []; + + foreach (string key in paths) + { + data.Add(key, Input.Input.SelectToken(key)); + } + + return JsonConvert.SerializeObject(data, new JsonSerializerSettings()); + } + #endregion #region "Генерация сущности" @@ -146,8 +161,29 @@ internal abstract class AbstractEntityProcessor( /// protected virtual void AddSource() { - foreach (EntitySource entitySource in GetSources()) + string[] sourceNames = Input + .GetRequired("source") + .Select(token => token.ToString()) + .ToArray(); + + if (sourceNames.Length == 0) { + throw new( + $"Источники для сущности \"{Entity.Name[LocaleType.English]}\" " + + $"(Aonprd id \"{Input.GetRequired("id")}\") не найдены." + ); + } + + foreach (string sourceName in sourceNames) + { + EntitySource entitySource = new() + { + Entity = Entity, + SourceId = Globals.SourceIds[sourceName], + Suffix = StringHelper.ResolveSource(Input.GetRequired("source_markdown")), + IsRemaster = Input.GetOptional("legacy_id") is not null, + }; + Entity.Sources.Add(entitySource); } } @@ -160,7 +196,7 @@ internal abstract class AbstractEntityProcessor( EntityExternal external = new() { Entity = Entity, - Target = Globals.AonprdTarget, + TargetId = Globals.AonprdTarget.Id, ExternalId = Input.GetRequired("id"), Suffix = Input.GetRequired("url"), Md5 = Input.Md5(), @@ -182,26 +218,11 @@ internal abstract class AbstractEntityProcessor( return; } - IEnumerable names = traitsJson.ToArray().Select(jToken => jToken.ToString()); - List traitEntities = Context - .Traits.Select(t => t.Entity) - // ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall - .Where(e => Pf2eDbContext.LocaleSearchArray(e.Name, names)) - .ToList(); + List names = traitsJson.ToArray().Select(jToken => jToken.ToString()).ToList(); - List traits = Context - .Traits.Include(t => t.Entity) - .Where(t => traitEntities.Contains(t.Entity)) - .ToList(); + List traitIds = names.Select(name => Globals.TraitIds[name]).ToList(); - foreach (string name in names) - { - if (!traits.Any(t => t.Entity.Name.Contains(name))) - { - //TODO - //throw new Exception($"Trait '{name}' is not found!!!"); - } - } + List traits = Context.Traits.Where(t => traitIds.Contains(t.Id)).ToList(); foreach (Trait trait in traits) { @@ -209,138 +230,29 @@ internal abstract class AbstractEntityProcessor( } } - #endregion - /// - /// Обработка сущности, отмеченной как устаревшей. - /// - /// Обработка происходит в классе + /// Добавление устаревших сущностей. /// - protected virtual void ProcessLegacy() + protected virtual void AddLegacy() { JArray? legacyIdArray = Input.GetOptional("legacy_id"); - if (legacyIdArray is null) + if (legacyIdArray is null || legacyIdArray.Count == 0) { return; } string[] legacyIds = legacyIdArray.ToArray().Select(t => t.ToString()).ToArray(); + List legacyEntities = Context + .LegacyEntityExternals.Where(lee => legacyIds.Contains(lee.ExternalId)) + .Select(lee => lee.LegacyEntity) + .ToList(); - if (legacyIds.Length == 0) + foreach (LegacyEntity legacyEntity in legacyEntities) { - return; - } - - foreach (string legacyId in legacyIds) - { - LegacyEntityHandler? handler = LegacyHandlers.FirstOrDefault(h => - h.LegacyId == legacyId - ); - - if (handler is not null) - { - handler.Apply(Entity); - } - else - { - handler = new(legacyId, Entity); - LegacyHandlers.Add(handler); - } + Entity.LegacyEntities.Add(legacyEntity); } } - protected virtual void ProcessRemaster() - { - JArray? remasterIdArray = Input.GetOptional("remaster_id"); - - if (remasterIdArray is null) - { - return; - } - - if (remasterIdArray.Count > 1) - { - throw new("Несколько отметок ремастер-сущности на устаревшие!"); - } - - string[] remasterIds = remasterIdArray.ToArray().Select(t => t.ToString()).ToArray(); - - foreach (string remasterId in remasterIds) - { - LegacyEntityHandler? handler = null; - //LegacyHandlers.FirstOrDefault(h => - // h.RemasterEntities.Contains(remasterId) - //); - - if (handler is null) - { - handler = new(this); - LegacyHandlers.Add(handler); - } - else { } - } - - throw new LegacyItemException(Input.GetRequired("name")); - } - - /// - /// Получение списка источников сущности. - /// - /// - public List GetSources() - { - string[] sourceNames = Input - .GetRequired("source") - .Select(token => token.ToString()) - .ToArray(); - - if (sourceNames.Length == 0) - { - throw new( - $"Source for entity with id \"{Entity.Name[LocaleType.English]}\" " - + $"(Aonprd id \"{Input.GetRequired("id")}\") is not found" - ); - } - - List entitySources = []; - - foreach (string sourceName in sourceNames) - { - Source source = - Context.Sources.FirstOrDefault(s => s.Name == sourceName) - ?? throw new($"Source \"{sourceName}\" did not found"); - - EntitySource entitySource = new() - { - Entity = Entity, - Source = source, - Suffix = StringHelper.ResolveSource(Input.GetRequired("source_markdown")), - IsRemaster = Input.GetOptional("legacy_id") is not null, - }; - - entitySources.Add(entitySource); - } - - return entitySources; - } - - /// - /// Генерация JSON-строки из объектов, располагаюхися по путям, указанным в . - /// - /// Коллекция путей, из которых необходимо достатить объекты. - /// JSON-строка. - public string GenerateJsonString(params ICollection paths) - { - Dictionary data = []; - - foreach (string key in paths) - { - data.Add(key, Input.Input.SelectToken(key)); - } - - return JsonConvert.SerializeObject(data, new JsonSerializerSettings()); - } - - protected static readonly ConcurrentBag LegacyHandlers = []; + #endregion } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs index 8b3a1aa..5d3eb47 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs @@ -18,11 +18,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; /// internal abstract class AbstractSubclassProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Subclass; @@ -41,7 +40,7 @@ internal abstract class AbstractSubclassProcessor( public abstract string GroupName { get; } /// - public override void Add() => Context.Subclasses.Add(Object); + protected override void Add() => Context.Subclasses.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs index 7b8f8ee..ca297c4 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs @@ -12,10 +12,9 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; Group = ProcessorGroupEnum.AonprdPreparation )] [ProcessorDependencies(["aonprd:repository"])] -internal class ExternalProcessor(Pf2eDbContext context, AonprdBuilderGlobals globals) - : AbstractProcessor +internal class ExternalProcessor(AonprdBuilderGlobals globals) : AbstractProcessor { - public Pf2eDbContext Context { get; } = context; + public Pf2eDbContext Context { get; } = new(); public AonprdBuilderGlobals Globals { get; } = globals; public ExternalTarget AonprdTarget = null!; @@ -33,6 +32,7 @@ internal class ExternalProcessor(Pf2eDbContext context, AonprdBuilderGlobals glo public override void Save() { Context.ExternalTargets.Add(AonprdTarget); + Context.SaveChanges(); Globals.AonprdTarget = AonprdTarget; } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs index f0c311d..692f8ae 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs @@ -5,7 +5,6 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Base.Legacy; -using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Common.I18n; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; @@ -15,17 +14,17 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; InnerType = null, CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Создание ссылок на устаревшие сущности", - Group = ProcessorGroupEnum.AonprdPreparation + Group = ProcessorGroupEnum.AonprdPreparation, + Parallel = true )] [ProcessorDependencies(["aonprd:source", "aonprd:external"])] internal class LegacyProcessor( ProcessorInput input, - Pf2eDbContext context, AonprdBuilderGlobals globals, StringHelper stringHelper ) : AbstractInputProcessor(input) { - public Pf2eDbContext Context { get; } = context; + public Pf2eDbContext Context { get; } = new(); public AonprdBuilderGlobals Globals { get; } = globals; public StringHelper StringHelper { get; } = stringHelper; @@ -33,8 +32,6 @@ internal class LegacyProcessor( public override void Process() { - JArray? remasterIdArray = Input.GetOptional("remaster_id"); - if (Input.GetOptional("remaster_id") is null) { throw new SilentSkipItemException(); @@ -53,18 +50,6 @@ internal class LegacyProcessor( /// Генерация источников сущности. /// protected virtual void AddSource() - { - foreach (LegacyEntitySource entitySource in GetSources()) - { - LegacyEntity.Sources.Add(entitySource); - } - } - - /// - /// Получение списка источников сущности. - /// - /// - public List GetSources() { string[] sourceNames = Input .GetRequired("source") @@ -78,27 +63,25 @@ internal class LegacyProcessor( + $"(Aonprd id \"{Input.GetRequired("id")}\") is not found" ); } - - List entitySources = []; - + /* + Dictionary sourceIds = Context + .Sources.AsNoTracking() + .Where(s => sourceNames.Contains(s.Name)) + .ToDictionary(s => s.Name, s => s.Id); + */ foreach (string sourceName in sourceNames) { - Source source = - Context.Sources.FirstOrDefault(s => s.Name == sourceName) - ?? throw new($"Source \"{sourceName}\" did not found"); - LegacyEntitySource entitySource = new() { LegacyEntity = LegacyEntity, - Source = source, + SourceId = Globals.SourceIds[sourceName], Suffix = StringHelper.ResolveSource(Input.GetRequired("source_markdown")), IsRemaster = Input.GetOptional("legacy_id") is not null, }; - entitySources.Add(entitySource); + Context.LegacyEntitySources.Add(entitySource); + LegacyEntity.Sources.Add(entitySource); } - - return entitySources; } /// @@ -109,7 +92,7 @@ internal class LegacyProcessor( LegacyEntityExternal external = new() { LegacyEntity = LegacyEntity, - Target = Globals.AonprdTarget, + TargetId = Globals.AonprdTarget.Id, ExternalId = Input.GetRequired("id"), Suffix = Input.GetRequired("url"), Md5 = Input.Md5(), @@ -121,5 +104,10 @@ internal class LegacyProcessor( public override void Save() { Context.LegacyEntities.Add(LegacyEntity); + + Context.SaveChanges(false); + Context.Dispose(); } + + private static readonly Lock Lock = new(); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs index 570cf42..88b29fc 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs @@ -1,4 +1,5 @@ using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Base.Source; @@ -14,10 +15,11 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; Group = ProcessorGroupEnum.AonprdPreparation )] [ProcessorDependencies(["aonprd:compendium"])] -internal class SourceProcessor(ProcessorInput input, Pf2eDbContext context) +internal class SourceProcessor(ProcessorInput input, AonprdBuilderGlobals globals) : AbstractInputProcessor(input) { - public Pf2eDbContext Context { get; } = context; + public AonprdBuilderGlobals Globals { get; } = globals; + public Pf2eDbContext Context { get; } = new(); /// /// Источник сущности. @@ -39,5 +41,8 @@ internal class SourceProcessor(ProcessorInput input, Pf2eDbContext context) public override void Save() { Context.Sources.Add(Source); + Context.SaveChanges(); + + Globals.SourceIds.Add(Source.Name, Source.Id); } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs index 47d2cc9..a8632e7 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs @@ -24,11 +24,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasse )] internal class ApparitionProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractSubclassProcessor(input, context, stringHelper, helper, globals) +) : AbstractSubclassProcessor(input, stringHelper, helper, globals) { /// public override Subclass Object { get; } = new(); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs index 2cbc2fc..88204dd 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs @@ -18,11 +18,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class ActionProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Action; @@ -31,7 +30,7 @@ internal class ActionProcessor( public override Pf2eModel.Model.Structure.Action Object { get; } = new(); /// - public override void Add() => Context.Actions.Add(Object); + protected override void Add() => Context.Actions.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs index 3631619..9891f4b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs @@ -20,11 +20,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:language"])] internal class AncestryProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Ancestry; @@ -33,7 +32,7 @@ internal class AncestryProcessor( public override Ancestry Object { get; } = new(); /// - public override void Add() => Context.Ancestries.Add(Object); + protected override void Add() => Context.Ancestries.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs index e8d9b62..afa6f1b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs @@ -19,11 +19,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class ArchetypeProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Archetype; @@ -32,7 +31,7 @@ internal class ArchetypeProcessor( public override Archetype Object { get; } = new(); /// - public override void Add() => Context.Archetypes.Add(Object); + protected override void Add() => Context.Archetypes.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs index 2d857d6..d8313e8 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs @@ -21,11 +21,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class BackgroundProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Background; @@ -34,7 +33,7 @@ internal class BackgroundProcessor( public override Background Object { get; } = new(); /// - public override void Add() => Context.Backgrounds.Add(Object); + protected override void Add() => Context.Backgrounds.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index 869bcd2..a6c50bf 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -25,11 +25,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])] internal partial class ClassProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Class; @@ -38,7 +37,7 @@ internal partial class ClassProcessor( public override Class Object { get; } = new(); /// - public override void Add() => Context.Classes.Add(Object); + protected override void Add() => Context.Classes.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs index e1e01da..146883f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs @@ -20,11 +20,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:archetype"])] internal class FeatProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Feat; @@ -33,7 +32,7 @@ internal class FeatProcessor( public override Feat Object { get; } = new(); /// - public override void Add() => Context.Feats.Add(Object); + protected override void Add() => Context.Feats.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs index 9ded0f6..eccfc72 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs @@ -19,11 +19,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class LanguageProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Language; @@ -32,5 +31,5 @@ internal class LanguageProcessor( public override Language Object { get; } = new(); /// - public override void Add() => Context.Languages.Add(Object); + protected override void Add() => Context.Languages.Add(Object); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs index 1a73ccd..617e8d2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs @@ -23,11 +23,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])] internal partial class RitualProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Ritual; @@ -36,7 +35,7 @@ internal partial class RitualProcessor( public override Ritual Object { get; } = new(); /// - public override void Add() => Context.Rituals.Add(Object); + protected override void Add() => Context.Rituals.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs index 6534154..f62bf09 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs @@ -5,7 +5,6 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; -using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; @@ -18,17 +17,16 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rul "aonprd:rules", InnerType = "rules", CompendiumType = ProcessorCompendiumEnum.Aonprd, - Description = "Создание цели внешних ссылок", + Description = "Правила", Group = ProcessorGroupEnum.AonprdEntities )] [ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])] internal class RuleProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Rule; @@ -37,7 +35,7 @@ internal class RuleProcessor( public override Rule Object { get; } = new(); /// - public override void Add() => Context.Rules.Add(Object); + protected override void Add() => Context.Rules.Add(Object); /// /// Структура генерации содержания правил. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs index 9a4aaf9..dcfa38b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs @@ -22,11 +22,10 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:trait", "aonprd:source"])] internal class SpellProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Spell; @@ -35,7 +34,7 @@ internal class SpellProcessor( public override Spell Object { get; } = new(); /// - public override void Add() => Context.Spells.Add(Object); + protected override void Add() => Context.Spells.Add(Object); /// public override void Process() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs index 80e55db..7e4d907 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -4,6 +4,8 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; +using Pf2eModel.Model.Base.Legacy; +using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; @@ -23,7 +25,7 @@ internal class TraitProcessor( StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals -) : AbstractEntityProcessor(input, context, stringHelper, helper, globals) +) : AbstractEntityProcessor(input, stringHelper, helper, globals) { /// public override EntityType Type => EntityType.Trait; @@ -32,7 +34,17 @@ internal class TraitProcessor( public override Trait Object { get; } = new(); /// - public override void Add() => Context.Traits.Add(Object); + protected override void Add() + { + Context.Traits.Add(Object); + + Globals.TraitIds[Entity.Name[LocaleType.English]] = Entity.Id; + + foreach (LegacyEntity legacyEntity in Entity.LegacyEntities) + { + Globals.TraitIds[legacyEntity.Name[LocaleType.English]] = Entity.Id; + } + } /// public override void Process() diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index 9d74fa0..78e431a 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; @@ -16,15 +17,16 @@ internal sealed class ProcessorExecutor( ProcessProgress progress, Pf2eDbContext context, AonprdCompendium aonprdCompendium, - FoundryEnCompendium foundryEnCompendium + FoundryEnCompendium foundryEnCompendium, + ApplicationSettings settings ) { public IServiceProvider ServiceProvider { get; } = serviceProvider; public ProcessorCollection Collection { get; } = collection; public ProcessProgress Progress { get; } = progress; - public Pf2eDbContext Context { get; } = context; public AonprdCompendium AonprdCompendium { get; } = aonprdCompendium; public FoundryEnCompendium FoundryEnCompendium { get; } = foundryEnCompendium; + public ApplicationSettings Settings { get; } = settings; public void Start() { @@ -48,31 +50,36 @@ internal sealed class ProcessorExecutor( worker.DoWork += (_, _) => { - foreach (IProcessor processor in IterateProcessors(item)) - { - try - { - processor.Process(); - processor.Save(); - } - catch (SkipItemException exception) - { - Progress.Information(exception.Message); - } - catch (SilentSkipItemException) - { - // - } - } - Context.SaveChanges(); + Parallel.ForEach( + IterateProcessors(item), + new() { MaxDegreeOfParallelism = item.Parallel ? Settings.MaxThreads : 1 }, + RunProcessor + ); }; - worker.RunWorkerCompleted += Continue; + worker.RunWorkerCompleted += (_, _) => Continue(); worker.RunWorkerAsync(); } - private void Continue(object? sender, RunWorkerCompletedEventArgs e) + private void RunProcessor(IProcessor processor, ParallelLoopState state, long arg3) + { + try + { + processor.Process(); + processor.Save(); + } + catch (SkipItemException exception) + { + Progress.Information(exception.Message); + } + catch (SilentSkipItemException) + { + // + } + } + + private void Continue() { Progress.Processors.End(); _currentIndex++; diff --git a/Pf2eModel/EfCleanHelper.cs b/Pf2eModel/EfCleanHelper.cs index 1cb9cee..f26bc7e 100644 --- a/Pf2eModel/EfCleanHelper.cs +++ b/Pf2eModel/EfCleanHelper.cs @@ -69,7 +69,7 @@ public static class EfCleanHelper public static string Truncate(this DbSet dbSet) where T : class { - string cmd = $"TRUNCATE TABLE {TableName(dbSet)} CASCADE;"; + string cmd = $"TRUNCATE TABLE {TableName(dbSet)} RESTART IDENTITY CASCADE;"; DbContext context = dbSet.GetService().Context; context.Database.ExecuteSqlRaw(cmd); return cmd; @@ -87,7 +87,7 @@ public static class EfCleanHelper foreach (IEntityType entityType in tables) { - string cmd = $"TRUNCATE TABLE {GetName(entityType)} CASCADE;"; + string cmd = $"TRUNCATE TABLE {GetName(entityType)} RESTART IDENTITY CASCADE;"; context.Database.ExecuteSqlRaw(cmd); cmdList.Add(cmd); } diff --git a/Pf2eModel/Migrations/20250514102154_1.Designer.cs b/Pf2eModel/Migrations/20250514120355_1.Designer.cs similarity index 99% rename from Pf2eModel/Migrations/20250514102154_1.Designer.cs rename to Pf2eModel/Migrations/20250514120355_1.Designer.cs index fc193fc..f1f0fcb 100644 --- a/Pf2eModel/Migrations/20250514102154_1.Designer.cs +++ b/Pf2eModel/Migrations/20250514120355_1.Designer.cs @@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum; namespace Pf2eModel.Migrations { [DbContext(typeof(Pf2eDbContext))] - [Migration("20250514102154_1")] + [Migration("20250514120355_1")] partial class _1 { /// diff --git a/Pf2eModel/Migrations/20250514102154_1.cs b/Pf2eModel/Migrations/20250514120355_1.cs similarity index 100% rename from Pf2eModel/Migrations/20250514102154_1.cs rename to Pf2eModel/Migrations/20250514120355_1.cs diff --git a/Pf2eModel/Model/Base/Entity.cs b/Pf2eModel/Model/Base/Entity.cs index 89cff39..ecc28fc 100644 --- a/Pf2eModel/Model/Base/Entity.cs +++ b/Pf2eModel/Model/Base/Entity.cs @@ -104,5 +104,5 @@ public class Entity /// /// Коллекция устаревших сущностей. /// - public IEnumerable LegacyEntities { get; set; } = []; + public ICollection LegacyEntities { get; set; } = []; } diff --git a/Pf2eModel/Pf2eDbContext.cs b/Pf2eModel/Pf2eDbContext.cs index 2ecf8c7..0d49ace 100644 --- a/Pf2eModel/Pf2eDbContext.cs +++ b/Pf2eModel/Pf2eDbContext.cs @@ -136,6 +136,7 @@ public partial class Pf2eDbContext : DbContext // optionsBuilder.LogTo(Console.WriteLine); optionsBuilder.UseSnakeCaseNamingConvention(); + optionsBuilder.EnableSensitiveDataLogging(); optionsBuilder.UseNpgsql( databaseConnection, From 4a7067f0b991ed878b76170056b44ca5a8536d69 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Thu, 15 May 2025 02:05:18 +0400 Subject: [PATCH 12/33] =?UTF-8?q?=D0=9A=D0=B0=D0=BF=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D1=87=D0=B8=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper.sln | 6 ---- .../Forms/Controls/CoreBindingNavigator.cs | 2 +- .../DatabaseViewer/DatabaseQueryPerformer.cs | 2 +- .../DatabaseViewerControl.Designer.cs | 2 +- .../DatabaseViewer/DatabaseViewerControl.cs | 5 +-- .../EntityTreeViewerControl.Designer.cs | 2 +- .../EntityTreeViewerControl.cs | 4 +-- .../EntityTreeViewerControlRow.cs | 9 +++--- .../EntityTreeViewer/StructureBuilder.cs | 4 +-- .../Controls/ProcessorCollectionControl.cs | 2 +- .../Forms/EntityViewForm.Designer.cs | 10 +++--- Pf2eDataScrapper/Forms/EntityViewForm.cs | 4 +-- Pf2eDataScrapper/Forms/ProgressForm.cs | 1 - Pf2eDataScrapper/Helpers/EntityView.cs | 2 +- Pf2eDataScrapper/Helpers/ProcessProgress.cs | 4 +-- Pf2eDataScrapper/ImportApplication.cs | 3 +- .../Importers/AbstractImporter.cs | 4 +-- .../Implementations/Aonprd/AonprdImporter.cs | 2 +- .../Aonprd/AonprdRepository.cs | 3 +- .../Aonprd/Helpers/StringHelper.cs | 2 +- .../Processors/AbstractEntityProcessor.cs | 5 ++- .../Processors/AbstractSubclassProcessor.cs | 2 +- .../Processors/Common/LegacyProcessor.cs | 4 +-- .../Subclasses/ApparitionProcessor.cs | 2 +- .../Processors/Types/ActionProcessor.cs | 3 +- .../Processors/Types/AncestryProcessor.cs | 3 +- .../Processors/Types/ArchetypeProcessor.cs | 3 +- .../Processors/Types/BackgroundProcessor.cs | 3 +- .../Aonprd/Processors/Types/ClassProcessor.cs | 2 +- .../Aonprd/Processors/Types/FeatProcessor.cs | 2 +- .../Processors/Types/LanguageProcessor.cs | 3 +- .../Processors/Types/RitualProcessor.cs | 6 ++-- .../Processors/Types/Rules/RuleProcessor.cs | 3 +- .../Aonprd/Processors/Types/SpellProcessor.cs | 7 ++-- .../Aonprd/Processors/Types/TraitProcessor.cs | 4 +-- .../Processors/FoundryEnAbstractProcessor.cs | 1 - .../Preparation/CompendiumFolderProcessor.cs | 2 +- .../AbstractImporterRepository.cs | 1 - .../Importers/Repositories/GitRepository.cs | 1 - .../Processors/AbstractProcessor.cs | 4 +-- .../Processors/ProcessorExecutor.cs | 5 +-- Pf2eModel/EfCleanHelper.cs | 2 +- Pf2eModel/Pf2eDbContextModel.cs | 32 ------------------- 43 files changed, 55 insertions(+), 118 deletions(-) diff --git a/Pf2eDataScrapper.sln b/Pf2eDataScrapper.sln index 9c6ae20..2326c11 100644 --- a/Pf2eDataScrapper.sln +++ b/Pf2eDataScrapper.sln @@ -19,8 +19,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "output", "output", "{36D591 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pf2eModel", "Pf2eModel\Pf2eModel.csproj", "{D32B7787-D912-4B21-B341-7C1F42F62D36}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pf2eViewer", "Pf2eViewer\Pf2eViewer.csproj", "{9F492EA7-1E1A-439D-A028-506BA445ABE4}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,10 +33,6 @@ Global {D32B7787-D912-4B21-B341-7C1F42F62D36}.Debug|Any CPU.Build.0 = Debug|Any CPU {D32B7787-D912-4B21-B341-7C1F42F62D36}.Release|Any CPU.ActiveCfg = Release|Any CPU {D32B7787-D912-4B21-B341-7C1F42F62D36}.Release|Any CPU.Build.0 = Release|Any CPU - {9F492EA7-1E1A-439D-A028-506BA445ABE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F492EA7-1E1A-439D-A028-506BA445ABE4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F492EA7-1E1A-439D-A028-506BA445ABE4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F492EA7-1E1A-439D-A028-506BA445ABE4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs b/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs index dc1f2e2..e0d4f03 100644 --- a/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs +++ b/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs @@ -1,4 +1,4 @@ -namespace Pf2eViewer; +namespace Pf2eDataScrapper.Forms.Controls; /// /// Элемент управления прокрутки страниц diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs index 3623ce1..b6d8d2a 100644 --- a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs +++ b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs @@ -3,7 +3,7 @@ using Pf2eModel; using Pf2eModel.Model.Base; using Pf2eModel.Model.Enum; -namespace Pf2eViewer.DatabaseViewer; +namespace Pf2eDataScrapper.Forms.Controls.DatabaseViewer; /// /// Класс, выполняющий страничные запросы к базе данных. diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs index 51bca07..374dca0 100644 --- a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs +++ b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs @@ -1,4 +1,4 @@ -namespace Pf2eViewer.DatabaseViewer; +namespace Pf2eDataScrapper.Forms.Controls.DatabaseViewer; partial class DatabaseViewerControl { diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs index d056a03..f0dc02a 100644 --- a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs @@ -1,12 +1,13 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using Pf2eDataScrapper.Helpers; using Pf2eModel; using Pf2eModel.Model.Base; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Timer = System.Windows.Forms.Timer; -namespace Pf2eViewer.DatabaseViewer; +namespace Pf2eDataScrapper.Forms.Controls.DatabaseViewer; /// /// Класс элемента управления, который отображает сущности в виде таблицы. @@ -87,7 +88,7 @@ public partial class DatabaseViewerControl : UserControl /// /// Флаг, указывающий, что элемент управления занят выполнением запроса к базе данных. /// - private bool _isBusy = true; + private bool _isBusy; /// /// Объект, который выполняет запрос к базе данных. diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs index a0d535f..e6444fd 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs @@ -1,4 +1,4 @@ -namespace Pf2eViewer.EntityTreeViewer; +namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; sealed partial class EntityTreeViewerControl { diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs index e41f95c..c286747 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; +using Pf2eDataScrapper.Helpers; -namespace Pf2eViewer.EntityTreeViewer; +namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; /// /// Элемент управления для отображения структуры сущности. diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs index 601ea84..672bd51 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs @@ -1,10 +1,9 @@ using FontAwesome.Sharp; -using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; using Font = System.Drawing.Font; using FontStyle = System.Drawing.FontStyle; using Image = System.Drawing.Image; -namespace Pf2eViewer.EntityTreeViewer; +namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; /// /// Элемент управления для отображения строки поля сущности. @@ -86,7 +85,7 @@ internal class EntityTreeViewerControlRow } } } - private bool _hovered = false; + private bool _hovered; /// /// Индекс строки в представлении. @@ -96,7 +95,7 @@ internal class EntityTreeViewerControlRow /// /// Кнопка раскрытия/сворачивания. /// - public IconButton? Button { get; set; } = null; + public IconButton? Button { get; set; } #endregion @@ -440,7 +439,7 @@ internal class EntityTreeViewerControlRow Width, ]; - Pen p = new(Color.Aqua, 1); + Pen _ = new(Color.Aqua, 1); for (int i = 0; i < 3; i++) { diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs index d1681c9..731b64e 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs @@ -153,7 +153,7 @@ internal class StructureProcessedObject(object value) { public readonly object Value = value; - private StructureFieldComplex? _field = null; + private StructureFieldComplex? _field; public StructureFieldComplex? Field { @@ -422,7 +422,7 @@ internal class StructureFieldComplex : StructureField UpdateFieldVisibility(); } } - private bool _collapsed = false; + private bool _collapsed; /// public override bool Visible diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs index 07b2a5c..2e33460 100644 --- a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs @@ -90,7 +90,7 @@ internal partial class ProcessorCollectionControl : UserControl foreach ((string key, CheckBox checkBox) in ItemCheckBoxes) { - ProcessorCollectionItem item = Collection![key]; + ProcessorCollectionItem item = Collection[key]; if (checkStates.TryGetValue(item.Group, out CheckState state)) { diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs index a063fba..e2982e4 100644 --- a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs @@ -1,7 +1,7 @@ -using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; -using Pf2eViewer.DatabaseViewer; +using Pf2eDataScrapper.Forms.Controls.DatabaseViewer; +using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; -namespace Pf2eViewer; +namespace Pf2eDataScrapper.Forms; partial class EntityViewForm { @@ -37,7 +37,7 @@ partial class EntityViewForm ResultSplitContainer = new SplitContainer(); DescriptionTextBox = new TextBox(); DatabaseViewer = new DatabaseViewerControl(); - EntityTreeViewerControl = new Pf2eViewer.EntityTreeViewer.EntityTreeViewerControl(); + EntityTreeViewerControl = new EntityTreeViewerControl(); MainMenuStrip = new MenuStrip(); FileToolStripMenuItem = new ToolStripMenuItem(); ExitToolStripMenuItem = new ToolStripMenuItem(); @@ -233,7 +233,7 @@ partial class EntityViewForm private ListBox TypesListBox; private SplitContainer ResultSplitContainer; private TextBox DescriptionTextBox; - private EntityTreeViewer.EntityTreeViewerControl EntityTreeViewerControl; + private EntityTreeViewerControl EntityTreeViewerControl; private DatabaseViewerControl DatabaseViewer; private MenuStrip MainMenuStrip; private ToolStripMenuItem FileToolStripMenuItem; diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.cs b/Pf2eDataScrapper/Forms/EntityViewForm.cs index 8aa18e1..bed9bc3 100644 --- a/Pf2eDataScrapper/Forms/EntityViewForm.cs +++ b/Pf2eDataScrapper/Forms/EntityViewForm.cs @@ -1,9 +1,9 @@ using System.ComponentModel; -using Pf2eDataScrapper; +using Pf2eDataScrapper.Helpers; using Pf2eModel; using Pf2eModel.Model.Enum; -namespace Pf2eViewer; +namespace Pf2eDataScrapper.Forms; /// /// Основная форма приложения. diff --git a/Pf2eDataScrapper/Forms/ProgressForm.cs b/Pf2eDataScrapper/Forms/ProgressForm.cs index 18f6a89..32cdd08 100644 --- a/Pf2eDataScrapper/Forms/ProgressForm.cs +++ b/Pf2eDataScrapper/Forms/ProgressForm.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Forms; diff --git a/Pf2eDataScrapper/Helpers/EntityView.cs b/Pf2eDataScrapper/Helpers/EntityView.cs index cd440b2..dd9c5fe 100644 --- a/Pf2eDataScrapper/Helpers/EntityView.cs +++ b/Pf2eDataScrapper/Helpers/EntityView.cs @@ -5,7 +5,7 @@ using Pf2eModel.Model.Base; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; -namespace Pf2eViewer; +namespace Pf2eDataScrapper.Helpers; /// /// Класс, обрабатывающий запросы к сущностям и подключающий необходимые данные. diff --git a/Pf2eDataScrapper/Helpers/ProcessProgress.cs b/Pf2eDataScrapper/Helpers/ProcessProgress.cs index bb3d6b7..d4f6d26 100644 --- a/Pf2eDataScrapper/Helpers/ProcessProgress.cs +++ b/Pf2eDataScrapper/Helpers/ProcessProgress.cs @@ -63,7 +63,7 @@ internal class ProcessProgress : AbstractLogger Update?.Invoke(); } } - private int _count = 0; + private int _count; /// /// Индекс текущего обрабатываемого процесса. @@ -77,7 +77,7 @@ internal class ProcessProgress : AbstractLogger Update?.Invoke(); } } - private int _current = 0; + private int _current; /// /// Название текущего обрабатываемого процесса. diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index 3400960..ff3510f 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -4,7 +4,6 @@ using Pf2eDataScrapper.Forms; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; -using Pf2eViewer; using Slugify; namespace Pf2eDataScrapper; @@ -57,7 +56,7 @@ internal sealed class ImportApplication : ApplicationContext #region "Жизненный цикл" - private Form? CurrentForm { get; set; } = null; + private Form? CurrentForm { get; set; } public delegate void ApplicationStateChangeDelegate(object sender, ApplicationStage nextStage); diff --git a/Pf2eDataScrapper/Importers/AbstractImporter.cs b/Pf2eDataScrapper/Importers/AbstractImporter.cs index d8641a6..8c35683 100644 --- a/Pf2eDataScrapper/Importers/AbstractImporter.cs +++ b/Pf2eDataScrapper/Importers/AbstractImporter.cs @@ -1,6 +1,4 @@ -using Pf2eDataScrapper.Importers.Repositories; - -namespace Pf2eDataScrapper.Importers; +namespace Pf2eDataScrapper.Importers; /// /// Абстрактный класс импортёра. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs index 2bbf2a3..21d2094 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs index 7569127..c5b523b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs @@ -1,7 +1,6 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Repositories; -using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; @@ -13,7 +12,7 @@ internal class AonprdRepository(AonprdImporter importer) : AbstractImporterRepos /// /// URL для доступа к API Aonprd. /// - public readonly string SearchUrl = "https://elasticsearch.aonprd.com"; + public const string SearchUrl = "https://elasticsearch.aonprd.com"; /// /// Текущая ревизия данных. Значение обновляется при обновлении репозитория. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs index 12e25b0..159ade1 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; /// /// Класс-помощник, для обработки строкового ввода с Архивов Нефиса. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index 1395ede..d035a3c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -1,9 +1,7 @@ -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; @@ -15,6 +13,7 @@ using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs index 5d3eb47..f0b9ba1 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs @@ -1,6 +1,5 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; @@ -8,6 +7,7 @@ using Pf2eModel.Model.Base; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs index 692f8ae..e376d9b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs @@ -1,11 +1,11 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Common.I18n; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; @@ -108,6 +108,4 @@ internal class LegacyProcessor( Context.SaveChanges(false); Context.Dispose(); } - - private static readonly Lock Lock = new(); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs index a8632e7..ec100ae 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs @@ -1,6 +1,5 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; @@ -9,6 +8,7 @@ using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs index 88204dd..50f0559 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs @@ -1,10 +1,9 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel; using Pf2eModel.Model.Enum; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs index 9891f4b..b6b9d2d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs @@ -1,12 +1,11 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs index afa6f1b..dfbfa11 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ArchetypeProcessor.cs @@ -1,11 +1,10 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs index d8313e8..7408247 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/BackgroundProcessor.cs @@ -1,13 +1,12 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.BackgroundRelation; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index a6c50bf..19c407d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -2,7 +2,6 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; @@ -12,6 +11,7 @@ using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.ClassRelation; using static System.Text.RegularExpressions.Regex; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs index 146883f..636ddc4 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs @@ -1,12 +1,12 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs index eccfc72..9cfc4ef 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/LanguageProcessor.cs @@ -1,11 +1,10 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs index 617e8d2..407af42 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs @@ -2,7 +2,6 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; @@ -10,6 +9,7 @@ using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.RitualRelation; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; @@ -43,9 +43,9 @@ internal partial class RitualProcessor( base.Process(); Object.Level = Input.GetRequired("level"); - Object.Duration = Input.GetOptional("duration", -1); + Object.Duration = Input.GetOptional("duration", -1); - Object.Range = Input.GetOptional("range", -1); + Object.Range = Input.GetOptional("range", -1); Object.RangeString = Input.GetOptionalLocale("range_raw", LocaleType.English); Object.CastTime = CalculateCastTime(Input.GetRequired("actions")); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs index f62bf09..d60aa25 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs @@ -1,15 +1,14 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Rule = Pf2eModel.Model.Structure.Rule; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs index dcfa38b..631ff74 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs @@ -1,14 +1,13 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.SpellRelation; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; @@ -44,8 +43,8 @@ internal class SpellProcessor( Object.Level = Input.GetRequired("level"); Object.Actions = Input.EnumOptional("actions", ActionEnum.Unknown); Object.IsFocus = Input.GetRequired("spell_type") == "Focus"; - Object.Duration = Input.GetOptional("duration", -1); - Object.Range = Input.GetOptional("range", -1); + 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); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs index 7e4d907..fb5cd39 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -1,13 +1,12 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel; using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; +using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; @@ -21,7 +20,6 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; [ProcessorDependencies(["aonprd:legacy"])] internal class TraitProcessor( ProcessorInput input, - Pf2eDbContext context, StringHelper stringHelper, DatabaseHelper helper, AonprdBuilderGlobals globals diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs index 5874ed4..829fc9d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs @@ -7,7 +7,6 @@ using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model; using Pf2eModel.Model.Base; -using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs index da6b757..c073400 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs @@ -21,7 +21,7 @@ internal class CompendiumFolderProcessor( private CompendiumFolderCollection Folders { get; } = folders; private string Dir => Repository.RepositoryDirPath; - private int NextFolderId { get; set; } = 0; + private int NextFolderId { get; set; } public void Process() { diff --git a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs index 9d09d4a..ce3682f 100644 --- a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Repositories; diff --git a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs index db78e45..a63b640 100644 --- a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Importers.Repositories; diff --git a/Pf2eDataScrapper/Processors/AbstractProcessor.cs b/Pf2eDataScrapper/Processors/AbstractProcessor.cs index 0731b1c..216dfc9 100644 --- a/Pf2eDataScrapper/Processors/AbstractProcessor.cs +++ b/Pf2eDataScrapper/Processors/AbstractProcessor.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Pf2eDataScrapper.App; -using Pf2eModel; +using Pf2eDataScrapper.App; namespace Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index 78e431a..8ea6ec1 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -1,5 +1,4 @@ using System.ComponentModel; -using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; @@ -7,7 +6,6 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Importers.Implementations.FoundryEn; -using Pf2eModel; namespace Pf2eDataScrapper.Processors; @@ -15,7 +13,6 @@ internal sealed class ProcessorExecutor( IServiceProvider serviceProvider, ProcessorCollection collection, ProcessProgress progress, - Pf2eDbContext context, AonprdCompendium aonprdCompendium, FoundryEnCompendium foundryEnCompendium, ApplicationSettings settings @@ -37,7 +34,7 @@ internal sealed class ProcessorExecutor( StartItem(); } - private int _currentIndex = 0; + private int _currentIndex; private void StartItem() { diff --git a/Pf2eModel/EfCleanHelper.cs b/Pf2eModel/EfCleanHelper.cs index f26bc7e..524e14f 100644 --- a/Pf2eModel/EfCleanHelper.cs +++ b/Pf2eModel/EfCleanHelper.cs @@ -22,7 +22,7 @@ public static class EfCleanHelper object? schema = entityType.FindAnnotation("Relational:Schema")?.Value; string tableName = - entityType.GetAnnotation("Relational:TableName")?.Value?.ToString() + entityType.GetAnnotation("Relational:TableName").Value?.ToString() ?? throw new($"Не могу определить название таблицы для типа {entityType}"); string schemaName = schema?.ToString() ?? defaultSchemaName; diff --git a/Pf2eModel/Pf2eDbContextModel.cs b/Pf2eModel/Pf2eDbContextModel.cs index 94afdf3..0d53019 100644 --- a/Pf2eModel/Pf2eDbContextModel.cs +++ b/Pf2eModel/Pf2eDbContextModel.cs @@ -11,7 +11,6 @@ using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; -using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.AncestryRelation; using Pf2eModel.Model.Structure.ArchetypeRelation; @@ -85,37 +84,6 @@ public partial class Pf2eDbContext .IsRequired(required); } - /// - /// Определяет перечисления, используемые в модели. - /// - /// Построитель модели. - private void DefineEnums(ModelBuilder modelBuilder) - { - modelBuilder - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum() - .HasPostgresEnum(); - } - /// /// Определяет базовые сущности и их связи. /// From afdc5d3f81fa7d3435a17c4a5bd6e79f0cd7da40 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Thu, 15 May 2025 09:49:57 +0400 Subject: [PATCH 13/33] =?UTF-8?q?=D0=9E=D1=82=D0=BA=D0=B0=D0=B7=20=D0=BE?= =?UTF-8?q?=D1=82=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=D0=B9?= =?UTF-8?q?=20=D0=BB=D0=B5=D0=B3=D0=B0=D1=81=D0=B8-=D1=81=D1=83=D1=89?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Forms/EntityViewForm.Designer.cs | 32 +-- Pf2eDataScrapper/Forms/EntityViewForm.resx | 2 +- Pf2eDataScrapper/Forms/StartForm.Designer.cs | 26 +- Pf2eDataScrapper/Forms/StartForm.resx | 2 +- .../Processors/AbstractEntityProcessor.cs | 32 --- .../Processors/Common/LegacyProcessor.cs | 111 -------- .../Aonprd/Processors/Types/TraitProcessor.cs | 6 - .../Processors/FoundryEnAbstractProcessor.cs | 2 +- ...signer.cs => 20250515054227_1.Designer.cs} | 269 +++++++----------- ...0250514120355_1.cs => 20250515054227_1.cs} | 209 ++++++-------- .../Migrations/Pf2eDbContextModelSnapshot.cs | 267 +++++++---------- Pf2eModel/Model/Base/Entity.cs | 13 +- Pf2eModel/Model/Base/EntityAlias.cs | 37 +++ Pf2eModel/Model/Base/Legacy/LegacyEntity.cs | 42 --- .../Model/Base/Legacy/LegacyEntityExternal.cs | 58 ---- .../Model/Base/Legacy/LegacyEntitySource.cs | 59 ---- Pf2eModel/Model/Base/LegacyRemaster.cs | 35 +++ Pf2eModel/Model/Base/Source/Source.cs | 6 - .../Model/Base/Targets/ExternalTarget.cs | 6 - Pf2eModel/Pf2eDbContext.cs | 9 +- Pf2eModel/Pf2eDbContextModel.cs | 51 ++-- 21 files changed, 415 insertions(+), 859 deletions(-) delete mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs rename Pf2eModel/Migrations/{20250514120355_1.Designer.cs => 20250515054227_1.Designer.cs} (94%) rename Pf2eModel/Migrations/{20250514120355_1.cs => 20250515054227_1.cs} (94%) create mode 100644 Pf2eModel/Model/Base/EntityAlias.cs delete mode 100644 Pf2eModel/Model/Base/Legacy/LegacyEntity.cs delete mode 100644 Pf2eModel/Model/Base/Legacy/LegacyEntityExternal.cs delete mode 100644 Pf2eModel/Model/Base/Legacy/LegacyEntitySource.cs create mode 100644 Pf2eModel/Model/Base/LegacyRemaster.cs diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs index e2982e4..3a18dd8 100644 --- a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs @@ -38,7 +38,7 @@ partial class EntityViewForm DescriptionTextBox = new TextBox(); DatabaseViewer = new DatabaseViewerControl(); EntityTreeViewerControl = new EntityTreeViewerControl(); - MainMenuStrip = new MenuStrip(); + MenuStrip = new MenuStrip(); FileToolStripMenuItem = new ToolStripMenuItem(); ExitToolStripMenuItem = new ToolStripMenuItem(); WindowsToolStripMenuItem = new ToolStripMenuItem(); @@ -54,7 +54,7 @@ partial class EntityViewForm ((System.ComponentModel.ISupportInitialize)ResultSplitContainer).BeginInit(); ResultSplitContainer.Panel2.SuspendLayout(); ResultSplitContainer.SuspendLayout(); - MainMenuStrip.SuspendLayout(); + MenuStrip.SuspendLayout(); SuspendLayout(); // // RootSplitContainer @@ -110,10 +110,6 @@ partial class EntityViewForm ResultSplitContainer.Location = new Point(0, 0); ResultSplitContainer.Name = "ResultSplitContainer"; // - // ResultSplitContainer.Panel1 - // - ResultSplitContainer.Panel1.Controls.Add(EntityTreeViewerControl); - // // ResultSplitContainer.Panel2 // ResultSplitContainer.Panel2.Controls.Add(DescriptionTextBox); @@ -156,14 +152,14 @@ partial class EntityViewForm EntityTreeViewerControl.TabIndex = 0; EntityTreeViewerControl.TypeFontSize = 7F; // - // MainMenuStrip + // MenuStrip // - MainMenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem }); - MainMenuStrip.Location = new Point(0, 0); - MainMenuStrip.Name = "MainMenuStrip"; - MainMenuStrip.Size = new Size(1384, 24); - MainMenuStrip.TabIndex = 1; - MainMenuStrip.Text = "menuStrip1"; + MenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem }); + MenuStrip.Location = new Point(0, 0); + MenuStrip.Name = "MainMenuStrip"; + MenuStrip.Size = new Size(1384, 24); + MenuStrip.TabIndex = 1; + MenuStrip.Text = "menuStrip1"; // // FileToolStripMenuItem // @@ -175,7 +171,7 @@ partial class EntityViewForm // ExitToolStripMenuItem // ExitToolStripMenuItem.Name = "ExitToolStripMenuItem"; - ExitToolStripMenuItem.Size = new Size(180, 22); + ExitToolStripMenuItem.Size = new Size(109, 22); ExitToolStripMenuItem.Text = "Выход"; ExitToolStripMenuItem.Click += ExitToolStripMenuItem_Click; // @@ -206,7 +202,7 @@ partial class EntityViewForm AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(1384, 861); Controls.Add(RootSplitContainer); - Controls.Add(MainMenuStrip); + Controls.Add(MenuStrip); Name = "EntityViewForm"; Text = "DataBaseViewer"; RootSplitContainer.Panel1.ResumeLayout(false); @@ -220,8 +216,8 @@ partial class EntityViewForm ResultSplitContainer.Panel2.PerformLayout(); ((System.ComponentModel.ISupportInitialize)ResultSplitContainer).EndInit(); ResultSplitContainer.ResumeLayout(false); - MainMenuStrip.ResumeLayout(false); - MainMenuStrip.PerformLayout(); + MenuStrip.ResumeLayout(false); + MenuStrip.PerformLayout(); ResumeLayout(false); PerformLayout(); } @@ -235,7 +231,7 @@ partial class EntityViewForm private TextBox DescriptionTextBox; private EntityTreeViewerControl EntityTreeViewerControl; private DatabaseViewerControl DatabaseViewer; - private MenuStrip MainMenuStrip; + private MenuStrip MenuStrip; private ToolStripMenuItem FileToolStripMenuItem; private ToolStripMenuItem ExitToolStripMenuItem; private ToolStripMenuItem WindowsToolStripMenuItem; diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.resx b/Pf2eDataScrapper/Forms/EntityViewForm.resx index 51c499e..9b2a006 100644 --- a/Pf2eDataScrapper/Forms/EntityViewForm.resx +++ b/Pf2eDataScrapper/Forms/EntityViewForm.resx @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 17, 17 diff --git a/Pf2eDataScrapper/Forms/StartForm.Designer.cs b/Pf2eDataScrapper/Forms/StartForm.Designer.cs index 297306d..7519395 100644 --- a/Pf2eDataScrapper/Forms/StartForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/StartForm.Designer.cs @@ -36,7 +36,7 @@ partial class StartForm SelectAllButton = new Button(); DeselectAllButton = new Button(); StartButton = new Button(); - MainMenuStrip = new MenuStrip(); + MenuStrip = new MenuStrip(); FileToolStripMenuItem = new ToolStripMenuItem(); ExitToolStripMenuItem = new ToolStripMenuItem(); WindowsToolStripMenuItem = new ToolStripMenuItem(); @@ -49,7 +49,7 @@ partial class StartForm MainSplitContainer.SuspendLayout(); PropertiesGroupBox.SuspendLayout(); ProcessorsGroupBox.SuspendLayout(); - MainMenuStrip.SuspendLayout(); + MenuStrip.SuspendLayout(); SuspendLayout(); // // MainSplitContainer @@ -143,14 +143,14 @@ partial class StartForm StartButton.UseVisualStyleBackColor = true; StartButton.Click += StartButton_Click; // - // MainMenuStrip + // MenuStrip // - MainMenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem, StartToolStripMenuItem }); - MainMenuStrip.Location = new Point(0, 0); - MainMenuStrip.Name = "MainMenuStrip"; - MainMenuStrip.Size = new Size(931, 24); - MainMenuStrip.TabIndex = 1; - MainMenuStrip.Text = "menuStrip1"; + MenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem, StartToolStripMenuItem }); + MenuStrip.Location = new Point(0, 0); + MenuStrip.Name = "MenuStrip"; + MenuStrip.Size = new Size(931, 24); + MenuStrip.TabIndex = 1; + MenuStrip.Text = "menuStrip1"; // // FileToolStripMenuItem // @@ -200,7 +200,7 @@ partial class StartForm AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(931, 677); Controls.Add(MainSplitContainer); - Controls.Add(MainMenuStrip); + Controls.Add(MenuStrip); Name = "StartForm"; Text = "Настройки импорта данных"; Load += StartForm_Load; @@ -210,8 +210,8 @@ partial class StartForm MainSplitContainer.ResumeLayout(false); PropertiesGroupBox.ResumeLayout(false); ProcessorsGroupBox.ResumeLayout(false); - MainMenuStrip.ResumeLayout(false); - MainMenuStrip.PerformLayout(); + MenuStrip.ResumeLayout(false); + MenuStrip.PerformLayout(); ResumeLayout(false); PerformLayout(); } @@ -226,7 +226,7 @@ partial class StartForm private Button SelectAllButton; private Button DeselectAllButton; private Controls.ProcessorCollectionControl ProcessorCollectionControl; - private MenuStrip MainMenuStrip; + private MenuStrip MenuStrip; private ToolStripMenuItem FileToolStripMenuItem; private ToolStripMenuItem ExitToolStripMenuItem; private ToolStripMenuItem WindowsToolStripMenuItem; diff --git a/Pf2eDataScrapper/Forms/StartForm.resx b/Pf2eDataScrapper/Forms/StartForm.resx index fc37e2c..2099966 100644 --- a/Pf2eDataScrapper/Forms/StartForm.resx +++ b/Pf2eDataScrapper/Forms/StartForm.resx @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 17, 17 \ No newline at end of file diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index d035a3c..9121e01 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -7,7 +7,6 @@ using Pf2eDataScrapper.Processors; using Pf2eModel; using Pf2eModel.Model; using Pf2eModel.Model.Base; -using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; @@ -59,17 +58,10 @@ internal abstract class AbstractEntityProcessor( /// public override void Process() { - if (Input.GetOptional("remaster_id") is not null) - { - throw new LegacyItemException(Input.GetOptional("name", "")); - } - AddEntity(); AddSource(); AddExternal(); AddTraits(); - - AddLegacy(); } /// @@ -229,29 +221,5 @@ internal abstract class AbstractEntityProcessor( } } - /// - /// Добавление устаревших сущностей. - /// - protected virtual void AddLegacy() - { - JArray? legacyIdArray = Input.GetOptional("legacy_id"); - - if (legacyIdArray is null || legacyIdArray.Count == 0) - { - return; - } - - string[] legacyIds = legacyIdArray.ToArray().Select(t => t.ToString()).ToArray(); - List legacyEntities = Context - .LegacyEntityExternals.Where(lee => legacyIds.Contains(lee.ExternalId)) - .Select(lee => lee.LegacyEntity) - .ToList(); - - foreach (LegacyEntity legacyEntity in legacyEntities) - { - Entity.LegacyEntities.Add(legacyEntity); - } - } - #endregion } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs deleted file mode 100644 index e376d9b..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/LegacyProcessor.cs +++ /dev/null @@ -1,111 +0,0 @@ -using Newtonsoft.Json.Linq; -using Pf2eDataScrapper.App; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; -using Pf2eDataScrapper.Processors; -using Pf2eModel; -using Pf2eModel.Model.Base.Legacy; -using Pf2eModel.Model.Common.I18n; -using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper; - -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; - -[ProcessorCollection( - "aonprd:legacy", - InnerType = null, - CompendiumType = ProcessorCompendiumEnum.Aonprd, - Description = "Создание ссылок на устаревшие сущности", - Group = ProcessorGroupEnum.AonprdPreparation, - Parallel = true -)] -[ProcessorDependencies(["aonprd:source", "aonprd:external"])] -internal class LegacyProcessor( - ProcessorInput input, - AonprdBuilderGlobals globals, - StringHelper stringHelper -) : AbstractInputProcessor(input) -{ - public Pf2eDbContext Context { get; } = new(); - public AonprdBuilderGlobals Globals { get; } = globals; - public StringHelper StringHelper { get; } = stringHelper; - - private LegacyEntity LegacyEntity { get; set; } = null!; - - public override void Process() - { - if (Input.GetOptional("remaster_id") is null) - { - throw new SilentSkipItemException(); - } - - LegacyEntity = new() - { - Name = { [LocaleType.English] = Input.GetOptional("name", "sidebar-29") }, - }; - - AddSource(); - AddExternal(); - } - - /// - /// Генерация источников сущности. - /// - protected virtual void AddSource() - { - string[] sourceNames = Input - .GetRequired("source") - .Select(token => token.ToString()) - .ToArray(); - - if (sourceNames.Length == 0) - { - throw new( - $"Source for entity with id \"{LegacyEntity.Name[LocaleType.English]}\" " - + $"(Aonprd id \"{Input.GetRequired("id")}\") is not found" - ); - } - /* - Dictionary sourceIds = Context - .Sources.AsNoTracking() - .Where(s => sourceNames.Contains(s.Name)) - .ToDictionary(s => s.Name, s => s.Id); - */ - foreach (string sourceName in sourceNames) - { - LegacyEntitySource entitySource = new() - { - LegacyEntity = LegacyEntity, - SourceId = Globals.SourceIds[sourceName], - Suffix = StringHelper.ResolveSource(Input.GetRequired("source_markdown")), - IsRemaster = Input.GetOptional("legacy_id") is not null, - }; - - Context.LegacyEntitySources.Add(entitySource); - LegacyEntity.Sources.Add(entitySource); - } - } - - /// - /// Генерация внешних ссылок сущности. - /// - protected virtual void AddExternal() - { - LegacyEntityExternal external = new() - { - LegacyEntity = LegacyEntity, - TargetId = Globals.AonprdTarget.Id, - ExternalId = Input.GetRequired("id"), - Suffix = Input.GetRequired("url"), - Md5 = Input.Md5(), - }; - - Context.LegacyEntityExternals.Add(external); - } - - public override void Save() - { - Context.LegacyEntities.Add(LegacyEntity); - - Context.SaveChanges(false); - Context.Dispose(); - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs index fb5cd39..db6443d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -2,7 +2,6 @@ using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; -using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; @@ -37,11 +36,6 @@ internal class TraitProcessor( Context.Traits.Add(Object); Globals.TraitIds[Entity.Name[LocaleType.English]] = Entity.Id; - - foreach (LegacyEntity legacyEntity in Entity.LegacyEntities) - { - Globals.TraitIds[legacyEntity.Name[LocaleType.English]] = Entity.Id; - } } /// diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs index 829fc9d..3c25f0f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs @@ -56,7 +56,7 @@ internal abstract class FoundryEnAbstractProcessor( { Type = LocaleType.English, Alias = Entity.Group, - EntityId = Entity.Id, + LegacyId = Entity.Id, Entity = Entity }; Context.EntityAliases.Add(alias); diff --git a/Pf2eModel/Migrations/20250514120355_1.Designer.cs b/Pf2eModel/Migrations/20250515054227_1.Designer.cs similarity index 94% rename from Pf2eModel/Migrations/20250514120355_1.Designer.cs rename to Pf2eModel/Migrations/20250515054227_1.Designer.cs index f1f0fcb..c3d5113 100644 --- a/Pf2eModel/Migrations/20250514120355_1.Designer.cs +++ b/Pf2eModel/Migrations/20250515054227_1.Designer.cs @@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum; namespace Pf2eModel.Migrations { [DbContext(typeof(Pf2eDbContext))] - [Migration("20250514120355_1")] + [Migration("20250515054227_1")] partial class _1 { /// @@ -86,25 +86,6 @@ namespace Pf2eModel.Migrations b.ToTable("entity_keyword", (string)null); }); - modelBuilder.Entity("EntityLegacyEntity", b => - { - b.Property("LegacyEntitiesId") - .HasColumnType("integer") - .HasColumnName("legacy_entities_id"); - - b.Property("RemasterEntitiesId") - .HasColumnType("integer") - .HasColumnName("remaster_entities_id"); - - b.HasKey("LegacyEntitiesId", "RemasterEntitiesId") - .HasName("pk_entity_legacy_entity"); - - b.HasIndex("RemasterEntitiesId") - .HasDatabaseName("ix_entity_legacy_entity_remaster_entities_id"); - - b.ToTable("entity_legacy_entity", (string)null); - }); - modelBuilder.Entity("EntityTrait", b => { b.Property("EntitiesId") @@ -194,6 +175,40 @@ namespace Pf2eModel.Migrations b.ToTable("entities", (string)null); }); + modelBuilder.Entity("Pf2eModel.Model.Base.EntityAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasColumnOrder(1) + .HasComment("Идентификатор"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EntityId") + .HasColumnType("integer") + .HasColumnName("entity_id") + .HasColumnOrder(2) + .HasComment("Связанная сущность"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("name") + .HasColumnOrder(3) + .HasComment("Алиас"); + + b.HasKey("Id") + .HasName("pk_entity_aliases"); + + b.HasIndex("EntityId") + .HasDatabaseName("ix_entity_aliases_entity_id"); + + b.ToTable("entity_aliases", (string)null); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Keywords.Keyword", b => { b.Property("Id") @@ -259,121 +274,52 @@ namespace Pf2eModel.Migrations b.ToTable("keyword_aliases", (string)null); }); - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b => + modelBuilder.Entity("Pf2eModel.Model.Base.LegacyRemaster", b => { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("LegacyId") .HasColumnType("integer") - .HasColumnName("id") - .HasColumnOrder(1); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("name") - .HasColumnOrder(2) - .HasComment("Отображаемое имя"); - - b.HasKey("Id") - .HasName("pk_legacy_entities"); - - b.ToTable("legacy_entities", (string)null); - }); - - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b => - { - b.Property("LegacyEntityId") - .HasColumnType("integer") - .HasColumnName("legacy_entity_id") + .HasColumnName("legacy_id") .HasColumnOrder(1) .HasComment("Идентификатор сущности"); - b.Property("TargetId") + b.Property("RemasterId") .HasColumnType("integer") - .HasColumnName("target_id") + .HasColumnName("remaster_id") .HasColumnOrder(2) .HasComment("Идентификатор цели"); - b.Property("ExternalId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("external_id") - .HasColumnOrder(3); - - b.Property("Md5") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("md5") - .HasColumnOrder(5) - .HasComment("Контрольная сумма объекта"); - - b.Property("Suffix") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("suffix") - .HasColumnOrder(4) - .HasComment("Часть URL до объекта. Для источников Foundry - полный UUID объекта"); - - b.HasKey("LegacyEntityId", "TargetId") - .HasName("pk_legacy_entity_externals"); - - b.HasIndex("TargetId") - .HasDatabaseName("ix_legacy_entity_externals_target_id"); - - b.ToTable("legacy_entity_externals", (string)null); - }); - - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("LegacyEntitiesId") .HasColumnType("integer") - .HasColumnName("id") - .HasColumnOrder(0) - .HasComment("Идентификатор"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IsRemaster") - .HasColumnType("boolean") - .HasColumnName("is_remaster") - .HasColumnOrder(4) - .HasComment("Флаг, указывающий, что объект является частью ремастера"); + .HasColumnName("legacy_entities_id"); b.Property("LegacyEntityId") .HasColumnType("integer") - .HasColumnName("legacy_entity_id") - .HasColumnOrder(1) - .HasComment("Идентификатор устаревшей сущности"); + .HasColumnName("legacy_entity_id"); - b.Property("SourceId") + b.Property("RemasterEntitiesId") .HasColumnType("integer") - .HasColumnName("source_id") - .HasColumnOrder(2) - .HasComment("Идентификатор источника"); + .HasColumnName("remaster_entities_id"); - b.Property("Suffix") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("suffix") - .HasColumnOrder(3) - .HasComment("Номер страницы, эррата и т.д."); + b.Property("RemasterEntityId") + .HasColumnType("integer") + .HasColumnName("remaster_entity_id"); - b.HasKey("Id") - .HasName("pk_legacy_entity_sources"); + b.HasKey("LegacyId", "RemasterId") + .HasName("pk_legacy_remasters"); + + b.HasIndex("LegacyEntitiesId") + .HasDatabaseName("ix_legacy_remasters_legacy_entities_id"); b.HasIndex("LegacyEntityId") - .HasDatabaseName("ix_legacy_entity_sources_legacy_entity_id"); + .HasDatabaseName("ix_legacy_remasters_legacy_entity_id"); - b.HasIndex("SourceId") - .HasDatabaseName("ix_legacy_entity_sources_source_id"); + b.HasIndex("RemasterEntitiesId") + .HasDatabaseName("ix_legacy_remasters_remaster_entities_id"); - b.ToTable("legacy_entity_sources", (string)null); + b.HasIndex("RemasterEntityId") + .HasDatabaseName("ix_legacy_remasters_remaster_entity_id"); + + b.ToTable("legacy_remasters", (string)null); }); modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b => @@ -1984,23 +1930,6 @@ namespace Pf2eModel.Migrations .HasConstraintName("fk_entity_keyword_keywords_keywords_id"); }); - modelBuilder.Entity("EntityLegacyEntity", b => - { - b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", null) - .WithMany() - .HasForeignKey("LegacyEntitiesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entity_legacy_entity_legacy_entities_legacy_entities_id"); - - b.HasOne("Pf2eModel.Model.Base.Entity", null) - .WithMany() - .HasForeignKey("RemasterEntitiesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entity_legacy_entity_entities_remaster_entities_id"); - }); - modelBuilder.Entity("EntityTrait", b => { b.HasOne("Pf2eModel.Model.Base.Entity", null) @@ -2018,6 +1947,18 @@ namespace Pf2eModel.Migrations .HasConstraintName("fk_entity_trait_traits_traits_id"); }); + modelBuilder.Entity("Pf2eModel.Model.Base.EntityAlias", b => + { + b.HasOne("Pf2eModel.Model.Base.Entity", "Entity") + .WithMany("Aliases") + .HasForeignKey("EntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_entity_aliases_entities_entity_id"); + + b.Navigation("Entity"); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Keywords.KeywordAlias", b => { b.HasOne("Pf2eModel.Model.Base.Keywords.Keyword", "Keyword") @@ -2030,46 +1971,39 @@ namespace Pf2eModel.Migrations b.Navigation("Keyword"); }); - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b => + modelBuilder.Entity("Pf2eModel.Model.Base.LegacyRemaster", b => { - b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity") - .WithMany("Externals") + b.HasOne("Pf2eModel.Model.Base.Entity", null) + .WithMany() + .HasForeignKey("LegacyEntitiesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_remasters_entities_legacy_entities_id"); + + b.HasOne("Pf2eModel.Model.Base.Entity", "LegacyEntity") + .WithMany() .HasForeignKey("LegacyEntityId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_legacy_entity_externals_legacy_entities_legacy_entity_id"); + .HasConstraintName("fk_legacy_remasters_entities_legacy_entity_id"); - b.HasOne("Pf2eModel.Model.Base.Targets.ExternalTarget", "Target") - .WithMany("LegacyEntityExternals") - .HasForeignKey("TargetId") + b.HasOne("Pf2eModel.Model.Base.Entity", null) + .WithMany() + .HasForeignKey("RemasterEntitiesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_legacy_entity_externals_external_targets_target_id"); + .HasConstraintName("fk_legacy_remasters_entities_remaster_entities_id"); + + b.HasOne("Pf2eModel.Model.Base.Entity", "RemasterEntity") + .WithMany() + .HasForeignKey("RemasterEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_remasters_entities_remaster_entity_id"); b.Navigation("LegacyEntity"); - b.Navigation("Target"); - }); - - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b => - { - b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity") - .WithMany("Sources") - .HasForeignKey("LegacyEntityId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_legacy_entity_sources_legacy_entities_legacy_entity_id"); - - b.HasOne("Pf2eModel.Model.Base.Source.Source", "Source") - .WithMany("LegacyEntitySources") - .HasForeignKey("SourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_legacy_entity_sources_sources_source_id"); - - b.Navigation("LegacyEntity"); - - b.Navigation("Source"); + b.Navigation("RemasterEntity"); }); modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b => @@ -2700,6 +2634,8 @@ namespace Pf2eModel.Migrations modelBuilder.Entity("Pf2eModel.Model.Base.Entity", b => { + b.Navigation("Aliases"); + b.Navigation("Externals"); b.Navigation("Sources"); @@ -2710,25 +2646,14 @@ namespace Pf2eModel.Migrations b.Navigation("Aliases"); }); - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b => - { - b.Navigation("Externals"); - - b.Navigation("Sources"); - }); - modelBuilder.Entity("Pf2eModel.Model.Base.Source.Source", b => { b.Navigation("EntitySources"); - - b.Navigation("LegacyEntitySources"); }); modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b => { b.Navigation("EntityExternals"); - - b.Navigation("LegacyEntityExternals"); }); modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b => diff --git a/Pf2eModel/Migrations/20250514120355_1.cs b/Pf2eModel/Migrations/20250515054227_1.cs similarity index 94% rename from Pf2eModel/Migrations/20250514120355_1.cs rename to Pf2eModel/Migrations/20250515054227_1.cs index fefb752..3eee44b 100644 --- a/Pf2eModel/Migrations/20250514120355_1.cs +++ b/Pf2eModel/Migrations/20250515054227_1.cs @@ -101,19 +101,6 @@ namespace Pf2eModel.Migrations table.PrimaryKey("pk_keywords", x => x.id); }); - migrationBuilder.CreateTable( - name: "legacy_entities", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - name = table.Column(type: "jsonb", nullable: false, comment: "Отображаемое имя") - }, - constraints: table => - { - table.PrimaryKey("pk_legacy_entities", x => x.id); - }); - migrationBuilder.CreateTable( name: "sources", columns: table => new @@ -261,6 +248,26 @@ namespace Pf2eModel.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "entity_aliases", + columns: table => new + { + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + entity_id = table.Column(type: "integer", nullable: false, comment: "Связанная сущность"), + name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Алиас") + }, + constraints: table => + { + table.PrimaryKey("pk_entity_aliases", x => x.id); + table.ForeignKey( + name: "fk_entity_aliases_entities_entity_id", + column: x => x.entity_id, + principalTable: "entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "feats", columns: table => new @@ -304,6 +311,46 @@ namespace Pf2eModel.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "legacy_remasters", + columns: table => new + { + legacy_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор сущности"), + remaster_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор цели"), + legacy_entity_id = table.Column(type: "integer", nullable: false), + remaster_entity_id = table.Column(type: "integer", nullable: false), + legacy_entities_id = table.Column(type: "integer", nullable: false), + remaster_entities_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_legacy_remasters", x => new { x.legacy_id, x.remaster_id }); + table.ForeignKey( + name: "fk_legacy_remasters_entities_legacy_entities_id", + column: x => x.legacy_entities_id, + principalTable: "entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_legacy_remasters_entities_legacy_entity_id", + column: x => x.legacy_entity_id, + principalTable: "entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_legacy_remasters_entities_remaster_entities_id", + column: x => x.remaster_entities_id, + principalTable: "entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_legacy_remasters_entities_remaster_entity_id", + column: x => x.remaster_entity_id, + principalTable: "entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "rituals", columns: table => new @@ -477,57 +524,6 @@ namespace Pf2eModel.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "entity_legacy_entity", - columns: table => new - { - legacy_entities_id = table.Column(type: "integer", nullable: false), - remaster_entities_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_entity_legacy_entity", x => new { x.legacy_entities_id, x.remaster_entities_id }); - table.ForeignKey( - name: "fk_entity_legacy_entity_entities_remaster_entities_id", - column: x => x.remaster_entities_id, - principalTable: "entities", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_entity_legacy_entity_legacy_entities_legacy_entities_id", - column: x => x.legacy_entities_id, - principalTable: "legacy_entities", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "legacy_entity_externals", - columns: table => new - { - legacy_entity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор сущности"), - target_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор цели"), - external_id = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - suffix = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Часть URL до объекта. Для источников Foundry - полный UUID объекта"), - md5 = table.Column(type: "character varying(32)", maxLength: 32, nullable: false, comment: "Контрольная сумма объекта") - }, - constraints: table => - { - table.PrimaryKey("pk_legacy_entity_externals", x => new { x.legacy_entity_id, x.target_id }); - table.ForeignKey( - name: "fk_legacy_entity_externals_external_targets_target_id", - column: x => x.target_id, - principalTable: "external_targets", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_legacy_entity_externals_legacy_entities_legacy_entity_id", - column: x => x.legacy_entity_id, - principalTable: "legacy_entities", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "entity_sources", columns: table => new @@ -556,34 +552,6 @@ namespace Pf2eModel.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "legacy_entity_sources", - columns: table => new - { - id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - legacy_entity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор устаревшей сущности"), - source_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор источника"), - suffix = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Номер страницы, эррата и т.д."), - is_remaster = table.Column(type: "boolean", nullable: false, comment: "Флаг, указывающий, что объект является частью ремастера") - }, - constraints: table => - { - table.PrimaryKey("pk_legacy_entity_sources", x => x.id); - table.ForeignKey( - name: "fk_legacy_entity_sources_legacy_entities_legacy_entity_id", - column: x => x.legacy_entity_id, - principalTable: "legacy_entities", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_legacy_entity_sources_sources_source_id", - column: x => x.source_id, - principalTable: "sources", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "ancestry_boosts", columns: table => new @@ -1343,6 +1311,11 @@ namespace Pf2eModel.Migrations table: "deity_weapons", column: "deity_id"); + migrationBuilder.CreateIndex( + name: "ix_entity_aliases_entity_id", + table: "entity_aliases", + column: "entity_id"); + migrationBuilder.CreateIndex( name: "ix_entity_externals_target_id", table: "entity_externals", @@ -1353,11 +1326,6 @@ namespace Pf2eModel.Migrations table: "entity_keyword", column: "keywords_id"); - migrationBuilder.CreateIndex( - name: "ix_entity_legacy_entity_remaster_entities_id", - table: "entity_legacy_entity", - column: "remaster_entities_id"); - migrationBuilder.CreateIndex( name: "ix_entity_sources_entity_id", table: "entity_sources", @@ -1394,19 +1362,24 @@ namespace Pf2eModel.Migrations column: "entity_id"); migrationBuilder.CreateIndex( - name: "ix_legacy_entity_externals_target_id", - table: "legacy_entity_externals", - column: "target_id"); + name: "ix_legacy_remasters_legacy_entities_id", + table: "legacy_remasters", + column: "legacy_entities_id"); migrationBuilder.CreateIndex( - name: "ix_legacy_entity_sources_legacy_entity_id", - table: "legacy_entity_sources", + name: "ix_legacy_remasters_legacy_entity_id", + table: "legacy_remasters", column: "legacy_entity_id"); migrationBuilder.CreateIndex( - name: "ix_legacy_entity_sources_source_id", - table: "legacy_entity_sources", - column: "source_id"); + name: "ix_legacy_remasters_remaster_entities_id", + table: "legacy_remasters", + column: "remaster_entities_id"); + + migrationBuilder.CreateIndex( + name: "ix_legacy_remasters_remaster_entity_id", + table: "legacy_remasters", + column: "remaster_entity_id"); migrationBuilder.CreateIndex( name: "ix_ritual_checks_ritual_id", @@ -1518,15 +1491,15 @@ namespace Pf2eModel.Migrations migrationBuilder.DropTable( name: "deity_weapons"); + migrationBuilder.DropTable( + name: "entity_aliases"); + migrationBuilder.DropTable( name: "entity_externals"); migrationBuilder.DropTable( name: "entity_keyword"); - migrationBuilder.DropTable( - name: "entity_legacy_entity"); - migrationBuilder.DropTable( name: "entity_sources"); @@ -1546,10 +1519,7 @@ namespace Pf2eModel.Migrations name: "keyword_aliases"); migrationBuilder.DropTable( - name: "legacy_entity_externals"); - - migrationBuilder.DropTable( - name: "legacy_entity_sources"); + name: "legacy_remasters"); migrationBuilder.DropTable( name: "ritual_areas"); @@ -1587,6 +1557,12 @@ namespace Pf2eModel.Migrations migrationBuilder.DropTable( name: "deities"); + migrationBuilder.DropTable( + name: "external_targets"); + + migrationBuilder.DropTable( + name: "sources"); + migrationBuilder.DropTable( name: "traits"); @@ -1596,15 +1572,6 @@ namespace Pf2eModel.Migrations migrationBuilder.DropTable( name: "keywords"); - migrationBuilder.DropTable( - name: "external_targets"); - - migrationBuilder.DropTable( - name: "legacy_entities"); - - migrationBuilder.DropTable( - name: "sources"); - migrationBuilder.DropTable( name: "rituals"); diff --git a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs index 77fe09f..a353789 100644 --- a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs +++ b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs @@ -83,25 +83,6 @@ namespace Pf2eModel.Migrations b.ToTable("entity_keyword", (string)null); }); - modelBuilder.Entity("EntityLegacyEntity", b => - { - b.Property("LegacyEntitiesId") - .HasColumnType("integer") - .HasColumnName("legacy_entities_id"); - - b.Property("RemasterEntitiesId") - .HasColumnType("integer") - .HasColumnName("remaster_entities_id"); - - b.HasKey("LegacyEntitiesId", "RemasterEntitiesId") - .HasName("pk_entity_legacy_entity"); - - b.HasIndex("RemasterEntitiesId") - .HasDatabaseName("ix_entity_legacy_entity_remaster_entities_id"); - - b.ToTable("entity_legacy_entity", (string)null); - }); - modelBuilder.Entity("EntityTrait", b => { b.Property("EntitiesId") @@ -191,6 +172,40 @@ namespace Pf2eModel.Migrations b.ToTable("entities", (string)null); }); + modelBuilder.Entity("Pf2eModel.Model.Base.EntityAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasColumnOrder(1) + .HasComment("Идентификатор"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EntityId") + .HasColumnType("integer") + .HasColumnName("entity_id") + .HasColumnOrder(2) + .HasComment("Связанная сущность"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("name") + .HasColumnOrder(3) + .HasComment("Алиас"); + + b.HasKey("Id") + .HasName("pk_entity_aliases"); + + b.HasIndex("EntityId") + .HasDatabaseName("ix_entity_aliases_entity_id"); + + b.ToTable("entity_aliases", (string)null); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Keywords.Keyword", b => { b.Property("Id") @@ -256,121 +271,52 @@ namespace Pf2eModel.Migrations b.ToTable("keyword_aliases", (string)null); }); - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b => + modelBuilder.Entity("Pf2eModel.Model.Base.LegacyRemaster", b => { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("LegacyId") .HasColumnType("integer") - .HasColumnName("id") - .HasColumnOrder(1); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("name") - .HasColumnOrder(2) - .HasComment("Отображаемое имя"); - - b.HasKey("Id") - .HasName("pk_legacy_entities"); - - b.ToTable("legacy_entities", (string)null); - }); - - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b => - { - b.Property("LegacyEntityId") - .HasColumnType("integer") - .HasColumnName("legacy_entity_id") + .HasColumnName("legacy_id") .HasColumnOrder(1) .HasComment("Идентификатор сущности"); - b.Property("TargetId") + b.Property("RemasterId") .HasColumnType("integer") - .HasColumnName("target_id") + .HasColumnName("remaster_id") .HasColumnOrder(2) .HasComment("Идентификатор цели"); - b.Property("ExternalId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("external_id") - .HasColumnOrder(3); - - b.Property("Md5") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("md5") - .HasColumnOrder(5) - .HasComment("Контрольная сумма объекта"); - - b.Property("Suffix") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("suffix") - .HasColumnOrder(4) - .HasComment("Часть URL до объекта. Для источников Foundry - полный UUID объекта"); - - b.HasKey("LegacyEntityId", "TargetId") - .HasName("pk_legacy_entity_externals"); - - b.HasIndex("TargetId") - .HasDatabaseName("ix_legacy_entity_externals_target_id"); - - b.ToTable("legacy_entity_externals", (string)null); - }); - - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("LegacyEntitiesId") .HasColumnType("integer") - .HasColumnName("id") - .HasColumnOrder(0) - .HasComment("Идентификатор"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IsRemaster") - .HasColumnType("boolean") - .HasColumnName("is_remaster") - .HasColumnOrder(4) - .HasComment("Флаг, указывающий, что объект является частью ремастера"); + .HasColumnName("legacy_entities_id"); b.Property("LegacyEntityId") .HasColumnType("integer") - .HasColumnName("legacy_entity_id") - .HasColumnOrder(1) - .HasComment("Идентификатор устаревшей сущности"); + .HasColumnName("legacy_entity_id"); - b.Property("SourceId") + b.Property("RemasterEntitiesId") .HasColumnType("integer") - .HasColumnName("source_id") - .HasColumnOrder(2) - .HasComment("Идентификатор источника"); + .HasColumnName("remaster_entities_id"); - b.Property("Suffix") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("suffix") - .HasColumnOrder(3) - .HasComment("Номер страницы, эррата и т.д."); + b.Property("RemasterEntityId") + .HasColumnType("integer") + .HasColumnName("remaster_entity_id"); - b.HasKey("Id") - .HasName("pk_legacy_entity_sources"); + b.HasKey("LegacyId", "RemasterId") + .HasName("pk_legacy_remasters"); + + b.HasIndex("LegacyEntitiesId") + .HasDatabaseName("ix_legacy_remasters_legacy_entities_id"); b.HasIndex("LegacyEntityId") - .HasDatabaseName("ix_legacy_entity_sources_legacy_entity_id"); + .HasDatabaseName("ix_legacy_remasters_legacy_entity_id"); - b.HasIndex("SourceId") - .HasDatabaseName("ix_legacy_entity_sources_source_id"); + b.HasIndex("RemasterEntitiesId") + .HasDatabaseName("ix_legacy_remasters_remaster_entities_id"); - b.ToTable("legacy_entity_sources", (string)null); + b.HasIndex("RemasterEntityId") + .HasDatabaseName("ix_legacy_remasters_remaster_entity_id"); + + b.ToTable("legacy_remasters", (string)null); }); modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b => @@ -1981,23 +1927,6 @@ namespace Pf2eModel.Migrations .HasConstraintName("fk_entity_keyword_keywords_keywords_id"); }); - modelBuilder.Entity("EntityLegacyEntity", b => - { - b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", null) - .WithMany() - .HasForeignKey("LegacyEntitiesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entity_legacy_entity_legacy_entities_legacy_entities_id"); - - b.HasOne("Pf2eModel.Model.Base.Entity", null) - .WithMany() - .HasForeignKey("RemasterEntitiesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entity_legacy_entity_entities_remaster_entities_id"); - }); - modelBuilder.Entity("EntityTrait", b => { b.HasOne("Pf2eModel.Model.Base.Entity", null) @@ -2015,6 +1944,18 @@ namespace Pf2eModel.Migrations .HasConstraintName("fk_entity_trait_traits_traits_id"); }); + modelBuilder.Entity("Pf2eModel.Model.Base.EntityAlias", b => + { + b.HasOne("Pf2eModel.Model.Base.Entity", "Entity") + .WithMany("Aliases") + .HasForeignKey("EntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_entity_aliases_entities_entity_id"); + + b.Navigation("Entity"); + }); + modelBuilder.Entity("Pf2eModel.Model.Base.Keywords.KeywordAlias", b => { b.HasOne("Pf2eModel.Model.Base.Keywords.Keyword", "Keyword") @@ -2027,46 +1968,39 @@ namespace Pf2eModel.Migrations b.Navigation("Keyword"); }); - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b => + modelBuilder.Entity("Pf2eModel.Model.Base.LegacyRemaster", b => { - b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity") - .WithMany("Externals") + b.HasOne("Pf2eModel.Model.Base.Entity", null) + .WithMany() + .HasForeignKey("LegacyEntitiesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_remasters_entities_legacy_entities_id"); + + b.HasOne("Pf2eModel.Model.Base.Entity", "LegacyEntity") + .WithMany() .HasForeignKey("LegacyEntityId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_legacy_entity_externals_legacy_entities_legacy_entity_id"); + .HasConstraintName("fk_legacy_remasters_entities_legacy_entity_id"); - b.HasOne("Pf2eModel.Model.Base.Targets.ExternalTarget", "Target") - .WithMany("LegacyEntityExternals") - .HasForeignKey("TargetId") + b.HasOne("Pf2eModel.Model.Base.Entity", null) + .WithMany() + .HasForeignKey("RemasterEntitiesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_legacy_entity_externals_external_targets_target_id"); + .HasConstraintName("fk_legacy_remasters_entities_remaster_entities_id"); + + b.HasOne("Pf2eModel.Model.Base.Entity", "RemasterEntity") + .WithMany() + .HasForeignKey("RemasterEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_legacy_remasters_entities_remaster_entity_id"); b.Navigation("LegacyEntity"); - b.Navigation("Target"); - }); - - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b => - { - b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity") - .WithMany("Sources") - .HasForeignKey("LegacyEntityId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_legacy_entity_sources_legacy_entities_legacy_entity_id"); - - b.HasOne("Pf2eModel.Model.Base.Source.Source", "Source") - .WithMany("LegacyEntitySources") - .HasForeignKey("SourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_legacy_entity_sources_sources_source_id"); - - b.Navigation("LegacyEntity"); - - b.Navigation("Source"); + b.Navigation("RemasterEntity"); }); modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b => @@ -2697,6 +2631,8 @@ namespace Pf2eModel.Migrations modelBuilder.Entity("Pf2eModel.Model.Base.Entity", b => { + b.Navigation("Aliases"); + b.Navigation("Externals"); b.Navigation("Sources"); @@ -2707,25 +2643,14 @@ namespace Pf2eModel.Migrations b.Navigation("Aliases"); }); - modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b => - { - b.Navigation("Externals"); - - b.Navigation("Sources"); - }); - modelBuilder.Entity("Pf2eModel.Model.Base.Source.Source", b => { b.Navigation("EntitySources"); - - b.Navigation("LegacyEntitySources"); }); modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b => { b.Navigation("EntityExternals"); - - b.Navigation("LegacyEntityExternals"); }); modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b => diff --git a/Pf2eModel/Model/Base/Entity.cs b/Pf2eModel/Model/Base/Entity.cs index ecc28fc..823839c 100644 --- a/Pf2eModel/Model/Base/Entity.cs +++ b/Pf2eModel/Model/Base/Entity.cs @@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Base.Keywords; -using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common.I18n; @@ -81,6 +80,11 @@ public class Entity [Comment("Флаг, указывающий, что сущность был проверен и не требует автоматической перезаписи")] public bool? IsChecked { get; set; } = false; + /// + /// Коллекция алиасов + /// + public ICollection Aliases { get; set; } = []; + /// /// Коллекция признаков, связанных с сущностью. /// @@ -104,5 +108,10 @@ public class Entity /// /// Коллекция устаревших сущностей. /// - public ICollection LegacyEntities { get; set; } = []; + public ICollection LegacyEntities { get; set; } = []; + + /// + /// Коллекция обновлённых сущностей. + /// + public ICollection RemasterEntities { get; set; } = []; } diff --git a/Pf2eModel/Model/Base/EntityAlias.cs b/Pf2eModel/Model/Base/EntityAlias.cs new file mode 100644 index 0000000..b91132f --- /dev/null +++ b/Pf2eModel/Model/Base/EntityAlias.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Pf2eModel.Model.Base; + +public class EntityAlias +{ + /// + /// Идентификатор источника. + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Column(Order = 1)] + [Comment("Идентификатор")] + public int Id { get; set; } + + /// + /// Идентификатор связанной сущности. + /// + [Column(Order = 2)] + [Comment("Связанная сущность")] + public int EntityId { get; set; } + + /// + /// Отображаемое имя источника. + /// + [Column(Order = 3)] + [MaxLength(Pf2eDbContext.NameLength)] + [Comment("Алиас")] + public string Name { get; set; } = null!; + + /// + /// Связанная + /// + public Entity Entity { get; set; } = null!; +} diff --git a/Pf2eModel/Model/Base/Legacy/LegacyEntity.cs b/Pf2eModel/Model/Base/Legacy/LegacyEntity.cs deleted file mode 100644 index af1251a..0000000 --- a/Pf2eModel/Model/Base/Legacy/LegacyEntity.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; -using Pf2eModel.Model.Common.I18n; - -namespace Pf2eModel.Model.Base.Legacy; - -/// -/// Информация об устаревшей сущности -/// -public class LegacyEntity -{ - /// - /// Идентификатор сущности. - /// - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [Column(Order = 1)] - public int Id { get; set; } - - /// - /// Отображаемое имя сущности. - /// - [Column(Order = 2)] - [Comment("Отображаемое имя")] - public LocaleString Name { get; set; } = []; - - /// - /// Коллекция обновлённых сущностей. - /// - public ICollection RemasterEntities { get; set; } = []; - - /// - /// Коллекция источников, связанных с сущностью. - /// - public ICollection Sources { get; set; } = []; - - /// - /// Коллекция внешних ссылок, связанных с сущностью. - /// - public ICollection Externals { get; set; } = []; -} diff --git a/Pf2eModel/Model/Base/Legacy/LegacyEntityExternal.cs b/Pf2eModel/Model/Base/Legacy/LegacyEntityExternal.cs deleted file mode 100644 index 962635d..0000000 --- a/Pf2eModel/Model/Base/Legacy/LegacyEntityExternal.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; -using Pf2eModel.Model.Base.Targets; - -namespace Pf2eModel.Model.Base.Legacy; - -/// -/// Внешняя ссылка сущности. -/// -[PrimaryKey(nameof(LegacyEntityId), nameof(TargetId))] -public class LegacyEntityExternal -{ - /// - /// Идентификатор сущности. - /// - [Column(Order = 1)] - [Comment("Идентификатор сущности")] - public int LegacyEntityId { get; set; } - - /// - /// Идентификатор цели. - /// - [Column(Order = 2)] - [Comment("Идентификатор цели")] - public int TargetId { get; set; } - - [Column(Order = 3)] - [MaxLength(255)] - public string? ExternalId { get; set; } = null; - - /// - /// Конечная часть URL внешней ссылки. - /// Для источников Foundry - полный UUID объекта. - /// - [Column(Order = 4)] - [MaxLength(255)] - [Comment("Часть URL до объекта. Для источников Foundry - полный UUID объекта")] - public string Suffix { get; set; } = ""; - - /// - /// Контрольная сумма объекта. - /// - [Column(Order = 5)] - [MaxLength(32)] - [Comment("Контрольная сумма объекта")] - public string Md5 { get; set; } = null!; - - /// - /// Сущность, которая ссылается на внешнюю ссылку. - /// - public LegacyEntity LegacyEntity { get; set; } = null!; - - /// - /// Цель внешней ссылки. - /// - public ExternalTarget Target { get; set; } = null!; -} diff --git a/Pf2eModel/Model/Base/Legacy/LegacyEntitySource.cs b/Pf2eModel/Model/Base/Legacy/LegacyEntitySource.cs deleted file mode 100644 index a3d959d..0000000 --- a/Pf2eModel/Model/Base/Legacy/LegacyEntitySource.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; - -namespace Pf2eModel.Model.Base.Legacy; - -/// -/// Связь между сущностью и источником. -/// -public class LegacyEntitySource -{ - /// - /// Идентификатор связи. - /// - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [Column(Order = 0)] - [Comment("Идентификатор")] - public int Id { get; set; } - - /// - /// Идентификатор устаревшей сущности. - /// - [Column(Order = 1)] - [Comment("Идентификатор устаревшей сущности")] - public int LegacyEntityId { get; set; } - - /// - /// Идентификатор источника. - /// - [Column(Order = 2)] - [Comment("Идентификатор источника")] - public int SourceId { get; set; } - - /// - /// Дополнительная информация о источнике, например, номер страницы, номер тома и т.д. - /// - [Column(Order = 3)] - [MaxLength(255)] - [Comment("Номер страницы, эррата и т.д.")] - public string Suffix { get; set; } = null!; - - /// - /// Флаг, указывающий, что объект является частью ремастера. - /// - [Column(Order = 4)] - [Comment("Флаг, указывающий, что объект является частью ремастера")] - public bool IsRemaster { get; set; } - - /// - /// Сущность, к которой относится источник. - /// - public LegacyEntity LegacyEntity { get; set; } = null!; - - /// - /// Источник, к которому относится сущность. - /// - public Source.Source Source { get; set; } = null!; -} diff --git a/Pf2eModel/Model/Base/LegacyRemaster.cs b/Pf2eModel/Model/Base/LegacyRemaster.cs new file mode 100644 index 0000000..c41db87 --- /dev/null +++ b/Pf2eModel/Model/Base/LegacyRemaster.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Pf2eModel.Model.Base; + +/// +/// Связь устаревших и обновлённых сущностей +/// +[PrimaryKey(nameof(LegacyId), nameof(RemasterId))] +public class LegacyRemaster +{ + /// + /// Идентификатор устаревшей сущности. + /// + [Column(Order = 1)] + [Comment("Идентификатор сущности")] + public int LegacyId { get; set; } + + /// + /// Идентификатор обновлённой сущности. + /// + [Column(Order = 2)] + [Comment("Идентификатор цели")] + public int RemasterId { get; set; } + + /// + /// Устаревшая сущность. + /// + public Entity LegacyEntity { get; set; } = null!; + + /// + /// Обновлённая сущность. + /// + public Entity RemasterEntity { get; set; } = null!; +} diff --git a/Pf2eModel/Model/Base/Source/Source.cs b/Pf2eModel/Model/Base/Source/Source.cs index 6a88179..7f098f1 100644 --- a/Pf2eModel/Model/Base/Source/Source.cs +++ b/Pf2eModel/Model/Base/Source/Source.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; -using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Base.Source; @@ -46,9 +45,4 @@ public class Source /// Связанные сущности. /// public ICollection EntitySources { get; set; } = []; - - /// - /// Связанные сущности. - /// - public ICollection LegacyEntitySources { get; set; } = []; } diff --git a/Pf2eModel/Model/Base/Targets/ExternalTarget.cs b/Pf2eModel/Model/Base/Targets/ExternalTarget.cs index 6602b5b..44b74bb 100644 --- a/Pf2eModel/Model/Base/Targets/ExternalTarget.cs +++ b/Pf2eModel/Model/Base/Targets/ExternalTarget.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; -using Pf2eModel.Model.Base.Legacy; namespace Pf2eModel.Model.Base.Targets; @@ -47,9 +46,4 @@ public class ExternalTarget /// Коллекция сущностей, которые ссылаются на эту цель. /// public ICollection EntityExternals { get; set; } = []; - - /// - /// Коллекция устаревших сущностей, которые ссылаются на эту цель. - /// - public ICollection LegacyEntityExternals { get; set; } = []; } diff --git a/Pf2eModel/Pf2eDbContext.cs b/Pf2eModel/Pf2eDbContext.cs index 0d49ace..2adc7be 100644 --- a/Pf2eModel/Pf2eDbContext.cs +++ b/Pf2eModel/Pf2eDbContext.cs @@ -3,7 +3,6 @@ using dotenv.net.Utilities; using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Base; using Pf2eModel.Model.Base.Keywords; -using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common; @@ -78,6 +77,8 @@ public partial class Pf2eDbContext : DbContext public virtual DbSet Entities { get; set; } + public virtual DbSet EntityAliases { get; set; } + public virtual DbSet EntityExternals { get; set; } public virtual DbSet EntitySources { get; set; } @@ -98,11 +99,7 @@ public partial class Pf2eDbContext : DbContext public virtual DbSet Languages { get; set; } - public virtual DbSet LegacyEntities { get; set; } - - public virtual DbSet LegacyEntityExternals { get; set; } - - public virtual DbSet LegacyEntitySources { get; set; } + public virtual DbSet LegacyRemasters { get; set; } public virtual DbSet Rituals { get; set; } diff --git a/Pf2eModel/Pf2eDbContextModel.cs b/Pf2eModel/Pf2eDbContextModel.cs index 0d53019..1905681 100644 --- a/Pf2eModel/Pf2eDbContextModel.cs +++ b/Pf2eModel/Pf2eDbContextModel.cs @@ -6,7 +6,6 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Pf2eModel.Model; using Pf2eModel.Model.Base; using Pf2eModel.Model.Base.Keywords; -using Pf2eModel.Model.Base.Legacy; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Common; @@ -98,6 +97,13 @@ public partial class Pf2eDbContext entity.HasMany(e => e.Keywords).WithMany(t => t.Entities); + // EntityAliases + entity + .HasMany(e => e.Aliases) + .WithOne(a => a.Entity) + .HasForeignKey(a => a.EntityId) + .OnDelete(DeleteBehavior.Cascade); + // EntitySource entity .HasMany(e => e.Sources) @@ -111,28 +117,19 @@ public partial class Pf2eDbContext .WithOne(e => e.Entity) .HasForeignKey(e => e.EntityId) .OnDelete(DeleteBehavior.Cascade); - }); - // LegacyEntity - modelBuilder.Entity(entity => - { - BindLocaleString(entity, l => l.Name); - - entity.HasMany(l => l.RemasterEntities).WithMany(e => e.LegacyEntities); - - // LegacyEntitySource + // LegacyRemaster entity - .HasMany(e => e.Sources) - .WithOne(e => e.LegacyEntity) - .HasForeignKey(e => e.LegacyEntityId) - .OnDelete(DeleteBehavior.Cascade); - - // LegacyEntityExternal - entity - .HasMany(e => e.Externals) - .WithOne(e => e.LegacyEntity) - .HasForeignKey(e => e.LegacyEntityId) - .OnDelete(DeleteBehavior.Cascade); + .HasMany(e => e.LegacyEntities) + .WithMany(e => e.RemasterEntities) + .UsingEntity( + //l => + // l.HasOne(lr => lr.LegacyEntity).WithMany().HasForeignKey(lr => lr.LegacyId), + //r => + // r.HasOne(lr => lr.RemasterEntity) + // .WithMany() + // .HasForeignKey(lr => lr.RemasterId) + ); }); // Source @@ -146,12 +143,6 @@ public partial class Pf2eDbContext .HasForeignKey(e => e.SourceId) .OnDelete(DeleteBehavior.Cascade); - entity - .HasMany(s => s.LegacyEntitySources) - .WithOne(e => e.Source) - .HasForeignKey(e => e.SourceId) - .OnDelete(DeleteBehavior.Cascade); - // + EntitySource }); modelBuilder.Entity(entity => { }); @@ -164,12 +155,6 @@ public partial class Pf2eDbContext .WithOne(e => e.Target) .HasForeignKey(e => e.TargetId) .OnDelete(DeleteBehavior.Cascade); - - entity - .HasMany(t => t.LegacyEntityExternals) - .WithOne(e => e.Target) - .HasForeignKey(e => e.TargetId) - .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { }); From e46cd99f418e65845590534da51bf2c9a6a44049 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Thu, 15 May 2025 10:21:18 +0400 Subject: [PATCH 14/33] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=87=D0=B5=D0=BA=20=D1=81?= =?UTF-8?q?=D0=B2=D1=8F=D0=B7=D0=B5=D0=B9=20=D1=83=D1=81=D1=82=D0=B0=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=88=D0=B8=D1=85=20=D0=B8=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D1=91=D0=BD=D0=BD=D1=8B=D1=85=20=D1=81=D1=83?= =?UTF-8?q?=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B5=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/ProcessorGroupEnum.cs | 3 + .../Finalization/LegacyRemasterProcessors.cs | 58 +++++++++++++++++++ .../Aonprd/Processors/Types/TraitProcessor.cs | 2 +- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs diff --git a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs index fcf7fc6..563e41e 100644 --- a/Pf2eDataScrapper/App/ProcessorGroupEnum.cs +++ b/Pf2eDataScrapper/App/ProcessorGroupEnum.cs @@ -17,6 +17,9 @@ public enum ProcessorGroupEnum [Description("Архивы Нефиса: Подклассы")] AonprdSubclasses, + [Description("Архивы Нефиса: Завершение")] + AonprdFinalization, + [Description("Foundry EN: Подготовка")] FoundryEnPreparation, diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs new file mode 100644 index 0000000..d5a9479 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; +using Pf2eDataScrapper.App; +using Pf2eDataScrapper.Processors; +using Pf2eModel; +using Pf2eModel.Model.Base.Targets; + +namespace Pf2eDataScrapper.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; set; } = new(); + + public override void Process() + { + string aonprdId = Input.GetRequired("id"); + EntityExternal entity = + Context.EntityExternals.AsNoTracking().FirstOrDefault(x => x.ExternalId == aonprdId) + ?? throw new SilentSkipItemException(); + int entityId = entity.EntityId; + + foreach (int remasterId in GetEntityIds("remaster_id")) + { + Context.LegacyRemasters.Add(new() { LegacyId = entityId, RemasterId = remasterId }); + } + + foreach (int legacyId in GetEntityIds("legacy_id")) + { + Context.LegacyRemasters.Add(new() { LegacyId = legacyId, RemasterId = entityId }); + } + } + + protected List GetEntityIds(string path) + { + List aonprdIds = (Input.GetOptional("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(); + } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs index db6443d..033ece3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/TraitProcessor.cs @@ -16,7 +16,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types; Description = "Признаки", Group = ProcessorGroupEnum.AonprdEntities )] -[ProcessorDependencies(["aonprd:legacy"])] +[ProcessorDependencies(["aonprd:external", "aonprd:source"])] internal class TraitProcessor( ProcessorInput input, StringHelper stringHelper, From f1ca9ec3e41f830440194b1507bfcd9be7cf31b1 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Fri, 16 May 2025 18:28:53 +0400 Subject: [PATCH 15/33] =?UTF-8?q?=D0=A1=D0=BE=D1=85=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=81=D0=B5=D1=85=20=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B2=D1=88=D0=B8=D1=85=20=D1=8D?= =?UTF-8?q?=D0=BB=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Importers/AbstractCompendium.cs | 14 ++-- .../Importers/AbstractImporter.cs | 4 +- .../Aonprd/AonprdRepository.cs | 4 +- .../Aonprd/Compendium/AonprdCompendium.cs | 43 +++++-------- .../Implementations/Aonprd/Helpers/RuleToc.cs | 3 +- .../Finalization/LegacyRemasterProcessors.cs | 2 +- .../Preparation/CompendiumLoadProcessor.cs | 3 +- .../Preparation/SchemaBuildProcessor.cs | 3 +- .../Compendium/FoundryEnCompendium.cs | 51 +++++++++++++++ .../FoundryEn/FoundryEnCompendium.cs | 64 ------------------- .../Preparation/CompendiumFolderProcessor.cs | 3 +- .../Preparation/CompendiumItemsProcessor.cs | 3 +- .../Preparation/SchemaBuildProcessor.cs | 4 +- .../AbstractImporterRepository.cs | 3 +- .../Importers/Repositories/GitRepository.cs | 1 + 15 files changed, 95 insertions(+), 110 deletions(-) create mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs delete mode 100644 Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs diff --git a/Pf2eDataScrapper/Importers/AbstractCompendium.cs b/Pf2eDataScrapper/Importers/AbstractCompendium.cs index 96818ef..dc1108e 100644 --- a/Pf2eDataScrapper/Importers/AbstractCompendium.cs +++ b/Pf2eDataScrapper/Importers/AbstractCompendium.cs @@ -18,25 +18,19 @@ internal abstract class AbstractCompendium /// public string Dir => Repository.RepositoryDirPath; - /// - /// Возвращает количество элементов в компендиуме. - /// - /// Количество элементов. - public abstract int Count(); - /// /// Возвращает количество элементов для указанного типа данных. /// - /// Тип данных. + /// Типы данных. /// Количество элементов. - public abstract int Count(string type); + public abstract int Count(params ICollection type); /// /// Получение итератора сущностей. Если указан тип - то только указанного типа. /// - /// Тип сущностей, которые нужно получить. + /// Типы сущностей, которые нужно получить. /// Итератор сущностей. public abstract IEnumerable> Iterate( - string? type = null + params ICollection type ); } diff --git a/Pf2eDataScrapper/Importers/AbstractImporter.cs b/Pf2eDataScrapper/Importers/AbstractImporter.cs index 8c35683..1fc79f9 100644 --- a/Pf2eDataScrapper/Importers/AbstractImporter.cs +++ b/Pf2eDataScrapper/Importers/AbstractImporter.cs @@ -1,4 +1,6 @@ -namespace Pf2eDataScrapper.Importers; +using System.IO; + +namespace Pf2eDataScrapper.Importers; /// /// Абстрактный класс импортёра. diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs index c5b523b..ccb52cd 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using System.IO; +using System.Net.Http; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Repositories; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs index 24adc29..9ee710d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.IO; using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; @@ -8,6 +9,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; /// internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCompendium { + /// public override AonprdRepository Repository { get; } = repository; /// @@ -15,29 +17,22 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo /// public ConcurrentBag Items { get; } = []; - /// - /// Возвращает количество элементов в компендиуме. - /// - /// Количество элементов. - public override int Count() => Items.Count; + /// + public override int Count(params ICollection types) => + types.Count == 0 ? Items.Count : Items.Count(i => types.Contains(i.Type)); - /// - /// Возвращает количество элементов для указанного типа данных. - /// - /// Тип данных. - /// Количество элементов. - public override int Count(string type) => Items.Count(i => i.Type == type); - - /// - /// Получение итератора сущностей. Если указан тип - то только указанного типа. - /// - /// Тип сущностей, которые нужно получить. - /// Итератор сущностей. - public override IEnumerable> Iterate(string? type = null) + /// + public override IEnumerable> Iterate( + params ICollection types + ) { - IEnumerable files = type is null - ? Items.Select(t => t.File).Distinct() - : Items.Where(t => t.Type == type).Select(t => t.File).Distinct(); + IEnumerable 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) { @@ -57,16 +52,12 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo if ( id is null || !fileReferences.TryGetValue(id, out AonprdCompendiumItem? reference) + || !types.Contains(reference.Type) ) { continue; } - if (type is not null && reference.Type != type) - { - continue; - } - yield return new(reference, (JObject)item); } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs index 6ab17bc..84b3a64 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs @@ -1,4 +1,5 @@ -using Pf2eModel.Model.Base.Source; +using System.IO; +using Pf2eModel.Model.Base.Source; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs index d5a9479..001f71e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs @@ -46,7 +46,7 @@ internal class LegacyRemasterProcessors(ProcessorInput input) : AbstractInputPro return Context .EntityExternals.AsNoTracking() - .Where(x => aonprdIds.Contains(x.ExternalId)) + .Where(x => aonprdIds.Contains(x.ExternalId!)) .Select(x => x.EntityId) .ToList(); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/CompendiumLoadProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/CompendiumLoadProcessor.cs index 09e1885..57c53d9 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/CompendiumLoadProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/CompendiumLoadProcessor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.IO; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs index 33ef7d2..6de832c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs @@ -1,4 +1,5 @@ -using Pf2eDataScrapper.App; +using System.IO; +using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.SchemaBuilder; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs new file mode 100644 index 0000000..15e3103 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs @@ -0,0 +1,51 @@ +using System.Collections.Concurrent; +using System.IO; +using Newtonsoft.Json.Linq; + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; + +/// +/// Загрузчик данных компендиума. +/// +/// Репозиторий данных. +internal sealed class FoundryEnCompendium( + FoundryEnRepository repository, + CompendiumFolderCollection folders +) : AbstractCompendium +{ + /// + public override FoundryEnRepository Repository { get; } = repository; + + /// + /// Набор папок компендиума + /// + public CompendiumFolderCollection Folders { get; } = folders; + + /// + /// Коллекция всех записей компендиума. + /// + public ConcurrentBag Items { get; } = []; + + /// + public override int Count(params ICollection types) => + types.Count == 0 ? Items.Count : Items.Count(i => types.Contains(i.Type)); + + /// + public override IEnumerable> Iterate( + params ICollection types + ) + { + foreach ( + FoundryEnCompendiumItem item in types.Count == 0 + ? Items + : Items.Where(i => types.Contains(i.Type)) + ) + { + using StreamReader fileReader = new( + Path.Combine(Repository.RepositoryDirPath, item.File) + ); + + yield return new(item, JObject.Parse(fileReader.ReadToEnd())); + } + } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs deleted file mode 100644 index 6341c0c..0000000 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/FoundryEnCompendium.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Concurrent; -using Newtonsoft.Json.Linq; -using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; - -namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn; - -/// -/// Загрузчик данных компендиума. -/// -/// Репозиторий данных. -internal sealed class FoundryEnCompendium( - FoundryEnRepository repository, - CompendiumFolderCollection folders -) : AbstractCompendium -{ - /// - /// Репозиторий данных. - /// - public override FoundryEnRepository Repository { get; } = repository; - - /// - /// Набор папок компендиума - /// - public CompendiumFolderCollection Folders { get; } = folders; - - /// - /// Коллекция всех записей компендиума. - /// - public ConcurrentBag Items { get; } = []; - - /// - /// Возвращает количество элементов в компендиуме. - /// - /// Количество элементов. - public override int Count() => Items.Count; - - /// - /// Возвращает количество элементов для указанного типа данных. - /// - /// Тип данных. - /// Количество элементов. - public override int Count(string type) => Items.Count(i => i.Type == type); - - /// - /// Получение итератора сущностей. Если указан тип - то только указанного типа. - /// - /// Тип сущностей, которые нужно получить. - /// Итератор сущностей. - public override IEnumerable> Iterate(string? type = null) - { - IEnumerable items = type is null - ? Items - : Items.Where(i => i.Type == type); - - foreach (FoundryEnCompendiumItem item in items) - { - using StreamReader fileReader = new( - Path.Combine(Repository.RepositoryDirPath, item.File) - ); - - yield return new(item, JObject.Parse(fileReader.ReadToEnd())); - } - } -} diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs index c073400..74a8e3c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumFolderProcessor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.IO; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs index 7fd6260..ac5a0e3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.IO; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs index 905ea16..84adc47 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs @@ -1,5 +1,7 @@ -using Pf2eDataScrapper.App; +using System.IO; +using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; using Pf2eDataScrapper.SchemaBuilder; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation; diff --git a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs index ce3682f..b05659f 100644 --- a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs @@ -1,4 +1,5 @@ -using Pf2eDataScrapper.Helpers; +using System.IO; +using Pf2eDataScrapper.Helpers; namespace Pf2eDataScrapper.Importers.Repositories; diff --git a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs index a63b640..1fa47af 100644 --- a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.IO; using Pf2eDataScrapper.Helpers; namespace Pf2eDataScrapper.Importers.Repositories; From 1d63b695d1e16dea2d29cafad60336dbc122918e Mon Sep 17 00:00:00 2001 From: kozhilya Date: Fri, 16 May 2025 18:29:10 +0400 Subject: [PATCH 16/33] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=85=D0=BE=D0=B4=D0=B0=20=D0=BD=D0=B0?= =?UTF-8?q?=20WPF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/ProcessorCollection.cs | 8 + Pf2eDataScrapper/ApplicationSettings.cs | 1 + Pf2eDataScrapper/EnvSettings.cs | 3 +- .../EntityTreeViewerControl.cs | 11 + .../EntityViewerControl.Designer.cs | 36 ++ .../EntityTreeViewer/EntityViewerControl.cs | 18 + .../EntityTreeViewer/EntityViewerControl.resx | 120 +++++ .../Forms/EntityViewForm.Designer.cs | 30 +- Pf2eDataScrapper/Forms/EntityViewForm.cs | 4 +- Pf2eDataScrapper/ImportApplication.cs | 113 ++--- Pf2eDataScrapper/Pf2eDataScrapper.csproj | 1 + .../Processors/ProcessorExecutor.cs | 16 +- Pf2eDataScrapper/Program.cs | 3 + .../AbstractSchemaBuildProcessor.cs | 3 +- .../WPF/Components/DbEntityListControl.xaml | 88 ++++ .../Components/DbEntityListControl.xaml.cs | 28 ++ .../ProcessorCollectionControl.xaml | 414 ++++++++++++++++++ .../ProcessorCollectionControl.xaml.cs | 160 +++++++ .../WPF/Stores/SelectedEntityStore.cs | 20 + .../WPF/Stores/SelectedEntityTypeStore.cs | 20 + Pf2eDataScrapper/WPF/ViewModelBase.cs | 23 + .../WPF/ViewModels/DbEntityListViewModel.cs | 155 +++++++ .../WPF/ViewModels/EntityFormViewModel.cs | 49 +++ Pf2eDataScrapper/WPF/Views/EntityForm.xaml | 69 +++ Pf2eDataScrapper/WPF/Views/EntityForm.xaml.cs | 29 ++ .../WPF/Views/ProgressFormWpf.xaml | 85 ++++ .../WPF/Views/ProgressFormWpf.xaml.cs | 78 ++++ Pf2eDataScrapper/WPF/Views/StartForm.xaml | 61 +++ Pf2eDataScrapper/WPF/Views/StartForm.xaml.cs | 35 ++ .../WPF/WpfServiceProviderExtension.cs | 21 + ...signer.cs => 20250515080518_1.Designer.cs} | 2 +- ...0250515054227_1.cs => 20250515080518_1.cs} | 0 32 files changed, 1621 insertions(+), 83 deletions(-) create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx create mode 100644 Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml create mode 100644 Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml create mode 100644 Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs create mode 100644 Pf2eDataScrapper/WPF/Stores/SelectedEntityTypeStore.cs create mode 100644 Pf2eDataScrapper/WPF/ViewModelBase.cs create mode 100644 Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs create mode 100644 Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs create mode 100644 Pf2eDataScrapper/WPF/Views/EntityForm.xaml create mode 100644 Pf2eDataScrapper/WPF/Views/EntityForm.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml create mode 100644 Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/Views/StartForm.xaml create mode 100644 Pf2eDataScrapper/WPF/Views/StartForm.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs rename Pf2eModel/Migrations/{20250515054227_1.Designer.cs => 20250515080518_1.Designer.cs} (99%) rename Pf2eModel/Migrations/{20250515054227_1.cs => 20250515080518_1.cs} (100%) diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index 74d435c..56d21db 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -184,6 +184,14 @@ internal class ProcessorCollection Items = AllItems.Where(item => nameFlags.ContainsKey(item.Name)).ToList(); } + + /// + /// Получить список типов, которые обрабатываются в компендиуме. + /// + /// Тип компендиума. + /// Набор типов. + public ICollection GetTypesOfCompendium(ProcessorCompendiumEnum compendium) => + Items.Select(item => item.InnerType).OfType().ToList(); } /// diff --git a/Pf2eDataScrapper/ApplicationSettings.cs b/Pf2eDataScrapper/ApplicationSettings.cs index f5b4938..6a0320f 100644 --- a/Pf2eDataScrapper/ApplicationSettings.cs +++ b/Pf2eDataScrapper/ApplicationSettings.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.IO; using dotenv.net; using dotenv.net.Utilities; diff --git a/Pf2eDataScrapper/EnvSettings.cs b/Pf2eDataScrapper/EnvSettings.cs index f5b02c1..1777b12 100644 --- a/Pf2eDataScrapper/EnvSettings.cs +++ b/Pf2eDataScrapper/EnvSettings.cs @@ -1,4 +1,5 @@ -using dotenv.net; +using System.IO; +using dotenv.net; using dotenv.net.Utilities; namespace Pf2eDataScrapper; diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs index c286747..ce1d0bc 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs @@ -167,6 +167,7 @@ public sealed partial class EntityTreeViewerControl : UserControl public EntityTreeViewerControl() { InitializeComponent(); + ContentImage = new(ContentWidth, Height); DoubleBuffered = true; VerticalScroll.Visible = true; @@ -213,6 +214,11 @@ public sealed partial class EntityTreeViewerControl : UserControl /// public void UpdateRenders() { + if (EntityView?.Entity is null) + { + return; + } + int maxNameWidth = -1; foreach (EntityTreeViewerControlRow row in _viewerRows) @@ -239,6 +245,11 @@ public sealed partial class EntityTreeViewerControl : UserControl /// public void UpdateView() { + if (EntityView?.Entity is null) + { + return; + } + _viewerRows.ForEach(row => row.UpdateButton()); List rows = _viewerRows.Where(r => r.Item.Visible).ToList(); diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs new file mode 100644 index 0000000..6ac4ecf --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs @@ -0,0 +1,36 @@ +namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; + +partial class EntityViewerControl +{ + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + } + + #endregion +} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs new file mode 100644 index 0000000..c9cab89 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; +public partial class EntityViewerControl : UserControl +{ + public EntityViewerControl() + { + InitializeComponent(); + } +} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs index 3a18dd8..66ae9be 100644 --- a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs @@ -37,7 +37,7 @@ partial class EntityViewForm ResultSplitContainer = new SplitContainer(); DescriptionTextBox = new TextBox(); DatabaseViewer = new DatabaseViewerControl(); - EntityTreeViewerControl = new EntityTreeViewerControl(); + _entityTreeViewerCntrl = new EntityTreeViewerControl(); MenuStrip = new MenuStrip(); FileToolStripMenuItem = new ToolStripMenuItem(); ExitToolStripMenuItem = new ToolStripMenuItem(); @@ -137,20 +137,20 @@ partial class EntityViewForm DatabaseViewer.Size = new Size(322, 731); DatabaseViewer.TabIndex = 1; // - // EntityTreeViewerControl + // _entityTreeViewerCntrl // - EntityTreeViewerControl.AutoScroll = true; - EntityTreeViewerControl.ButtonSize = 15; - EntityTreeViewerControl.DepthSize = 15; - EntityTreeViewerControl.Dock = DockStyle.Fill; - EntityTreeViewerControl.Font = new Font("Consolas", 9F); - EntityTreeViewerControl.HoverColor = SystemColors.ControlDark; - EntityTreeViewerControl.ItemHeight = 20; - EntityTreeViewerControl.Location = new Point(0, 0); - EntityTreeViewerControl.Name = "EntityTreeViewerControl"; - EntityTreeViewerControl.Size = new Size(582, 861); - EntityTreeViewerControl.TabIndex = 0; - EntityTreeViewerControl.TypeFontSize = 7F; + _entityTreeViewerCntrl.AutoScroll = true; + _entityTreeViewerCntrl.ButtonSize = 15; + _entityTreeViewerCntrl.DepthSize = 15; + _entityTreeViewerCntrl.Dock = DockStyle.Fill; + _entityTreeViewerCntrl.Font = new Font("Consolas", 9F); + _entityTreeViewerCntrl.HoverColor = SystemColors.ControlDark; + _entityTreeViewerCntrl.ItemHeight = 20; + _entityTreeViewerCntrl.Location = new Point(0, 0); + _entityTreeViewerCntrl.Name = "_entityTreeViewerCntrl"; + _entityTreeViewerCntrl.Size = new Size(582, 861); + _entityTreeViewerCntrl.TabIndex = 0; + _entityTreeViewerCntrl.TypeFontSize = 7F; // // MenuStrip // @@ -229,7 +229,7 @@ partial class EntityViewForm private ListBox TypesListBox; private SplitContainer ResultSplitContainer; private TextBox DescriptionTextBox; - private EntityTreeViewerControl EntityTreeViewerControl; + private EntityTreeViewerControl _entityTreeViewerCntrl; private DatabaseViewerControl DatabaseViewer; private MenuStrip MenuStrip; private ToolStripMenuItem FileToolStripMenuItem; diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.cs b/Pf2eDataScrapper/Forms/EntityViewForm.cs index bed9bc3..ade43ff 100644 --- a/Pf2eDataScrapper/Forms/EntityViewForm.cs +++ b/Pf2eDataScrapper/Forms/EntityViewForm.cs @@ -18,10 +18,10 @@ internal partial class EntityViewForm : Form [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public EntityView? CurrentView { - get => EntityTreeViewerControl.EntityView; + get => _entityTreeViewerCntrl.EntityView; set { - EntityTreeViewerControl.EntityView = value; + _entityTreeViewerCntrl.EntityView = value; Application.DoEvents(); } } diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index ff3510f..3111401 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -1,10 +1,14 @@ -using Microsoft.Extensions.DependencyInjection; +using System.ComponentModel; +using System.Windows; +using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.App; -using Pf2eDataScrapper.Forms; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.WPF; +using Pf2eDataScrapper.WPF.Views; using Pf2eModel; using Slugify; +using StartForm = Pf2eDataScrapper.WPF.Views.StartForm; namespace Pf2eDataScrapper; @@ -20,7 +24,8 @@ internal sealed class ImportApplication : ApplicationContext Settings = settings; Processors = processors; - CurrentStage = ApplicationStage.InitialSettings; + //CurrentStage = ApplicationStage.InitialSettings; + CurrentStage = ApplicationStage.Viewer; } #region "Поля" @@ -46,30 +51,20 @@ internal sealed class ImportApplication : ApplicationContext #region "Формы" - private StartForm? _startForm; + private StartForm? _startFormWpf; - private ProgressForm? _progressForm; + private ProgressFormWpf? _progressFormWpf; - private EntityViewForm? _entityViewForm; + private EntityForm? _entityForm; #endregion #region "Жизненный цикл" - private Form? CurrentForm { get; set; } + private Window? CurrentWpfForm { get; set; } public delegate void ApplicationStateChangeDelegate(object sender, ApplicationStage nextStage); - public void ApplicationStateChange(object sender, ApplicationStage nextStage) - { - if (sender != CurrentForm) - { - return; - } - - CurrentStage = nextStage; - } - private void StartStage(ApplicationStage stage) { switch (stage) @@ -80,13 +75,13 @@ internal sealed class ImportApplication : ApplicationContext ); case ApplicationStage.InitialSettings: - _startForm = ActivatorUtilities.CreateInstance(ServiceProvider); - _startForm.StartPosition = FormStartPosition.CenterParent; - _startForm.ApplicationStateChange += ApplicationStateChange; - _startForm.FormClosing += HandleFormClosing; - _startForm.Show(); + _startFormWpf = ActivatorUtilities.CreateInstance(ServiceProvider); + _startFormWpf.WindowStartupLocation = WindowStartupLocation.CenterOwner; + _startFormWpf.Closing += Wpf_HandleFormClosing; + _startFormWpf.ApplicationStateChange += ApplicationStateChange; + _startFormWpf.Show(); - CurrentForm = _startForm; + CurrentWpfForm = _startFormWpf; break; case ApplicationStage.ImportProcess: @@ -94,38 +89,37 @@ internal sealed class ImportApplication : ApplicationContext ServiceProvider ); - _progressForm = ActivatorUtilities.CreateInstance(ServiceProvider); - _progressForm.StartPosition = FormStartPosition.CenterParent; - _progressForm.FormClosing += HandleFormClosing; + _progressFormWpf = ActivatorUtilities.CreateInstance( + ServiceProvider + ); + _progressFormWpf.WindowStartupLocation = WindowStartupLocation.CenterOwner; + _progressFormWpf.Closing += Wpf_HandleFormClosing; + _progressFormWpf.ApplicationStateChange += ApplicationStateChange; - _progressForm.Show(); - - CurrentForm = _progressForm; + CurrentWpfForm = _progressFormWpf; + _progressFormWpf.Show(); executor.Start(); break; case ApplicationStage.Viewer: - _entityViewForm = ActivatorUtilities.CreateInstance( - ServiceProvider - ); - _entityViewForm.StartPosition = FormStartPosition.CenterParent; - _entityViewForm.ApplicationStateChange += ApplicationStateChange; - _entityViewForm.FormClosing += HandleFormClosing; + _entityForm = ActivatorUtilities.CreateInstance(ServiceProvider); + _entityForm.WindowStartupLocation = WindowStartupLocation.CenterOwner; + _entityForm.Closing += Wpf_HandleFormClosing; + //_entityForm.ApplicationStateChange += ApplicationStateChange; + _entityForm.Show(); - _entityViewForm.Show(); - CurrentForm = _entityViewForm; + CurrentWpfForm = _entityForm; break; default: throw new ArgumentOutOfRangeException(); } - - Application.DoEvents(); } private void CloseStage(ApplicationStage stage) { - CurrentForm = null; + CurrentWpfForm = null; + switch (stage) { case ApplicationStage.BootUp: @@ -133,38 +127,48 @@ internal sealed class ImportApplication : ApplicationContext break; case ApplicationStage.InitialSettings: - _startForm!.Hide(); - _startForm!.Close(); - _startForm = null; + _startFormWpf!.Hide(); + _startFormWpf!.Close(); + _startFormWpf = null; break; case ApplicationStage.ImportProcess: break; case ApplicationStage.Viewer: - _entityViewForm!.Hide(); - _entityViewForm!.Close(); - _entityViewForm = null; + _entityForm!.Hide(); + _entityForm!.Close(); + _entityForm = null; break; default: throw new ArgumentOutOfRangeException(); } - - Application.DoEvents(); } - private void HandleFormClosing(object? sender, FormClosingEventArgs e) + #endregion + + #region "События" + + public void ApplicationStateChange(object sender, ApplicationStage nextStage) { - if (sender != CurrentForm) + if (sender?.Equals(CurrentWpfForm) ?? false) + { + CurrentStage = nextStage; + } + } + + private void Wpf_HandleFormClosing(object? sender, CancelEventArgs e) + { + if (sender?.Equals(CurrentWpfForm) ?? false) + { + ExitThread(); + } + else { e.Cancel = true; - return; } - - ExitThread(); } - #endregion #region "Внедрение зависимостей" @@ -177,6 +181,7 @@ internal sealed class ImportApplication : ApplicationContext { // Контекст базы данных services.AddEntityFrameworkNpgsql(); + services.AddDbContext(); //services.AddDbContextPool(options => //{ diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj index a299921..ef7689d 100644 --- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj +++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj @@ -5,6 +5,7 @@ enable true enable + True diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index 8ea6ec1..560b179 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -5,7 +5,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; -using Pf2eDataScrapper.Importers.Implementations.FoundryEn; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; namespace Pf2eDataScrapper.Processors; @@ -116,15 +116,13 @@ internal sealed class ProcessorExecutor( _ => throw new($"Не могу выбрать импортёр для группы {item.Group}"), }; - Progress.Local.Reset( - item.InnerType is null ? compendium.Count() : compendium.Count(item.InnerType) - ); + ICollection types = item.InnerType is null + ? Collection.GetTypesOfCompendium(item.Compendium ?? throw new("impossible")) + : [item.InnerType]; - foreach ( - (AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate( - item.InnerType - ) - ) + Progress.Local.Reset(compendium.Count(types)); + + foreach ((AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate(types)) { Progress.Local.Start(compendiumItem.Id); diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs index f86694d..71d8b6c 100644 --- a/Pf2eDataScrapper/Program.cs +++ b/Pf2eDataScrapper/Program.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.Importers.Implementations.Aonprd; using Pf2eDataScrapper.Importers.Implementations.FoundryEn; +using Pf2eDataScrapper.WPF; namespace Pf2eDataScrapper; @@ -37,6 +38,8 @@ internal static class Program { IServiceCollection services = new ServiceCollection(); + services.RegisterWpfServices(); + ImportApplication.RegisterServices(services); AonprdImporter.RegisterServices(services); diff --git a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs index bce0c7b..6212448 100644 --- a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.IO; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml new file mode 100644 index 0000000..e595937 --- /dev/null +++ b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + - - + + diff --git a/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml.cs b/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml.cs index 8080899..842ef58 100644 --- a/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml.cs +++ b/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml.cs @@ -24,7 +24,7 @@ namespace Pf2eDataScrapper.WPF.Components; internal partial class EntityViewControl : System.Windows.Controls.UserControl { - public EntityViewViewModel ViewModel + public EntityViewViewModel? ViewModel { get => _viewModel; set @@ -33,7 +33,7 @@ internal partial class EntityViewControl : System.Windows.Controls.UserControl DataContext = ViewModel; } } - private EntityViewViewModel _viewModel; + private EntityViewViewModel? _viewModel; #region "Поля элемента управления" diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureRow.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureRow.cs index 28991e7..4d0a76c 100644 --- a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureRow.cs +++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureRow.cs @@ -1,13 +1,10 @@ -using System.CodeDom; -using System.Globalization; -using System.Security.Cryptography; +using System.Globalization; using System.Windows; using System.Windows.Data; using System.Windows.Media; using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header; -using Brush = System.Windows.Media.Brush; using Brushes = System.Windows.Media.Brushes; -using Color = System.Windows.Media.Color; +using Image = System.Windows.Controls.Image; namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure; @@ -33,6 +30,11 @@ internal class StructureRow(StructureField item) /// public bool Visible => Item.Visible; + /// + /// Флаг, указывающий, отображается ли строка. + /// + public Visibility ButtonVisibility => HasButton ? Visibility.Visible : Visibility.Collapsed; + /// /// Глубина отображения поля. /// @@ -68,9 +70,6 @@ internal class StructureRow(StructureField item) // [ValueConversion(typeof(IEnumerable), typeof(Image), ParameterType = typeof(int))] public class LadderToImageConverter : IMultiValueConverter { - #region IValueConverter Members - - public object? Convert( object[] value, Type targetType, @@ -78,7 +77,10 @@ public class LadderToImageConverter : IMultiValueConverter System.Globalization.CultureInfo culture ) { - if (value.Length != 2) + if ( + value.Length != 2 + || !value[0].GetType().IsAssignableTo(typeof(IEnumerable)) + ) { return null; } @@ -91,19 +93,14 @@ public class LadderToImageConverter : IMultiValueConverter } int n = ladder.Count(); - if (!int.TryParse((string)value[1], out int itemHeight)) - { - return null; - } + int itemHeight = (int)value[1]; DrawingGroup result = new(); - // Rectangle - result.Children.Add( new GeometryDrawing() { - Geometry = new RectangleGeometry(new(0, 0, n * 100, 100)), + Geometry = new RectangleGeometry(new(0, 0, n * itemHeight, itemHeight)), Pen = new(Brushes.Transparent, 0), } ); @@ -116,16 +113,13 @@ public class LadderToImageConverter : IMultiValueConverter } ); - return result; + return new DrawingImage(result); } private GeometryGroup GetLadderGeometryGroup(IEnumerable ladder, int h) { - float h2 = h * 0.5f; GeometryGroup geometryGroup = new(); - - //geometryGroup.Children.Add(new RectangleGeometry(new(0, 0, ladder.Count() * 100, 100))); - + float h2 = h * 0.5f; int i = 0; foreach (LadderType ladderType in ladder) @@ -139,20 +133,23 @@ public class LadderToImageConverter : IMultiValueConverter new LineGeometry(new(i * h + h2, 0), new(i * h + h2, h2)) ); geometryGroup.Children.Add( - new LineGeometry(new(i * h + h2, 50), new(i * h + h, h2)) + new LineGeometry(new(i * h + h2, h2), new(i * h + h, h2)) ); break; + case LadderType.Through: geometryGroup.Children.Add( new LineGeometry(new(i * h + h2, 0), new(i * h + h2, h)) ); + if (i == ladder.Count() - 1) { geometryGroup.Children.Add( - new LineGeometry(new(i * h + h2, h2), new(i * h + h2, h2)) + new LineGeometry(new(i * h + h2, h2), new(i * h + h, h2)) ); } break; + default: throw new ArgumentOutOfRangeException(nameof(ladder)); } @@ -172,6 +169,4 @@ public class LadderToImageConverter : IMultiValueConverter { throw new NotSupportedException(); } - - #endregion } diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewViewModel.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewViewModel.cs index a89abd2..36171af 100644 --- a/Pf2eDataScrapper/WPF/ViewModels/EntityViewViewModel.cs +++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewViewModel.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Data; -using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; +using System.Collections.ObjectModel; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.WPF.Stores; using Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure; From 03a644624f5b040a740a46d1f548383aaa51ccf1 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Tue, 20 May 2025 10:27:30 +0400 Subject: [PATCH 19/33] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20Windows.Forms=20=D0=B8=20=D1=80=D0=B5=D1=84?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20using?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/ProcessorAttribute.cs | 5 +- Pf2eDataScrapper/App/ProcessorCollection.cs | 5 +- Pf2eDataScrapper/ApplicationSettings.cs | 3 +- .../Forms/Controls/CoreBindingNavigator.cs | 22 - .../DatabaseViewer/DatabaseQueryPerformer.cs | 130 ---- .../DatabaseViewerControl.Designer.cs | 148 ----- .../DatabaseViewer/DatabaseViewerControl.cs | 391 ----------- .../DatabaseViewer/DatabaseViewerControl.resx | 132 ---- .../EntityTreeViewerControl.Designer.cs | 60 -- .../EntityTreeViewerControl.cs | 407 ------------ .../EntityTreeViewerControl.resx | 120 ---- .../EntityTreeViewerControlRow.cs | 607 ------------------ .../EntityViewerControl.Designer.cs | 36 -- .../EntityTreeViewer/EntityViewerControl.cs | 18 - .../EntityTreeViewer/EntityViewerControl.resx | 120 ---- .../ProcessorCollectionControl.Designer.cs | 60 -- .../Controls/ProcessorCollectionControl.cs | 173 ----- .../Controls/ProcessorCollectionControl.resx | 120 ---- .../Forms/EntityViewForm.Designer.cs | 240 ------- Pf2eDataScrapper/Forms/EntityViewForm.cs | 99 --- Pf2eDataScrapper/Forms/EntityViewForm.resx | 126 ---- .../Forms/ProgressForm.Designer.cs | 128 ---- Pf2eDataScrapper/Forms/ProgressForm.cs | 72 --- Pf2eDataScrapper/Forms/ProgressForm.resx | 120 ---- Pf2eDataScrapper/Forms/StartForm.Designer.cs | 236 ------- Pf2eDataScrapper/Forms/StartForm.cs | 107 --- Pf2eDataScrapper/Forms/StartForm.resx | 123 ---- Pf2eDataScrapper/Helpers/DatabaseHelper.cs | 3 +- Pf2eDataScrapper/Helpers/EntityView.cs | 5 +- Pf2eDataScrapper/ImportApplication.cs | 14 +- .../Importers/AbstractCompendium.cs | 4 +- .../Aonprd/AonprdRepository.cs | 5 +- .../Aonprd/Compendium/AonprdCompendium.cs | 5 +- .../Aonprd/Helpers/AonprdBuilderGlobals.cs | 3 +- .../Helpers/AonprdProcessorInputExtension.cs | 3 +- .../Implementations/Aonprd/Helpers/RuleToc.cs | 5 +- .../Aonprd/Helpers/StringHelper.cs | 4 +- .../Processors/AbstractEntityProcessor.cs | 4 +- .../Processors/AbstractSubclassProcessor.cs | 3 +- .../Processors/Common/SourceProcessor.cs | 3 +- .../Finalization/LegacyRemasterProcessors.cs | 4 +- .../Preparation/SchemaBuildProcessor.cs | 5 +- .../Subclasses/ApparitionProcessor.cs | 4 +- .../Processors/Types/AncestryProcessor.cs | 5 +- .../Aonprd/Processors/Types/ClassProcessor.cs | 5 +- .../Aonprd/Processors/Types/FeatProcessor.cs | 3 +- .../Processors/Types/RitualProcessor.cs | 5 +- .../Processors/Types/Rules/RuleProcessor.cs | 3 +- .../Types/Rules/RulesOrderProcessor.cs | 2 + .../Types/Rules/RulesTocProcessor.cs | 3 +- .../Aonprd/Processors/Types/SpellProcessor.cs | 4 +- .../FoundryEn/Compendium/CompendiumFolder.cs | 3 +- .../Compendium/CompendiumFolderCollection.cs | 5 +- .../Compendium/FoundryEnCompendium.cs | 5 +- .../Processors/FoundryEnAbstractProcessor.cs | 5 +- .../Preparation/CompendiumItemsProcessor.cs | 4 +- .../Preparation/SchemaBuildProcessor.cs | 5 +- .../Preparation/SchemaTypeResolver.cs | 3 +- Pf2eDataScrapper/Pf2eDataScrapper.csproj | 18 +- .../Processors/ProcessorExecutor.cs | 10 +- Pf2eDataScrapper/Processors/ProcessorInput.cs | 5 +- Pf2eDataScrapper/Program.cs | 9 +- .../AbstractSchemaBuildProcessor.cs | 4 +- Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs | 4 +- .../SchemaBuilder/TypeResolver.cs | 5 +- .../SchemaBuilder/Types/AbstractSchemaType.cs | 3 +- .../SchemaBuilder/Types/ArraySchemaType.cs | 4 +- .../Types/DictionarySchemaType.cs | 3 +- .../SchemaBuilder/Types/IgnoreSchemaType.cs | 3 +- .../SchemaBuilder/Types/ObjectSchemaType.cs | 5 +- .../SchemaBuilder/Types/StaticSchemaType.cs | 4 +- .../WPF/Components/EntityViewControl.xaml | 1 - .../WPF/Components/EntityViewControl.xaml.cs | 1 - .../ProcessorCollectionControl.xaml.cs | 5 +- .../WPF/Stores/SelectedEntityStore.cs | 3 +- .../WPF/Stores/SelectedEntityTypeStore.cs | 3 +- Pf2eDataScrapper/WPF/ViewModelBase.cs | 3 +- .../WPF/ViewModels/DbEntityListViewModel.cs | 6 +- .../WPF/ViewModels/EntityFormViewModel.cs | 5 +- .../EntityViewStructure/StructureBuilder.cs | 6 +- .../EntityViewStructure/StructureField.cs | 8 +- .../StructureFieldComplex.cs | 7 +- .../EntityViewStructure/StructureFieldEnum.cs | 6 +- .../StructureFieldPrimitive.cs | 6 +- .../StructureProcessedObject.cs | 6 +- .../EntityViewStructure/StructureRow.cs | 5 +- .../WPF/ViewModels/EntityViewViewModel.cs | 4 +- .../WPF/Views/ProgressFormWpf.xaml.cs | 4 +- 88 files changed, 209 insertions(+), 3889 deletions(-) delete mode 100644 Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.resx delete mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.resx delete mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx delete mode 100644 Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.Designer.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs delete mode 100644 Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.resx delete mode 100644 Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs delete mode 100644 Pf2eDataScrapper/Forms/EntityViewForm.cs delete mode 100644 Pf2eDataScrapper/Forms/EntityViewForm.resx delete mode 100644 Pf2eDataScrapper/Forms/ProgressForm.Designer.cs delete mode 100644 Pf2eDataScrapper/Forms/ProgressForm.cs delete mode 100644 Pf2eDataScrapper/Forms/ProgressForm.resx delete mode 100644 Pf2eDataScrapper/Forms/StartForm.Designer.cs delete mode 100644 Pf2eDataScrapper/Forms/StartForm.cs delete mode 100644 Pf2eDataScrapper/Forms/StartForm.resx diff --git a/Pf2eDataScrapper/App/ProcessorAttribute.cs b/Pf2eDataScrapper/App/ProcessorAttribute.cs index 3497ce6..9cc8fc1 100644 --- a/Pf2eDataScrapper/App/ProcessorAttribute.cs +++ b/Pf2eDataScrapper/App/ProcessorAttribute.cs @@ -1,4 +1,7 @@ -namespace Pf2eDataScrapper.App; +using System; +using System.Collections.Generic; + +namespace Pf2eDataScrapper.App; /// /// Общий аттрибут обработчика. diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index 56d21db..57a5d71 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -1,4 +1,7 @@ -using System.Reflection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.App; diff --git a/Pf2eDataScrapper/ApplicationSettings.cs b/Pf2eDataScrapper/ApplicationSettings.cs index 6a0320f..dd191ad 100644 --- a/Pf2eDataScrapper/ApplicationSettings.cs +++ b/Pf2eDataScrapper/ApplicationSettings.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; using System.IO; using dotenv.net; using dotenv.net.Utilities; diff --git a/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs b/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs deleted file mode 100644 index e0d4f03..0000000 --- a/Pf2eDataScrapper/Forms/Controls/CoreBindingNavigator.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Pf2eDataScrapper.Forms.Controls; - -/// -/// Элемент управления прокрутки страниц -/// -internal sealed class CoreBindingNavigator : BindingNavigator -{ - public CoreBindingNavigator() - { - AddStandardItems(); - - if (DeleteItem != null) - { - DeleteItem.Visible = false; - } - - if (AddNewItem != null) - { - AddNewItem.Visible = false; - } - } -} diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs deleted file mode 100644 index b6d8d2a..0000000 --- a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseQueryPerformer.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.Collections.Concurrent; -using Pf2eModel; -using Pf2eModel.Model.Base; -using Pf2eModel.Model.Enum; - -namespace Pf2eDataScrapper.Forms.Controls.DatabaseViewer; - -/// -/// Класс, выполняющий страничные запросы к базе данных. -/// -internal class DatabaseQueryPerformer -{ - /// - /// Тип сущностей, которую необходимо получить. - /// - public required EntityType Type; - - /// - /// Номер страницы, которую необходимо получить. - /// - public required int Page; - - /// - /// Количество элементов на странице. - /// - public required int PerPage; - - /// - /// Поисковая строка, по которой будет производиться фильтрация. - /// - public required string Search = ""; - - /// - /// Поток, в котором будет выполняться запрос к базе данных. - /// - private readonly Thread _queryThread; - - /// - /// Конструктор класса, который инициализирует поток для выполнения запроса к базе данных. - /// - public DatabaseQueryPerformer() - { - _queryThread = new(PerformQuery); - } - - /// - /// Запускает поток для выполнения запроса к базе данных. - /// - public void Query() - { - _queryThread.Start(); - } - - /// - /// Прерывает выполнение потока, если он ещё не завершён. - /// - public void Abort() - { - _queryThread.Interrupt(); - } - - /// - /// Выполняет запрос к базе данных и обрабатывает результаты. - /// - private void PerformQuery() - { - try - { - Pf2eDbContext dbContext = new(); - - IQueryable query = - Search == "" - ? dbContext.Entities.Where(e => e.Type == Type) - : - // ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall - dbContext.Entities.Where(e => - e.Type == Type && Pf2eDbContext.LocaleSearch(e.Name, Search) - ); - - int count = query.Count(); - - List entities = query - .OrderBy(e => e.Id) - .Skip((Page - 1) * PerPage) - .Take(PerPage) - .ToList(); - - DatabaseQueryResult result = new(count, entities); - - OnQueryCompleted?.Invoke(this, result); - } - catch (ThreadInterruptedException) - { - // Ignore - } - } - - /// - /// Делегат, который будет вызван по завершении выполнения запроса к базе данных. - /// - /// Объект класса, выполневшего запрос. - /// Результат запроса. - public delegate void DatabaseQueryPerformerDelegate( - DatabaseQueryPerformer sender, - DatabaseQueryResult result - ); - - /// - /// Событие, которое будет вызвано по завершении выполнения запроса к базе данных. - /// - public event DatabaseQueryPerformerDelegate? OnQueryCompleted; -} - -/// -/// Класс, который содержит результат выполнения запроса к базе данных. -/// -/// Общее количество найденных сущностей. -/// Набор найденных сущностей. -internal class DatabaseQueryResult(int count, ICollection entities) -{ - /// - /// Общее количество найденных сущностей. - /// - public int Count { get; } = count; - - /// - /// Набор найденных сущностей. - /// - public ConcurrentBag Entities { get; } = new(entities); -} diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs deleted file mode 100644 index 374dca0..0000000 --- a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.Designer.cs +++ /dev/null @@ -1,148 +0,0 @@ -namespace Pf2eDataScrapper.Forms.Controls.DatabaseViewer; - -partial class DatabaseViewerControl -{ - /// - /// Обязательная переменная конструктора. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Освободить все используемые ресурсы. - /// - /// истинно, если управляемый ресурс должен быть удален; иначе ложно. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Код, автоматически созданный конструктором компонентов - - /// - /// Требуемый метод для поддержки конструктора — не изменяйте - /// содержимое этого метода с помощью редактора кода. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - BindingNavigator = new CoreBindingNavigator(); - SearchTextBox = new TextBox(); - DataGridView = new DataGridView(); - DbBindingSource = new BindingSource(components); - PagesBindingSource = new BindingSource(components); - Id = new DataGridViewTextBoxColumn(); - EntityName = new DataGridViewTextBoxColumn(); - Rarity = new DataGridViewTextBoxColumn(); - (BindingNavigator).BeginInit(); - ((System.ComponentModel.ISupportInitialize)DataGridView).BeginInit(); - ((System.ComponentModel.ISupportInitialize)DbBindingSource).BeginInit(); - ((System.ComponentModel.ISupportInitialize)PagesBindingSource).BeginInit(); - SuspendLayout(); - // - // BindingNavigator - // - BindingNavigator.AddNewItem = null; - BindingNavigator.DeleteItem = null; - BindingNavigator.ImeMode = ImeMode.NoControl; - BindingNavigator.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow; - BindingNavigator.Location = new Point(0, 0); - BindingNavigator.Name = "BindingNavigator"; - BindingNavigator.Size = new Size(345, 25); - BindingNavigator.Stretch = true; - BindingNavigator.TabIndex = 1; - BindingNavigator.Text = "coreBindingNavigator1"; - BindingNavigator.RefreshItems += BindingNavigator_RefreshItems; - // - // SearchTextBox - // - SearchTextBox.Dock = DockStyle.Top; - SearchTextBox.Location = new Point(0, 25); - SearchTextBox.Name = "SearchTextBox"; - SearchTextBox.PlaceholderText = "Поиск"; - SearchTextBox.Size = new Size(345, 23); - SearchTextBox.TabIndex = 3; - SearchTextBox.TextChanged += SearchTextBox_TextChanged; - SearchTextBox.Leave += SearchTextBox_Leave; - // - // DataGridView - // - DataGridView.AllowUserToAddRows = false; - DataGridView.AllowUserToDeleteRows = false; - DataGridView.AutoGenerateColumns = false; - DataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader; - DataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders; - DataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; - DataGridView.Columns.AddRange(new DataGridViewColumn[] { Id, EntityName, Rarity }); - DataGridView.DataSource = DbBindingSource; - DataGridView.Dock = DockStyle.Fill; - DataGridView.Location = new Point(0, 48); - DataGridView.MultiSelect = false; - DataGridView.Name = "DataGridView"; - DataGridView.ReadOnly = true; - DataGridView.RowHeadersVisible = false; - DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect; - DataGridView.Size = new Size(345, 345); - DataGridView.TabIndex = 4; - DataGridView.RowStateChanged += DataGridView_RowStateChanged; - // - // Id - // - Id.DataPropertyName = "Id"; - Id.HeaderText = "Id"; - Id.Name = "Id"; - Id.ReadOnly = true; - Id.Resizable = DataGridViewTriState.False; - Id.SortMode = DataGridViewColumnSortMode.NotSortable; - Id.Width = 5; - // - // EntityName - // - EntityName.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; - EntityName.DataPropertyName = "Name"; - EntityName.HeaderText = "Name"; - EntityName.Name = "EntityName"; - EntityName.ReadOnly = true; - EntityName.Resizable = DataGridViewTriState.False; - EntityName.SortMode = DataGridViewColumnSortMode.NotSortable; - // - // Rarity - // - Rarity.DataPropertyName = "Rarity"; - Rarity.HeaderText = "Rarity"; - Rarity.Name = "Rarity"; - Rarity.ReadOnly = true; - Rarity.Resizable = DataGridViewTriState.False; - Rarity.SortMode = DataGridViewColumnSortMode.NotSortable; - Rarity.Width = 5; - // - // DatabaseViewerControl - // - AutoScaleDimensions = new SizeF(7F, 15F); - AutoScaleMode = AutoScaleMode.Font; - Controls.Add(DataGridView); - Controls.Add(SearchTextBox); - Controls.Add(BindingNavigator); - Name = "DatabaseViewerControl"; - Size = new Size(345, 393); - (BindingNavigator).EndInit(); - ((System.ComponentModel.ISupportInitialize)DataGridView).EndInit(); - ((System.ComponentModel.ISupportInitialize)DbBindingSource).EndInit(); - ((System.ComponentModel.ISupportInitialize)PagesBindingSource).EndInit(); - ResumeLayout(false); - PerformLayout(); - } - - #endregion - private CoreBindingNavigator BindingNavigator; - private TextBox SearchTextBox; - private DataGridView DataGridView; - private BindingSource DbBindingSource; - private BindingSource PagesBindingSource; - private DataGridViewTextBoxColumn Id; - private DataGridViewTextBoxColumn EntityName; - private DataGridViewTextBoxColumn Rarity; -} diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs deleted file mode 100644 index f0dc02a..0000000 --- a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.cs +++ /dev/null @@ -1,391 +0,0 @@ -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using Pf2eDataScrapper.Helpers; -using Pf2eModel; -using Pf2eModel.Model.Base; -using Pf2eModel.Model.Common.I18n; -using Pf2eModel.Model.Enum; -using Timer = System.Windows.Forms.Timer; - -namespace Pf2eDataScrapper.Forms.Controls.DatabaseViewer; - -/// -/// Класс элемента управления, который отображает сущности в виде таблицы. -/// -public partial class DatabaseViewerControl : UserControl -{ - #region "Поля и свойства" - - /// - /// Контекст базы данных, который используется для получения сущностей. - /// - public readonly Pf2eDbContext Context = new(); - - /// - /// Количество элементов на странице. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int PerPage - { - get => _perPage; - set - { - _perPage = value; - UpdateView(); - } - } - private int _perPage = 30; - - /// - /// Номер страницы, которую необходимо отобразить. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int Page - { - get => _page; - set - { - _page = value; - UpdateView(); - } - } - private int _page = 1; - - /// - /// Тип сущности, которую необходимо отобразить. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public EntityType Type - { - get => _type; - set - { - _page = 1; - _type = value; - UpdateView(); - } - } - private EntityType _type; - - /// - /// Количество элементов, которые были получены из базы данных. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int Count { get; private set; } - - /// - /// Номер последней страницы, которая может быть отображена. - /// - public int MaxPage => Convert.ToInt32(Math.Ceiling(1.0 * Count / PerPage)); - - /// - /// Выбранная в элементе управления сущность. - /// - [AllowNull] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public EntityView CurrentEntity { get; private set; } - - /// - /// Флаг, указывающий, что элемент управления занят выполнением запроса к базе данных. - /// - private bool _isBusy; - - /// - /// Объект, который выполняет запрос к базе данных. - /// - private DatabaseQueryPerformer? _performer; - - /// - /// Таймер, который используется для задержки обработки события изменения текста в поле поиска. - /// - private Timer? _typingTimer; - - /// - /// Строка, которая хранит последнее значение, введенное в поле поиска. - /// - private string _lastSearch = ""; - - #endregion - - /// - /// Конструктор класса, который инициализирует элемент управления и связывает его с источником данных. - /// - public DatabaseViewerControl() - { - InitializeComponent(); - - BindingNavigator.BindingSource = DbBindingSource; - //DataGridView.AutoGenerateColumns = true; - DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect; - - _isBusy = false; - } - - #region "События элемента управления" - - /// - /// Обработчик события, который вызывается при изменении текущей страницы в элементе управления. - /// - /// Источник события. - /// Объект, содержащий данные события. - private void BindingNavigator_RefreshItems(object sender, EventArgs e) - { - if (_isBusy) - { - return; - } - - Page = (int)(BindingNavigator?.BindingSource?.Current ?? 1); - UpdateView(); - } - - /// - /// Обработчик события, который вызывается при изменении выделенной строки в элементе управления DataGridView. - /// - /// Источник события. - /// Объект, содержащий данные события. - private void DataGridView_RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e) - { - if (_isBusy) - { - return; - } - - UpdateEntity(); - } - - /// - /// Обработчик события, который вызывается когда поле поиска теряет фокус. - /// - /// Источник события. - /// Объект, содержащий данные события. - private void SearchTextBox_Leave(object sender, EventArgs e) - { - if (_isBusy || SearchTextBox.Text == _lastSearch) - { - return; - } - - UpdateView(); - } - - /// - /// Обработчик события, который вызывается при изменении текста в поле поиска. - /// - /// Источник события. - /// Объект, содержащий данные события. - private void SearchTextBox_TextChanged(object sender, EventArgs e) - { - if (_isBusy) - { - return; - } - - if (_typingTimer == null) - { - _typingTimer = new(); - _typingTimer.Interval = 3000; - _typingTimer.Tick += HandleTypingTimerTimeout; - } - _typingTimer.Stop(); // Resets the timer - _typingTimer.Tag = (sender as TextBox)!.Text; // This should be done with EventArgs - _typingTimer.Start(); - } - - /// - /// Обработчик события, который вызывается при истечении времени таймера ввода текста. - /// - /// Источник события. - /// Объект, содержащий данные события. - private void HandleTypingTimerTimeout(object? sender, EventArgs e) - { - if (_isBusy || sender is not Timer timer) - { - return; - } - - UpdateView(); - timer.Stop(); - } - - #endregion - - #region "Обновление данных" - - /// - /// Устанавливает статус занятости элемента управления. - /// - /// Новый статус занятости. - private void SetBusyStatus(bool busy) - { - if (InvokeRequired) - { - Invoke(SetBusyStatus, busy); - return; - } - - _isBusy = busy; - - BindingNavigator.Enabled = !busy; - DataGridView.Enabled = !busy; - SearchTextBox.Enabled = !busy; - - DataGridView.ColumnHeadersHeightSizeMode = !busy - ? DataGridViewColumnHeadersHeightSizeMode.AutoSize - : DataGridViewColumnHeadersHeightSizeMode.DisableResizing; - } - - /// - /// Обновляет данные в элементе управления. - /// - /// Проигнорировать флаг занятости. - public void UpdateView(bool force = false) - { - if (_isBusy && !force) - { - return; - } - - SetBusyStatus(true); - - _performer?.Abort(); - - _performer = new() - { - Type = Type, - Page = Page, - PerPage = PerPage, - Search = SearchTextBox.Text, - }; - - _performer.OnQueryCompleted += HandleQueryCompleted; - - _performer.Query(); - } - - /// - /// Обработчик события, который вызывается по завершении выполнения запроса к базе данных. - /// - /// Источник события. - /// Результат выполнения события - private void HandleQueryCompleted(DatabaseQueryPerformer sender, DatabaseQueryResult result) - { - if (sender != _performer) - { - return; - } - - Count = result.Count; - _lastSearch = SearchTextBox.Text; - - InvokeControl( - BindingNavigator, - b => - { - PagesBindingSource.DataSource = Enumerable.Range(1, MaxPage).ToList(); - b.BindingSource = PagesBindingSource; - } - ); - - InvokeControl( - DataGridView, - view => - { - view.DataSource = result - .Entities.Select(e => new EntityGridRow(e)) - .OrderBy(r => r.Id) - .ToList(); - - SetBusyStatus(false); - - if (view.Rows.Count > 0) - { - view.Rows[0].Selected = true; - UpdateEntity(); - } - } - ); - } - - /// - /// Вызывает действие на указанном элементе управления в нужном потоке , если это необходимо. - /// - /// - /// - /// - private void InvokeControl(T control, Action action) - where T : Control - { - if (control.InvokeRequired) - { - control.Invoke(action, control); - } - else - { - action(control); - } - } - - /// - /// Обновить сущность, которая была выбрана в элементе управления DataGridView. - /// - private void UpdateEntity() - { - if (DataGridView.SelectedRows.Count == 0) - { - return; - } - - EntityGridRow? data = (EntityGridRow?)DataGridView.SelectedRows[0].DataBoundItem; - - try - { - CurrentEntity = data is not null ? new(Context, Type, data.Id) : null; - } - catch (Exception) - { - // ignored - } - - EntityChange?.Invoke(CurrentEntity); - } - - #endregion - - #region Output events - - /// - /// Делегат, который будет вызван при изменении выбранной сущности. - /// - /// Новая выбранная сущность. - public delegate void EntityChangeHandler(EntityView newEntity); - - /// - /// Событие, которое вызывается при изменении выбранной сущности. - /// - public event EntityChangeHandler? EntityChange; - - #endregion - - /// - /// Класс, который представляет строку в таблице сущностей. - /// - /// Сущность, связанная со строкой - internal class EntityGridRow(Entity entity) - { - /// - /// Идентификатор сущности. - /// - public int Id { get; set; } = entity.Id; - - /// - /// Название сущности. - /// - public string Name { get; set; } = entity.Name[LocaleType.English]; - - /// - /// Редкость сущности. - /// - public EntityRarity Rarity { get; set; } = entity.Rarity; - } -} diff --git a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.resx b/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.resx deleted file mode 100644 index aea47c3..0000000 --- a/Pf2eDataScrapper/Forms/Controls/DatabaseViewer/DatabaseViewerControl.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - True - - - True - - - 17, 17 - - - 163, 17 - - \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs deleted file mode 100644 index e6444fd..0000000 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.Designer.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; - -sealed partial class EntityTreeViewerControl -{ - /// - /// Обязательная переменная конструктора. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Освободить все используемые ресурсы. - /// - /// истинно, если управляемый ресурс должен быть удален; иначе ложно. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Код, автоматически созданный конструктором компонентов - - /// - /// Требуемый метод для поддержки конструктора — не изменяйте - /// содержимое этого метода с помощью редактора кода. - /// - private void InitializeComponent() - { - ContentPanel = new PictureBox(); - ((System.ComponentModel.ISupportInitialize)ContentPanel).BeginInit(); - SuspendLayout(); - // - // ContentPanel - // - ContentPanel.Location = new Point(0, 0); - ContentPanel.Name = "ContentPanel"; - ContentPanel.Size = new Size(400, 500); - ContentPanel.TabIndex = 0; - ContentPanel.TabStop = false; - ContentPanel.MouseMove += ContentPanel_MouseMove; - // - // EntityTreeViewerControl - // - AutoScaleDimensions = new SizeF(7F, 15F); - AutoScaleMode = AutoScaleMode.Font; - AutoScroll = true; - Controls.Add(ContentPanel); - Name = "EntityTreeViewerControl"; - Size = new Size(400, 500); - Resize += EntityTreeViewerControl_Resize; - ((System.ComponentModel.ISupportInitialize)ContentPanel).EndInit(); - ResumeLayout(false); - } - - #endregion - - public PictureBox ContentPanel; -} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs deleted file mode 100644 index e05d7b3..0000000 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs +++ /dev/null @@ -1,407 +0,0 @@ -using System.ComponentModel; -using Pf2eDataScrapper.Helpers; -using Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure; - -namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; - -/// -/// Элемент управления для отображения структуры сущности. -/// -public sealed partial class EntityTreeViewerControl : UserControl -{ - #region "Поля элемента управления" - - /// - /// Высота строки поля структуры. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] - [Category("Entity Render")] - [Description("The height of each item in the tree view.")] - public int ItemHeight - { - get => _itemHeight; - set - { - _itemHeight = value; - UpdateView(); - } - } - private int _itemHeight = 20; - - /// - /// Сущность, которая будет отображаться в элементе управления. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [Browsable(false)] - [EditorBrowsable(EditorBrowsableState.Never)] - public EntityView? EntityView - { - get => _entityView; - set - { - _entityView = value; - UpdateData(); - } - } - private EntityView? _entityView; - - /// - /// Ширина отступа глубины. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] - [Category("Entity Render")] - [Description("The size of tab.")] - public int DepthSize - { - get => _depthSize; - set - { - _depthSize = value; - UpdateView(); - } - } - private int _depthSize = 20; - - /// - /// Размер шрифта типа поля сущности. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] - [Category("Entity Render")] - [Description("The size of tab.")] - public float TypeFontSize - { - get => _typeFontSize; - set - { - _typeFontSize = value; - UpdateView(); - } - } - private float _typeFontSize = 9.5f; - - /// - /// Размер кнопки плюс/минус. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] - [Category("Entity Render")] - [Description("The size of plus/minus button.")] - public int ButtonSize - { - get => _buttonSize; - set - { - _buttonSize = value; - UpdateView(); - } - } - private int _buttonSize = 20; - - /// - /// Ширина имени поля сущности. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [Browsable(false)] - [EditorBrowsable(EditorBrowsableState.Never)] - public int NameWidth { get; private set; } = 200; - - /// - /// Цвет фона строки поля при наведении на элемент управления. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] - [Category("Entity Render")] - [Description("Color on item hover.")] - public Color HoverColor - { - get => _hoverColor; - set - { - _hoverColor = value; - UpdateView(); - } - } - private Color _hoverColor = SystemColors.ControlDark; - - /// - /// Изображение, используемое для отображения содержимого элемента управления. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [Browsable(false)] - [EditorBrowsable(EditorBrowsableState.Never)] - public Bitmap ContentImage { get; set; } - - #endregion - - #region "Вычисляемые поля" - - /// - /// Ширина области содержимого элемента управления. - /// - [Browsable(false)] - [EditorBrowsable(EditorBrowsableState.Never)] - public int ContentWidth => Width - SystemInformation.VerticalScrollBarWidth - 2; - - #endregion - - #region "Приватные поля" - - - /// - /// Коллекция полей структуры. - /// - private readonly List _rows = []; - - /// - /// Коллекция строк полей структуры, которые будут отображаться в элементе управления. - /// - private List _viewerRows = []; - - /// - /// Флаг, указывающий, что элемент управления в данный момент перерисовывается. - /// - private bool _rendering; - - #endregion - - /// - /// Конструктор класса . - /// - public EntityTreeViewerControl() - { - InitializeComponent(); - - ContentImage = new(ContentWidth, Height); - DoubleBuffered = true; - VerticalScroll.Visible = true; - HorizontalScroll.Visible = false; - - ContentPanel.MouseWheel += ContentPanel_MouseWheel; - } - - #region "Методы обновления" - - /// - /// Обновление данных в элементе управления. - /// - public void UpdateData() - { - if (EntityView?.Entity is null) - { - return; - } - - _rows.Clear(); - ContentPanel.Controls.Clear(); - - StructureBuilder builder = new(); - - _rows.Add(builder.Create("Entity", EntityView.Entity)); - - if (EntityView.SubEntity is not null) - { - string typeName = EntityView.SubEntity.GetType().Name; - - _rows.Add(builder.Create(typeName, EntityView.SubEntity)); - } - - _viewerRows = _rows - .SelectMany(r => r.GetRows(row => new EntityTreeViewerControlRow(this, row))) - .ToList(); - - UpdateRenders(); - } - - /// - /// Обновление отрисовок в элементе управления. - /// - public void UpdateRenders() - { - if (EntityView?.Entity is null) - { - return; - } - - int maxNameWidth = -1; - - foreach (EntityTreeViewerControlRow row in _viewerRows) - { - int nameWidth = row.NameWidth + row.Item.Depth * DepthSize; - maxNameWidth = Math.Max(maxNameWidth, nameWidth); - } - - NameWidth = maxNameWidth + 3; - - // Debug.WriteLine($@"Height Inner {ContentWidth} / Outer {Width}"); - // Debug.WriteLine($@"Name {NameWidth} / Full {ContentWidth}"); - - foreach (EntityTreeViewerControlRow row in _viewerRows) - { - row.UpdateCache(); - } - - UpdateView(); - } - - /// - /// Обновление отображения в элементе управления. - /// - public void UpdateView() - { - if (EntityView?.Entity is null) - { - return; - } - - _viewerRows.ForEach(row => row.UpdateButton()); - - List rows = _viewerRows.Where(r => r.Item.Visible).ToList(); - if (rows.Count == 0) - { - return; - } - - BuildLadder(rows); - - _rendering = true; - ContentImage = new(ContentWidth, rows.Count * ItemHeight); - Graphics g = Graphics.FromImage(ContentImage); - - int viewRow = 0; - foreach (EntityTreeViewerControlRow row in rows) - { - row.ViewIndex = viewRow++; - row.Invalidate(g); - } - - g.Save(); - - ContentPanel.Image = ContentImage; - ContentPanel.Width = ContentImage.Width; - ContentPanel.Height = ContentImage.Height; - - _rendering = false; - } - - #endregion - - #region "Вспомогательные методы" - - /// - /// Построение лестницы для строк полей структуры. - /// - /// Набор строк полей структуры. - private void BuildLadder(List rows) - { - List lastLadder = []; - - for (int i = rows.Count - 1; i >= 0; i--) - { - EntityTreeViewerControlRow row = rows[i]; - List ladder; - - if (row.Depth == 0) - { - ladder = []; - } - else if (row.Depth < lastLadder.Count) - { - ladder = - [ - .. lastLadder[..(row.Depth - 1)], - lastLadder[row.Depth - 1] == LadderType.Empty - ? LadderType.Last - : LadderType.Through, - ]; - } - else if (row.Depth == lastLadder.Count) - { - ladder = lastLadder; - } - else - { - ladder = - [ - .. lastLadder, - .. Enumerable.Repeat(LadderType.Empty, row.Depth - lastLadder.Count - 1), - LadderType.Last, - ]; - } - - row.Ladder = ladder; - - lastLadder = ladder - .Select(l => (l == LadderType.Last) ? LadderType.Through : l) - .ToList(); - } - } - - #endregion - - #region "События элемента управления" - - /// - /// Обработчик события изменения размера элемента управления. - /// - /// Источник события. - /// Объект события. - private void EntityTreeViewerControl_Resize(object sender, EventArgs e) - { - if (_rendering) - { - return; - } - - ContentPanel.Width = Width; - - UpdateRenders(); - } - - /// - /// Обработчик события прокрутки элемента управления. - /// - /// Источник события. - /// Объект события. - private void ContentPanel_MouseWheel(object? sender, MouseEventArgs e) - { - /* - int initValue = VerticalScroll.Value; - int delta = -e.Delta / 120; - - VerticalScroll.Value = Math.Clamp( - initValue + delta, - VerticalScrollBar.Minimum, - VerticalScrollBar.Maximum - VerticalScrollBar.LargeChange + 1 - ); - - if (VerticalScrollBar.Value != initValue) - { - ContentPanel.Invalidate(); - } - */ - } - - /// - /// Обработчик события движения мыши над элементом управления. - /// - /// Источник события. - /// Объект события. - private void ContentPanel_MouseMove(object sender, MouseEventArgs e) - { - PointF p = new(e.X, e.Y); - - foreach (EntityTreeViewerControlRow row in _viewerRows) - { - bool contained = row.Target.Contains(p); - - if (contained != row.Hovered) - { - Rectangle r = new(); - row.RealTarget.Intersect(r); - ContentPanel.Invalidate(r); - } - - row.Hovered = contained; - } - } - - #endregion -} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.resx b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.resx deleted file mode 100644 index 8b2ff64..0000000 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs deleted file mode 100644 index f8c204f..0000000 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs +++ /dev/null @@ -1,607 +0,0 @@ -using FontAwesome.Sharp; -using Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure; -using Font = System.Drawing.Font; -using FontStyle = System.Drawing.FontStyle; -using Image = System.Drawing.Image; - -namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; - -/// -/// Элемент управления для отображения строки поля сущности. -/// -internal class EntityTreeViewerControlRow -{ - /// - /// Элемент управления, которому принадлежит эта строка. - /// - public readonly EntityTreeViewerControl ParentControl; - - /// - /// Отображаемая строка поля сущности. - /// - public readonly StructureField Item; - - #region "Поля строки" - - /// - /// Кэш изображения строки. - /// - public Image Cache - { - get - { - if (_cache == null) - { - UpdateCache(); - } - return _cache!; - } - set => _cache = value; - } - private Image? _cache; - - /// - /// Кэш изображения строки при наведении. - /// - public Image HoveredCache - { - get - { - if (_hoveredCache == null) - { - UpdateCache(); - } - return _hoveredCache!; - } - set => _hoveredCache = value; - } - private Image? _hoveredCache; - - /// - /// Список типов лестницы. - /// - public List Ladder - { - get => _ladder; - set - { - _ladder = value; - _cache = null; - } - } - private List _ladder = []; - - /// - /// Проверяет, находится ли курсор над строкой. - /// - public bool Hovered - { - get => _hovered; - set - { - if (value != _hovered) - { - _hovered = value; - Invalidate(); - } - } - } - private bool _hovered; - - /// - /// Индекс строки в представлении. - /// - public int ViewIndex { get; set; } // = -1; - - /// - /// Кнопка раскрытия/сворачивания. - /// - public IconButton? Button { get; set; } - - #endregion - - #region "Поля для рендеринга" - - /// - public Font Font - { - get => ParentControl.Font; - set => ParentControl.Font = value; - } - - /// - public Color ForeColor - { - get => ParentControl.ForeColor; - set => ParentControl.ForeColor = value; - } - - /// - public Color BackColor - { - get => ParentControl.BackColor; - set => ParentControl.BackColor = value; - } - - /// - public Color HoverColor - { - get => ParentControl.HoverColor; - set => ParentControl.HoverColor = value; - } - - /// - public int ItemHeight - { - get => ParentControl.ItemHeight; - set => ParentControl.ItemHeight = value; - } - - /// - public int DepthSize - { - get => ParentControl.DepthSize; - set => ParentControl.DepthSize = value; - } - - /// - public int ButtonSize - { - get => ParentControl.ButtonSize; - set => ParentControl.ButtonSize = value; - } - - /// - public float TypeFontSize - { - get => ParentControl.TypeFontSize; - set => ParentControl.TypeFontSize = value; - } - - #endregion - - #region "Вычисляемые поля" - - /// - /// Ширина строки. - /// - public int Width => ParentControl.ContentWidth; - - /// - /// Высота строки. - /// - public int Height => ItemHeight; - - /// - /// Флаг наличия кнопки. - /// - public bool HasButton => Item.FieldType == StructureFieldType.Complex; - - /// - /// Глубина отображения строки. - /// - public int Depth => Item.Depth; - - /// - /// Ширина имени в строке. - /// - public int NameWidth - { - get - { - Graphics g = Graphics.FromImage(Cache); - return Convert.ToInt32(g.MeasureString(Item.Name, Font).Width + DepthSize); - } - } - - /// - /// Строковое представление типа поля. - /// - public string TypeText - { - get - { - string result = Item.GetTypeString(); - if (result != "") - { - result = $@"({result})"; - } - return result; - } - } - - /// - /// Шрифт для отображения типа поля. - /// - public Font TypeFont => new(Font.FontFamily, TypeFontSize); - - /// - /// Строковое представление значения поля. - /// - public string ValueText => Item.GetValueString(); - - /// - /// Шрифт для отображения значения поля. - /// - public Font ValueFont => - new( - Font.FontFamily, - Font.Size, - Item.FieldType == StructureFieldType.Complex ? FontStyle.Italic : FontStyle.Regular - ); - - /// - /// Прямоугольник, занимаемый строкой в представлении. - /// - public RectangleF Target => new(0, ViewIndex * Height, Width, Height); - - /// - /// Прямоугольник, занимаемый строкой в родительском представлении. - /// - public RectangleF RealTarget => - new(0, ViewIndex * Height - ParentControl.VerticalScroll.Value, Width, Height); - - #endregion - - /// - /// Список данных для рендеринга строк. - /// - private readonly List _renderData = []; - - #region "Основные методы" - - /// - /// Создание элемента управления строки поля сущности. - /// - /// Родительский элемент управления. - /// Строка поля структуры объекта. - public EntityTreeViewerControlRow(EntityTreeViewerControl parent, StructureField item) - { - ParentControl = parent; - Item = item; - ApplyItem(); - UpdateCache(); - - UpdateButton(); - } - - /// - /// Применение свойств к элементу управления. - /// - private void ApplyItem() - { - _renderData.Clear(); - - _renderData.Add(new(Item.Name, Font, ForeColor)); - _renderData.Add(new(TypeText, TypeFont, GetValueColor())); - _renderData.Add( - new(ValueText.Replace("\r", "").Replace('\n', ' '), ValueFont, GetValueColor()) - ); - - if (HasButton) - { - Button = new() - { - Width = ButtonSize, - Height = ButtonSize, - IconSize = ButtonSize, - IconChar = IconChar.MinusSquare, - ImageAlign = ContentAlignment.MiddleCenter, - TextAlign = ContentAlignment.MiddleCenter, - Visible = false, - }; - - Button.Click += Button_Click; - - ParentControl.ContentPanel.Controls.Add(Button); - } - } - - /// - /// Обновление кэша изображения строки. - /// - public void UpdateCache() - { - Cache = RenderCache(BackColor); - HoveredCache = RenderCache(HoverColor); - } - - /// - /// Создание кэша изображения строки. - /// - /// Цвет заднего фона. - /// Кеш изображения строки. - private Image RenderCache(Color backColor) - { - Image cache = new Bitmap(Width, Height); - Graphics g = Graphics.FromImage(cache); - - g.FillRectangle(new SolidBrush(backColor), 0, 0, Width, Height); - - RenderLadder(g); - RenderContent(g); - - g.Save(); - return cache; - } - - /// - /// Запрос на перерисовку строки. - /// - /// Графический объект. - public void Invalidate(Graphics? g = null) - { - g ??= Graphics.FromImage(ParentControl.ContentImage); - - g.DrawImage(Hovered ? HoveredCache : Cache, Target); - - UpdateButton(); - } - - #endregion - - #region "Кнопка" - - /// - /// Обновление кнопки раскрытия/сворачивания. - /// - public void UpdateButton() - { - if (Button is null) - { - return; - } - - int cx = Convert.ToInt32((0.5 + Depth) * DepthSize - 0.5 * ButtonSize); - int cy = Convert.ToInt32((0.5 + ViewIndex) * ItemHeight - 0.5 * ButtonSize); - - Button.Left = cx; - Button.Top = cy; - - Button!.IconChar = ((StructureFieldComplex)Item).Collapsed - ? IconChar.PlusSquare - : IconChar.MinusSquare; - Button.Visible = Item.Visible; - } - - /// - /// Событие нажатия кнопки раскрытия/сворачивания. - /// - /// Источник события. - /// Событие нажатие кнопки - private void Button_Click(object? sender, EventArgs e) - { - if (Item is StructureFieldComplex complex) - { - complex.Collapsed = !complex.Collapsed; - ParentControl.UpdateView(); - } - } - - #endregion - - #region "Отрисовка" - - /// - /// Отрисовка лестницы. - /// - /// Графический объект. - private void RenderLadder(Graphics g) - { - int n = Depth; - int h = ItemHeight; - int w = DepthSize; - - if (_ladder.Count < Depth) - { - return; - } - - Pen pen = new(Color.FromArgb(127, ForeColor.R, ForeColor.G, ForeColor.B), 1); - pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; - - float cy = 0.5f * h; - - for (int i = 0; i < n; i++) - { - float cx = (i + 1.5f) * w; - - switch (_ladder[i]) - { - case LadderType.Last: - g.DrawLine(pen, cx, 0, cx, cy); - break; - case LadderType.Through: - g.DrawLine(pen, cx, 0, cx, h); - break; - case LadderType.Empty: - default: - break; - } - - if (i == n - 1) - { - g.DrawLine(pen, cx, cy, (i + 2) * w, cy); - } - } - } - - /// - /// Отрисовка содержимого строки. - /// - /// Графический объект. - private void RenderContent(Graphics g) - { - float[] xs = - [ - DepthSize * (Item.Depth + 1), - ParentControl.NameWidth, - ParentControl.NameWidth + g.MeasureString(TypeText, TypeFont).Width + 3, - Width, - ]; - - Pen _ = new(Color.Aqua, 1); - - for (int i = 0; i < 3; i++) - { - StringRenderData item = _renderData[i]; - - item.Left = xs[i]; - item.Width = xs[i + 1] - xs[i]; - item.Height = ItemHeight; - - // g.DrawRectangle(p, item.Rect); - item.Draw(g); - } - } - - #endregion - - #region Функции-помощники в отрисовке - - /// - /// Смешивание цвета с цветом текста. - /// - /// Цвет оттенка. - /// Степень смешения. - /// Смешенный цвет. - private Color BlendColor(Color color, double rate = 0.5) - { - return BlendColor(ForeColor, color, rate); - } - - /// - /// Смешивание цветов. - /// - /// Первый смешиваемый цвет. - /// Второй смешиваемый цвет. - /// Степень смешения. - /// Смешенный цвет. - private Color BlendColor(Color color1, Color color2, double rate) - { - return Color.FromArgb( - Mix(color1.A, color2.A), - Mix(color1.R, color2.R), - Mix(color1.G, color2.G), - Mix(color1.B, color2.B) - ); - - int Mix(int a, int b) => Convert.ToInt32(Math.Round(a * rate + b * (1 - rate))); - } - - /// - /// Получение цвета текста по типу значения поля. - /// - /// Цвет текста. - public Color GetValueColor() - { - return Item switch - { - StructureFieldPrimitive primitive => Type.GetTypeCode(primitive.Type) switch - { - TypeCode.Boolean => BlendColor(Color.DarkGoldenrod), - TypeCode.Int32 => BlendColor(Color.Blue), - TypeCode.Double => BlendColor(Color.Aqua), - TypeCode.String => BlendColor(Color.Green), - _ => ForeColor, - }, - StructureFieldEnum => BlendColor(Color.Red), - StructureFieldNull => BlendColor(BackColor), - _ => ForeColor, - }; - } - - #endregion - - /// - /// Класс для хранения данных рендеринга строки. - /// - /// Текст отображаемой строки. - /// Шрифт отображаемой строки. - /// Цвет отображаемой строки. - private class StringRenderData(string text, Font font, Color color) - { - /// - /// Текст отображаемой строки. - /// - public string Text { get; } = text; - - /// - /// Шрифт отображаемой строки. - /// - public Font Font { get; } = font; - - /// - /// Цвет отображаемой строки. - /// - public Color Color { get; } = color; - - /// - /// Левый край отображаемой строки. - /// - public float Left { get; set; } - - /// - /// Ширина отображаемой строки. - /// - public float Width { get; set; } - - /// - /// Высота отображаемой строки. - /// - public float Height { get; set; } - - /// - /// Прямоугольник, занимаемый строкой. - /// - public RectangleF Rect => new(Left, 0, Width, Height); - - /// - /// Уместить строку в занимаемый прямоугольник. - /// - /// Элемент графики. - /// Занимаемый прямоугольник - /// Строка, на которую будет заканчиваться значнеие при переполнении. - /// - private string Fit(Graphics g, RectangleF rect, string filler = "…") - { - if (Text.Length == 0 || g.MeasureString(Text, Font).Width <= rect.Width) - { - return Text; - } - - string txt = Text.Length > 100 ? Text[..100] : Text; - - int i = txt.Length; - - while (g.MeasureString(txt + filler, Font).Width > rect.Width) - { - txt = txt[..--i]; - if (i == 0) - break; - } - - return txt + filler; - } - - /// - /// Отрисовка строки. - /// - /// Элемент графики. - public void Draw(Graphics g) - { - g.DrawString( - Fit(g, Rect), - Font, - new SolidBrush(Color), - Rect, - new StringFormat - { - Alignment = StringAlignment.Near, - LineAlignment = StringAlignment.Center, - FormatFlags = StringFormatFlags.NoWrap, - } - ); - } - } -} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs deleted file mode 100644 index 6ac4ecf..0000000 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; - -partial class EntityViewerControl -{ - /// - /// Обязательная переменная конструктора. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Освободить все используемые ресурсы. - /// - /// истинно, если управляемый ресурс должен быть удален; иначе ложно. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Код, автоматически созданный конструктором компонентов - - /// - /// Требуемый метод для поддержки конструктора — не изменяйте - /// содержимое этого метода с помощью редактора кода. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - } - - #endregion -} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs deleted file mode 100644 index c9cab89..0000000 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; -public partial class EntityViewerControl : UserControl -{ - public EntityViewerControl() - { - InitializeComponent(); - } -} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx deleted file mode 100644 index 1af7de1..0000000 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.Designer.cs deleted file mode 100644 index bc10be6..0000000 --- a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.Designer.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Pf2eDataScrapper.Forms.Controls; - -partial class ProcessorCollectionControl -{ - /// - /// Обязательная переменная конструктора. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Освободить все используемые ресурсы. - /// - /// истинно, если управляемый ресурс должен быть удален; иначе ложно. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Код, автоматически созданный конструктором компонентов - - /// - /// Требуемый метод для поддержки конструктора — не изменяйте - /// содержимое этого метода с помощью редактора кода. - /// - private void InitializeComponent() - { - RootFlowLayoutPanel = new FlowLayoutPanel(); - SuspendLayout(); - // - // RootFlowLayoutPanel - // - RootFlowLayoutPanel.AutoScroll = true; - RootFlowLayoutPanel.BackColor = SystemColors.Window; - RootFlowLayoutPanel.BorderStyle = BorderStyle.FixedSingle; - RootFlowLayoutPanel.Dock = DockStyle.Fill; - RootFlowLayoutPanel.FlowDirection = FlowDirection.TopDown; - RootFlowLayoutPanel.Location = new Point(0, 0); - RootFlowLayoutPanel.Name = "RootFlowLayoutPanel"; - RootFlowLayoutPanel.Size = new Size(432, 406); - RootFlowLayoutPanel.TabIndex = 0; - RootFlowLayoutPanel.WrapContents = false; - // - // ProcessorCollectionControl - // - AutoScaleDimensions = new SizeF(7F, 15F); - AutoScaleMode = AutoScaleMode.Font; - Controls.Add(RootFlowLayoutPanel); - Name = "ProcessorCollectionControl"; - Size = new Size(432, 406); - ResumeLayout(false); - } - - #endregion - - private FlowLayoutPanel RootFlowLayoutPanel; -} diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs deleted file mode 100644 index 2e33460..0000000 --- a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.ComponentModel; -using Pf2eDataScrapper.App; - -namespace Pf2eDataScrapper.Forms.Controls; - -internal partial class ProcessorCollectionControl : UserControl -{ - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public ProcessorCollection Collection - { - get => _collection; - set - { - _collection = value; - SetContent(); - } - } - private ProcessorCollection _collection = null!; - - public ProcessorCollectionControl() - { - InitializeComponent(); - } - - private Dictionary GroupCheckBoxes { get; } = []; - - private Dictionary GroupFlowLayoutPanels { get; } = []; - - private Dictionary ItemCheckBoxes { get; } = []; - - private void SetContent() - { - RootFlowLayoutPanel.Controls.Clear(); - - foreach (ProcessorGroupEnum group in Enum.GetValues()) - { - GroupCheckBoxes[group] = new() - { - Text = group.GetDescription(), - Tag = group, - Dock = DockStyle.Fill, - ThreeState = true, - Checked = true, - AutoCheck = false, - CheckAlign = ContentAlignment.TopLeft, - }; - - GroupCheckBoxes[group].Click += GroupCheckBox_Click; - - GroupFlowLayoutPanels[group] = new() - { - AutoSize = true, - Dock = DockStyle.Fill, - FlowDirection = FlowDirection.LeftToRight, - WrapContents = true, - BorderStyle = BorderStyle.FixedSingle, - Margin = new(10, 0, 10, 15), - }; - - RootFlowLayoutPanel.Controls.Add(GroupCheckBoxes[group]); - RootFlowLayoutPanel.Controls.Add(GroupFlowLayoutPanels[group]); - } - - foreach (ProcessorCollectionItem item in Collection.AllItems) - { - ItemCheckBoxes[item.Name] = new() - { - Text = item.Description, - Tag = item, - AutoSize = true, - // Dock = DockStyle.Fill, - // MaximumSize = new Size(Width, 20), - // Width = 200, - Checked = item.Active, - CheckAlign = ContentAlignment.TopLeft, - }; - - ItemCheckBoxes[item.Name].CheckedChanged += ItemCheckBox_CheckedChanged; - - GroupFlowLayoutPanels[item.Group].Controls.Add(ItemCheckBoxes[item.Name]); - } - - UpdateContent(); - } - - private void UpdateContent() - { - Dictionary checkStates = []; - List checkedItems = []; - - foreach ((string key, CheckBox checkBox) in ItemCheckBoxes) - { - ProcessorCollectionItem item = Collection[key]; - - if (checkStates.TryGetValue(item.Group, out CheckState state)) - { - /* - * | Checked | Unchecked | Indeterminate - * -------+---------------+---------------+--------------- - * true | Checked | Indeterminate | Indeterminate - * false | Indeterminate | Unchecked | Indeterminate - */ - - checkStates[item.Group] = - checkBox.Checked && state == CheckState.Checked ? CheckState.Checked - : !checkBox.Checked && state == CheckState.Unchecked ? CheckState.Unchecked - : CheckState.Indeterminate; - } - else - { - checkStates[item.Group] = checkBox.Checked - ? CheckState.Checked - : CheckState.Unchecked; - } - - if (checkBox.Checked) - { - checkedItems.Add(key); - } - } - - foreach ((ProcessorGroupEnum groupEnum, CheckBox checkBox) in GroupCheckBoxes) - { - checkBox.CheckState = checkStates.GetValueOrDefault( - groupEnum, - CheckState.Indeterminate - ); - } - - Collection.FilterProcessors(checkedItems); - } - - private void GroupCheckBox_Click(object? sender, EventArgs e) - { - if (sender is null) - { - return; - } - - CheckBox groupCheckBox = (CheckBox)sender; - ProcessorGroupEnum groupEnum = GroupCheckBoxes - .First(pair => pair.Value == groupCheckBox) - .Key; - - CheckState nextState = groupCheckBox.CheckState switch - { - CheckState.Unchecked => CheckState.Checked, - CheckState.Checked => CheckState.Unchecked, - CheckState.Indeterminate => CheckState.Checked, - _ => throw new ArgumentOutOfRangeException( - $"Неизвестное состояние {groupCheckBox.CheckState}" - ), - }; - - groupCheckBox.CheckState = nextState; - - foreach ( - (string _, CheckBox checkBox) in ItemCheckBoxes.Where(pair => - ((ProcessorCollectionItem?)pair.Value.Tag)?.Group == groupEnum - ) - ) - { - checkBox.Checked = nextState == CheckState.Checked; - } - - UpdateContent(); - } - - private void ItemCheckBox_CheckedChanged(object? sender, EventArgs e) - { - UpdateContent(); - } -} diff --git a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.resx b/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.resx deleted file mode 100644 index 8b2ff64..0000000 --- a/Pf2eDataScrapper/Forms/Controls/ProcessorCollectionControl.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs deleted file mode 100644 index 66ae9be..0000000 --- a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs +++ /dev/null @@ -1,240 +0,0 @@ -using Pf2eDataScrapper.Forms.Controls.DatabaseViewer; -using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; - -namespace Pf2eDataScrapper.Forms; - -partial class EntityViewForm -{ - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - RootSplitContainer = new SplitContainer(); - InnerSplitContainer = new SplitContainer(); - TypesListBox = new ListBox(); - ResultSplitContainer = new SplitContainer(); - DescriptionTextBox = new TextBox(); - DatabaseViewer = new DatabaseViewerControl(); - _entityTreeViewerCntrl = new EntityTreeViewerControl(); - MenuStrip = new MenuStrip(); - FileToolStripMenuItem = new ToolStripMenuItem(); - ExitToolStripMenuItem = new ToolStripMenuItem(); - WindowsToolStripMenuItem = new ToolStripMenuItem(); - ImportSettingsWindowsToolStripMenuItem = new ToolStripMenuItem(); - EntityViewWindowToolStripMenuItem = new ToolStripMenuItem(); - ((System.ComponentModel.ISupportInitialize)RootSplitContainer).BeginInit(); - RootSplitContainer.Panel1.SuspendLayout(); - RootSplitContainer.Panel2.SuspendLayout(); - RootSplitContainer.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)InnerSplitContainer).BeginInit(); - InnerSplitContainer.Panel1.SuspendLayout(); - InnerSplitContainer.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)ResultSplitContainer).BeginInit(); - ResultSplitContainer.Panel2.SuspendLayout(); - ResultSplitContainer.SuspendLayout(); - MenuStrip.SuspendLayout(); - SuspendLayout(); - // - // RootSplitContainer - // - RootSplitContainer.Dock = DockStyle.Fill; - RootSplitContainer.Location = new Point(0, 24); - RootSplitContainer.Name = "RootSplitContainer"; - // - // RootSplitContainer.Panel1 - // - RootSplitContainer.Panel1.Controls.Add(InnerSplitContainer); - RootSplitContainer.Panel1MinSize = 200; - // - // RootSplitContainer.Panel2 - // - RootSplitContainer.Panel2.Controls.Add(ResultSplitContainer); - RootSplitContainer.Panel2MinSize = 400; - RootSplitContainer.Size = new Size(1384, 837); - RootSplitContainer.SplitterDistance = 322; - RootSplitContainer.TabIndex = 0; - // - // InnerSplitContainer - // - InnerSplitContainer.Dock = DockStyle.Fill; - InnerSplitContainer.Location = new Point(0, 0); - InnerSplitContainer.Name = "InnerSplitContainer"; - InnerSplitContainer.Orientation = Orientation.Horizontal; - // - // InnerSplitContainer.Panel1 - // - InnerSplitContainer.Panel1.Controls.Add(TypesListBox); - // - // InnerSplitContainer.Panel2 - // - InnerSplitContainer.Panel2.Controls.Add(DatabaseViewer); - InnerSplitContainer.Size = new Size(322, 861); - InnerSplitContainer.SplitterDistance = 126; - InnerSplitContainer.TabIndex = 1; - // - // TypesListBox - // - TypesListBox.Dock = DockStyle.Fill; - TypesListBox.FormattingEnabled = true; - TypesListBox.Location = new Point(0, 0); - TypesListBox.Name = "TypesListBox"; - TypesListBox.Size = new Size(322, 122); - TypesListBox.TabIndex = 0; - TypesListBox.SelectedIndexChanged += TypesListBox_SelectedIndexChanged; - // - // ResultSplitContainer - // - ResultSplitContainer.Dock = DockStyle.Fill; - ResultSplitContainer.Location = new Point(0, 0); - ResultSplitContainer.Name = "ResultSplitContainer"; - // - // ResultSplitContainer.Panel2 - // - ResultSplitContainer.Panel2.Controls.Add(DescriptionTextBox); - ResultSplitContainer.Size = new Size(1058, 837); - ResultSplitContainer.SplitterDistance = 582; - ResultSplitContainer.TabIndex = 1; - // - // DescriptionTextBox - // - DescriptionTextBox.Dock = DockStyle.Fill; - DescriptionTextBox.Font = new Font("Consolas", 9F); - DescriptionTextBox.Location = new Point(0, 0); - DescriptionTextBox.Multiline = true; - DescriptionTextBox.Name = "DescriptionTextBox"; - DescriptionTextBox.ReadOnly = true; - DescriptionTextBox.ScrollBars = ScrollBars.Vertical; - DescriptionTextBox.Size = new Size(472, 837); - DescriptionTextBox.TabIndex = 0; - // - // DatabaseViewer - // - DatabaseViewer.Dock = DockStyle.Fill; - DatabaseViewer.Location = new Point(0, 0); - DatabaseViewer.Name = "DatabaseViewer"; - DatabaseViewer.Size = new Size(322, 731); - DatabaseViewer.TabIndex = 1; - // - // _entityTreeViewerCntrl - // - _entityTreeViewerCntrl.AutoScroll = true; - _entityTreeViewerCntrl.ButtonSize = 15; - _entityTreeViewerCntrl.DepthSize = 15; - _entityTreeViewerCntrl.Dock = DockStyle.Fill; - _entityTreeViewerCntrl.Font = new Font("Consolas", 9F); - _entityTreeViewerCntrl.HoverColor = SystemColors.ControlDark; - _entityTreeViewerCntrl.ItemHeight = 20; - _entityTreeViewerCntrl.Location = new Point(0, 0); - _entityTreeViewerCntrl.Name = "_entityTreeViewerCntrl"; - _entityTreeViewerCntrl.Size = new Size(582, 861); - _entityTreeViewerCntrl.TabIndex = 0; - _entityTreeViewerCntrl.TypeFontSize = 7F; - // - // MenuStrip - // - MenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem }); - MenuStrip.Location = new Point(0, 0); - MenuStrip.Name = "MainMenuStrip"; - MenuStrip.Size = new Size(1384, 24); - MenuStrip.TabIndex = 1; - MenuStrip.Text = "menuStrip1"; - // - // FileToolStripMenuItem - // - FileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ExitToolStripMenuItem }); - FileToolStripMenuItem.Name = "FileToolStripMenuItem"; - FileToolStripMenuItem.Size = new Size(48, 20); - FileToolStripMenuItem.Text = "Файл"; - // - // ExitToolStripMenuItem - // - ExitToolStripMenuItem.Name = "ExitToolStripMenuItem"; - ExitToolStripMenuItem.Size = new Size(109, 22); - ExitToolStripMenuItem.Text = "Выход"; - ExitToolStripMenuItem.Click += ExitToolStripMenuItem_Click; - // - // WindowsToolStripMenuItem - // - WindowsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ImportSettingsWindowsToolStripMenuItem, EntityViewWindowToolStripMenuItem }); - WindowsToolStripMenuItem.Name = "WindowsToolStripMenuItem"; - WindowsToolStripMenuItem.Size = new Size(47, 20); - WindowsToolStripMenuItem.Text = "Окна"; - // - // ImportSettingsWindowsToolStripMenuItem - // - ImportSettingsWindowsToolStripMenuItem.Name = "ImportSettingsWindowsToolStripMenuItem"; - ImportSettingsWindowsToolStripMenuItem.Size = new Size(228, 22); - ImportSettingsWindowsToolStripMenuItem.Text = "Настройка импорта данных"; - ImportSettingsWindowsToolStripMenuItem.Click += ImportSettingsWindowsToolStripMenuItem_Click; - // - // EntityViewWindowToolStripMenuItem - // - EntityViewWindowToolStripMenuItem.Enabled = false; - EntityViewWindowToolStripMenuItem.Name = "EntityViewWindowToolStripMenuItem"; - EntityViewWindowToolStripMenuItem.Size = new Size(228, 22); - EntityViewWindowToolStripMenuItem.Text = "Просмотр сущностей"; - // - // EntityViewForm - // - AutoScaleDimensions = new SizeF(7F, 15F); - AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1384, 861); - Controls.Add(RootSplitContainer); - Controls.Add(MenuStrip); - Name = "EntityViewForm"; - Text = "DataBaseViewer"; - RootSplitContainer.Panel1.ResumeLayout(false); - RootSplitContainer.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)RootSplitContainer).EndInit(); - RootSplitContainer.ResumeLayout(false); - InnerSplitContainer.Panel1.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)InnerSplitContainer).EndInit(); - InnerSplitContainer.ResumeLayout(false); - ResultSplitContainer.Panel2.ResumeLayout(false); - ResultSplitContainer.Panel2.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)ResultSplitContainer).EndInit(); - ResultSplitContainer.ResumeLayout(false); - MenuStrip.ResumeLayout(false); - MenuStrip.PerformLayout(); - ResumeLayout(false); - PerformLayout(); - } - - #endregion - - private SplitContainer RootSplitContainer; - private SplitContainer InnerSplitContainer; - private ListBox TypesListBox; - private SplitContainer ResultSplitContainer; - private TextBox DescriptionTextBox; - private EntityTreeViewerControl _entityTreeViewerCntrl; - private DatabaseViewerControl DatabaseViewer; - private MenuStrip MenuStrip; - private ToolStripMenuItem FileToolStripMenuItem; - private ToolStripMenuItem ExitToolStripMenuItem; - private ToolStripMenuItem WindowsToolStripMenuItem; - private ToolStripMenuItem ImportSettingsWindowsToolStripMenuItem; - private ToolStripMenuItem EntityViewWindowToolStripMenuItem; -} diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.cs b/Pf2eDataScrapper/Forms/EntityViewForm.cs deleted file mode 100644 index ade43ff..0000000 --- a/Pf2eDataScrapper/Forms/EntityViewForm.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.ComponentModel; -using Pf2eDataScrapper.Helpers; -using Pf2eModel; -using Pf2eModel.Model.Enum; - -namespace Pf2eDataScrapper.Forms; - -/// -/// Основная форма приложения. -/// -internal partial class EntityViewForm : Form -{ - public Pf2eDbContext Context { get; } - - /// - /// Элемент управления для отображения дерева сущностей. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public EntityView? CurrentView - { - get => _entityTreeViewerCntrl.EntityView; - set - { - _entityTreeViewerCntrl.EntityView = value; - Application.DoEvents(); - } - } - - /// - /// Конструктор класса . - /// - public EntityViewForm(Pf2eDbContext context) - { - Context = context; - - InitializeComponent(); - - DatabaseViewer.EntityChange += DatabaseViewer_EntityChange; - - Reset(); - } - - /// - /// Сброс состояния формы. - /// - public void Reset() - { - TypesListBox.DataSource = Context - .Entities.Select(e => e.Type) - .Distinct() - .OrderBy(t => t.ToString()) - .ToList(); - - if (TypesListBox.Items.Count > 0) - { - TypesListBox.SelectedIndex = 0; - } - } - - /// - /// Обработчик события изменения выбранного элемента в списке типов сущностей. - /// - /// Источник события. - /// Данные события. - private void TypesListBox_SelectedIndexChanged(object sender, EventArgs _) - { - if (TypesListBox.SelectedItem is null) - { - return; - } - - CurrentView = null; - - DatabaseViewer.Type = (EntityType)TypesListBox.SelectedItem; - } - - /// - /// Обработчик события изменения выбранной сущности в дереве сущностей. - /// - /// Новая выбранная сущность. - private void DatabaseViewer_EntityChange(EntityView newEntity) - { - CurrentView = newEntity; - - DescriptionTextBox.Text = newEntity.Entity!.Description; - } - - private void ExitToolStripMenuItem_Click(object sender, EventArgs e) - { - Close(); - } - - private void ImportSettingsWindowsToolStripMenuItem_Click(object sender, EventArgs e) - { - ApplicationStateChange?.Invoke(this, ApplicationStage.InitialSettings); - } - - public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange; -} diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.resx b/Pf2eDataScrapper/Forms/EntityViewForm.resx deleted file mode 100644 index 9b2a006..0000000 --- a/Pf2eDataScrapper/Forms/EntityViewForm.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - - 51 - - \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs b/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs deleted file mode 100644 index 1d2a967..0000000 --- a/Pf2eDataScrapper/Forms/ProgressForm.Designer.cs +++ /dev/null @@ -1,128 +0,0 @@ -namespace Pf2eDataScrapper.Forms; - -partial class ProgressForm -{ - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - ProcessorsProgressBar = new ProgressBar(); - ProcessorsLabel = new Label(); - LocalLabel = new Label(); - LocalProgressBar = new ProgressBar(); - MessageListView = new ListView(); - TimeColumnHeader = new ColumnHeader(); - TypeColumnHeader = new ColumnHeader(); - MessageColumnHeader = new ColumnHeader(); - SuspendLayout(); - // - // ProcessorsProgressBar - // - ProcessorsProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - ProcessorsProgressBar.Location = new Point(12, 27); - ProcessorsProgressBar.Name = "ProcessorsProgressBar"; - ProcessorsProgressBar.Size = new Size(537, 23); - ProcessorsProgressBar.TabIndex = 0; - // - // ProcessorsLabel - // - ProcessorsLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - ProcessorsLabel.Location = new Point(12, 9); - ProcessorsLabel.Name = "ProcessorsLabel"; - ProcessorsLabel.Size = new Size(537, 15); - ProcessorsLabel.TabIndex = 1; - ProcessorsLabel.Text = "ProcessorsLabel"; - // - // LocalLabel - // - LocalLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - LocalLabel.Location = new Point(12, 59); - LocalLabel.Name = "LocalLabel"; - LocalLabel.Size = new Size(537, 15); - LocalLabel.TabIndex = 2; - LocalLabel.Text = "LocalLabel"; - // - // LocalProgressBar - // - LocalProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - LocalProgressBar.Location = new Point(12, 77); - LocalProgressBar.Name = "LocalProgressBar"; - LocalProgressBar.Size = new Size(537, 23); - LocalProgressBar.TabIndex = 3; - // - // MessageListView - // - MessageListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - MessageListView.Columns.AddRange(new ColumnHeader[] { TimeColumnHeader, TypeColumnHeader, MessageColumnHeader }); - MessageListView.GridLines = true; - MessageListView.HeaderStyle = ColumnHeaderStyle.Nonclickable; - MessageListView.Location = new Point(12, 114); - MessageListView.Name = "MessageListView"; - MessageListView.Size = new Size(537, 450); - MessageListView.TabIndex = 4; - MessageListView.UseCompatibleStateImageBehavior = false; - MessageListView.View = View.Details; - // - // TimeColumnHeader - // - TimeColumnHeader.Text = "Время"; - TimeColumnHeader.Width = 80; - // - // TypeColumnHeader - // - TypeColumnHeader.Text = "Тип"; - TypeColumnHeader.Width = 80; - // - // MessageColumnHeader - // - MessageColumnHeader.Text = "Сообщение"; - MessageColumnHeader.Width = 2000; - // - // ProgressForm - // - AutoScaleDimensions = new SizeF(7F, 15F); - AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(561, 576); - Controls.Add(MessageListView); - Controls.Add(LocalProgressBar); - Controls.Add(LocalLabel); - Controls.Add(ProcessorsLabel); - Controls.Add(ProcessorsProgressBar); - Name = "ProgressForm"; - Text = "ProgressForm"; - ResumeLayout(false); - } - - #endregion - - private ProgressBar ProcessorsProgressBar; - private Label ProcessorsLabel; - private Label LocalLabel; - private ProgressBar LocalProgressBar; - private ListView MessageListView; - private ColumnHeader TimeColumnHeader; - private ColumnHeader TypeColumnHeader; - private ColumnHeader MessageColumnHeader; -} \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/ProgressForm.cs b/Pf2eDataScrapper/Forms/ProgressForm.cs deleted file mode 100644 index 32cdd08..0000000 --- a/Pf2eDataScrapper/Forms/ProgressForm.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Pf2eDataScrapper.Helpers; - -namespace Pf2eDataScrapper.Forms; - -internal partial class ProgressForm : Form -{ - public ProcessProgress Progress { get; } - - public ProgressForm(ProcessProgress progress) - { - Progress = progress; - - InitializeComponent(); - - Progress.ProgressUpdate += Progress_ProgressUpdate; - Progress.ProgressMessage += Progress_ProgressMessage; - - MessageListView - .GetType() - .GetProperty( - "DoubleBuffered", - System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic - )! - .SetValue(MessageListView, true, null); - } - - private void Progress_ProgressUpdate(ProcessProgress sender) - { - if (InvokeRequired) - { - Invoke(Progress_ProgressUpdate, sender); - return; - } - - ProcessorsLabel.Text = sender.Processors.ToString(); - ProcessorsProgressBar.Maximum = sender.Processors.Count; - ProcessorsProgressBar.Value = sender.Processors.Current; - - LocalLabel.Text = sender.Local.ToString(); - LocalProgressBar.Maximum = sender.Local.Count; - LocalProgressBar.Value = sender.Local.Current; - - Application.DoEvents(); - } - - private void Progress_ProgressMessage(ProcessProgress sender, LogLevel level, string message) - { - if (InvokeRequired) - { - Invoke(Progress_ProgressMessage, sender, level, message); - return; - } - - ListViewItem item = new([DateTime.Now.ToString("HH:mm:ss.fff"), level.ToString(), message]) - { - BackColor = level switch - { - LogLevel.Important => Color.Salmon, - LogLevel.Warning => Color.Khaki, - LogLevel.Information => Color.White, - LogLevel.Progress => Color.LightGray, - _ => Color.White, - }, - }; - - MessageListView.Items.Add(item); - //MessageListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); - //MessageListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); - - Application.DoEvents(); - } -} diff --git a/Pf2eDataScrapper/Forms/ProgressForm.resx b/Pf2eDataScrapper/Forms/ProgressForm.resx deleted file mode 100644 index 8b2ff64..0000000 --- a/Pf2eDataScrapper/Forms/ProgressForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/StartForm.Designer.cs b/Pf2eDataScrapper/Forms/StartForm.Designer.cs deleted file mode 100644 index 7519395..0000000 --- a/Pf2eDataScrapper/Forms/StartForm.Designer.cs +++ /dev/null @@ -1,236 +0,0 @@ -namespace Pf2eDataScrapper.Forms; - -partial class StartForm -{ - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - MainSplitContainer = new SplitContainer(); - PropertiesGroupBox = new GroupBox(); - SettingsPropertyGrid = new PropertyGrid(); - ProcessorsGroupBox = new GroupBox(); - ProcessorCollectionControl = new Pf2eDataScrapper.Forms.Controls.ProcessorCollectionControl(); - SelectAllButton = new Button(); - DeselectAllButton = new Button(); - StartButton = new Button(); - MenuStrip = new MenuStrip(); - FileToolStripMenuItem = new ToolStripMenuItem(); - ExitToolStripMenuItem = new ToolStripMenuItem(); - WindowsToolStripMenuItem = new ToolStripMenuItem(); - ImportSettingsWindowsToolStripMenuItem = new ToolStripMenuItem(); - EntityViewWindowToolStripMenuItem = new ToolStripMenuItem(); - StartToolStripMenuItem = new ToolStripMenuItem(); - ((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit(); - MainSplitContainer.Panel1.SuspendLayout(); - MainSplitContainer.Panel2.SuspendLayout(); - MainSplitContainer.SuspendLayout(); - PropertiesGroupBox.SuspendLayout(); - ProcessorsGroupBox.SuspendLayout(); - MenuStrip.SuspendLayout(); - SuspendLayout(); - // - // MainSplitContainer - // - MainSplitContainer.Dock = DockStyle.Fill; - MainSplitContainer.Location = new Point(0, 24); - MainSplitContainer.Name = "MainSplitContainer"; - // - // MainSplitContainer.Panel1 - // - MainSplitContainer.Panel1.Controls.Add(PropertiesGroupBox); - MainSplitContainer.Panel1MinSize = 300; - // - // MainSplitContainer.Panel2 - // - MainSplitContainer.Panel2.Controls.Add(ProcessorsGroupBox); - MainSplitContainer.Panel2.Controls.Add(StartButton); - MainSplitContainer.Panel2MinSize = 300; - MainSplitContainer.Size = new Size(931, 653); - MainSplitContainer.SplitterDistance = 300; - MainSplitContainer.TabIndex = 0; - // - // PropertiesGroupBox - // - PropertiesGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - PropertiesGroupBox.Controls.Add(SettingsPropertyGrid); - PropertiesGroupBox.Location = new Point(3, 3); - PropertiesGroupBox.Name = "PropertiesGroupBox"; - PropertiesGroupBox.Size = new Size(294, 647); - PropertiesGroupBox.TabIndex = 0; - PropertiesGroupBox.TabStop = false; - PropertiesGroupBox.Text = "Свойства"; - // - // SettingsPropertyGrid - // - SettingsPropertyGrid.BackColor = SystemColors.Control; - SettingsPropertyGrid.Dock = DockStyle.Fill; - SettingsPropertyGrid.Location = new Point(3, 19); - SettingsPropertyGrid.Name = "SettingsPropertyGrid"; - SettingsPropertyGrid.Size = new Size(288, 625); - SettingsPropertyGrid.TabIndex = 0; - // - // ProcessorsGroupBox - // - ProcessorsGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - ProcessorsGroupBox.Controls.Add(ProcessorCollectionControl); - ProcessorsGroupBox.Controls.Add(SelectAllButton); - ProcessorsGroupBox.Controls.Add(DeselectAllButton); - ProcessorsGroupBox.Location = new Point(3, 3); - ProcessorsGroupBox.Name = "ProcessorsGroupBox"; - ProcessorsGroupBox.Size = new Size(621, 578); - ProcessorsGroupBox.TabIndex = 1; - ProcessorsGroupBox.TabStop = false; - ProcessorsGroupBox.Text = "Обработчики"; - // - // ProcessorCollectionControl - // - ProcessorCollectionControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - ProcessorCollectionControl.Location = new Point(6, 48); - ProcessorCollectionControl.Name = "ProcessorCollectionControl"; - ProcessorCollectionControl.Size = new Size(609, 524); - ProcessorCollectionControl.TabIndex = 2; - // - // SelectAllButton - // - SelectAllButton.Location = new Point(149, 19); - SelectAllButton.Name = "SelectAllButton"; - SelectAllButton.Size = new Size(137, 23); - SelectAllButton.TabIndex = 1; - SelectAllButton.Text = "Выделить все"; - SelectAllButton.UseVisualStyleBackColor = true; - // - // DeselectAllButton - // - DeselectAllButton.Location = new Point(6, 19); - DeselectAllButton.Name = "DeselectAllButton"; - DeselectAllButton.Size = new Size(137, 23); - DeselectAllButton.TabIndex = 0; - DeselectAllButton.Text = "Снять выделение всех"; - DeselectAllButton.UseVisualStyleBackColor = true; - // - // StartButton - // - StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold); - StartButton.Location = new Point(3, 587); - StartButton.Name = "StartButton"; - StartButton.Size = new Size(621, 63); - StartButton.TabIndex = 0; - StartButton.Text = "Запуск"; - StartButton.UseVisualStyleBackColor = true; - StartButton.Click += StartButton_Click; - // - // MenuStrip - // - MenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem, StartToolStripMenuItem }); - MenuStrip.Location = new Point(0, 0); - MenuStrip.Name = "MenuStrip"; - MenuStrip.Size = new Size(931, 24); - MenuStrip.TabIndex = 1; - MenuStrip.Text = "menuStrip1"; - // - // FileToolStripMenuItem - // - FileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ExitToolStripMenuItem }); - FileToolStripMenuItem.Name = "FileToolStripMenuItem"; - FileToolStripMenuItem.Size = new Size(48, 20); - FileToolStripMenuItem.Text = "Файл"; - // - // ExitToolStripMenuItem - // - ExitToolStripMenuItem.Name = "ExitToolStripMenuItem"; - ExitToolStripMenuItem.Size = new Size(109, 22); - ExitToolStripMenuItem.Text = "Выход"; - ExitToolStripMenuItem.Click += ExitToolStripMenuItem_Click; - // - // WindowsToolStripMenuItem - // - WindowsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ImportSettingsWindowsToolStripMenuItem, EntityViewWindowToolStripMenuItem }); - WindowsToolStripMenuItem.Name = "WindowsToolStripMenuItem"; - WindowsToolStripMenuItem.Size = new Size(47, 20); - WindowsToolStripMenuItem.Text = "Окна"; - // - // ImportSettingsWindowsToolStripMenuItem - // - ImportSettingsWindowsToolStripMenuItem.Enabled = false; - ImportSettingsWindowsToolStripMenuItem.Name = "ImportSettingsWindowsToolStripMenuItem"; - ImportSettingsWindowsToolStripMenuItem.Size = new Size(228, 22); - ImportSettingsWindowsToolStripMenuItem.Text = "Настройка импорта данных"; - // - // EntityViewWindowToolStripMenuItem - // - EntityViewWindowToolStripMenuItem.Name = "EntityViewWindowToolStripMenuItem"; - EntityViewWindowToolStripMenuItem.Size = new Size(228, 22); - EntityViewWindowToolStripMenuItem.Text = "Просмотр сущностей"; - EntityViewWindowToolStripMenuItem.Click += EntityViewWindowToolStripMenuItem_Click; - // - // StartToolStripMenuItem - // - StartToolStripMenuItem.Name = "StartToolStripMenuItem"; - StartToolStripMenuItem.Size = new Size(108, 20); - StartToolStripMenuItem.Text = "Запуск импорта"; - StartToolStripMenuItem.Click += StartToolStripMenuItem_Click; - // - // StartForm - // - AutoScaleDimensions = new SizeF(7F, 15F); - AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(931, 677); - Controls.Add(MainSplitContainer); - Controls.Add(MenuStrip); - Name = "StartForm"; - Text = "Настройки импорта данных"; - Load += StartForm_Load; - MainSplitContainer.Panel1.ResumeLayout(false); - MainSplitContainer.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)MainSplitContainer).EndInit(); - MainSplitContainer.ResumeLayout(false); - PropertiesGroupBox.ResumeLayout(false); - ProcessorsGroupBox.ResumeLayout(false); - MenuStrip.ResumeLayout(false); - MenuStrip.PerformLayout(); - ResumeLayout(false); - PerformLayout(); - } - - #endregion - - private SplitContainer MainSplitContainer; - private GroupBox PropertiesGroupBox; - private PropertyGrid SettingsPropertyGrid; - private Button StartButton; - private GroupBox ProcessorsGroupBox; - private Button SelectAllButton; - private Button DeselectAllButton; - private Controls.ProcessorCollectionControl ProcessorCollectionControl; - private MenuStrip MenuStrip; - private ToolStripMenuItem FileToolStripMenuItem; - private ToolStripMenuItem ExitToolStripMenuItem; - private ToolStripMenuItem WindowsToolStripMenuItem; - private ToolStripMenuItem ImportSettingsWindowsToolStripMenuItem; - private ToolStripMenuItem EntityViewWindowToolStripMenuItem; - private ToolStripMenuItem StartToolStripMenuItem; -} \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/StartForm.cs b/Pf2eDataScrapper/Forms/StartForm.cs deleted file mode 100644 index 899bbda..0000000 --- a/Pf2eDataScrapper/Forms/StartForm.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Pf2eDataScrapper.App; - -namespace Pf2eDataScrapper.Forms; - -internal partial class StartForm : Form -{ - private ProcessorCollection ProcessorCollection { get; } - public ApplicationSettings Settings { get; } - - public StartForm(ProcessorCollection processorCollection, ApplicationSettings settings) - { - ProcessorCollection = processorCollection; - Settings = settings; - - InitializeComponent(); - } - - private void StartForm_Load(object sender, EventArgs e) - { - ProcessorCollectionControl.Collection = ProcessorCollection; - - SettingsPropertyGrid.SelectedObject = Settings; - } - - /* - private void PopulateListView() - { - Dictionary> items = []; - - foreach (ProcessorCollectionItem item in ProcessorCollection.AllItems) - { - items.TryAdd(item.Group, []); - items[item.Group].Add(item); - } - - ProcessorsListView.Groups.Clear(); - - foreach (ProcessorGroupEnum group in Enum.GetValues()) - { - if (!items.TryGetValue(group, out List? list)) - { - continue; - } - - ListViewGroup listViewGroup = new(group.GetDescription()) - { - TaskLink = "Выбрать/снять все", - }; - ProcessorsListView.Groups.Add(listViewGroup); - - foreach (ProcessorCollectionItem item in list) - { - ListViewItem listViewItem = new() - { - Group = listViewGroup, - Text = item.Description, - Checked = true, - ToolTipText = item.Name, - }; - ProcessorsListView.Items.Add(listViewItem); - } - } - } - - private void ProcessorsListView_GroupTaskLinkClick(object sender, ListViewGroupEventArgs e) - { - ListViewGroup group = ProcessorsListView.Groups[e.GroupIndex]; - - bool allSelected = group.Items.Cast().All(item => item.Checked); - - foreach (ListViewItem item in group.Items.Cast()) - { - item.Checked = !allSelected; - } - } - */ - #region "Кнопка старта" - - private void StartButton_Click(object sender, EventArgs e) - { - ApplicationStateChange?.Invoke(this, ApplicationStage.ImportProcess); - } - - #endregion - - #region "Основное меню" - - - private void ExitToolStripMenuItem_Click(object sender, EventArgs e) - { - Close(); - } - - private void EntityViewWindowToolStripMenuItem_Click(object sender, EventArgs e) - { - ApplicationStateChange?.Invoke(this, ApplicationStage.Viewer); - } - - private void StartToolStripMenuItem_Click(object sender, EventArgs e) - { - ApplicationStateChange?.Invoke(this, ApplicationStage.ImportProcess); - } - - public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange; - - #endregion -} diff --git a/Pf2eDataScrapper/Forms/StartForm.resx b/Pf2eDataScrapper/Forms/StartForm.resx deleted file mode 100644 index 2099966..0000000 --- a/Pf2eDataScrapper/Forms/StartForm.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/Pf2eDataScrapper/Helpers/DatabaseHelper.cs b/Pf2eDataScrapper/Helpers/DatabaseHelper.cs index bcf30e0..690f4f3 100644 --- a/Pf2eDataScrapper/Helpers/DatabaseHelper.cs +++ b/Pf2eDataScrapper/Helpers/DatabaseHelper.cs @@ -1,4 +1,5 @@ -using Pf2eModel; +using System.Linq; +using Pf2eModel; using Slugify; namespace Pf2eDataScrapper.Helpers; diff --git a/Pf2eDataScrapper/Helpers/EntityView.cs b/Pf2eDataScrapper/Helpers/EntityView.cs index dd9c5fe..fb818db 100644 --- a/Pf2eDataScrapper/Helpers/EntityView.cs +++ b/Pf2eDataScrapper/Helpers/EntityView.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; using Pf2eModel; using Pf2eModel.Model; using Pf2eModel.Model.Base; diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index cb6de5d..43425af 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -1,6 +1,6 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; using System.Windows; -using System.Windows.Forms.Integration; using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; @@ -9,11 +9,10 @@ using Pf2eDataScrapper.WPF; using Pf2eDataScrapper.WPF.Views; using Pf2eModel; using Slugify; -using StartForm = Pf2eDataScrapper.WPF.Views.StartForm; namespace Pf2eDataScrapper; -internal sealed class ImportApplication : ApplicationContext +internal sealed class ImportApplication : Application { public ImportApplication( IServiceProvider serviceProvider, @@ -82,7 +81,6 @@ internal sealed class ImportApplication : ApplicationContext _startFormWpf.ApplicationStateChange += ApplicationStateChange; _startFormWpf.Show(); - ElementHost.EnableModelessKeyboardInterop(_startFormWpf); CurrentWpfForm = _startFormWpf; break; @@ -99,7 +97,6 @@ internal sealed class ImportApplication : ApplicationContext _progressFormWpf.ApplicationStateChange += ApplicationStateChange; _progressFormWpf.Show(); - ElementHost.EnableModelessKeyboardInterop(_progressFormWpf); CurrentWpfForm = _progressFormWpf; executor.Start(); @@ -112,7 +109,6 @@ internal sealed class ImportApplication : ApplicationContext //_entityForm.ApplicationStateChange += ApplicationStateChange; _entityForm.Show(); - ElementHost.EnableModelessKeyboardInterop(_entityForm); CurrentWpfForm = _entityForm; break; @@ -147,7 +143,7 @@ internal sealed class ImportApplication : ApplicationContext break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(stage)); } } @@ -167,7 +163,7 @@ internal sealed class ImportApplication : ApplicationContext { if (sender?.Equals(CurrentWpfForm) ?? false) { - ExitThread(); + Shutdown(); } else { diff --git a/Pf2eDataScrapper/Importers/AbstractCompendium.cs b/Pf2eDataScrapper/Importers/AbstractCompendium.cs index dc1108e..ee6d349 100644 --- a/Pf2eDataScrapper/Importers/AbstractCompendium.cs +++ b/Pf2eDataScrapper/Importers/AbstractCompendium.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Repositories; namespace Pf2eDataScrapper.Importers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs index ccb52cd..c075b3d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs @@ -1,5 +1,8 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; using System.Net.Http; +using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Repositories; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs index 9ee710d..ea64077 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs @@ -1,5 +1,8 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; +using System.Linq; using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs index f8891ca..97cbc53 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs @@ -1,4 +1,5 @@ -using Pf2eModel.Model.Base.Targets; +using System.Collections.Generic; +using Pf2eModel.Model.Base.Targets; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs index b29847a..b3f407e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdProcessorInputExtension.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.Processors; using Pf2eModel.Model.Enum; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs index 84b3a64..cfa169a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/RuleToc.cs @@ -1,4 +1,7 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using Pf2eModel.Model.Base.Source; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs index 159ade1..2613b27 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs @@ -1,4 +1,6 @@ -using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index 9121e01..2fc750e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs index f0b9ba1..fac791d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractSubclassProcessor.cs @@ -1,4 +1,5 @@ -using Pf2eDataScrapper.App; +using System.Linq; +using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs index 88b29fc..d8215ba 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs @@ -1,4 +1,5 @@ -using Pf2eDataScrapper.App; +using System; +using Pf2eDataScrapper.App; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; using Pf2eModel; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs index 001f71e..6a6b1a7 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs @@ -1,4 +1,6 @@ -using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs index 6de832c..2fe2017 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SchemaBuildProcessor.cs @@ -1,4 +1,7 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs index ec100ae..b0451aa 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs @@ -1,4 +1,6 @@ -using Pf2eDataScrapper.App; +using System.Collections.Generic; +using System.Linq; +using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs index b6b9d2d..babaafc 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/AncestryProcessor.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index 19c407d..8047147 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -1,4 +1,7 @@ -using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs index 636ddc4..6697968 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/FeatProcessor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Linq; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs index 407af42..dc658a5 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/RitualProcessor.cs @@ -1,4 +1,7 @@ -using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs index d60aa25..729f807 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Linq; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs index 6c17394..7d0b629 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesOrderProcessor.cs @@ -5,6 +5,8 @@ // так что ставить порядок ВСЕХ правил в архиве не получится, только тех, что есть в содержании. // Ну а пока просто расставляем родительские и дочерние элементы. +using System.Collections.Generic; +using System.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs index 052f55d..6ba76db 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System.Linq; +using System.Text.RegularExpressions; using System.Web; using AngleSharp; using AngleSharp.Dom; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs index 631ff74..4a61ede 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/SpellProcessor.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolder.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolder.cs index 49f1f15..a7ff17d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolder.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System; +using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolderCollection.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolderCollection.cs index 69d0bf0..7528df6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolderCollection.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/CompendiumFolderCollection.cs @@ -1,4 +1,7 @@ -namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; +using System.Collections.Generic; +using System.Linq; + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; /// /// Коллекция папок компендиума diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs index 15e3103..7dfc785 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs @@ -1,5 +1,8 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; +using System.Linq; using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs index 3c25f0f..214e593 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/FoundryEnAbstractProcessor.cs @@ -1,4 +1,7 @@ -using System.Security.Cryptography; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; using System.Text; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json.Linq; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs index ac5a0e3..5093b44 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/CompendiumItemsProcessor.cs @@ -1,4 +1,6 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; +using System.Linq; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs index 84adc47..1cac93c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaBuildProcessor.cs @@ -1,4 +1,7 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaTypeResolver.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaTypeResolver.cs index e741c24..a469666 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaTypeResolver.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Processors/Preparation/SchemaTypeResolver.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.SchemaBuilder; using Pf2eDataScrapper.SchemaBuilder.Types; diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj index ef7689d..13cb96a 100644 --- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj +++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj @@ -3,8 +3,8 @@ WinExe net9.0-windows7.0 enable - true - enable + False + disable True @@ -39,18 +39,4 @@ Always - - - Component - - - UserControl - - - UserControl - - - Form - - diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index 560b179..b4d97f8 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -1,4 +1,7 @@ -using System.ComponentModel; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; @@ -49,7 +52,10 @@ internal sealed class ProcessorExecutor( { Parallel.ForEach( IterateProcessors(item), - new() { MaxDegreeOfParallelism = item.Parallel ? Settings.MaxThreads : 1 }, + new ParallelOptions() + { + MaxDegreeOfParallelism = item.Parallel ? Settings.MaxThreads : 1, + }, RunProcessor ); }; diff --git a/Pf2eDataScrapper/Processors/ProcessorInput.cs b/Pf2eDataScrapper/Processors/ProcessorInput.cs index e0663b1..632d605 100644 --- a/Pf2eDataScrapper/Processors/ProcessorInput.cs +++ b/Pf2eDataScrapper/Processors/ProcessorInput.cs @@ -1,4 +1,7 @@ -using System.Security.Cryptography; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; using System.Text; using Newtonsoft.Json.Linq; using NpgsqlTypes; diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs index 71d8b6c..b6dd3a8 100644 --- a/Pf2eDataScrapper/Program.cs +++ b/Pf2eDataScrapper/Program.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using System.Windows; +using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.Importers.Implementations.Aonprd; using Pf2eDataScrapper.Importers.Implementations.FoundryEn; using Pf2eDataScrapper.WPF; @@ -17,7 +19,7 @@ internal static class Program // see https://aka.ms/applicationconfiguration. ApplicationConfiguration.Initialize(); - Application.SetHighDpiMode(HighDpiMode.SystemAware); + // Application.SetHighDpiMode(HighDpiMode.SystemAware); /* #pragma warning disable WFO5001 Application.SetColorMode(SystemColorMode.System); @@ -31,7 +33,8 @@ internal static class Program serviceProvider ); - Application.Run(app); + app.Run(); + // Application.Run(app); } private static IServiceCollection RegisterServices() diff --git a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs index 6212448..db2d0df 100644 --- a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs @@ -1,4 +1,6 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; diff --git a/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs b/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs index cd7792f..544d28d 100644 --- a/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs +++ b/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.SchemaBuilder.Types; namespace Pf2eDataScrapper.SchemaBuilder; diff --git a/Pf2eDataScrapper/SchemaBuilder/TypeResolver.cs b/Pf2eDataScrapper/SchemaBuilder/TypeResolver.cs index b436c9d..47a69db 100644 --- a/Pf2eDataScrapper/SchemaBuilder/TypeResolver.cs +++ b/Pf2eDataScrapper/SchemaBuilder/TypeResolver.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.SchemaBuilder.Types; namespace Pf2eDataScrapper.SchemaBuilder; diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/AbstractSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/AbstractSchemaType.cs index af83044..d56d83f 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/AbstractSchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/AbstractSchemaType.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.SchemaBuilder.Types; diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/ArraySchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/ArraySchemaType.cs index 3c928d5..aaaf7f7 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/ArraySchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/ArraySchemaType.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.SchemaBuilder.Types; diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/DictionarySchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/DictionarySchemaType.cs index cce825d..8506386 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/DictionarySchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/DictionarySchemaType.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.SchemaBuilder.Types; diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/IgnoreSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/IgnoreSchemaType.cs index 698fed7..3fff5d7 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/IgnoreSchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/IgnoreSchemaType.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.SchemaBuilder.Types; diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/ObjectSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/ObjectSchemaType.cs index e573405..7385f7a 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/ObjectSchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/ObjectSchemaType.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.SchemaBuilder.Types; diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/StaticSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/StaticSchemaType.cs index 76a5498..3ca17a5 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/StaticSchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/StaticSchemaType.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; namespace Pf2eDataScrapper.SchemaBuilder.Types; diff --git a/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml b/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml index e525e0c..702e2ce 100644 --- a/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml +++ b/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml @@ -62,7 +62,6 @@ - + - - - - - - - - - - - - - - diff --git a/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml b/Pf2eDataScrapper/WPF/EntityViewer/Component/EntityListControl.xaml similarity index 94% rename from Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml rename to Pf2eDataScrapper/WPF/EntityViewer/Component/EntityListControl.xaml index cb9c466..69b7ad6 100644 --- a/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml +++ b/Pf2eDataScrapper/WPF/EntityViewer/Component/EntityListControl.xaml @@ -1,13 +1,13 @@  /// Логика взаимодействия для DbEntityListControl.xaml /// -internal partial class DbEntityListControl : System.Windows.Controls.UserControl +internal partial class EntityListControl : System.Windows.Controls.UserControl { - public DbEntityListViewModel? ViewModel + public EntityListViewModel? ViewModel { get => _viewModel; set @@ -18,9 +18,9 @@ internal partial class DbEntityListControl : System.Windows.Controls.UserControl } } - private DbEntityListViewModel? _viewModel; + private EntityListViewModel? _viewModel; - public DbEntityListControl() + public EntityListControl() { InitializeComponent(); } diff --git a/Pf2eDataScrapper/WPF/EntityViewer/Component/ViewControl.xaml b/Pf2eDataScrapper/WPF/EntityViewer/Component/ViewControl.xaml new file mode 100644 index 0000000..89257ff --- /dev/null +++ b/Pf2eDataScrapper/WPF/EntityViewer/Component/ViewControl.xaml @@ -0,0 +1,123 @@ + + + + /FontAwesome.Sharp;component/fonts/#Font Awesome 6 Free Solid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + Text="{Binding Type}"> + + + + + + + Text="{Binding Value}" + TextTrimming="CharacterEllipsis"> + + + + + + + + + + + + + + + diff --git a/Pf2eDataScrapper/WPF/EntityViewer/EntityForm.xaml b/Pf2eDataScrapper/WPF/EntityViewer/EntityForm.xaml index 79774ab..e1055bb 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/EntityForm.xaml +++ b/Pf2eDataScrapper/WPF/EntityViewer/EntityForm.xaml @@ -5,16 +5,18 @@ xmlns:component="clr-namespace:Pf2eDataScrapper.WPF.EntityViewer.Component" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:viewmodel="clr-namespace:Pf2eDataScrapper.WPF.EntityViewer.ViewModel" Title="EntityForm" Width="1400" Height="800" + d:DataContext="{d:DesignInstance Type=viewmodel:EntityFormViewModel}" x:ClassModifier="internal" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Loaded="Window_Loaded" mc:Ignorable="d"> - + @@ -47,6 +49,11 @@ Cursor="SizeNS" ResizeDirection="Rows" /> + internal partial class EntityListControl : System.Windows.Controls.UserControl { - public EntityListViewModel? ViewModel - { - get => _viewModel; - set - { - _viewModel = value; - DataContext = ViewModel; - } - } - private EntityListViewModel? _viewModel; public EntityListControl() @@ -29,22 +19,22 @@ internal partial class EntityListControl : System.Windows.Controls.UserControl private void FirstButton_OnClick(object sender, RoutedEventArgs e) { - ViewModel?.FirstPage(); + // DataContext?.FirstPage(); } private void PreviousButton_OnClick(object sender, RoutedEventArgs e) { - ViewModel?.PreviousPage(); + //ViewModel?.PreviousPage(); } private void NextButton_OnClick(object sender, RoutedEventArgs e) { - ViewModel?.NextPage(); + //ViewModel?.NextPage(); } private void LastButton_OnClick(object sender, RoutedEventArgs e) { - ViewModel?.LastPage(); + //ViewModel?.LastPage(); } private void TextBox_PreviewTextInput( diff --git a/Pf2eDataScrapper/WPF/EntityViewer/EntityViewStructure/StructureRow.cs b/Pf2eDataScrapper/WPF/EntityViewer/EntityViewStructure/StructureRow.cs index e1a8665..f2d2b25 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/EntityViewStructure/StructureRow.cs +++ b/Pf2eDataScrapper/WPF/EntityViewer/EntityViewStructure/StructureRow.cs @@ -54,8 +54,8 @@ internal class StructureRow : INotifyPropertyChanged /// public string ButtonIcon => HasButton && !((StructureFieldComplex)Field).Collapsed - ? IconChar.PlusSquare.ToChar() - : IconChar.MinusSquare.ToChar(); + ? IconChar.MinusSquare.ToChar() + : IconChar.PlusSquare.ToChar(); /// /// Глубина отображения поля. diff --git a/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/EntityListViewModel.cs b/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/EntityListViewModel.cs index 8a42d3f..1598578 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/EntityListViewModel.cs +++ b/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/EntityListViewModel.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading; -using AngleSharp.Browser; using LinqToDB.DataProvider.Access; using Pf2eDataScrapper.WPF.EntityViewer.Stores; using Pf2eModel; @@ -129,13 +128,41 @@ internal class EntityListViewModel : ViewModelBase #region "Методы кнопок" - public void FirstPage() => Page = 1; + private PageNavigationCommand? _firstPageCommand; + public PageNavigationCommand FirstPageCommand + { + get + { + return _firstPageCommand ??= new PageNavigationCommand(() => { Page = 1; }); + } + } - public void PreviousPage() => Page--; + private PageNavigationCommand? _previousPageCommand; + public PageNavigationCommand PreviousPageCommand + { + get + { + return _previousPageCommand ??= new PageNavigationCommand(() => { Page--; }); + } + } - public void NextPage() => Page++; + private PageNavigationCommand? _nextPageCommand; + public PageNavigationCommand NextPageCommand + { + get + { + return _nextPageCommand ??= new PageNavigationCommand(() => { Page++; }); + } + } - public void LastPage() => Page = PageCount; + private PageNavigationCommand? _lastPageCommand; + public PageNavigationCommand LastPageCommand + { + get + { + return _lastPageCommand ??= new PageNavigationCommand(() => { Page = PageCount; }); + } + } public bool HasPrevious => Page != 1; @@ -167,3 +194,20 @@ internal class EntityListViewModel : ViewModelBase #endregion } + +public class PageNavigationCommand(Action action) : System.Windows.Input.ICommand +{ + public Action Action { get; } = action; + + public event EventHandler? CanExecuteChanged; + + public bool CanExecute(object? parameter) + { + return true; + } + + public void Execute(object? parameter) + { + Action.Invoke(); + } +} From 81695587f1fa473d052386bc1d54c92e5d59f4d8 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Wed, 21 May 2025 18:36:32 +0400 Subject: [PATCH 25/33] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D1=8C=D0=B1=D1=8B=20=D1=81=20=D0=B4=D1=83?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=80=D1=83=D1=8E=D1=89=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D1=81=D1=8F=20slug-=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/Helpers/EntityView.cs | 8 ++ .../Aonprd/Helpers/AonprdBuilderGlobals.cs | 106 ++++++++++++++++++ .../Aonprd/Helpers/StringHelper.cs | 5 +- .../Processors/AbstractEntityProcessor.cs | 4 +- .../Finalization/LegacyRemasterProcessors.cs | 16 ++- .../Subclasses/ApparitionProcessor.cs | 5 +- .../Processors/Types/ActionProcessor.cs | 13 ++- .../Processors/Types/Rules/RuleProcessor.cs | 6 +- .../Types/Rules/RulesTocProcessor.cs | 18 ++- .../EntityViewer/Component/ViewControl.xaml | 14 ++- .../WPF/EntityViewer/EntityForm.xaml | 2 +- .../WPF/ProgressForm/ProgressForm.xaml.cs | 2 +- 12 files changed, 176 insertions(+), 23 deletions(-) diff --git a/Pf2eDataScrapper/Helpers/EntityView.cs b/Pf2eDataScrapper/Helpers/EntityView.cs index fb818db..59c2b2b 100644 --- a/Pf2eDataScrapper/Helpers/EntityView.cs +++ b/Pf2eDataScrapper/Helpers/EntityView.cs @@ -110,6 +110,14 @@ public class EntityView .ThenInclude(c => c.Training) .AsQueryable() ), + new Getter( + EntityType.Subclass, + context => + context + .Subclasses.Include(s => s.Trainings) + .ThenInclude(t => t.Training) + .Include(s => s.Entities) + ), ]; /// diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs index 97cbc53..0446482 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/AonprdBuilderGlobals.cs @@ -27,4 +27,110 @@ internal class AonprdBuilderGlobals /// Конструктор класса . /// public AonprdBuilderGlobals() { } + + /// + /// Получить суффикс для ссылки. + /// + /// Идентификатор Aounprd. + /// Суффикс для ссылки или пустая строка. + public string GetSlugSuffix(string id) + { + return SlugSuffixes.TryGetValue(id, out var slug) ? $"-{slug}" : string.Empty; + } + + /// + /// Словарей суффиксов для сущностей + /// + public Dictionary SlugSuffixes { get; } = + new() + { + ["feat-5997"] = "legacy", + ["feat-5730"] = "dragonblood", + ["feat-6469"] = "general", + ["feat-4344"] = "archetype", + ["feat-3549"] = "vampire", + ["feat-7380"] = "mythic", + ["feat-5737"] = "dragonblood", + ["feat-6284"] = "bounty-hunter", + ["feat-2664"] = "archetype", + ["feat-6883"] = "kitsune", + ["feat-6209"] = "investigator", + ["feat-6477"] = "undead-slayer", + ["feat-7637"] = "dandy", + ["feat-7004"] = "yaoguai", + ["feat-4380"] = "stonebound", + }; + + /// + /// Словарь допольнительных проверок связей. + /// Ключ - устаревшая сущность, значение - обновлённая. + /// + public Dictionary LegacyRemasterExtra { get; } = + new() + { + ["archetype-83"] = "archetype-129", + ["archetype-86"] = "archetype-198", + ["feat-5997"] = "feat-7108", + ["feat-4088"] = "feat-5491", + ["feat-1177"] = "feat-4553", + ["feat-2986"] = "feat-4340", + ["feat-1753"] = "feat-6031", + ["feat-2729"] = "feat-4077", + ["feat-480"] = "feat-4850", + ["feat-2989"] = "feat-4343", + ["feat-2987"] = "feat-4341", + ["feat-2990"] = "feat-4346", + ["feat-936"] = "feat-2269", + ["feat-2992"] = "feat-4350", + ["feat-2991"] = "feat-4349", + ["feat-2742"] = "feat-4078", + ["feat-1169"] = "feat-5055", + ["feat-2743"] = "feat-4079", + ["feat-963"] = "feat-4519", + ["feat-1171"] = "feat-4858", + ["feat-1167"] = "feat-5880", + ["feat-455"] = "feat-6007", + ["feat-2993"] = "feat-4352", + ["language-91"] = "language-180", + ["ritual-31"] = "ritual-191", + ["spell-571"] = "spell-1975", + ["spell-1052"] = "spell-1309", + ["spell-1053"] = "spell-1306", + ["spell-843"] = "spell-2376", + ["spell-578"] = "spell-1566", + ["spell-551"] = "spell-2003", + ["spell-553"] = "spell-2012", + ["spell-646"] = "spell-1394", + ["spell-1054"] = "spell-1305", + ["spell-559"] = "spell-2417", + ["spell-1055"] = "spell-1311", + ["spell-1056"] = "spell-1310", + ["spell-841"] = "spell-1107", + ["spell-1057"] = "spell-1307", + ["spell-560"] = "spell-2418", + ["spell-1058"] = "spell-1304", + ["spell-641"] = "spell-1413", + ["spell-1059"] = "spell-1308", + }; + + /// + /// Словарь сущностей, которые нужно слить с другой/другими. + /// Ключ - пропускаемая сущность. Значение - целевая сущность. + /// + public Dictionary MergeSources { get; } = new() { ["spell-654"] = "spell-819" }; } + +/* + Проверка уникальности: + +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') + */ diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs index 93b1f87..8ee6e66 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Helpers/StringHelper.cs @@ -40,8 +40,9 @@ internal partial class StringHelper { MarkdownLinkSplit result = new(); - Match match = LinkRegex().Match(input); - if (match.Captures.Count > 0) + MatchCollection matches = LinkRegex().Matches(input); + + foreach (Match match in matches) { result.Links.Add( new() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs index f0d7780..2c4e175 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/AbstractEntityProcessor.cs @@ -139,7 +139,9 @@ internal abstract class AbstractEntityProcessor( Entity.Img = ""; Entity.Description = GetDescription(); Entity.Rarity = Input.EnumOptional("rarity", EntityRarity.Common); - Entity.Slug = Helper.GenerateSlug(Entity.Name[LocaleType.English]); + Entity.Slug = + Helper.GenerateSlug(Entity.Name[LocaleType.English]) + + Globals.GetSlugSuffix(Input.GetRequired("id")); Entity.IsTranslated = false; Entity.IsChecked = false; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs index 5963fc1..2961ffc 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Finalization/LegacyRemasterProcessors.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.App; using Pf2eDataScrapper.Processors; using Pf2eModel; +using Pf2eModel.Model.Base; using Pf2eModel.Model.Base.Targets; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Finalization; @@ -28,11 +30,23 @@ internal class LegacyRemasterProcessors(ProcessorInput input) : AbstractInputPro 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 Exception($"Неизвестная сущность с ID {entityId}"); 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 Exception($"Неизвестная сущность с ID {remasterId}"); + + if (legacyEntity.Slug == remasterEntity.Slug) + { + legacyEntity.Slug += "-legacy"; + } } + /* foreach (int legacyId in GetEntityIds("legacy_id")) { diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs index b0451aa..7dd7b8e 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Subclasses/ApparitionProcessor.cs @@ -19,7 +19,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasse InnerType = "apparition", CompendiumType = ProcessorCompendiumEnum.Aonprd, Description = "Apparitions", - Group = ProcessorGroupEnum.AonprdSubclasses + Group = ProcessorGroupEnum.AonprdSubclasses, + Active = false )] [ProcessorDependencies( ["aonprd:trait", "aonprd:source", "aonprd:class", "aonprd:spell", "aonprd:skill"] @@ -44,7 +45,7 @@ internal class ApparitionProcessor( public override void ProcessObject() { // Spells - string spellMd = Input.GetRequired("spells_markdown"); + string spellMd = Input.GetRequired("spell_markdown"); List spellUrls = StringHelper .ParseMarkdownLinks(spellMd) .Links.Select(l => l.Url) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs index 50f0559..e142199 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ActionProcessor.cs @@ -34,10 +34,7 @@ internal class ActionProcessor( /// public override void Process() { - if (Input.GetRequired("markdown").StartsWith("**Activate**")) - { - throw new SkipItemException(Input.GetRequired("name"), "activating action"); - } + Check(); base.Process(); @@ -45,6 +42,14 @@ internal class ActionProcessor( Object.Extra = GenerateJsonString("school"); } + public void Check() + { + if (Input.GetRequired("markdown").StartsWith("**Activate")) + { + throw new SkipItemException(Input.GetRequired("name"), "activating action"); + } + } + /// protected override string[] AddedDescriptionPaths() => [ diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs index 729f807..eb6deb2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RuleProcessor.cs @@ -51,7 +51,7 @@ internal class RuleProcessor( AddExternal(); AddTraits(); - RuleTocBook book = Toc[GetSource().Id]; + RuleTocBook book = Toc[GetSourceId()]; string[] breadcrumbs = (Input.GetOptional("breadcrumbs") ?? []) .Select(token => token.ToString()) @@ -119,11 +119,11 @@ internal class RuleProcessor( /// Получение источника, к которому относится правило. /// /// Источник, к которому относится правило. - private Source GetSource() + private int GetSourceId() { foreach (EntitySource entitySource in Entity.Sources) { - return entitySource.Source; + return entitySource.SourceId; } throw new("Source not found"); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs index 6ba76db..af2f841 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/Rules/RulesTocProcessor.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Text.RegularExpressions; using System.Web; using AngleSharp; @@ -116,7 +117,20 @@ internal partial class RulesTocProcessor(Pf2eDbContext context) : IProcessor { IConfiguration config = Configuration.Default.WithDefaultLoader(); IBrowsingContext context = BrowsingContext.New(config); - IDocument document = context.OpenAsync(Url).Result; + 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"); diff --git a/Pf2eDataScrapper/WPF/EntityViewer/Component/ViewControl.xaml b/Pf2eDataScrapper/WPF/EntityViewer/Component/ViewControl.xaml index 32ff84b..be95e75 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/Component/ViewControl.xaml +++ b/Pf2eDataScrapper/WPF/EntityViewer/Component/ViewControl.xaml @@ -140,17 +140,19 @@ - - + Background="Transparent" + BorderThickness="0" + IsReadOnly="True" + Text="{Binding Value, Mode=OneWay}"> + - - + + diff --git a/Pf2eDataScrapper/WPF/EntityViewer/EntityForm.xaml b/Pf2eDataScrapper/WPF/EntityViewer/EntityForm.xaml index e1055bb..6132c5f 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/EntityForm.xaml +++ b/Pf2eDataScrapper/WPF/EntityViewer/EntityForm.xaml @@ -50,7 +50,7 @@ ResizeDirection="Rows" /> /// Объект обработки вводимого JSON. +/// [DI] Помощник обработки строк. +/// [DI] Помощник работы с БД. +/// [DI] Глобальные параметра обработки Архивов Нефиса. /// internal abstract class AbstractEntityProcessor( ProcessorInput input, @@ -224,7 +227,6 @@ internal abstract class AbstractEntityProcessor( /// /// Генерация списка признаков сущности. /// - /// protected virtual void AddTraits() { JToken? traitsJson = Input.Input.SelectToken("trait"); diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs index 2d50737..a3db82f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Types/ClassProcessor.cs @@ -57,10 +57,10 @@ internal partial class ClassProcessor( foreach (AttributeValues attribute in attributes) { - ClassKeyAbility classKeyAbility = new() { Class = Object, Value = attribute }; + ClassKeyAttribute classKeyAttribute = new() { Class = Object, Value = attribute }; - Context.ClassKeyAbilities.Add(classKeyAbility); - Object.ClassKeyAbilities.Add(classKeyAbility); + Context.ClassKeyAbilities.Add(classKeyAttribute); + Object.ClassKeyAbilities.Add(classKeyAttribute); } ClassTraining[] classTrainings = diff --git a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs index 7dfc785..b60154c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/FoundryEn/Compendium/FoundryEnCompendium.cs @@ -11,6 +11,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; /// Загрузчик данных компендиума. /// /// Репозиторий данных. +/// [DI] Коллекция папок компендиумов. internal sealed class FoundryEnCompendium( FoundryEnRepository repository, CompendiumFolderCollection folders diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj index 876b404..3d53295 100644 --- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj +++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj @@ -6,6 +6,9 @@ False disable True + true + ..\Documentation\$(MSBuildProjectName) + Name @@ -14,6 +17,7 @@ + diff --git a/Pf2eDataScrapper/Processors/Attributes/ProcessorGroupEnum.cs b/Pf2eDataScrapper/Processors/Attributes/ProcessorGroupEnum.cs index e03caf4..427c040 100644 --- a/Pf2eDataScrapper/Processors/Attributes/ProcessorGroupEnum.cs +++ b/Pf2eDataScrapper/Processors/Attributes/ProcessorGroupEnum.cs @@ -3,45 +3,101 @@ using System.Reflection; namespace Pf2eDataScrapper.Processors.Attributes; +/// +/// Перечисление для групп обработчиков. +/// public enum ProcessorGroupEnum { + /// + /// Общий обработчик. + /// [Description("Общие процессоры")] General, + /// + /// Обработчик для подготовки данных Архивов Нефиса. + /// [Description("Архивы Нефиса: Подготовка")] AonprdPreparation, + /// + /// Обработчик основных сущностей Архивов Нефиса. + /// [Description("Архивы Нефиса: Основные сущности")] AonprdEntities, + /// + /// Обработчик подклассов Архивов Нефиса. + /// [Description("Архивы Нефиса: Подклассы")] AonprdSubclasses, + /// + /// Обработчик для завершения обработки данных Архивов Нефиса. + /// [Description("Архивы Нефиса: Завершение")] AonprdFinalization, + /// + /// Обработчик для подготовки данных Foundry EN. + /// [Description("Foundry EN: Подготовка")] FoundryEnPreparation, + /// + /// Обработчик основных сущностей Foundry EN. + /// [Description("Foundry EN: Основные сущности")] FoundryEnEntities, } +/// +/// Перечисление для типов компендиумов обработчиков. +/// public enum ProcessorCompendiumEnum { + /// + /// Компедиум Архивов Нефиса. + /// Aonprd, + + /// + /// Компедиум Foundry EN. + /// FoundryEn, } +/// +/// Перечисление для типов фабрик обработчиков. +/// public enum ProcessorFactoryTypeEnum { + /// + /// Простая фабрика одиночного обработчика. + /// Simple, + + /// + /// Фабрика для создания обработчиков, которые могут обрабатывать типы данных компедиума. + /// Compendium, + + /// + /// Фабрика для создания обработчиков, которые могут обрабатывать итераторы данных. + /// Iterator, } +/// +/// Расширение для перечисления . +/// public static class ProcessorGroupEnumExtension { + /// + /// Получает описание группы обработчика из атрибута . + /// + /// Перечисление группы обработчика. + /// Строка с описанием группы обработчика. public static string GetDescription(this ProcessorGroupEnum group) { return group diff --git a/Pf2eDataScrapper/Processors/Definitions/IProcessor.cs b/Pf2eDataScrapper/Processors/Definitions/IProcessor.cs index 6a880a2..f51074d 100644 --- a/Pf2eDataScrapper/Processors/Definitions/IProcessor.cs +++ b/Pf2eDataScrapper/Processors/Definitions/IProcessor.cs @@ -1,5 +1,8 @@ namespace Pf2eDataScrapper.Processors.Definitions; +/// +/// Интерфейс для обработчиков данных. +/// public interface IProcessor { /// diff --git a/Pf2eDataScrapper/Processors/ProcessorInput.cs b/Pf2eDataScrapper/Processors/ProcessorInput.cs index 632d605..38e66c2 100644 --- a/Pf2eDataScrapper/Processors/ProcessorInput.cs +++ b/Pf2eDataScrapper/Processors/ProcessorInput.cs @@ -14,6 +14,7 @@ namespace Pf2eDataScrapper.Processors; /// /// Название объекта, ассоциированного с вводом. /// JSON-объект, который будет использоваться для доступа к данным. +/// [DI] Набор интерфейсов, разрешающих значения перечислений. internal class ProcessorInput(string name, JObject input, IEnumerable enumSolvers) { /// @@ -207,14 +208,29 @@ internal class ProcessorInput(string name, JObject input, IEnumerable +/// Интерфейс для решения значений перечислений. +/// internal interface IEnumSolver { } -public abstract class EnumSolver : IEnumSolver - where T : Enum +/// +/// Абстрактный класс для разрешения значений перечислений. +/// +/// Тип перечисления, который будет разрешаться. +public abstract class EnumSolver : IEnumSolver + where TEnum : Enum { - public abstract Dictionary Data { get; } + /// + /// Словарь, содержащий данные для разрешения значений перечислений. + /// + public abstract Dictionary Data { get; } - public T? SolveEnum(string input) + /// + /// Метод для разрешения значения перечисления по строковому вводу. + /// + /// Строка, представляющая значение перечисления. + /// Элемент перечисления , если найден; иначе null. + public TEnum? SolveEnum(string input) { return Data.GetValueOrDefault(input); } diff --git a/Pf2eDataScrapper/WPF/EntityViewer/EntityViewStructure/StructureRow.cs b/Pf2eDataScrapper/WPF/EntityViewer/EntityViewStructure/StructureRow.cs index 90deed2..a81b1ab 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/EntityViewStructure/StructureRow.cs +++ b/Pf2eDataScrapper/WPF/EntityViewer/EntityViewStructure/StructureRow.cs @@ -1,21 +1,20 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Drawing; using System.Globalization; -using System.Linq; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using FontAwesome.Sharp; -using static System.Runtime.InteropServices.JavaScript.JSType; -using static LinqToDB.Sql; using Color = System.Windows.Media.Color; namespace Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure; +/// +/// Строка структуры, представляющая поле сущности. +/// internal class StructureRow : INotifyPropertyChanged { /// @@ -147,54 +146,89 @@ internal class StructureRow : INotifyPropertyChanged /// При изменении поля. /// /// Название поля. - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new(propertyName)); } } +/// +/// Команда для сворачивания/разворачивания поля структуры. +/// +/// Строка структуры, содержащая поле. internal class CollapseCommand(StructureRow structureRow) : ICommand { + /// + /// Строка структуры, содержащая поле, для которого выполняется команда. + /// private StructureRow StructureRow { get; } = structureRow; + /// + /// Событие, вызываемое при изменении возможности выполнения команды. + /// public event EventHandler? CanExecuteChanged; + /// + /// Проверяет, может ли команда быть выполнена с указанным параметром. + /// + /// Параметр, передаваемый команде. + /// true, если команда может быть выполнена; в противном случае false. public bool CanExecute(object? parameter) { return true; } + /// + /// Выполняет команду с указанным параметром. + /// + /// Параметр, передаваемый команде. public void Execute(object? parameter) { - var field = (StructureFieldComplex)StructureRow.Field; + StructureFieldComplex field = (StructureFieldComplex)StructureRow.Field; field.Collapsed = !field.Collapsed; } } +/// +/// Преобразователь, который адаптирует цвет в зависимости от типа целевого свойства. +/// public class ColorAdapterConverter : IMultiValueConverter { + /// + /// Смешивает два компнента цвета с заданным коэффициентом смешивания. + /// + /// Первый компонент цвета. + /// Второй компонент цвета. + /// Коэффициент смешивания, где 0.0 означает полный компонент , а 1.0 означает полный компонент . + /// Смешанный компонент цвета. private byte Mix(byte a, byte b, double rate) => System.Convert.ToByte(Math.Round(a * rate + b * (1 - rate))); + /// + /// Преобразует массив значений в целевой тип, усредняя цвета. + /// + /// Массив значений, содержащий цвета. + /// Целевой тип, в который нужно преобразовать значения. + /// Параметр преобразования, не используется. + /// Культура, используемая для преобразования, не используется. + /// Результат преобразования в целевой тип, который может быть null, если преобразование невозможно. public object? Convert(object[] value, Type targetType, object parameter, CultureInfo culture) { - Color textColor = System.Windows.SystemColors.WindowTextColor; + Color textColor = SystemColors.WindowTextColor; double a = textColor.A; double r = textColor.R; double g = textColor.G; double b = textColor.B; int n = 1 + value.Length; - foreach (var item in value) + foreach (object item in value) { - if (item == null || item is not Color) + if (item is not Color color) { n--; continue; } - Color color = (Color)item; - a += color.A; r += color.R; g += color.G; @@ -208,14 +242,23 @@ public class ColorAdapterConverter : IMultiValueConverter System.Convert.ToByte(Math.Round(b / n)) ); - var @switch = new Dictionary> + Dictionary> @switch = new() { - { typeof(System.Windows.Media.Brush), () => new SolidColorBrush(resultColor) }, + { typeof(Brush), () => new SolidColorBrush(resultColor) }, }; - return @switch.TryGetValue(targetType, out var action) ? action() : null; + return @switch.TryGetValue(targetType, out Func? action) ? action() : null; } + /// + /// Преобразует значение обратно в массив типов, но не реализован. + /// + /// + /// + /// + /// + /// + /// public object[] ConvertBack( object value, Type[] targetType, diff --git a/Pf2eDataScrapper/WPF/EntityViewer/Stores/SelectedEntityStore.cs b/Pf2eDataScrapper/WPF/EntityViewer/Stores/SelectedEntityStore.cs index f8d262d..fd8d90a 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/Stores/SelectedEntityStore.cs +++ b/Pf2eDataScrapper/WPF/EntityViewer/Stores/SelectedEntityStore.cs @@ -3,10 +3,17 @@ using Pf2eModel.Model.Base; namespace Pf2eDataScrapper.WPF.EntityViewer.Stores; +/// +/// Хранилище выбранной сущности. +/// public class SelectedEntityStore { + /// private Entity? _selectedEntity; + /// + /// Сущность, выбранная в интерфейсе. + /// public Entity? SelectedEntity { get => _selectedEntity; @@ -17,5 +24,8 @@ public class SelectedEntityStore } } + /// + /// Событие, вызываемое при изменении выбранной сущности. + /// public event Action? SelectedEntityChanged; } diff --git a/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/EntityListViewModel.cs b/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/EntityListViewModel.cs index 1598578..1a11cfa 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/EntityListViewModel.cs +++ b/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/EntityListViewModel.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Threading; -using LinqToDB.DataProvider.Access; using Pf2eDataScrapper.WPF.EntityViewer.Stores; using Pf2eModel; using Pf2eModel.Model.Base; @@ -133,7 +131,10 @@ internal class EntityListViewModel : ViewModelBase { get { - return _firstPageCommand ??= new PageNavigationCommand(() => { Page = 1; }); + return _firstPageCommand ??= new PageNavigationCommand(() => + { + Page = 1; + }); } } @@ -142,7 +143,10 @@ internal class EntityListViewModel : ViewModelBase { get { - return _previousPageCommand ??= new PageNavigationCommand(() => { Page--; }); + return _previousPageCommand ??= new PageNavigationCommand(() => + { + Page--; + }); } } @@ -151,7 +155,10 @@ internal class EntityListViewModel : ViewModelBase { get { - return _nextPageCommand ??= new PageNavigationCommand(() => { Page++; }); + return _nextPageCommand ??= new PageNavigationCommand(() => + { + Page++; + }); } } @@ -160,7 +167,10 @@ internal class EntityListViewModel : ViewModelBase { get { - return _lastPageCommand ??= new PageNavigationCommand(() => { Page = PageCount; }); + return _lastPageCommand ??= new PageNavigationCommand(() => + { + Page = PageCount; + }); } } @@ -195,17 +205,36 @@ internal class EntityListViewModel : ViewModelBase #endregion } +/// +/// Команда для навигации по страницам. +/// +/// Действие, выполняемое при выполнении команды. public class PageNavigationCommand(Action action) : System.Windows.Input.ICommand { + /// + /// Действие, выполняемое при выполнении команды. + /// public Action Action { get; } = action; + /// + /// Событие, вызываемое при изменении возможности выполнения команды. + /// public event EventHandler? CanExecuteChanged; + /// + /// Проверяет, может ли команда быть выполнена с указанным параметром. + /// + /// Параметр команды. + /// Флаг, указывающий, может ли команда быть выполнена. public bool CanExecute(object? parameter) { return true; } + /// + /// Выполняет команду с указанным параметром. + /// + /// Параметр команды. public void Execute(object? parameter) { Action.Invoke(); diff --git a/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/ViewViewModel.cs b/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/ViewViewModel.cs index f4f7f68..f45db08 100644 --- a/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/ViewViewModel.cs +++ b/Pf2eDataScrapper/WPF/EntityViewer/ViewModel/ViewViewModel.cs @@ -108,7 +108,6 @@ internal class ViewViewModel : ViewModelBase /// /// Построение лестницы для строк полей структуры. /// - /// Набор строк полей структуры. private void BuildLadder() { List lastLadder = []; diff --git a/Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs b/Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs index 7c68770..a1e712a 100644 --- a/Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs +++ b/Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs @@ -4,8 +4,16 @@ using Pf2eDataScrapper.WPF.EntityViewer.ViewModel; namespace Pf2eDataScrapper.WPF; +/// +/// Расширение для IServiceCollection, позволяющее регистрировать сервисы WPF. +/// public static class WpfServiceProviderExtension { + /// + /// Регистрация сервисов WPF в IServiceCollection. + /// + /// Коллекция сервисов. + /// Коллекция сервисов ( для Fluent API). public static IServiceCollection RegisterWpfServices(this IServiceCollection collection) { return collection diff --git a/Pf2eModel/Migrations/20250522130215_1.Designer.cs b/Pf2eModel/Migrations/20250601111002_1.Designer.cs similarity index 99% rename from Pf2eModel/Migrations/20250522130215_1.Designer.cs rename to Pf2eModel/Migrations/20250601111002_1.Designer.cs index d6a39c2..c4607c3 100644 --- a/Pf2eModel/Migrations/20250522130215_1.Designer.cs +++ b/Pf2eModel/Migrations/20250601111002_1.Designer.cs @@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum; namespace Pf2eModel.Migrations { [DbContext(typeof(Pf2eDbContext))] - [Migration("20250522130215_1")] + [Migration("20250601111002_1")] partial class _1 { /// diff --git a/Pf2eModel/Migrations/20250522130215_1.cs b/Pf2eModel/Migrations/20250601111002_1.cs similarity index 100% rename from Pf2eModel/Migrations/20250522130215_1.cs rename to Pf2eModel/Migrations/20250601111002_1.cs diff --git a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs index 1692a71..0f51b16 100644 --- a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs +++ b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs @@ -884,7 +884,7 @@ namespace Pf2eModel.Migrations b.ToTable("classes", (string)null); }); - modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.ClassKeyAbility", b => + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.ClassKeyAttribute", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -2185,7 +2185,7 @@ namespace Pf2eModel.Migrations b.Navigation("Entity"); }); - modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.ClassKeyAbility", b => + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.ClassKeyAttribute", b => { b.HasOne("Pf2eModel.Model.Structure.Class", "Class") .WithMany("ClassKeyAbilities") diff --git a/Pf2eModel/Model/Base/EntityAlias.cs b/Pf2eModel/Model/Base/EntityAlias.cs index b91132f..bb40b07 100644 --- a/Pf2eModel/Model/Base/EntityAlias.cs +++ b/Pf2eModel/Model/Base/EntityAlias.cs @@ -4,6 +4,9 @@ using Microsoft.EntityFrameworkCore; namespace Pf2eModel.Model.Base; +/// +/// Алиас сущности. +/// public class EntityAlias { /// diff --git a/Pf2eModel/Model/Base/Targets/EntityExternal.cs b/Pf2eModel/Model/Base/Targets/EntityExternal.cs index 56f4951..d5d0470 100644 --- a/Pf2eModel/Model/Base/Targets/EntityExternal.cs +++ b/Pf2eModel/Model/Base/Targets/EntityExternal.cs @@ -24,8 +24,12 @@ public class EntityExternal [Comment("Идентификатор цели")] public int TargetId { get; set; } + /// + /// Идентификатор элемента по ссылке. + /// [Column(Order = 3)] [MaxLength(255)] + [Comment("Идентификатор элемента в ссылке")] public string? ExternalId { get; set; } = null; /// diff --git a/Pf2eModel/Model/Common/I18n/LocaleType.cs b/Pf2eModel/Model/Common/I18n/LocaleType.cs index eef6702..06da1a6 100644 --- a/Pf2eModel/Model/Common/I18n/LocaleType.cs +++ b/Pf2eModel/Model/Common/I18n/LocaleType.cs @@ -7,21 +7,27 @@ namespace Pf2eModel.Model.Common.I18n; /// public enum LocaleType { + /// + /// Английский язык (основной). + /// [Description("en")] English, - [Description("en.legacy")] - LegacyEnglish, - + /// + /// Русский язык. + /// [Description("ru")] Russian, - [Description("ru.legacy")] - Legacy, - + /// + /// Русский язык (вариант HobbyGames). + /// [Description("ru.hobby")] Hobby, + /// + /// Другой язык. + /// [Description("other")] Other, } diff --git a/Pf2eModel/Model/Enum/ActionCategory.cs b/Pf2eModel/Model/Enum/ActionCategory.cs index 7dd5b0d..5d69681 100644 --- a/Pf2eModel/Model/Enum/ActionCategory.cs +++ b/Pf2eModel/Model/Enum/ActionCategory.cs @@ -19,39 +19,75 @@ public enum ActionCategory [PgName("archetype")] Archetype, + /// + /// Дествие навыка. + /// [PgName("skill-action")] SkillAction, + /// + /// Действие классовой способности. + /// [PgName("class-feature")] ClassFeature, + /// + /// Действие классовой черты. + /// [PgName("class-feat")] ClassFeat, + /// + /// Действие атаки. + /// [PgName("offensive")] Offensive, + /// + /// Действие защиты. + /// [PgName("defensive")] Defensive, + /// + /// Действие взаимодействия. + /// [PgName("interaction")] Interaction, + /// + /// Действие фамильяра. + /// [PgName("familiar")] Familiar, + /// + /// Действие тактики армии. + /// [PgName("army_tactic")] ArmyTactic, + /// + /// Действие войны армии. + /// [PgName("army_war_action")] ArmyWarAction, + /// + /// Активность королевства. + /// [PgName("kingdom_activity")] KingdomActivity, + /// + /// Действие черты королевства. + /// [PgName("kingdom_feat")] KingdomFeat, + /// + /// Действие особенности королевства. + /// [PgName("kingdom_feature")] KingdomFeature, } diff --git a/Pf2eModel/Model/Enum/ActionEnum.cs b/Pf2eModel/Model/Enum/ActionEnum.cs index 7efeb59..a101f46 100644 --- a/Pf2eModel/Model/Enum/ActionEnum.cs +++ b/Pf2eModel/Model/Enum/ActionEnum.cs @@ -7,66 +7,129 @@ namespace Pf2eModel.Model.Enum; /// public enum ActionEnum { + /// + /// Неизвестное количество действий. + /// [PgName("unknown")] Unknown, + /// + /// Одно действие. + /// [PgName("1")] SingleAction, + /// + /// Два действия. + /// [PgName("2")] TwoActions, + /// + /// Три действия. + /// [PgName("3")] ThreeActions, + /// + /// Реакция. + /// [PgName("reaction")] Reaction, + /// + /// Свободное действие. + /// [PgName("free")] FreeAction, + /// + /// Пассивное действие. + /// [PgName("passive")] Passive, + /// + /// Иное количество действий, не указанное в перечислении. + /// [PgName("other")] Other, + /// + /// От одного до трёх действий. + /// [PgName("1-3")] SingleActionToThreeActions, + /// + /// От одного до двух действий. + /// [PgName("1-2")] SingleActionToTwoActions, + /// + /// От двух до трёх действий. + /// [PgName("2-3")] TwoActionsToThreeActions, + /// + /// От двух действий до двух раундов. + /// [PgName("2/2 rounds")] TwoActionsToTwoRounds, + /// + /// Реакция или одно действие. + /// [PgName("reaction/1")] ReactionOrOneAction, + /// + /// Реакция или два действия. + /// [PgName("reaction/2")] ReactionOrTwoAction, + /// + /// Реакция или три действия. + /// [PgName("reaction/3")] ReactionOrThreeAction, + /// + /// Свободное действие или одно действие. + /// [PgName("free/1")] FreeActionOrOneAction, + /// + /// Одная минута. + /// [PgName("1 minute")] OneMinute, + /// + /// Пять минут. + /// [PgName("5 minutes")] FiveMinutes, + /// + /// Десять минут. + /// [PgName("10 minutes")] TenMinutes, + /// + /// Тридцать минут. + /// [PgName("30 minutes")] ThirtyMinutes, + /// + /// Один час. + /// [PgName("1 hour")] OneHour, } diff --git a/Pf2eModel/Model/Enum/FeatCategory.cs b/Pf2eModel/Model/Enum/FeatCategory.cs index c85ba49..b475478 100644 --- a/Pf2eModel/Model/Enum/FeatCategory.cs +++ b/Pf2eModel/Model/Enum/FeatCategory.cs @@ -2,47 +2,92 @@ namespace Pf2eModel.Model.Enum; +/// +/// Категория черты. +/// public enum FeatCategory { + /// + /// Черта, которая не относится ни к одной из категорий. + /// [PgName("uncategorized")] Uncategorized, + /// + /// Черта наследия. Возможная коллизия между и . + /// [PgName("ancestry")] Ancestry, + /// + /// Черта наследия. Возможная коллизия между и . + /// [PgName("ancestry-feature")] AncestryFeature, + /// + /// Черта архетипа. + /// [PgName("archetype")] Archetype, + /// + /// Черта мифического призвания. + /// [PgName("calling")] Calling, + /// + /// Черта класса. Возможная коллизия между и . + /// [PgName("class")] Class, + /// + /// Черта класса. Возможная коллизия между и . + /// [PgName("class-feature")] ClassFeature, + /// + /// Черта навыка. + /// [PgName("skill")] Skill, + /// + /// Общая черта. + /// [PgName("general")] General, + /// + /// Бонусная черта. + /// [PgName("bonus")] Bonus, + /// + /// Божественный дар. + /// [PgName("deityboon")] Deityboon, + /// + /// Проклятие. + /// [PgName("curse")] Curse, + /// + /// Дар сообщества Pathfinder Society. + /// [PgName("pfsboon")] Pfsboon, + /// + /// Черта кампании. + /// [PgName("campaign-feature")] CampaignFeature, } diff --git a/Pf2eModel/Model/Structure/AncestryRelation/AncestryBoost.cs b/Pf2eModel/Model/Structure/AncestryRelation/AncestryBoost.cs index 63f2aa4..fdac625 100644 --- a/Pf2eModel/Model/Structure/AncestryRelation/AncestryBoost.cs +++ b/Pf2eModel/Model/Structure/AncestryRelation/AncestryBoost.cs @@ -1,25 +1,47 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; namespace Pf2eModel.Model.Structure.AncestryRelation; +/// +/// Элемент набора повышений атрибутов для родословной. +/// public class AncestryBoost { + /// + /// Идентификатор элемента. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] public int Id { get; set; } + /// + /// Идентификатор родословной, к которой относится этот элемент. + /// [Column(Order = 2)] + [Comment("Идентификатор родословной")] public int AncestryId { get; set; } + /// + /// Указывает, является ли этот элемент повышением атрибута. Если false - это снижение атрибута. + /// [Column(Order = 3)] + [Comment("Флаг повышения/снижения")] public bool Boost { get; set; } = true; + /// + /// Значение атрибута, которое повышается или понижается в этом элементе набора повышений атрибутов. + /// [Column(Order = 4)] + [Comment("Значение атрибута")] public Enum.AttributeValues Value { get; set; } + /// + /// Родословная, к которой относится этот элемент набора повышений атрибутов. + /// [JsonIgnore] public Ancestry Ancestry { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/AncestryRelation/AncestryItem.cs b/Pf2eModel/Model/Structure/AncestryRelation/AncestryItem.cs index 2136e3a..bd48c5b 100644 --- a/Pf2eModel/Model/Structure/AncestryRelation/AncestryItem.cs +++ b/Pf2eModel/Model/Structure/AncestryRelation/AncestryItem.cs @@ -4,19 +4,40 @@ using Pf2eModel.Model.Base; namespace Pf2eModel.Model.Structure.AncestryRelation; +/// +/// Связь между родословной и сущностью. +/// [PrimaryKey(nameof(AncestryId), nameof(ItemId))] public class AncestryItem { + /// + /// Идентификатор родословной. + /// [Column(Order = 1)] + [Comment("Идентификатор родословной")] public int AncestryId { get; set; } + /// + /// Идентификатор сущности, связанной с родословной. + /// [Column(Order = 2)] + [Comment("Идентификатор связанной сущности")] public int ItemId { get; set; } + /// + /// Уровень сущности в родословной. + /// [Column(Order = 3)] + [Comment("Уровень сущности в родословной")] public int Level { get; set; } + /// + /// Родословная, к которой относится сущность. + /// public Ancestry Ancestry { get; set; } = null!; + /// + /// Сущность, связанная с родословной. + /// public Entity Item { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/AncestryRelation/AncestryLanguage.cs b/Pf2eModel/Model/Structure/AncestryRelation/AncestryLanguage.cs index 92f00f3..68aff67 100644 --- a/Pf2eModel/Model/Structure/AncestryRelation/AncestryLanguage.cs +++ b/Pf2eModel/Model/Structure/AncestryRelation/AncestryLanguage.cs @@ -3,19 +3,40 @@ using Microsoft.EntityFrameworkCore; namespace Pf2eModel.Model.Structure.AncestryRelation; +/// +/// Связь между родословной и языком. +/// [PrimaryKey(nameof(AncestryId), nameof(LanguageId))] public class AncestryLanguage { + /// + /// Идентификатор родословной, к которой относится язык. + /// [Column(Order = 1)] + [Comment("Идентификатор родословной")] public int AncestryId { get; set; } + /// + /// Идентификатор языка, связанного с родословной. + /// [Column(Order = 2)] + [Comment("Идентификатор связанного языка")] public int LanguageId { get; set; } + /// + /// Является ли язык дополнительным для родословной. + /// [Column(Order = 3)] + [Comment("Флаг дополнительного")] public bool IsAdditional { get; set; } + /// + /// Родословная, к которой относится язык. + /// public Ancestry Ancestry { get; set; } = null!; + /// + /// Язык, связанный с родословной. + /// public Language Language { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/ArchetypeRelation/ArchetypeFeats.cs b/Pf2eModel/Model/Structure/ArchetypeRelation/ArchetypeFeats.cs index 2828a0b..59a62ca 100644 --- a/Pf2eModel/Model/Structure/ArchetypeRelation/ArchetypeFeats.cs +++ b/Pf2eModel/Model/Structure/ArchetypeRelation/ArchetypeFeats.cs @@ -2,10 +2,21 @@ namespace Pf2eModel.Model.Structure.ArchetypeRelation; +/// +/// Связь между архетипом и его чертами. +/// [PrimaryKey(nameof(ArchetypeId), nameof(FeatId))] public class ArchetypeFeats { + /// + /// Идентификатор архетипа, к которому относится черта. + /// + [Comment("Идентификатор архетипа")] public int ArchetypeId { get; set; } + /// + /// Идентификатор черты, связанной с архетипом. + /// + [Comment("Идентификатор черты")] public int FeatId { get; set; } } diff --git a/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundBoost.cs b/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundBoost.cs index d5b8d2b..6b2e2bf 100644 --- a/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundBoost.cs +++ b/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundBoost.cs @@ -1,23 +1,42 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.BackgroundRelation; +/// +/// Элемент набора атрибутов для предыстории. +/// public class BackgroundBoost { + /// + /// Идентификатор элемента набора атрибутов для предыстории. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор предыстории, к которой относится этот элемент набора атрибутов. + /// [Column(Order = 2)] + [Comment("Идентификатор предыстории")] public int BackgroundId { get; set; } + /// + /// Атрибут, к которому относится этот элемент набора атрибутов для предыстории. + /// [Column(Order = 3)] + [Comment("Атрибут")] public AttributeValues Attribute { get; set; } + /// + /// Предыстория, к которой относится этот элемент набора атрибутов. + /// [JsonIgnore] public Background Background { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundItem.cs b/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundItem.cs index 5642f35..e865a01 100644 --- a/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundItem.cs +++ b/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundItem.cs @@ -5,20 +5,40 @@ using Pf2eModel.Model.Base; namespace Pf2eModel.Model.Structure.BackgroundRelation; +/// +/// Элемент предыстории, который может быть способностью, навыком или другим элементом, связанным с предысторией. +/// [PrimaryKey(nameof(BackgroundId), nameof(EntityId))] public class BackgroundItem { + /// + /// Идентификатор элемента предыстории. + /// [Column(Order = 1)] + [Comment("Идентификатор")] public int BackgroundId { get; set; } + /// + /// Идентификатор сущности, связанной с предысторией. + /// [Column(Order = 2)] + [Comment("Идентификатор сущности")] public int EntityId { get; set; } + /// + /// Уровень элемента предыстории, если применимо. Может быть null, если уровень не указан или не применим. + /// [Column(Order = 3)] public int? Level { get; set; } + /// + /// Предыстория, к которой относится сущность. + /// [JsonIgnore] public Background Background { get; set; } = null!; + /// + /// Сущность, связанная с предысторией. + /// public Entity Entity { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundTraining.cs b/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundTraining.cs index 80fccc8..984336e 100644 --- a/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundTraining.cs +++ b/Pf2eModel/Model/Structure/BackgroundRelation/BackgroundTraining.cs @@ -1,29 +1,55 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Pf2eModel.Model.Common; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.BackgroundRelation; +/// +/// Элемент набора связей между предысторией и тренируемым навыком. +/// public class BackgroundTraining { + /// + /// Идентификатор элемента набора связей между предысторией и тренируемым навыком. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор предыстории, к которой относится тренировка. + /// [Column(Order = 2)] + [Comment("Идентификатор предыстории")] public int BackgroundId { get; set; } + /// + /// Идентификатор тренируемого навыка. + /// [Column(Order = 3)] + [Comment("Идентификатор тренируемого навыка")] public int TrainingId { get; set; } + /// + /// Ранг тренировки, который получает персонаж при выборе данной предыстории. + /// [Column(Order = 4)] + [Comment("Ранг тренировки")] public TrainingRank Rank { get; set; } + /// + /// Предыстория, к которой относится тренировка. + /// [JsonIgnore] public Background Background { get; set; } = null!; + /// + /// Тренируемый навык, который получает персонаж при выборе данной предыстории. + /// public Training Training { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/Class.cs b/Pf2eModel/Model/Structure/Class.cs index 1bb9cfa..766b1b9 100644 --- a/Pf2eModel/Model/Structure/Class.cs +++ b/Pf2eModel/Model/Structure/Class.cs @@ -54,7 +54,7 @@ public class Class : IEntity /// /// Аттрибуты класса. /// - public virtual ICollection ClassKeyAbilities { get; set; } = []; + public virtual ICollection ClassKeyAbilities { get; set; } = []; /// /// Прогрессия класса. diff --git a/Pf2eModel/Model/Structure/ClassRelation/ClassKeyAbility.cs b/Pf2eModel/Model/Structure/ClassRelation/ClassKeyAbility.cs deleted file mode 100644 index d64bc49..0000000 --- a/Pf2eModel/Model/Structure/ClassRelation/ClassKeyAbility.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; -using Pf2eModel.Model.Enum; - -namespace Pf2eModel.Model.Structure.ClassRelation; - -public class ClassKeyAbility -{ - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [Column(Order = 1)] - public int Id { get; set; } - - [Column(Order = 2)] - public int ClassId { get; set; } - - [Column(Order = 3)] - public AttributeValues Value { get; set; } - - [JsonIgnore] - public Class Class { get; set; } = null!; -} diff --git a/Pf2eModel/Model/Structure/ClassRelation/ClassKeyAttribute.cs b/Pf2eModel/Model/Structure/ClassRelation/ClassKeyAttribute.cs new file mode 100644 index 0000000..20502f0 --- /dev/null +++ b/Pf2eModel/Model/Structure/ClassRelation/ClassKeyAttribute.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Pf2eModel.Model.Enum; + +namespace Pf2eModel.Model.Structure.ClassRelation; + +/// +/// Набор ключевых атрибутов класса. +/// +public class ClassKeyAttribute +{ + /// + /// Идентификатор ключевого атрибута класса. + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Column(Order = 1)] + [Comment("Идентификатор")] + public int Id { get; set; } + + /// + /// Идентификатор класса, к которому относится ключевой атрибут. + /// + [Column(Order = 2)] + [Comment("Идентификатор класса")] + public int ClassId { get; set; } + + /// + /// Ключевой атрибут класса. + /// + [Column(Order = 3)] + [Comment("Атрибут")] + public AttributeValues Value { get; set; } + + /// + /// Класс, к которому относится ключевой атрибут. + /// + [JsonIgnore] + public Class Class { get; set; } = null!; +} diff --git a/Pf2eModel/Model/Structure/ClassRelation/ClassProgression.cs b/Pf2eModel/Model/Structure/ClassRelation/ClassProgression.cs index e8b1467..6c9260a 100644 --- a/Pf2eModel/Model/Structure/ClassRelation/ClassProgression.cs +++ b/Pf2eModel/Model/Structure/ClassRelation/ClassProgression.cs @@ -1,32 +1,62 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Pf2eModel.Model.Base; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.ClassRelation; +/// +/// Элемент прогрессии класса. +/// public class ClassProgression { + /// + /// Идентификатор прогрессии класса. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор класса, к которому относится элемент прогрессии. + /// [Column(Order = 2)] + [Comment("Идентификатор класса")] public int ClassId { get; set; } - [Column(Order = 31)] + /// + /// Уровень класса, на котором элемент прогрессии доступен. + /// + [Column(Order = 3)] + [Comment("Уровень класса")] public int Level { get; set; } + /// + /// Тип элемента прогрессии класса. + /// [Column(Order = 4)] + [Comment("Тип элемента прогрессии класса")] public ClassProgressionType Type { get; set; } + /// + /// Идентификатор сущности, связанной с элементом прогрессии класса (например, навык, атрибут и т.д.). + /// [Column(Order = 5)] + [Comment("Идентификатор сущности")] public int? EntityId { get; set; } + /// + /// Класс, к которому относится элемент прогрессии. + /// [JsonIgnore] public Class Class { get; set; } = null!; + /// + /// Сущность, связанная с элементом прогрессии класса (например, навык, атрибут и т.д.). + /// public Entity? Entity { get; set; } } diff --git a/Pf2eModel/Model/Structure/ClassRelation/ClassTraining.cs b/Pf2eModel/Model/Structure/ClassRelation/ClassTraining.cs index ae0d8bb..520804d 100644 --- a/Pf2eModel/Model/Structure/ClassRelation/ClassTraining.cs +++ b/Pf2eModel/Model/Structure/ClassRelation/ClassTraining.cs @@ -1,29 +1,55 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Pf2eModel.Model.Common; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.ClassRelation; +/// +/// Набор элементов, связывающий класс и тренируемый навык. +/// public class ClassTraining { + /// + /// Идентификатор набора элементов, связывающий класс и тренируемый навык. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор класса, к которому относится тренировка. + /// [Column(Order = 2)] + [Comment("Идентификатор класса")] public int ClassId { get; set; } + /// + /// Идентификатор тренируемого навыка, к которому относится тренировка. + /// [Column(Order = 3)] + [Comment("Идентификатор тренируемого навыка")] public int? TrainingId { get; set; } + /// + /// Ранг тренировки, определяющий уровень владения навыком. + /// [Column(Order = 4)] + [Comment("Ранг тренировки")] public TrainingRank Rank { get; set; } + /// + /// Класс, к которому относится тренировка. + /// [JsonIgnore] public Class Class { get; set; } = null!; + /// + /// Тренируемый навык, к которому относится тренировка. + /// public Training? Training { get; set; } } diff --git a/Pf2eModel/Model/Structure/ClassRelation/SubclassProvidedEntities.cs b/Pf2eModel/Model/Structure/ClassRelation/SubclassProvidedEntities.cs index 39d5869..814db91 100644 --- a/Pf2eModel/Model/Structure/ClassRelation/SubclassProvidedEntities.cs +++ b/Pf2eModel/Model/Structure/ClassRelation/SubclassProvidedEntities.cs @@ -2,10 +2,19 @@ namespace Pf2eModel.Model.Structure.ClassRelation; +/// +/// Элемент, связывающий сущность с подклассом. +/// [PrimaryKey(nameof(SubclassId), nameof(EntityId))] public class SubclassProvidedEntities { + /// + /// Идентификатор подкласса, к которому относится сущность. + /// public int SubclassId { get; set; } + /// + /// Идентификатор сущности, предоставляемой подклассом. + /// public int EntityId { get; set; } } diff --git a/Pf2eModel/Model/Structure/ClassRelation/SubclassTrainings.cs b/Pf2eModel/Model/Structure/ClassRelation/SubclassTrainings.cs index 389b7e5..284a571 100644 --- a/Pf2eModel/Model/Structure/ClassRelation/SubclassTrainings.cs +++ b/Pf2eModel/Model/Structure/ClassRelation/SubclassTrainings.cs @@ -1,27 +1,53 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Common; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.ClassRelation; +/// +/// Связь между подклассами и тренировками. +/// public class SubclassTrainings { + /// + /// Идентификатор связи между подклассом и тренировкой. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор связи")] public int Id { get; set; } + /// + /// Идентификатор подкласса, к которому относится тренировка. + /// [Column(Order = 2)] + [Comment("Идентификатор подкласса")] public int SubclassId { get; set; } + /// + /// Идентификатор тренировки, связанной с подклассом. + /// [Column(Order = 3)] + [Comment("Идентификатор тренировки")] public int TrainingId { get; set; } + /// + /// Ранг тренировки для данного подкласса. + /// [Column(Order = 4)] + [Comment("Ранг тренировки")] public TrainingRank Rank { get; set; } = TrainingRank.Trained; + /// + /// Подкласс, к которому относится тренировка. + /// public Subclass Subclass { get; set; } = null!; + /// + /// Тренировка, связанная с подклассом. + /// public Training Training { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/DeityRelation/DeityAttribute.cs b/Pf2eModel/Model/Structure/DeityRelation/DeityAttribute.cs index 9b186b6..776e702 100644 --- a/Pf2eModel/Model/Structure/DeityRelation/DeityAttribute.cs +++ b/Pf2eModel/Model/Structure/DeityRelation/DeityAttribute.cs @@ -4,14 +4,28 @@ using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.DeityRelation; +/// +/// Элемент отношения божества и атрибута. +/// [PrimaryKey(nameof(DeityId), nameof(Attribute))] public class DeityAttribute { + /// + /// Идентификатор божества. + /// [Column(Order = 1)] + [Comment("Идентификатор божества")] public int DeityId { get; set; } + /// + /// Атрибут божества. + /// [Column(Order = 2)] + [Comment("Атрибут божества")] public AttributeValues Attribute { get; set; } + /// + /// Божество, к которому относится атрибут. + /// public Deity Deity { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/DeityRelation/DeityDomain.cs b/Pf2eModel/Model/Structure/DeityRelation/DeityDomain.cs index 7e0e1dc..6d693d1 100644 --- a/Pf2eModel/Model/Structure/DeityRelation/DeityDomain.cs +++ b/Pf2eModel/Model/Structure/DeityRelation/DeityDomain.cs @@ -1,24 +1,46 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Pf2eModel.Model.Structure.DeityRelation; +/// +/// Элемент отношения божества и домена. +/// public class DeityDomain { + /// + /// Идентификатор отношения божества и домена. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор божества, к которому относится домен. + /// [Column(Order = 2)] + [Comment("Идентификатор божества")] public int DeityId { get; set; } + /// + /// Флаг, указывающий, является ли домен основным для божества. + /// [Column(Order = 3)] + [Comment("Флаг основного домена")] public bool IsPrimary { get; set; } + /// + /// Значение домена божества, например, "Свет", "Тьма" и т.д. + /// [Column(Order = 4)] [MaxLength(Pf2eDbContext.NameLength)] public string Value { get; set; } = null!; + /// + /// Божество, к которому относится домен. + /// public virtual Deity Deity { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/DeityRelation/DeitySpell.cs b/Pf2eModel/Model/Structure/DeityRelation/DeitySpell.cs index 0f448a8..11e0644 100644 --- a/Pf2eModel/Model/Structure/DeityRelation/DeitySpell.cs +++ b/Pf2eModel/Model/Structure/DeityRelation/DeitySpell.cs @@ -1,25 +1,51 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Pf2eModel.Model.Structure.DeityRelation; +/// +/// Связь божества и заклинания. +/// public class DeitySpell { + /// + /// Идентификатор связи божества и заклинания. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор божества, к которому относится заклинание. + /// [Column(Order = 2)] + [Comment("Идентификатор божества")] public int DeityId { get; set; } + /// + /// Уровень заклинания. + /// [Column(Order = 3)] + [Comment("Уровень заклинания")] public int Level { get; set; } + /// + /// Идентификатор заклинания, связанного с божеством. + /// [Column(Order = 4)] + [Comment("Идентификатор заклинания")] public int SpellId { get; set; } + /// + /// Божество, к которому относится заклинание. + /// public virtual Deity Deity { get; set; } = null!; + /// + /// Заклинание, связанное с божеством. + /// public virtual Spell Spell { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/DeityRelation/DeityWeapon.cs b/Pf2eModel/Model/Structure/DeityRelation/DeityWeapon.cs index 73a2bca..5224bef 100644 --- a/Pf2eModel/Model/Structure/DeityRelation/DeityWeapon.cs +++ b/Pf2eModel/Model/Structure/DeityRelation/DeityWeapon.cs @@ -1,21 +1,40 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Pf2eModel.Model.Structure.DeityRelation; +/// +/// Связь божества с оружием. +/// public class DeityWeapon { + /// + /// Идентификатор связи божества с оружием. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор божества, к которому относится оружие. + /// [Column(Order = 2)] + [Comment("Идентификатор божества")] public int DeityId { get; set; } + /// + /// Название оружия, связанного с божеством. Будет изменено на идентификатор оружия в будущем, когда будет реализована система оружия. + /// [Column(Order = 3)] [MaxLength(Pf2eDbContext.NameLength)] + [Comment("Название оружия")] public string Weapon { get; set; } = null!; + /// + /// Божество, к которому относится оружие. + /// public virtual Deity Deity { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/FeatRelation/FeatPrerequisite.cs b/Pf2eModel/Model/Structure/FeatRelation/FeatPrerequisite.cs index bff9311..c75b85a 100644 --- a/Pf2eModel/Model/Structure/FeatRelation/FeatPrerequisite.cs +++ b/Pf2eModel/Model/Structure/FeatRelation/FeatPrerequisite.cs @@ -1,21 +1,40 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Pf2eModel.Model.Structure.FeatRelation; +/// +/// Предварительное требование для черты. +/// public class FeatPrerequisite { + /// + /// Идентификатор предварительного требования для чериы. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор черты, к которой относится предварительное требование. + /// [Column(Order = 2)] + [Comment("Идентификатор черты")] public int FeatId { get; set; } + /// + /// Предварительное требование для черты. + /// [Column(Order = 3)] [MaxLength(255)] + [Comment("Предварительное требование")] public string Value { get; set; } = null!; + /// + /// Черта, к которой относится предварительное требование. + /// public virtual Feat Feat { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/RitualRelation/RitualArea.cs b/Pf2eModel/Model/Structure/RitualRelation/RitualArea.cs index cac4118..2697343 100644 --- a/Pf2eModel/Model/Structure/RitualRelation/RitualArea.cs +++ b/Pf2eModel/Model/Structure/RitualRelation/RitualArea.cs @@ -1,23 +1,42 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.RitualRelation; +/// +/// Область действия ритуала. +/// public class RitualArea { + /// + /// Идентификатор ритуала, к которому относится область действия. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] [Column(Order = 1)] + [Comment("Идентификатор ритуала")] public int RitualId { get; set; } + /// + /// Описание области действия ритуала. + /// [Column(Order = 2)] [MaxLength(255)] + [Comment("Описание области действия ритуала")] public LocaleString? Details { get; set; } + /// + /// Тип области действия ритуала. + /// [Column(Order = 3)] + [Comment("Тип области действия ритуала")] public SpellAreaType Type { get; set; } + /// + /// Ритуал, к которому относится область действия. + /// public virtual Ritual Ritual { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/RitualRelation/RitualCheck.cs b/Pf2eModel/Model/Structure/RitualRelation/RitualCheck.cs index a7742e3..6a4341c 100644 --- a/Pf2eModel/Model/Structure/RitualRelation/RitualCheck.cs +++ b/Pf2eModel/Model/Structure/RitualRelation/RitualCheck.cs @@ -1,34 +1,68 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.RitualRelation; +/// +/// Проверка для ритуала. +/// public class RitualCheck { + /// + /// Идентификатор проверки ритуала. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор ритуала, к которому относится проверка. + /// [Column(Order = 2)] + [Comment("Идентификатор ритуала")] public int RitualId { get; set; } - public Ritual Ritual { get; set; } = null!; - + /// + /// Идентификатор тренировки, связанной с проверкой ритуала. + /// [Column(Order = 3)] + [Comment("Идентификатор тренировки")] public int? TrainingId { get; set; } - public Training? Training { get; set; } = null!; - + /// + /// Дополнение к проверке. + /// [Column(Order = 4)] + [Comment("Дополнение к проверке")] public LocaleString? TrainingString { get; set; } = null; + /// + /// Ранг тренировки, связанной с проверкой ритуала. + /// [Column(Order = 5)] + [Comment("Ранг тренировки")] public TrainingRank Rank { get; set; } + /// + /// Флаг, указывающий, является ли проверка основной для ритуала. + /// [Column(Order = 6)] + [Comment("Флаг основной проверки")] public bool IsPrimary { get; set; } + + /// + /// Ритуал, к которому относится проверка. + /// + public Ritual Ritual { get; set; } = null!; + + /// + /// Тренировка, связанная с проверкой ритуала. + /// + public Training? Training { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/SpellRelation/SpellArea.cs b/Pf2eModel/Model/Structure/SpellRelation/SpellArea.cs index 7f6185b..8825aa6 100644 --- a/Pf2eModel/Model/Structure/SpellRelation/SpellArea.cs +++ b/Pf2eModel/Model/Structure/SpellRelation/SpellArea.cs @@ -1,26 +1,49 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.SpellRelation; +/// +/// Область заклинания. +/// public class SpellArea { + /// + /// Идентификатор области заклинания. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор заклинания, к которому относится область. + /// [Column(Order = 2)] + [Comment("Идентификатор заклинания")] public int SpellId { get; set; } + /// + /// Описание области заклинания. + /// [Column(Order = 3)] [MaxLength(255)] + [Comment("Описание области заклинания")] public LocaleString? Details { get; set; } + /// + /// Тип области заклинания. + /// [Column(Order = 4)] + [Comment("Тип области заклинания")] public SpellAreaType Type { get; set; } + /// + /// Заклинание, к которому относится область. + /// public virtual Spell Spell { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/SpellRelation/SpellDamage.cs b/Pf2eModel/Model/Structure/SpellRelation/SpellDamage.cs index 139883e..8c9dff8 100644 --- a/Pf2eModel/Model/Structure/SpellRelation/SpellDamage.cs +++ b/Pf2eModel/Model/Structure/SpellRelation/SpellDamage.cs @@ -1,43 +1,86 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Structure.SpellRelation; +/// +/// Урон заклинания. +/// public class SpellDamage { + /// + /// Идентификатор урона заклинания. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор урона заклинания")] public int Id { get; set; } + /// + /// Идентификатор заклинания, к которому относится урон. + /// [Column(Order = 2)] + [Comment("Идентификатор заклинания")] public int SpellId { get; set; } + /// + /// Уровень заклинания, к которому относится урон. + /// [Column(Order = 3)] [MaxLength(255)] + [Comment("Уровень заклинания")] public string Key { get; set; } = null!; + /// + /// Применяется ли модификатор к урону заклинания. + /// [Column(Order = 4)] + [Comment("Применяется модификатор?")] public bool? ApplyMod { get; set; } + /// + /// Категория урона заклинания, если применимо. + /// [Column(Order = 5)] + [Comment("Категория урона заклинания")] public SpellDamageCategory? Category { get; set; } + /// + /// Формула урона заклинания. + /// [Column(Order = 6)] [MaxLength(255)] + [Comment("Формула урона заклинания")] public string Formula { get; set; } = null!; + /// + /// Тип урона заклинания. + /// [Column(Order = 7)] + [Comment("Тип урона заклинания")] public SpellDamageKinds Kinds { get; set; } + /// + /// Материалы, необходимые для заклинания, если применимо. + /// [Column(Order = 8)] [MaxLength(255)] + [Comment("Материалы для заклинания")] public string? Materials { get; set; } + /// + /// Тип урона заклинания, если применимо (например, "fire", "cold" и т.д.). + /// [Column(Order = 9)] [MaxLength(255)] + [Comment("Тип урона заклинания")] public string Type { get; set; } = null!; + /// + /// Заклинание, к которому относится урон. + /// public virtual Spell Spell { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/SpellRelation/SpellDefense.cs b/Pf2eModel/Model/Structure/SpellRelation/SpellDefense.cs index c841a43..292c4af 100644 --- a/Pf2eModel/Model/Structure/SpellRelation/SpellDefense.cs +++ b/Pf2eModel/Model/Structure/SpellRelation/SpellDefense.cs @@ -1,27 +1,52 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Pf2eModel.Model.Structure.SpellRelation; +/// +/// Защита от заклинания. +/// public class SpellDefense { + /// + /// Идентификатор защиты от заклинания. + /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Column(Order = 1)] + [Comment("Идентификатор")] public int Id { get; set; } + /// + /// Идентификатор заклинания, к которому относится защита. + /// [Column(Order = 2)] + [Comment("Идентификатор заклинания")] public int SpellId { get; set; } + /// + /// Флаг, указывающий, является ли защита от заклинания пассивной (например, для заклинаний, которые не требуют активного действия для защиты). + /// [Column(Order = 3)] public bool IsPassive { get; set; } + /// + /// Флаг, указывающий, является ли защита базовой. + /// [Column(Order = 4)] + [Comment("Базовый спасборосок?")] public bool? IsBasic { get; set; } + /// + /// Статистика, используемая для защиты от заклинания (например, "Fortitude", "Reflex", "Will"). + /// [Column(Order = 5)] [MaxLength(255)] public string Statistic { get; set; } = null!; + /// + /// Заклинание, к которому относится защита. + /// public virtual Spell Spell { get; set; } = null!; } diff --git a/Pf2eModel/Model/Structure/Subclass.cs b/Pf2eModel/Model/Structure/Subclass.cs index 73cfb86..01e8471 100644 --- a/Pf2eModel/Model/Structure/Subclass.cs +++ b/Pf2eModel/Model/Structure/Subclass.cs @@ -49,7 +49,13 @@ public class Subclass : IEntity [Column(Order = 4)] public string Extra { get; set; } = "{}"; + /// + /// Коллекция сущностей, связанных с подклассом. + /// public ICollection Entities { get; set; } = []; + /// + /// Коллекция тренировок, связанных с подклассом. + /// public ICollection Trainings { get; set; } = []; } diff --git a/Pf2eModel/Pf2eDbContext.cs b/Pf2eModel/Pf2eDbContext.cs index 2adc7be..c7c5c52 100644 --- a/Pf2eModel/Pf2eDbContext.cs +++ b/Pf2eModel/Pf2eDbContext.cs @@ -21,16 +21,30 @@ namespace Pf2eModel; /// /// Контекст базы данных. +/// +/// Разделён на 2 файла для удобства навигации и чтения кода. +/// +/// - в `Pf2eDbContext.cs` находится набор таблиц и конфигурация БД, +/// - в `Pf2eDbContextModel.cs` находится Fluent API описание связей в модели. /// +// ReSharper disable once InconsistentNaming public partial class Pf2eDbContext : DbContext { + /// + /// Инициализирует новый экземпляр класса без параметров. + /// public Pf2eDbContext() { } + /// + /// Инициализирует новый экземпляр класса с указанными параметрами конфигурации базы данных. + /// + /// Конфигурация базы данных. public Pf2eDbContext(DbContextOptions options) : base(options) { } #region DbSets +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public virtual DbSet Actions { get; set; } public virtual DbSet Ancestries { get; set; } @@ -53,7 +67,7 @@ public partial class Pf2eDbContext : DbContext public virtual DbSet Classes { get; set; } - public virtual DbSet ClassKeyAbilities { get; set; } + public virtual DbSet ClassKeyAbilities { get; set; } public virtual DbSet ClassProgressions { get; set; } @@ -123,6 +137,7 @@ public partial class Pf2eDbContext : DbContext public virtual DbSet Traits { get; set; } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member #endregion /// @@ -166,3 +181,8 @@ public partial class Pf2eDbContext : DbContext ); } } + +/// +/// Набор классов модели ORM EntityFramework. +/// +internal static class NamespaceDoc { } diff --git a/Pf2eModel/Pf2eDbContextModel.cs b/Pf2eModel/Pf2eDbContextModel.cs index 2c3c06d..f203006 100644 --- a/Pf2eModel/Pf2eDbContextModel.cs +++ b/Pf2eModel/Pf2eDbContextModel.cs @@ -304,7 +304,7 @@ public partial class Pf2eDbContext { BindEntity(entity); }); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { entity .HasOne(d => d.Class) @@ -585,10 +585,22 @@ public partial class Pf2eDbContext ); } + /// + /// CLR-представление функции поиска в локализованной строке. + /// + /// Локализованная строка, в которой будет осуществляться поиск. + /// Искомое значение для поиска. + /// Флаг, указывающий, найдено ли искомое значение в локализованной строке. // ReSharper disable once InconsistentNaming public static bool LocaleSearch(LocaleString data, string search_value) => throw new InvalidOperationException("Only for LINQ translation"); + /// + /// CLR-представление функции поиска в локализованной строке с массивом значений для поиска. + /// + /// Локализованная строка, в которой будет осуществляться поиск. + /// Массив искомых значений для поиска. + /// Флаг, указывающий, найдено ли хотя бы одно искомое значение в локализованной строке. // ReSharper disable once InconsistentNaming public static bool LocaleSearchArray(LocaleString data, IEnumerable search_values) => throw new InvalidOperationException("Only for LINQ translation"); diff --git a/Pf2eModel/Pf2eModel.csproj b/Pf2eModel/Pf2eModel.csproj index 1cef725..68bbe83 100644 --- a/Pf2eModel/Pf2eModel.csproj +++ b/Pf2eModel/Pf2eModel.csproj @@ -4,6 +4,9 @@ enable enable True + true + ..\Documentation\$(MSBuildProjectName) + Name @@ -11,6 +14,7 @@ + diff --git a/README.md b/README.md index e672e51..550b352 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ - Создайте файл `.env` и добавьте в него содержимое из файла `.env.example`. Измените содержащиеся поля: - `DATA_DIR` - путь к папке для распаковки репозиториев обрабатываемых данных; - `OUTPUT_DIR` - путь к месту сохранения схем; - - `DATABASE_CONNECTION` - строка подключения к БД (только PostreSQL). -- Перейдите а папку `Pf2eModel` (`cd ../Pf2eModel`) и запустите `dotnet ef database update` для создания структуры БД в командной строке. + - `DATABASE_CONNECTION` - строка подключения к БД (только PostreSQL); + - `MAX_THREADS` - максимальное количество потоков при параллельной обработке. +- Перейдите а папку `Pf2eModel` (`cd ../Pf2eModel`) и запустите `dotnet ef database update` для создания структуры БД в командной строке. Если необходимо обновить структуру после изменения модели - используйте скрипт `RebuildDatabase.bat`. - Запустите проект `Pf2eDataScrapper` для парсинга информации. ## Роли проектов @@ -39,6 +40,12 @@ ### `Pf2eDataScrapper` +Основной проект обработки и отображения данных. + +Структура пространст имён: + +- `Helpers` - классы-помощники, которые используются в + #### `SchemaBuilder` Классы обработки вводимых JSON-данных и создание схемы данных для использовании при формировании обработчиков данных. From b96bd2a224b7767eff48cb951b07c5c091ef0c62 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Sun, 1 Jun 2025 20:38:43 +0400 Subject: [PATCH 31/33] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B?= =?UTF-8?q?=20=D1=81=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AbstractInputProcessor.md | 4 +- .../Pf2eDataScrapper.Helpers.md | 2 + ...rters.Implementations.Aonprd.Compendium.md | 2 + ...mporters.Implementations.Aonprd.Helpers.md | 2 + ...mplementations.Aonprd.Processors.Common.md | 8 - ...entations.Aonprd.Processors.Preparation.md | 4 + ...mentations.Aonprd.Processors.Subclasses.md | 2 + ...entations.Aonprd.Processors.Types.Rules.md | 2 + ...Implementations.Aonprd.Processors.Types.md | 2 + ...rters.Implementations.Aonprd.Processors.md | 2 + ...rapper.Importers.Implementations.Aonprd.md | 2 + ...rs.Implementations.FoundryEn.Compendium.md | 2 + ...ations.FoundryEn.Processors.Preparation.md | 2 + ...per.Importers.Implementations.FoundryEn.md | 2 + ...Pf2eDataScrapper.Importers.Repositories.md | 2 + .../Pf2eDataScrapper.Importers.md | 2 + .../Pf2eDataScrapper.Processors.Attributes.md | 2 + .../Pf2eDataScrapper.Processors.Collection.md | 2 + ...Pf2eDataScrapper.Processors.Definitions.md | 2 + .../Pf2eDataScrapper.Processors.Factory.md | 2 + .../Pf2eDataScrapper.Processors.md | 2 + .../Pf2eDataScrapper.SchemaBuilder.Types.md | 2 + .../Pf2eDataScrapper.SchemaBuilder.md | 2 + ...DataScrapper.WPF.EntityViewer.Component.md | 2 + ...er.WPF.EntityViewer.EntityViewStructure.md | 2 + ...f2eDataScrapper.WPF.EntityViewer.Stores.md | 2 + ...DataScrapper.WPF.EntityViewer.ViewModel.md | 2 + .../Pf2eDataScrapper.WPF.EntityViewer.md | 2 + .../Pf2eDataScrapper.WPF.ProgressForm.md | 2 + ...2eDataScrapper.WPF.StartForm.Components.md | 2 + .../Pf2eDataScrapper.WPF.StartForm.md | 2 + .../Pf2eDataScrapper/Pf2eDataScrapper.WPF.md | 2 + .../Pf2eDataScrapper/Pf2eDataScrapper.md | 2 + .../Pf2eDataScrapper/ProcessorGroupEnum.md | 12 +- Documentation/Pf2eDataScrapper/SchemaNode.md | 9 + .../SkillProcessor.Process().md | 2 +- .../Pf2eDataScrapper/SkillProcessor.Save().md | 2 +- .../SkillProcessor.Training.md | 2 +- .../Pf2eDataScrapper/SkillProcessor.md | 8 +- .../SourceProcessor.Process().md | 2 +- .../SourceProcessor.Save().md | 2 +- .../SourceProcessor.Source.md | 2 +- .../Pf2eDataScrapper/SourceProcessor.md | 8 +- Documentation/Pf2eDataScrapper/index.md | 63 +++-- ...Function.BuildTargetModel(ModelBuilder).md | 20 -- .../Function.Down(MigrationBuilder).md | 28 -- .../Function.Up(MigrationBuilder).md | 26 -- Documentation/Pf2eModel/Function.md | 21 -- .../Pf2eModel/Pf2eModel.Migrations.md | 8 - .../Pf2eModel.Model.Base.Keywords.md | 2 + .../Pf2eModel/Pf2eModel.Model.Base.Source.md | 2 + .../Pf2eModel/Pf2eModel.Model.Base.Targets.md | 2 + .../Pf2eModel/Pf2eModel.Model.Base.md | 2 + .../Pf2eModel/Pf2eModel.Model.Common.I18n.md | 2 + .../Pf2eModel/Pf2eModel.Model.Common.md | 2 + .../Pf2eModel/Pf2eModel.Model.Enum.md | 2 + ...eModel.Model.Structure.AncestryRelation.md | 2 + ...Model.Model.Structure.ArchetypeRelation.md | 2 + ...odel.Model.Structure.BackgroundRelation.md | 2 + ...Pf2eModel.Model.Structure.ClassRelation.md | 2 + ...Pf2eModel.Model.Structure.DeityRelation.md | 2 + .../Pf2eModel.Model.Structure.FeatRelation.md | 2 + ...f2eModel.Model.Structure.RitualRelation.md | 2 + ...Pf2eModel.Model.Structure.SpellRelation.md | 2 + .../Pf2eModel/Pf2eModel.Model.Structure.md | 3 + Documentation/Pf2eModel/Pf2eModel.Model.md | 2 + .../_1.BuildTargetModel(ModelBuilder).md | 20 -- .../Pf2eModel/_1.Down(MigrationBuilder).md | 28 -- .../Pf2eModel/_1.Up(MigrationBuilder).md | 26 -- Documentation/Pf2eModel/_1.md | 21 -- Documentation/Pf2eModel/index.md | 35 ++- Documentation/index.md | 2 + Pf2eDataScrapper/Documentation.cs | 247 ++++++++++++++++++ .../ExternalProcessor.cs | 3 +- .../{Common => Preparation}/SkillProcessor.cs | 2 +- .../SourceProcessor.cs | 2 +- .../Attributes/ProcessorGroupEnum.cs | 12 +- Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs | 8 + Pf2eModel/Documentation.cs | 153 +++++++++++ Pf2eModel/Pf2eDbContext.cs | 5 - README.md | 53 +--- 81 files changed, 602 insertions(+), 341 deletions(-) delete mode 100644 Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md delete mode 100644 Documentation/Pf2eModel/Function.BuildTargetModel(ModelBuilder).md delete mode 100644 Documentation/Pf2eModel/Function.Down(MigrationBuilder).md delete mode 100644 Documentation/Pf2eModel/Function.Up(MigrationBuilder).md delete mode 100644 Documentation/Pf2eModel/Function.md delete mode 100644 Documentation/Pf2eModel/Pf2eModel.Migrations.md delete mode 100644 Documentation/Pf2eModel/_1.BuildTargetModel(ModelBuilder).md delete mode 100644 Documentation/Pf2eModel/_1.Down(MigrationBuilder).md delete mode 100644 Documentation/Pf2eModel/_1.Up(MigrationBuilder).md delete mode 100644 Documentation/Pf2eModel/_1.md create mode 100644 Documentation/index.md create mode 100644 Pf2eDataScrapper/Documentation.cs rename Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/{Common => Preparation}/ExternalProcessor.cs (95%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/{Common => Preparation}/SkillProcessor.cs (98%) rename Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/{Common => Preparation}/SourceProcessor.cs (98%) create mode 100644 Pf2eModel/Documentation.cs diff --git a/Documentation/Pf2eDataScrapper/AbstractInputProcessor.md b/Documentation/Pf2eDataScrapper/AbstractInputProcessor.md index 2aca03b..a7d4e60 100644 --- a/Documentation/Pf2eDataScrapper/AbstractInputProcessor.md +++ b/Documentation/Pf2eDataScrapper/AbstractInputProcessor.md @@ -15,8 +15,8 @@ Inheritance [System\.Object](https://docs.microsoft.com/en-us/dotnet/api/System. Derived ↳ [AbstractEntityProcessor](AbstractEntityProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.AbstractEntityProcessor') -↳ [SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SkillProcessor') -↳ [SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SourceProcessor') +↳ [SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SkillProcessor') +↳ [SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SourceProcessor') Implements [IProcessor](IProcessor.md 'Pf2eDataScrapper\.Processors\.Definitions\.IProcessor') diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Helpers.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Helpers.md index 6767ed7..cf14dfd 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Helpers.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Helpers.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Helpers Namespace +Классы\-помощники, используемые во всех частях проекта\. + | Classes | | | :--- | :--- | | [DatabaseHelper](DatabaseHelper.md 'Pf2eDataScrapper\.Helpers\.DatabaseHelper') | Класс предоставляет методы для работы с базой данных\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium.md index d04ff9d..9d5a83c 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Compendium Namespace +Компендиум данных из сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. + | Classes | | | :--- | :--- | | [AonprdCompendium](AonprdCompendium.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Compendium\.AonprdCompendium') | Класс хранения данных компендиума\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.md index b5164b0..2dfd27b 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Helpers Namespace +Класс\-помощники, которые используются в импортере данных из сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. + | Classes | | | :--- | :--- | | [AonprdBuilderGlobals](AonprdBuilderGlobals.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Helpers\.AonprdBuilderGlobals') | Класс\-помощник, хранящий необходимые глобальные данные\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md deleted file mode 100644 index 2f38105..0000000 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md +++ /dev/null @@ -1,8 +0,0 @@ -#### [Pf2eDataScrapper](index.md 'index') - -## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common Namespace - -| Classes | | -| :--- | :--- | -| [SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SkillProcessor') | | -| [SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SourceProcessor') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md index fdf107c..cbb063f 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md @@ -2,7 +2,11 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation Namespace +Обработчики для подготовки данных [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. + | Classes | | | :--- | :--- | | [AonprdTypeResolver](AonprdTypeResolver.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.AonprdTypeResolver') | Класс для определения типа поля в схеме данных\. | | [SchemaBuildProcessor](SchemaBuildProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SchemaBuildProcessor') | | +| [SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SkillProcessor') | | +| [SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SourceProcessor') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses.md index b2045c2..65032f6 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Subclasses Namespace +Обработчики подклассов с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. + | Classes | | | :--- | :--- | | [ApparitionProcessor](ApparitionProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Subclasses\.ApparitionProcessor') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules.md index c72c49b..0a3be4c 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types\.Rules Namespace +Обработчики правил с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. + | Classes | | | :--- | :--- | | [RuleProcessor](RuleProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types\.Rules\.RuleProcessor') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.md index 55ec391..03f2a85 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types Namespace +Обработчики основных сущностей с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. + | Classes | | | :--- | :--- | | [ActionProcessor](ActionProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types\.ActionProcessor') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.md index 7a1d24d..d8d685d 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors Namespace +Обработчики данных из сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. + | Classes | | | :--- | :--- | | [AbstractEntityProcessor](AbstractEntityProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.AbstractEntityProcessor') | Абстрактный класс\-обработчик сущностей, полученных из Архивов Нефиса\. Класс предназначен для обработки данных с помощью различных обработчиков, для которых указан атрибут [ProcessorAttribute](ProcessorAttribute.md 'Pf2eDataScrapper\.Processors\.Attributes\.ProcessorAttribute')\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.md index eb9be36..ae9a2b0 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.Aonprd.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.Aonprd Namespace +Импортёр данных из сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. + | Classes | | | :--- | :--- | | [AonprdImporter](AonprdImporter.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.AonprdImporter') | Класс обработки данных с сайта Архивов Нефеса\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium.md index e90c46b..715689d 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Compendium Namespace +Компендиум данных из модуля [pf2e](https://github.com/foundryvtt/pf2e 'https://github\.com/foundryvtt/pf2e') для FoundryVTT\. + | Classes | | | :--- | :--- | | [CompendiumFolder](CompendiumFolder.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Compendium\.CompendiumFolder') | Папка компендиума\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation.md index 5fd5134..bb2cc59 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Processors\.Preparation Namespace +Обработчики для подготовки данных из модуля [pf2e](https://github.com/foundryvtt/pf2e 'https://github\.com/foundryvtt/pf2e') для FoundryVTT\. + | Classes | | | :--- | :--- | | [CompendiumFolderProcessor](CompendiumFolderProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Processors\.Preparation\.CompendiumFolderProcessor') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.md index f2d51bd..0f24784 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Implementations.FoundryEn.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn Namespace +Импортёр данных из модуля [pf2e](https://github.com/foundryvtt/pf2e 'https://github\.com/foundryvtt/pf2e') для FoundryVTT\. + | Classes | | | :--- | :--- | | [FoundryEnImporter](FoundryEnImporter.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.FoundryEnImporter') | Импортёр для модуля Pf2e FoundryVTT\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Repositories.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Repositories.md index 194dd64..e605d7a 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Repositories.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.Repositories.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers\.Repositories Namespace +Общее описание репозиториев для хранения данных, которые используются в импортёрах и процессорах\. + | Classes | | | :--- | :--- | | [AbstractImporterRepository](AbstractImporterRepository.md 'Pf2eDataScrapper\.Importers\.Repositories\.AbstractImporterRepository') | Класс абстрактного репозитория\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.md index aaa1068..2f4ecc0 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Importers.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Importers Namespace +Описание импортёров, которые определяют наборы обработчиков данных и элементов, необходимых для их работы\. + | Classes | | | :--- | :--- | | [AbstractCompendium](AbstractCompendium.md 'Pf2eDataScrapper\.Importers\.AbstractCompendium') | Компендиум данных\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Attributes.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Attributes.md index 7e2a198..5991609 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Attributes.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Attributes.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Processors\.Attributes Namespace +Классы\-аттрибуты, которые используются для описания обработчиков данных и их свойств\. + | Classes | | | :--- | :--- | | [AbstractProcessorAttribute](AbstractProcessorAttribute.md 'Pf2eDataScrapper\.Processors\.Attributes\.AbstractProcessorAttribute') | Абстрактный аттрибут обработчика\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Collection.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Collection.md index 97885f0..de9601d 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Collection.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Collection.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Processors\.Collection Namespace +Описание коллекции классов обработчиков данных\. + | Classes | | | :--- | :--- | | [ProcessorCollection](ProcessorCollection.md 'Pf2eDataScrapper\.Processors\.Collection\.ProcessorCollection') | Класс с коллекцией обработчиков\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Definitions.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Definitions.md index 5c15529..5616cdc 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Definitions.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Definitions.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Processors\.Definitions Namespace +Общее описание классов и интерфейсов, которые используются в обработчиках данных\. + | Classes | | | :--- | :--- | | [AbstractInputProcessor](AbstractInputProcessor.md 'Pf2eDataScrapper\.Processors\.Definitions\.AbstractInputProcessor') | Абстрактный класс\-обработчик данных, полученных из Архивов Нефиса\. Класс предназначен для обработки данных с помощью различных обработчиков, для которых указан атрибут [ProcessorAttribute](ProcessorAttribute.md 'Pf2eDataScrapper\.Processors\.Attributes\.ProcessorAttribute')\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Factory.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Factory.md index a9403ff..9f60e3e 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Factory.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.Factory.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Processors\.Factory Namespace +Фабрики для создания экземпляров обработчиков данных на основе их атрибутов и конфигурации\. + | Classes | | | :--- | :--- | | [AbstractProcessorFactory](AbstractProcessorFactory.md 'Pf2eDataScrapper\.Processors\.Factory\.AbstractProcessorFactory') | Абстрактная фабрика создания процессоров\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.md index a452374..92f44a5 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.Processors.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.Processors Namespace +Общее описание обработчиков данных\. + | Classes | | | :--- | :--- | | [EnumSolver<TEnum>](EnumSolver_TEnum_.md 'Pf2eDataScrapper\.Processors\.EnumSolver\') | Абстрактный класс для разрешения значений перечислений\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.SchemaBuilder.Types.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.SchemaBuilder.Types.md index 26083db..be7f60b 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.SchemaBuilder.Types.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.SchemaBuilder.Types.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.SchemaBuilder\.Types Namespace +Типы полей, которые используются в схемах данных для описания структуры вводимых JSON\-данных\. + | Classes | | | :--- | :--- | | [AbstractSchemaType](AbstractSchemaType.md 'Pf2eDataScrapper\.SchemaBuilder\.Types\.AbstractSchemaType') | Абстрактный тип представоления данных в схеме\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.SchemaBuilder.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.SchemaBuilder.md index f58ae27..bd1c8fd 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.SchemaBuilder.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.SchemaBuilder.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.SchemaBuilder Namespace +Классы обработки вводимых JSON\-данных и создание схемы данных для использовании при формировании обработчиков данных\. + | Classes | | | :--- | :--- | | [AbstractSchemaBuildProcessor](AbstractSchemaBuildProcessor.md 'Pf2eDataScrapper\.SchemaBuilder\.AbstractSchemaBuildProcessor') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.Component.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.Component.md index b12c707..cc8f4b4 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.Component.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.Component.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF\.EntityViewer\.Component Namespace +Компоненты, которые используются в WPF\-окне просмотра сущностей Pathfinder 2e\. + | Classes | | | :--- | :--- | | [EntityListControl](EntityListControl.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.Component\.EntityListControl') | Логика взаимодействия для DbEntityListControl\.xaml | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure.md index 0e7ed76..e2f6d41 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF\.EntityViewer\.EntityViewStructure Namespace +Структура представления сущностей Pathfinder 2e в WPF\-приложении, включая классы и интерфейсы для отображения данных\. + | Classes | | | :--- | :--- | | [CollapseCommand](CollapseCommand.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.EntityViewStructure\.CollapseCommand') | Команда для сворачивания/разворачивания поля структуры\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.Stores.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.Stores.md index 21f6a79..005f7a9 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.Stores.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.Stores.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF\.EntityViewer\.Stores Namespace +Хранилища данных для WPF\-приложения просмотра сущностей Pathfinder 2e, которые управляют состоянием и данными приложения\. + | Classes | | | :--- | :--- | | [SelectedEntityStore](SelectedEntityStore.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.Stores\.SelectedEntityStore') | Хранилище выбранной сущности\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.ViewModel.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.ViewModel.md index 35a79c7..1b4a99c 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.ViewModel.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.ViewModel.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF\.EntityViewer\.ViewModel Namespace +ViewModel для WPF\-приложения просмотра сущностей Pathfinder 2e, которые связывают данные и представление\. + | Classes | | | :--- | :--- | | [EntityListViewModel](EntityListViewModel.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.ViewModel\.EntityListViewModel') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.md index fc1cf27..0b523ef 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.EntityViewer.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF\.EntityViewer Namespace +WPF\-окно для просмотра сущностей Pathfinder 2e, с возможностью фильтрации и поиска по данным\. + | Classes | | | :--- | :--- | | [EntityForm](EntityForm.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.EntityForm') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.ProgressForm.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.ProgressForm.md index 0380474..e725907 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.ProgressForm.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.ProgressForm.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF\.ProgressForm Namespace +WPF\-окно для отображения прогресса выполнения операций, таких как импорт данных или обработка сущностей Pathfinder 2e\. + | Classes | | | :--- | :--- | | [ProgressForm](ProgressForm.md 'Pf2eDataScrapper\.WPF\.ProgressForm\.ProgressForm') | Логика взаимодействия для ProgressFormWpf\.xaml | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.StartForm.Components.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.StartForm.Components.md index aff6216..9815f70 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.StartForm.Components.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.StartForm.Components.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF\.StartForm\.Components Namespace +Компоненты, которые используются в WPF\-окне запуска приложения для выбора импортёра и настройки параметров обработки данных Pathfinder 2e\. + | Classes | | | :--- | :--- | | [ProcessorCollectionControl](ProcessorCollectionControl.md 'Pf2eDataScrapper\.WPF\.StartForm\.Components\.ProcessorCollectionControl') | Логика взаимодействия для ProcessorCollectionControl\.xaml | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.StartForm.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.StartForm.md index f79efde..2151766 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.StartForm.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.StartForm.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF\.StartForm Namespace +WPF\-окно для запуска приложения, которое позволяет пользователю выбрать импортёр и начать обработку данных Pathfinder 2e\. + | Classes | | | :--- | :--- | | [StartForm](StartForm.md 'Pf2eDataScrapper\.WPF\.StartForm\.StartForm') | | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.md index 644f34f..53f9d2d 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.WPF.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper\.WPF Namespace +WPF\-приложение для запуска обработчиков и просмотра данных Pathfinder 2e\. + | Classes | | | :--- | :--- | | [WpfServiceProviderExtension](WpfServiceProviderExtension.md 'Pf2eDataScrapper\.WPF\.WpfServiceProviderExtension') | Расширение для IServiceCollection, позволяющее регистрировать сервисы WPF\. | diff --git a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.md b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.md index a752eba..b21305f 100644 --- a/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.md +++ b/Documentation/Pf2eDataScrapper/Pf2eDataScrapper.md @@ -2,6 +2,8 @@ ## Pf2eDataScrapper Namespace +Модель классов ORM EntityFramework\. + | Classes | | | :--- | :--- | | [ApplicationSettings](ApplicationSettings.md 'Pf2eDataScrapper\.ApplicationSettings') | Настройки программы\. | diff --git a/Documentation/Pf2eDataScrapper/ProcessorGroupEnum.md b/Documentation/Pf2eDataScrapper/ProcessorGroupEnum.md index 32e3c17..b2f6c69 100644 --- a/Documentation/Pf2eDataScrapper/ProcessorGroupEnum.md +++ b/Documentation/Pf2eDataScrapper/ProcessorGroupEnum.md @@ -20,34 +20,34 @@ public enum ProcessorGroupEnum `AonprdPreparation` 1 -Обработчик для подготовки данных Архивов Нефиса\. +Обработчик для подготовки данных с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. `AonprdEntities` 2 -Обработчик основных сущностей Архивов Нефиса\. +Обработчик основных сущностей с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. `AonprdSubclasses` 3 -Обработчик подклассов Архивов Нефиса\. +Обработчик подклассов с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. `AonprdFinalization` 4 -Обработчик для завершения обработки данных Архивов Нефиса\. +Обработчик для завершения обработки данных с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. `FoundryEnPreparation` 5 -Обработчик для подготовки данных Foundry EN\. +Обработчик для подготовки данных из модуля [pf2e](https://github.com/foundryvtt/pf2e 'https://github\.com/foundryvtt/pf2e') для FoundryVTT\. `FoundryEnEntities` 6 -Обработчик основных сущностей Foundry EN\. \ No newline at end of file +Обработчик основных сущностей из модуля [pf2e](https://github.com/foundryvtt/pf2e 'https://github\.com/foundryvtt/pf2e') для FoundryVTT\. \ No newline at end of file diff --git a/Documentation/Pf2eDataScrapper/SchemaNode.md b/Documentation/Pf2eDataScrapper/SchemaNode.md index a6b2e1e..8ce9c64 100644 --- a/Documentation/Pf2eDataScrapper/SchemaNode.md +++ b/Documentation/Pf2eDataScrapper/SchemaNode.md @@ -11,6 +11,15 @@ internal class SchemaNode Inheritance [System\.Object](https://docs.microsoft.com/en-us/dotnet/api/System.Object 'System\.Object') 🡒 SchemaNode +### Example +Пример использования: + +```csharp +SchemaNode schema = new (); // Создание схемы данных +data = JObject.Parse(reader.ReadToEnd()); // Загрузка данных +schema.AddValue(data); // Добавление данных +``` + | Constructors | | | :--- | :--- | | [SchemaNode\(\)](SchemaNode.SchemaNode.md#Pf2eDataScrapper.SchemaBuilder.SchemaNode.SchemaNode() 'Pf2eDataScrapper\.SchemaBuilder\.SchemaNode\.SchemaNode\(\)') | Конструктор корневого элемента\. | diff --git a/Documentation/Pf2eDataScrapper/SkillProcessor.Process().md b/Documentation/Pf2eDataScrapper/SkillProcessor.Process().md index a0c20cc..844fccd 100644 --- a/Documentation/Pf2eDataScrapper/SkillProcessor.Process().md +++ b/Documentation/Pf2eDataScrapper/SkillProcessor.Process().md @@ -1,5 +1,5 @@ #### [Pf2eDataScrapper](index.md 'index') -### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common').[SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SkillProcessor') +### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation').[SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SkillProcessor') ## SkillProcessor\.Process\(\) Method diff --git a/Documentation/Pf2eDataScrapper/SkillProcessor.Save().md b/Documentation/Pf2eDataScrapper/SkillProcessor.Save().md index 6169c1e..b28d986 100644 --- a/Documentation/Pf2eDataScrapper/SkillProcessor.Save().md +++ b/Documentation/Pf2eDataScrapper/SkillProcessor.Save().md @@ -1,5 +1,5 @@ #### [Pf2eDataScrapper](index.md 'index') -### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common').[SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SkillProcessor') +### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation').[SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SkillProcessor') ## SkillProcessor\.Save\(\) Method diff --git a/Documentation/Pf2eDataScrapper/SkillProcessor.Training.md b/Documentation/Pf2eDataScrapper/SkillProcessor.Training.md index 2196c69..989f2ae 100644 --- a/Documentation/Pf2eDataScrapper/SkillProcessor.Training.md +++ b/Documentation/Pf2eDataScrapper/SkillProcessor.Training.md @@ -1,5 +1,5 @@ #### [Pf2eDataScrapper](index.md 'index') -### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common').[SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SkillProcessor') +### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation').[SkillProcessor](SkillProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SkillProcessor') ## SkillProcessor\.Training Field diff --git a/Documentation/Pf2eDataScrapper/SkillProcessor.md b/Documentation/Pf2eDataScrapper/SkillProcessor.md index 380eb5c..ad138ad 100644 --- a/Documentation/Pf2eDataScrapper/SkillProcessor.md +++ b/Documentation/Pf2eDataScrapper/SkillProcessor.md @@ -1,5 +1,5 @@ #### [Pf2eDataScrapper](index.md 'index') -### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common') +### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation') ## SkillProcessor Class @@ -11,9 +11,9 @@ Inheritance [System\.Object](https://docs.microsoft.com/en-us/dotnet/api/System. | Fields | | | :--- | :--- | -| [Training](SkillProcessor.Training.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SkillProcessor\.Training') | Обучаемый объект\. | +| [Training](SkillProcessor.Training.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SkillProcessor\.Training') | Обучаемый объект\. | | Methods | | | :--- | :--- | -| [Process\(\)](SkillProcessor.Process().md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SkillProcessor\.Process\(\)') | Основной метод обработки данных\. | -| [Save\(\)](SkillProcessor.Save().md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SkillProcessor\.Save\(\)') | Метод для сохранения изменений в базе данных\. | +| [Process\(\)](SkillProcessor.Process().md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SkillProcessor\.Process\(\)') | Основной метод обработки данных\. | +| [Save\(\)](SkillProcessor.Save().md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SkillProcessor\.Save\(\)') | Метод для сохранения изменений в базе данных\. | diff --git a/Documentation/Pf2eDataScrapper/SourceProcessor.Process().md b/Documentation/Pf2eDataScrapper/SourceProcessor.Process().md index c733c8a..017a191 100644 --- a/Documentation/Pf2eDataScrapper/SourceProcessor.Process().md +++ b/Documentation/Pf2eDataScrapper/SourceProcessor.Process().md @@ -1,5 +1,5 @@ #### [Pf2eDataScrapper](index.md 'index') -### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common').[SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SourceProcessor') +### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation').[SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SourceProcessor') ## SourceProcessor\.Process\(\) Method diff --git a/Documentation/Pf2eDataScrapper/SourceProcessor.Save().md b/Documentation/Pf2eDataScrapper/SourceProcessor.Save().md index 8a3ad7a..11d97bf 100644 --- a/Documentation/Pf2eDataScrapper/SourceProcessor.Save().md +++ b/Documentation/Pf2eDataScrapper/SourceProcessor.Save().md @@ -1,5 +1,5 @@ #### [Pf2eDataScrapper](index.md 'index') -### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common').[SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SourceProcessor') +### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation').[SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SourceProcessor') ## SourceProcessor\.Save\(\) Method diff --git a/Documentation/Pf2eDataScrapper/SourceProcessor.Source.md b/Documentation/Pf2eDataScrapper/SourceProcessor.Source.md index 10fa218..4af2d8f 100644 --- a/Documentation/Pf2eDataScrapper/SourceProcessor.Source.md +++ b/Documentation/Pf2eDataScrapper/SourceProcessor.Source.md @@ -1,5 +1,5 @@ #### [Pf2eDataScrapper](index.md 'index') -### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common').[SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SourceProcessor') +### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation').[SourceProcessor](SourceProcessor.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SourceProcessor') ## SourceProcessor\.Source Field diff --git a/Documentation/Pf2eDataScrapper/SourceProcessor.md b/Documentation/Pf2eDataScrapper/SourceProcessor.md index e7c3dd0..e8429b7 100644 --- a/Documentation/Pf2eDataScrapper/SourceProcessor.md +++ b/Documentation/Pf2eDataScrapper/SourceProcessor.md @@ -1,5 +1,5 @@ #### [Pf2eDataScrapper](index.md 'index') -### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common') +### [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation') ## SourceProcessor Class @@ -11,9 +11,9 @@ Inheritance [System\.Object](https://docs.microsoft.com/en-us/dotnet/api/System. | Fields | | | :--- | :--- | -| [Source](SourceProcessor.Source.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SourceProcessor\.Source') | Источник сущности\. | +| [Source](SourceProcessor.Source.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SourceProcessor\.Source') | Источник сущности\. | | Methods | | | :--- | :--- | -| [Process\(\)](SourceProcessor.Process().md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SourceProcessor\.Process\(\)') | Основной метод обработки данных\. | -| [Save\(\)](SourceProcessor.Save().md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common\.SourceProcessor\.Save\(\)') | Метод для сохранения изменений в базе данных\. | +| [Process\(\)](SourceProcessor.Process().md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SourceProcessor\.Process\(\)') | Основной метод обработки данных\. | +| [Save\(\)](SourceProcessor.Save().md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation\.SourceProcessor\.Save\(\)') | Метод для сохранения изменений в базе данных\. | diff --git a/Documentation/Pf2eDataScrapper/index.md b/Documentation/Pf2eDataScrapper/index.md index ef7d3f8..70d5df4 100644 --- a/Documentation/Pf2eDataScrapper/index.md +++ b/Documentation/Pf2eDataScrapper/index.md @@ -4,36 +4,35 @@ | Namespaces | | | :--- | :--- | -| [Pf2eDataScrapper](Pf2eDataScrapper.md 'Pf2eDataScrapper') | | -| [Pf2eDataScrapper\.Helpers](Pf2eDataScrapper.Helpers.md 'Pf2eDataScrapper\.Helpers') | | -| [Pf2eDataScrapper\.Importers](Pf2eDataScrapper.Importers.md 'Pf2eDataScrapper\.Importers') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd](Pf2eDataScrapper.Importers.Implementations.Aonprd.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Compendium](Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Compendium') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Helpers](Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Helpers') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Common') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Subclasses](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Subclasses') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types\.Rules](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types\.Rules') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn](Pf2eDataScrapper.Importers.Implementations.FoundryEn.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Compendium](Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Compendium') | | -| [Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Processors\.Preparation') | | -| [Pf2eDataScrapper\.Importers\.Repositories](Pf2eDataScrapper.Importers.Repositories.md 'Pf2eDataScrapper\.Importers\.Repositories') | | -| [Pf2eDataScrapper\.Processors](Pf2eDataScrapper.Processors.md 'Pf2eDataScrapper\.Processors') | | -| [Pf2eDataScrapper\.Processors\.Attributes](Pf2eDataScrapper.Processors.Attributes.md 'Pf2eDataScrapper\.Processors\.Attributes') | | -| [Pf2eDataScrapper\.Processors\.Collection](Pf2eDataScrapper.Processors.Collection.md 'Pf2eDataScrapper\.Processors\.Collection') | | -| [Pf2eDataScrapper\.Processors\.Definitions](Pf2eDataScrapper.Processors.Definitions.md 'Pf2eDataScrapper\.Processors\.Definitions') | | -| [Pf2eDataScrapper\.Processors\.Factory](Pf2eDataScrapper.Processors.Factory.md 'Pf2eDataScrapper\.Processors\.Factory') | | -| [Pf2eDataScrapper\.SchemaBuilder](Pf2eDataScrapper.SchemaBuilder.md 'Pf2eDataScrapper\.SchemaBuilder') | | -| [Pf2eDataScrapper\.SchemaBuilder\.Types](Pf2eDataScrapper.SchemaBuilder.Types.md 'Pf2eDataScrapper\.SchemaBuilder\.Types') | | -| [Pf2eDataScrapper\.WPF](Pf2eDataScrapper.WPF.md 'Pf2eDataScrapper\.WPF') | | -| [Pf2eDataScrapper\.WPF\.EntityViewer](Pf2eDataScrapper.WPF.EntityViewer.md 'Pf2eDataScrapper\.WPF\.EntityViewer') | | -| [Pf2eDataScrapper\.WPF\.EntityViewer\.Component](Pf2eDataScrapper.WPF.EntityViewer.Component.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.Component') | | -| [Pf2eDataScrapper\.WPF\.EntityViewer\.EntityViewStructure](Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.EntityViewStructure') | | -| [Pf2eDataScrapper\.WPF\.EntityViewer\.Stores](Pf2eDataScrapper.WPF.EntityViewer.Stores.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.Stores') | | -| [Pf2eDataScrapper\.WPF\.EntityViewer\.ViewModel](Pf2eDataScrapper.WPF.EntityViewer.ViewModel.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.ViewModel') | | -| [Pf2eDataScrapper\.WPF\.ProgressForm](Pf2eDataScrapper.WPF.ProgressForm.md 'Pf2eDataScrapper\.WPF\.ProgressForm') | | -| [Pf2eDataScrapper\.WPF\.StartForm](Pf2eDataScrapper.WPF.StartForm.md 'Pf2eDataScrapper\.WPF\.StartForm') | | -| [Pf2eDataScrapper\.WPF\.StartForm\.Components](Pf2eDataScrapper.WPF.StartForm.Components.md 'Pf2eDataScrapper\.WPF\.StartForm\.Components') | | +| [Pf2eDataScrapper](Pf2eDataScrapper.md 'Pf2eDataScrapper') | Модель классов ORM EntityFramework\. | +| [Pf2eDataScrapper\.Helpers](Pf2eDataScrapper.Helpers.md 'Pf2eDataScrapper\.Helpers') | Классы\-помощники, используемые во всех частях проекта\. | +| [Pf2eDataScrapper\.Importers](Pf2eDataScrapper.Importers.md 'Pf2eDataScrapper\.Importers') | Описание импортёров, которые определяют наборы обработчиков данных и элементов, необходимых для их работы\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd](Pf2eDataScrapper.Importers.Implementations.Aonprd.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd') | Импортёр данных из сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Compendium](Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Compendium') | Компендиум данных из сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Helpers](Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Helpers') | Класс\-помощники, которые используются в импортере данных из сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors') | Обработчики данных из сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Preparation') | Обработчики для подготовки данных [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Subclasses](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Subclasses') | Обработчики подклассов с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types') | Обработчики основных сущностей с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types\.Rules](Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules.md 'Pf2eDataScrapper\.Importers\.Implementations\.Aonprd\.Processors\.Types\.Rules') | Обработчики правил с сайта [Archives of Nethys Pathfinder 2e](https://aonprd.com/ 'https://aonprd\.com/')\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn](Pf2eDataScrapper.Importers.Implementations.FoundryEn.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn') | Импортёр данных из модуля [pf2e](https://github.com/foundryvtt/pf2e 'https://github\.com/foundryvtt/pf2e') для FoundryVTT\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Compendium](Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Compendium') | Компендиум данных из модуля [pf2e](https://github.com/foundryvtt/pf2e 'https://github\.com/foundryvtt/pf2e') для FoundryVTT\. | +| [Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Processors\.Preparation](Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation.md 'Pf2eDataScrapper\.Importers\.Implementations\.FoundryEn\.Processors\.Preparation') | Обработчики для подготовки данных из модуля [pf2e](https://github.com/foundryvtt/pf2e 'https://github\.com/foundryvtt/pf2e') для FoundryVTT\. | +| [Pf2eDataScrapper\.Importers\.Repositories](Pf2eDataScrapper.Importers.Repositories.md 'Pf2eDataScrapper\.Importers\.Repositories') | Общее описание репозиториев для хранения данных, которые используются в импортёрах и процессорах\. | +| [Pf2eDataScrapper\.Processors](Pf2eDataScrapper.Processors.md 'Pf2eDataScrapper\.Processors') | Общее описание обработчиков данных\. | +| [Pf2eDataScrapper\.Processors\.Attributes](Pf2eDataScrapper.Processors.Attributes.md 'Pf2eDataScrapper\.Processors\.Attributes') | Классы\-аттрибуты, которые используются для описания обработчиков данных и их свойств\. | +| [Pf2eDataScrapper\.Processors\.Collection](Pf2eDataScrapper.Processors.Collection.md 'Pf2eDataScrapper\.Processors\.Collection') | Описание коллекции классов обработчиков данных\. | +| [Pf2eDataScrapper\.Processors\.Definitions](Pf2eDataScrapper.Processors.Definitions.md 'Pf2eDataScrapper\.Processors\.Definitions') | Общее описание классов и интерфейсов, которые используются в обработчиках данных\. | +| [Pf2eDataScrapper\.Processors\.Factory](Pf2eDataScrapper.Processors.Factory.md 'Pf2eDataScrapper\.Processors\.Factory') | Фабрики для создания экземпляров обработчиков данных на основе их атрибутов и конфигурации\. | +| [Pf2eDataScrapper\.SchemaBuilder](Pf2eDataScrapper.SchemaBuilder.md 'Pf2eDataScrapper\.SchemaBuilder') | Классы обработки вводимых JSON\-данных и создание схемы данных для использовании при формировании обработчиков данных\. | +| [Pf2eDataScrapper\.SchemaBuilder\.Types](Pf2eDataScrapper.SchemaBuilder.Types.md 'Pf2eDataScrapper\.SchemaBuilder\.Types') | Типы полей, которые используются в схемах данных для описания структуры вводимых JSON\-данных\. | +| [Pf2eDataScrapper\.WPF](Pf2eDataScrapper.WPF.md 'Pf2eDataScrapper\.WPF') | WPF\-приложение для запуска обработчиков и просмотра данных Pathfinder 2e\. | +| [Pf2eDataScrapper\.WPF\.EntityViewer](Pf2eDataScrapper.WPF.EntityViewer.md 'Pf2eDataScrapper\.WPF\.EntityViewer') | WPF\-окно для просмотра сущностей Pathfinder 2e, с возможностью фильтрации и поиска по данным\. | +| [Pf2eDataScrapper\.WPF\.EntityViewer\.Component](Pf2eDataScrapper.WPF.EntityViewer.Component.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.Component') | Компоненты, которые используются в WPF\-окне просмотра сущностей Pathfinder 2e\. | +| [Pf2eDataScrapper\.WPF\.EntityViewer\.EntityViewStructure](Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.EntityViewStructure') | Структура представления сущностей Pathfinder 2e в WPF\-приложении, включая классы и интерфейсы для отображения данных\. | +| [Pf2eDataScrapper\.WPF\.EntityViewer\.Stores](Pf2eDataScrapper.WPF.EntityViewer.Stores.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.Stores') | Хранилища данных для WPF\-приложения просмотра сущностей Pathfinder 2e, которые управляют состоянием и данными приложения\. | +| [Pf2eDataScrapper\.WPF\.EntityViewer\.ViewModel](Pf2eDataScrapper.WPF.EntityViewer.ViewModel.md 'Pf2eDataScrapper\.WPF\.EntityViewer\.ViewModel') | ViewModel для WPF\-приложения просмотра сущностей Pathfinder 2e, которые связывают данные и представление\. | +| [Pf2eDataScrapper\.WPF\.ProgressForm](Pf2eDataScrapper.WPF.ProgressForm.md 'Pf2eDataScrapper\.WPF\.ProgressForm') | WPF\-окно для отображения прогресса выполнения операций, таких как импорт данных или обработка сущностей Pathfinder 2e\. | +| [Pf2eDataScrapper\.WPF\.StartForm](Pf2eDataScrapper.WPF.StartForm.md 'Pf2eDataScrapper\.WPF\.StartForm') | WPF\-окно для запуска приложения, которое позволяет пользователю выбрать импортёр и начать обработку данных Pathfinder 2e\. | +| [Pf2eDataScrapper\.WPF\.StartForm\.Components](Pf2eDataScrapper.WPF.StartForm.Components.md 'Pf2eDataScrapper\.WPF\.StartForm\.Components') | Компоненты, которые используются в WPF\-окне запуска приложения для выбора импортёра и настройки параметров обработки данных Pathfinder 2e\. | | [XamlGeneratedNamespace](XamlGeneratedNamespace.md 'XamlGeneratedNamespace') | | diff --git a/Documentation/Pf2eModel/Function.BuildTargetModel(ModelBuilder).md b/Documentation/Pf2eModel/Function.BuildTargetModel(ModelBuilder).md deleted file mode 100644 index f564140..0000000 --- a/Documentation/Pf2eModel/Function.BuildTargetModel(ModelBuilder).md +++ /dev/null @@ -1,20 +0,0 @@ -#### [Pf2eModel](index.md 'index') -### [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations').[Function](Function.md 'Pf2eModel\.Migrations\.Function') - -## Function\.BuildTargetModel\(ModelBuilder\) Method - -Implemented to build the [Microsoft\.EntityFrameworkCore\.Migrations\.Migration\.TargetModel](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration.TargetModel 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration\.TargetModel')\. - -```csharp -protected override void BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder); -``` -#### Parameters - - - -`modelBuilder` [Microsoft\.EntityFrameworkCore\.ModelBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.ModelBuilder 'Microsoft\.EntityFrameworkCore\.ModelBuilder') - -The [Microsoft\.EntityFrameworkCore\.ModelBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.ModelBuilder 'Microsoft\.EntityFrameworkCore\.ModelBuilder') to use to build the model\. - -### Remarks -See [Database migrations](https://aka.ms/efcore-docs-migrations 'https://aka\.ms/efcore\-docs\-migrations') for more information and examples\. \ No newline at end of file diff --git a/Documentation/Pf2eModel/Function.Down(MigrationBuilder).md b/Documentation/Pf2eModel/Function.Down(MigrationBuilder).md deleted file mode 100644 index 23affee..0000000 --- a/Documentation/Pf2eModel/Function.Down(MigrationBuilder).md +++ /dev/null @@ -1,28 +0,0 @@ -#### [Pf2eModel](index.md 'index') -### [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations').[Function](Function.md 'Pf2eModel\.Migrations\.Function') - -## Function\.Down\(MigrationBuilder\) Method - -Builds the operations that will migrate the database 'down'\. - -```csharp -protected override void Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder migrationBuilder); -``` -#### Parameters - - - -`migrationBuilder` [Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder 'Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder') - -The [Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder 'Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder') that will build the operations\. - -### Remarks - -That is, builds the operations that will take the database from the state left in by -this migration so that it returns to the state that it was in before this migration was applied. - -This method must be overridden in each class that inherits from [Microsoft\.EntityFrameworkCore\.Migrations\.Migration](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration') if -both 'up' and 'down' migrations are to be supported. If it is not overridden, then calling it -will throw and it will not be possible to migrate in the 'down' direction. - -See [Database migrations](https://aka.ms/efcore-docs-migrations 'https://aka\.ms/efcore\-docs\-migrations') for more information and examples. \ No newline at end of file diff --git a/Documentation/Pf2eModel/Function.Up(MigrationBuilder).md b/Documentation/Pf2eModel/Function.Up(MigrationBuilder).md deleted file mode 100644 index 5c0d3cd..0000000 --- a/Documentation/Pf2eModel/Function.Up(MigrationBuilder).md +++ /dev/null @@ -1,26 +0,0 @@ -#### [Pf2eModel](index.md 'index') -### [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations').[Function](Function.md 'Pf2eModel\.Migrations\.Function') - -## Function\.Up\(MigrationBuilder\) Method - -Builds the operations that will migrate the database 'up'\. - -```csharp -protected override void Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder migrationBuilder); -``` -#### Parameters - - - -`migrationBuilder` [Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder 'Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder') - -The [Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder 'Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder') that will build the operations\. - -### Remarks - -That is, builds the operations that will take the database from the state left in by the -previous migration so that it is up-to-date with regard to this migration. - -This method must be overridden in each class that inherits from [Microsoft\.EntityFrameworkCore\.Migrations\.Migration](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration'). - -See [Database migrations](https://aka.ms/efcore-docs-migrations 'https://aka\.ms/efcore\-docs\-migrations') for more information and examples. \ No newline at end of file diff --git a/Documentation/Pf2eModel/Function.md b/Documentation/Pf2eModel/Function.md deleted file mode 100644 index 3876926..0000000 --- a/Documentation/Pf2eModel/Function.md +++ /dev/null @@ -1,21 +0,0 @@ -#### [Pf2eModel](index.md 'index') -### [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations') - -## Function Class - -A base class inherited by each EF Core migration\. - -```csharp -public class Function : Microsoft.EntityFrameworkCore.Migrations.Migration -``` - -Inheritance [System\.Object](https://docs.microsoft.com/en-us/dotnet/api/System.Object 'System\.Object') 🡒 [Microsoft\.EntityFrameworkCore\.Migrations\.Migration](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration') 🡒 Function - -### Remarks -See [Database migrations](https://aka.ms/efcore-docs-migrations 'https://aka\.ms/efcore\-docs\-migrations') for more information and examples\. - -| Methods | | -| :--- | :--- | -| [BuildTargetModel\(ModelBuilder\)](Function.BuildTargetModel(ModelBuilder).md 'Pf2eModel\.Migrations\.Function\.BuildTargetModel\(Microsoft\.EntityFrameworkCore\.ModelBuilder\)') | Implemented to build the [Microsoft\.EntityFrameworkCore\.Migrations\.Migration\.TargetModel](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration.TargetModel 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration\.TargetModel')\. | -| [Down\(MigrationBuilder\)](Function.Down(MigrationBuilder).md 'Pf2eModel\.Migrations\.Function\.Down\(Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder\)') | Builds the operations that will migrate the database 'down'\. | -| [Up\(MigrationBuilder\)](Function.Up(MigrationBuilder).md 'Pf2eModel\.Migrations\.Function\.Up\(Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder\)') | Builds the operations that will migrate the database 'up'\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Migrations.md b/Documentation/Pf2eModel/Pf2eModel.Migrations.md deleted file mode 100644 index 5955cdd..0000000 --- a/Documentation/Pf2eModel/Pf2eModel.Migrations.md +++ /dev/null @@ -1,8 +0,0 @@ -#### [Pf2eModel](index.md 'index') - -## Pf2eModel\.Migrations Namespace - -| Classes | | -| :--- | :--- | -| [\_1](_1.md 'Pf2eModel\.Migrations\.\_1') | A base class inherited by each EF Core migration\. | -| [Function](Function.md 'Pf2eModel\.Migrations\.Function') | A base class inherited by each EF Core migration\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Base.Keywords.md b/Documentation/Pf2eModel/Pf2eModel.Model.Base.Keywords.md index 3dfcd45..d94c869 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Base.Keywords.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Base.Keywords.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Base\.Keywords Namespace +Классы, связанные с ключевыми словами, которые могут использоваться в разных типах сущностей\. + | Classes | | | :--- | :--- | | [Keyword](Keyword.md 'Pf2eModel\.Model\.Base\.Keywords\.Keyword') | Ключевое слово\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Base.Source.md b/Documentation/Pf2eModel/Pf2eModel.Model.Base.Source.md index 61837c2..3efaa69 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Base.Source.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Base.Source.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Base\.Source Namespace +Классы, связанные с источниками, которые могут использоваться в разных типах сущностей\. + | Classes | | | :--- | :--- | | [EntitySource](EntitySource.md 'Pf2eModel\.Model\.Base\.Source\.EntitySource') | Связь между сущностью и источником\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Base.Targets.md b/Documentation/Pf2eModel/Pf2eModel.Model.Base.Targets.md index 86d4753..dec68e5 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Base.Targets.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Base.Targets.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Base\.Targets Namespace +Классы, связанные с целями внешних ссылок, которые могут использоваться в разных типах сущностей\. + | Classes | | | :--- | :--- | | [EntityExternal](EntityExternal.md 'Pf2eModel\.Model\.Base\.Targets\.EntityExternal') | Внешняя ссылка сущности\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Base.md b/Documentation/Pf2eModel/Pf2eModel.Model.Base.md index 9071b1c..1fe05fb 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Base.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Base.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Base Namespace +Описание корневой сущности модели: класса [Entity](Entity.md 'Pf2eModel\.Model\.Base\.Entity') и его прямых связей\. + | Classes | | | :--- | :--- | | [Entity](Entity.md 'Pf2eModel\.Model\.Base\.Entity') | Корневая сущность\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Common.I18n.md b/Documentation/Pf2eModel/Pf2eModel.Model.Common.I18n.md index 6e6e854..90742fb 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Common.I18n.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Common.I18n.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Common\.I18n Namespace +Классы, связанные с интернационализацией \(i18n\) модели\. + | Classes | | | :--- | :--- | | [LocaleExtension](LocaleExtension.md 'Pf2eModel\.Model\.Common\.I18n\.LocaleExtension') | Расширения для работы с локализацией\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Common.md b/Documentation/Pf2eModel/Pf2eModel.Model.Common.md index 4cbb0c4..8c5dc99 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Common.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Common.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Common Namespace +Общее классы на которые могут ссылаться разные типа\. + | Classes | | | :--- | :--- | | [Frequency](Frequency.md 'Pf2eModel\.Model\.Common\.Frequency') | Частота использования\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Enum.md b/Documentation/Pf2eModel/Pf2eModel.Model.Enum.md index 0a9d510..45bce0b 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Enum.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Enum.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Enum Namespace +Описание всех перечислений, использующихся в модели\. + | Enums | | | :--- | :--- | | [ActionCategory](ActionCategory.md 'Pf2eModel\.Model\.Enum\.ActionCategory') | Тип действия\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.AncestryRelation.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.AncestryRelation.md index a3c7393..8dc63d0 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.AncestryRelation.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.AncestryRelation.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Structure\.AncestryRelation Namespace +Классы, описывающие связи класса [Ancestry](Ancestry.md 'Pf2eModel\.Model\.Structure\.Ancestry')\. + | Classes | | | :--- | :--- | | [AncestryBoost](AncestryBoost.md 'Pf2eModel\.Model\.Structure\.AncestryRelation\.AncestryBoost') | Элемент набора повышений атрибутов для родословной\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.ArchetypeRelation.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.ArchetypeRelation.md index 2c4c564..910e91e 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.ArchetypeRelation.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.ArchetypeRelation.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Structure\.ArchetypeRelation Namespace +Классы, описывающие связи класса [Archetype](Archetype.md 'Pf2eModel\.Model\.Structure\.Archetype')\. + | Classes | | | :--- | :--- | | [ArchetypeFeats](ArchetypeFeats.md 'Pf2eModel\.Model\.Structure\.ArchetypeRelation\.ArchetypeFeats') | Связь между архетипом и его чертами\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.BackgroundRelation.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.BackgroundRelation.md index 32552f0..6dcfcec 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.BackgroundRelation.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.BackgroundRelation.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Structure\.BackgroundRelation Namespace +Классы, описывающие связи класса [Background](Background.md 'Pf2eModel\.Model\.Structure\.Background')\. + | Classes | | | :--- | :--- | | [BackgroundBoost](BackgroundBoost.md 'Pf2eModel\.Model\.Structure\.BackgroundRelation\.BackgroundBoost') | Элемент набора атрибутов для предыстории\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.ClassRelation.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.ClassRelation.md index c5af544..b22deff 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.ClassRelation.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.ClassRelation.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Structure\.ClassRelation Namespace +Классы, описывающие связи класса [Class](Class.md 'Pf2eModel\.Model\.Structure\.Class')\. + | Classes | | | :--- | :--- | | [ClassKeyAttribute](ClassKeyAttribute.md 'Pf2eModel\.Model\.Structure\.ClassRelation\.ClassKeyAttribute') | Набор ключевых атрибутов класса\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.DeityRelation.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.DeityRelation.md index 7d04f72..9ed2bc7 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.DeityRelation.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.DeityRelation.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Structure\.DeityRelation Namespace +Классы, описывающие связи класса [Deity](Deity.md 'Pf2eModel\.Model\.Structure\.Deity')\. + | Classes | | | :--- | :--- | | [DeityAttribute](DeityAttribute.md 'Pf2eModel\.Model\.Structure\.DeityRelation\.DeityAttribute') | Элемент отношения божества и атрибута\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.FeatRelation.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.FeatRelation.md index 201e980..3af1f3e 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.FeatRelation.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.FeatRelation.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Structure\.FeatRelation Namespace +Классы, описывающие связи класса [Feat](Feat.md 'Pf2eModel\.Model\.Structure\.Feat')\. + | Classes | | | :--- | :--- | | [FeatPrerequisite](FeatPrerequisite.md 'Pf2eModel\.Model\.Structure\.FeatRelation\.FeatPrerequisite') | Предварительное требование для черты\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.RitualRelation.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.RitualRelation.md index acb463a..299dc72 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.RitualRelation.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.RitualRelation.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Structure\.RitualRelation Namespace +Классы, описывающие связи класса [Ritual](Ritual.md 'Pf2eModel\.Model\.Structure\.Ritual')\. + | Classes | | | :--- | :--- | | [RitualArea](RitualArea.md 'Pf2eModel\.Model\.Structure\.RitualRelation\.RitualArea') | Область действия ритуала\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.SpellRelation.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.SpellRelation.md index fa9d42b..8d37ad4 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.SpellRelation.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.SpellRelation.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model\.Structure\.SpellRelation Namespace +Классы, описывающие связи класса [Spell](Spell.md 'Pf2eModel\.Model\.Structure\.Spell')\. + | Classes | | | :--- | :--- | | [SpellArea](SpellArea.md 'Pf2eModel\.Model\.Structure\.SpellRelation\.SpellArea') | Область заклинания\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.md b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.md index cf07286..cb0d2ca 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.Structure.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.Structure.md @@ -2,6 +2,9 @@ ## Pf2eModel\.Model\.Structure Namespace +Основные типы, которые связаны 1\-к\-1 с [Entity](Entity.md 'Pf2eModel\.Model\.Base\.Entity')\. +Для указания этой связи используется интерфейс [IEntity](IEntity.md 'Pf2eModel\.Model\.IEntity')\. + | Classes | | | :--- | :--- | | [Action](Action.md 'Pf2eModel\.Model\.Structure\.Action') | Действие\. | diff --git a/Documentation/Pf2eModel/Pf2eModel.Model.md b/Documentation/Pf2eModel/Pf2eModel.Model.md index 96b65b7..e637d93 100644 --- a/Documentation/Pf2eModel/Pf2eModel.Model.md +++ b/Documentation/Pf2eModel/Pf2eModel.Model.md @@ -2,6 +2,8 @@ ## Pf2eModel\.Model Namespace +Модель классов ORM EntityFramework\. + | Interfaces | | | :--- | :--- | | [IEntity](IEntity.md 'Pf2eModel\.Model\.IEntity') | Интерфейс сущностей структуры\. | diff --git a/Documentation/Pf2eModel/_1.BuildTargetModel(ModelBuilder).md b/Documentation/Pf2eModel/_1.BuildTargetModel(ModelBuilder).md deleted file mode 100644 index 826875d..0000000 --- a/Documentation/Pf2eModel/_1.BuildTargetModel(ModelBuilder).md +++ /dev/null @@ -1,20 +0,0 @@ -#### [Pf2eModel](index.md 'index') -### [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations').[\_1](_1.md 'Pf2eModel\.Migrations\.\_1') - -## \_1\.BuildTargetModel\(ModelBuilder\) Method - -Implemented to build the [Microsoft\.EntityFrameworkCore\.Migrations\.Migration\.TargetModel](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration.TargetModel 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration\.TargetModel')\. - -```csharp -protected override void BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder); -``` -#### Parameters - - - -`modelBuilder` [Microsoft\.EntityFrameworkCore\.ModelBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.ModelBuilder 'Microsoft\.EntityFrameworkCore\.ModelBuilder') - -The [Microsoft\.EntityFrameworkCore\.ModelBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.ModelBuilder 'Microsoft\.EntityFrameworkCore\.ModelBuilder') to use to build the model\. - -### Remarks -See [Database migrations](https://aka.ms/efcore-docs-migrations 'https://aka\.ms/efcore\-docs\-migrations') for more information and examples\. \ No newline at end of file diff --git a/Documentation/Pf2eModel/_1.Down(MigrationBuilder).md b/Documentation/Pf2eModel/_1.Down(MigrationBuilder).md deleted file mode 100644 index a7e4f71..0000000 --- a/Documentation/Pf2eModel/_1.Down(MigrationBuilder).md +++ /dev/null @@ -1,28 +0,0 @@ -#### [Pf2eModel](index.md 'index') -### [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations').[\_1](_1.md 'Pf2eModel\.Migrations\.\_1') - -## \_1\.Down\(MigrationBuilder\) Method - -Builds the operations that will migrate the database 'down'\. - -```csharp -protected override void Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder migrationBuilder); -``` -#### Parameters - - - -`migrationBuilder` [Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder 'Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder') - -The [Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder 'Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder') that will build the operations\. - -### Remarks - -That is, builds the operations that will take the database from the state left in by -this migration so that it returns to the state that it was in before this migration was applied. - -This method must be overridden in each class that inherits from [Microsoft\.EntityFrameworkCore\.Migrations\.Migration](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration') if -both 'up' and 'down' migrations are to be supported. If it is not overridden, then calling it -will throw and it will not be possible to migrate in the 'down' direction. - -See [Database migrations](https://aka.ms/efcore-docs-migrations 'https://aka\.ms/efcore\-docs\-migrations') for more information and examples. \ No newline at end of file diff --git a/Documentation/Pf2eModel/_1.Up(MigrationBuilder).md b/Documentation/Pf2eModel/_1.Up(MigrationBuilder).md deleted file mode 100644 index 05a736f..0000000 --- a/Documentation/Pf2eModel/_1.Up(MigrationBuilder).md +++ /dev/null @@ -1,26 +0,0 @@ -#### [Pf2eModel](index.md 'index') -### [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations').[\_1](_1.md 'Pf2eModel\.Migrations\.\_1') - -## \_1\.Up\(MigrationBuilder\) Method - -Builds the operations that will migrate the database 'up'\. - -```csharp -protected override void Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder migrationBuilder); -``` -#### Parameters - - - -`migrationBuilder` [Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder 'Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder') - -The [Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder 'Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder') that will build the operations\. - -### Remarks - -That is, builds the operations that will take the database from the state left in by the -previous migration so that it is up-to-date with regard to this migration. - -This method must be overridden in each class that inherits from [Microsoft\.EntityFrameworkCore\.Migrations\.Migration](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration'). - -See [Database migrations](https://aka.ms/efcore-docs-migrations 'https://aka\.ms/efcore\-docs\-migrations') for more information and examples. \ No newline at end of file diff --git a/Documentation/Pf2eModel/_1.md b/Documentation/Pf2eModel/_1.md deleted file mode 100644 index 0cb0349..0000000 --- a/Documentation/Pf2eModel/_1.md +++ /dev/null @@ -1,21 +0,0 @@ -#### [Pf2eModel](index.md 'index') -### [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations') - -## \_1 Class - -A base class inherited by each EF Core migration\. - -```csharp -public class _1 : Microsoft.EntityFrameworkCore.Migrations.Migration -``` - -Inheritance [System\.Object](https://docs.microsoft.com/en-us/dotnet/api/System.Object 'System\.Object') 🡒 [Microsoft\.EntityFrameworkCore\.Migrations\.Migration](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration') 🡒 _1 - -### Remarks -See [Database migrations](https://aka.ms/efcore-docs-migrations 'https://aka\.ms/efcore\-docs\-migrations') for more information and examples\. - -| Methods | | -| :--- | :--- | -| [BuildTargetModel\(ModelBuilder\)](_1.BuildTargetModel(ModelBuilder).md 'Pf2eModel\.Migrations\.\_1\.BuildTargetModel\(Microsoft\.EntityFrameworkCore\.ModelBuilder\)') | Implemented to build the [Microsoft\.EntityFrameworkCore\.Migrations\.Migration\.TargetModel](https://docs.microsoft.com/en-us/dotnet/api/Microsoft.EntityFrameworkCore.Migrations.Migration.TargetModel 'Microsoft\.EntityFrameworkCore\.Migrations\.Migration\.TargetModel')\. | -| [Down\(MigrationBuilder\)](_1.Down(MigrationBuilder).md 'Pf2eModel\.Migrations\.\_1\.Down\(Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder\)') | Builds the operations that will migrate the database 'down'\. | -| [Up\(MigrationBuilder\)](_1.Up(MigrationBuilder).md 'Pf2eModel\.Migrations\.\_1\.Up\(Microsoft\.EntityFrameworkCore\.Migrations\.MigrationBuilder\)') | Builds the operations that will migrate the database 'up'\. | diff --git a/Documentation/Pf2eModel/index.md b/Documentation/Pf2eModel/index.md index 577925d..749c3be 100644 --- a/Documentation/Pf2eModel/index.md +++ b/Documentation/Pf2eModel/index.md @@ -5,21 +5,20 @@ | Namespaces | | | :--- | :--- | | [Pf2eModel](Pf2eModel.md 'Pf2eModel') | Набор классов модели ORM EntityFramework\. | -| [Pf2eModel\.Migrations](Pf2eModel.Migrations.md 'Pf2eModel\.Migrations') | | -| [Pf2eModel\.Model](Pf2eModel.Model.md 'Pf2eModel\.Model') | | -| [Pf2eModel\.Model\.Base](Pf2eModel.Model.Base.md 'Pf2eModel\.Model\.Base') | | -| [Pf2eModel\.Model\.Base\.Keywords](Pf2eModel.Model.Base.Keywords.md 'Pf2eModel\.Model\.Base\.Keywords') | | -| [Pf2eModel\.Model\.Base\.Source](Pf2eModel.Model.Base.Source.md 'Pf2eModel\.Model\.Base\.Source') | | -| [Pf2eModel\.Model\.Base\.Targets](Pf2eModel.Model.Base.Targets.md 'Pf2eModel\.Model\.Base\.Targets') | | -| [Pf2eModel\.Model\.Common](Pf2eModel.Model.Common.md 'Pf2eModel\.Model\.Common') | | -| [Pf2eModel\.Model\.Common\.I18n](Pf2eModel.Model.Common.I18n.md 'Pf2eModel\.Model\.Common\.I18n') | | -| [Pf2eModel\.Model\.Enum](Pf2eModel.Model.Enum.md 'Pf2eModel\.Model\.Enum') | | -| [Pf2eModel\.Model\.Structure](Pf2eModel.Model.Structure.md 'Pf2eModel\.Model\.Structure') | | -| [Pf2eModel\.Model\.Structure\.AncestryRelation](Pf2eModel.Model.Structure.AncestryRelation.md 'Pf2eModel\.Model\.Structure\.AncestryRelation') | | -| [Pf2eModel\.Model\.Structure\.ArchetypeRelation](Pf2eModel.Model.Structure.ArchetypeRelation.md 'Pf2eModel\.Model\.Structure\.ArchetypeRelation') | | -| [Pf2eModel\.Model\.Structure\.BackgroundRelation](Pf2eModel.Model.Structure.BackgroundRelation.md 'Pf2eModel\.Model\.Structure\.BackgroundRelation') | | -| [Pf2eModel\.Model\.Structure\.ClassRelation](Pf2eModel.Model.Structure.ClassRelation.md 'Pf2eModel\.Model\.Structure\.ClassRelation') | | -| [Pf2eModel\.Model\.Structure\.DeityRelation](Pf2eModel.Model.Structure.DeityRelation.md 'Pf2eModel\.Model\.Structure\.DeityRelation') | | -| [Pf2eModel\.Model\.Structure\.FeatRelation](Pf2eModel.Model.Structure.FeatRelation.md 'Pf2eModel\.Model\.Structure\.FeatRelation') | | -| [Pf2eModel\.Model\.Structure\.RitualRelation](Pf2eModel.Model.Structure.RitualRelation.md 'Pf2eModel\.Model\.Structure\.RitualRelation') | | -| [Pf2eModel\.Model\.Structure\.SpellRelation](Pf2eModel.Model.Structure.SpellRelation.md 'Pf2eModel\.Model\.Structure\.SpellRelation') | | +| [Pf2eModel\.Model](Pf2eModel.Model.md 'Pf2eModel\.Model') | Модель классов ORM EntityFramework\. | +| [Pf2eModel\.Model\.Base](Pf2eModel.Model.Base.md 'Pf2eModel\.Model\.Base') | Описание корневой сущности модели: класса [Entity](Entity.md 'Pf2eModel\.Model\.Base\.Entity') и его прямых связей\. | +| [Pf2eModel\.Model\.Base\.Keywords](Pf2eModel.Model.Base.Keywords.md 'Pf2eModel\.Model\.Base\.Keywords') | Классы, связанные с ключевыми словами, которые могут использоваться в разных типах сущностей\. | +| [Pf2eModel\.Model\.Base\.Source](Pf2eModel.Model.Base.Source.md 'Pf2eModel\.Model\.Base\.Source') | Классы, связанные с источниками, которые могут использоваться в разных типах сущностей\. | +| [Pf2eModel\.Model\.Base\.Targets](Pf2eModel.Model.Base.Targets.md 'Pf2eModel\.Model\.Base\.Targets') | Классы, связанные с целями внешних ссылок, которые могут использоваться в разных типах сущностей\. | +| [Pf2eModel\.Model\.Common](Pf2eModel.Model.Common.md 'Pf2eModel\.Model\.Common') | Общее классы на которые могут ссылаться разные типа\. | +| [Pf2eModel\.Model\.Common\.I18n](Pf2eModel.Model.Common.I18n.md 'Pf2eModel\.Model\.Common\.I18n') | Классы, связанные с интернационализацией \(i18n\) модели\. | +| [Pf2eModel\.Model\.Enum](Pf2eModel.Model.Enum.md 'Pf2eModel\.Model\.Enum') | Описание всех перечислений, использующихся в модели\. | +| [Pf2eModel\.Model\.Structure](Pf2eModel.Model.Structure.md 'Pf2eModel\.Model\.Structure') | Основные типы, которые связаны 1\-к\-1 с [Entity](Entity.md 'Pf2eModel\.Model\.Base\.Entity')\. Для указания этой связи используется интерфейс [IEntity](IEntity.md 'Pf2eModel\.Model\.IEntity')\. | +| [Pf2eModel\.Model\.Structure\.AncestryRelation](Pf2eModel.Model.Structure.AncestryRelation.md 'Pf2eModel\.Model\.Structure\.AncestryRelation') | Классы, описывающие связи класса [Ancestry](Ancestry.md 'Pf2eModel\.Model\.Structure\.Ancestry')\. | +| [Pf2eModel\.Model\.Structure\.ArchetypeRelation](Pf2eModel.Model.Structure.ArchetypeRelation.md 'Pf2eModel\.Model\.Structure\.ArchetypeRelation') | Классы, описывающие связи класса [Archetype](Archetype.md 'Pf2eModel\.Model\.Structure\.Archetype')\. | +| [Pf2eModel\.Model\.Structure\.BackgroundRelation](Pf2eModel.Model.Structure.BackgroundRelation.md 'Pf2eModel\.Model\.Structure\.BackgroundRelation') | Классы, описывающие связи класса [Background](Background.md 'Pf2eModel\.Model\.Structure\.Background')\. | +| [Pf2eModel\.Model\.Structure\.ClassRelation](Pf2eModel.Model.Structure.ClassRelation.md 'Pf2eModel\.Model\.Structure\.ClassRelation') | Классы, описывающие связи класса [Class](Class.md 'Pf2eModel\.Model\.Structure\.Class')\. | +| [Pf2eModel\.Model\.Structure\.DeityRelation](Pf2eModel.Model.Structure.DeityRelation.md 'Pf2eModel\.Model\.Structure\.DeityRelation') | Классы, описывающие связи класса [Deity](Deity.md 'Pf2eModel\.Model\.Structure\.Deity')\. | +| [Pf2eModel\.Model\.Structure\.FeatRelation](Pf2eModel.Model.Structure.FeatRelation.md 'Pf2eModel\.Model\.Structure\.FeatRelation') | Классы, описывающие связи класса [Feat](Feat.md 'Pf2eModel\.Model\.Structure\.Feat')\. | +| [Pf2eModel\.Model\.Structure\.RitualRelation](Pf2eModel.Model.Structure.RitualRelation.md 'Pf2eModel\.Model\.Structure\.RitualRelation') | Классы, описывающие связи класса [Ritual](Ritual.md 'Pf2eModel\.Model\.Structure\.Ritual')\. | +| [Pf2eModel\.Model\.Structure\.SpellRelation](Pf2eModel.Model.Structure.SpellRelation.md 'Pf2eModel\.Model\.Structure\.SpellRelation') | Классы, описывающие связи класса [Spell](Spell.md 'Pf2eModel\.Model\.Structure\.Spell')\. | diff --git a/Documentation/index.md b/Documentation/index.md new file mode 100644 index 0000000..a1e868e --- /dev/null +++ b/Documentation/index.md @@ -0,0 +1,2 @@ +- [`Pf2eModel`](Pf2eModel/index.md) - общая ORM-структура модели. +- [`Pf2eDataScrapper`](Pf2eDataScrapper/index.md) - загрузчик и обработчик данных. \ No newline at end of file diff --git a/Pf2eDataScrapper/Documentation.cs b/Pf2eDataScrapper/Documentation.cs new file mode 100644 index 0000000..9c48fc6 --- /dev/null +++ b/Pf2eDataScrapper/Documentation.cs @@ -0,0 +1,247 @@ +namespace Pf2eDataScrapper +{ + /// + /// Модель классов ORM EntityFramework. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Helpers +{ + /// + /// Классы-помощники, используемые во всех частях проекта. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers +{ + /// + /// Описание импортёров, которые определяют наборы обработчиков данных и элементов, необходимых для их работы. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd +{ + /// + /// Импортёр данных из сайта Archives of Nethys Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium +{ + /// + /// Компендиум данных из сайта Archives of Nethys Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers +{ + /// + /// Класс-помощники, которые используются в импортере данных из сайта Archives of Nethys Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors +{ + /// + /// Обработчики данных из сайта Archives of Nethys Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation +{ + /// + /// Обработчики для подготовки данных Archives of Nethys Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses +{ + /// + /// Обработчики подклассов с сайта Archives of Nethys Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types +{ + /// + /// Обработчики основных сущностей с сайта Archives of Nethys Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules +{ + /// + /// Обработчики правил с сайта Archives of Nethys Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn +{ + /// + /// Импортёр данных из модуля pf2e для FoundryVTT. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium +{ + /// + /// Компендиум данных из модуля pf2e для FoundryVTT. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation +{ + /// + /// Обработчики для подготовки данных из модуля pf2e для FoundryVTT. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Importers.Repositories +{ + /// + /// Общее описание репозиториев для хранения данных, которые используются в импортёрах и процессорах. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Processors +{ + /// + /// Общее описание обработчиков данных. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Processors.Attributes +{ + /// + /// Классы-аттрибуты, которые используются для описания обработчиков данных и их свойств. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Processors.Collection +{ + /// + /// Описание коллекции классов обработчиков данных. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Processors.Definitions +{ + /// + /// Общее описание классов и интерфейсов, которые используются в обработчиках данных. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.Processors.Factory +{ + /// + /// Фабрики для создания экземпляров обработчиков данных на основе их атрибутов и конфигурации. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.SchemaBuilder +{ + /// + /// Классы обработки вводимых JSON-данных и создание схемы данных для использовании при формировании обработчиков данных. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.SchemaBuilder.Types +{ + /// + /// Типы полей, которые используются в схемах данных для описания структуры вводимых JSON-данных. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF +{ + /// + /// WPF-приложение для запуска обработчиков и просмотра данных Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF.EntityViewer +{ + /// + /// WPF-окно для просмотра сущностей Pathfinder 2e, с возможностью фильтрации и поиска по данным. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF.EntityViewer.Component +{ + /// + /// Компоненты, которые используются в WPF-окне просмотра сущностей Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure +{ + /// + /// Структура представления сущностей Pathfinder 2e в WPF-приложении, включая классы и интерфейсы для отображения данных. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF.EntityViewer.Stores +{ + /// + /// Хранилища данных для WPF-приложения просмотра сущностей Pathfinder 2e, которые управляют состоянием и данными приложения. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF.EntityViewer.ViewModel +{ + /// + /// ViewModel для WPF-приложения просмотра сущностей Pathfinder 2e, которые связывают данные и представление. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF.ProgressForm +{ + /// + /// WPF-окно для отображения прогресса выполнения операций, таких как импорт данных или обработка сущностей Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF.StartForm +{ + /// + /// WPF-окно для запуска приложения, которое позволяет пользователю выбрать импортёр и начать обработку данных Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eDataScrapper.WPF.StartForm.Components +{ + /// + /// Компоненты, которые используются в WPF-окне запуска приложения для выбора импортёра и настройки параметров обработки данных Pathfinder 2e. + /// + internal static class NamespaceDoc { } +} diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/ExternalProcessor.cs similarity index 95% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/ExternalProcessor.cs index de49b73..52fe39d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/ExternalProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/ExternalProcessor.cs @@ -1,11 +1,10 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers; -using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.Processors.Attributes; using Pf2eDataScrapper.Processors.Definitions; using Pf2eModel; using Pf2eModel.Model.Base.Targets; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation; [Processor( "aonprd:external", diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SkillProcessor.cs similarity index 98% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SkillProcessor.cs index 768f45e..8bf81d7 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SkillProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SkillProcessor.cs @@ -6,7 +6,7 @@ using Pf2eModel.Model.Common; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation; [ProcessorCollection( "aonprd:skill", diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SourceProcessor.cs similarity index 98% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SourceProcessor.cs index 99c3b78..80b99a6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Common/SourceProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Processors/Preparation/SourceProcessor.cs @@ -7,7 +7,7 @@ using Pf2eModel; using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Enum; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common; +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation; [ProcessorCollection( "aonprd:source", diff --git a/Pf2eDataScrapper/Processors/Attributes/ProcessorGroupEnum.cs b/Pf2eDataScrapper/Processors/Attributes/ProcessorGroupEnum.cs index 427c040..41c56b2 100644 --- a/Pf2eDataScrapper/Processors/Attributes/ProcessorGroupEnum.cs +++ b/Pf2eDataScrapper/Processors/Attributes/ProcessorGroupEnum.cs @@ -15,37 +15,37 @@ public enum ProcessorGroupEnum General, /// - /// Обработчик для подготовки данных Архивов Нефиса. + /// Обработчик для подготовки данных с сайта Archives of Nethys Pathfinder 2e. /// [Description("Архивы Нефиса: Подготовка")] AonprdPreparation, /// - /// Обработчик основных сущностей Архивов Нефиса. + /// Обработчик основных сущностей с сайта Archives of Nethys Pathfinder 2e. /// [Description("Архивы Нефиса: Основные сущности")] AonprdEntities, /// - /// Обработчик подклассов Архивов Нефиса. + /// Обработчик подклассов с сайта Archives of Nethys Pathfinder 2e. /// [Description("Архивы Нефиса: Подклассы")] AonprdSubclasses, /// - /// Обработчик для завершения обработки данных Архивов Нефиса. + /// Обработчик для завершения обработки данных с сайта Archives of Nethys Pathfinder 2e. /// [Description("Архивы Нефиса: Завершение")] AonprdFinalization, /// - /// Обработчик для подготовки данных Foundry EN. + /// Обработчик для подготовки данных из модуля pf2e для FoundryVTT. /// [Description("Foundry EN: Подготовка")] FoundryEnPreparation, /// - /// Обработчик основных сущностей Foundry EN. + /// Обработчик основных сущностей из модуля pf2e для FoundryVTT. /// [Description("Foundry EN: Основные сущности")] FoundryEnEntities, diff --git a/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs b/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs index 544d28d..27cde43 100644 --- a/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs +++ b/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs @@ -8,6 +8,14 @@ namespace Pf2eDataScrapper.SchemaBuilder; /// /// Элемент схемы данных. /// +/// +/// Пример использования: +/// +/// SchemaNode schema = new (); // Создание схемы данных +/// data = JObject.Parse(reader.ReadToEnd()); // Загрузка данных +/// schema.AddValue(data); // Добавление данных +/// +/// internal class SchemaNode { /// diff --git a/Pf2eModel/Documentation.cs b/Pf2eModel/Documentation.cs new file mode 100644 index 0000000..6736583 --- /dev/null +++ b/Pf2eModel/Documentation.cs @@ -0,0 +1,153 @@ +namespace Pf2eModel +{ + /// + /// Набор классов модели ORM EntityFramework. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model +{ + /// + /// Модель классов ORM EntityFramework. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Base +{ + /// + /// Описание корневой сущности модели: класса и его прямых связей. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Base.Keywords +{ + /// + /// Классы, связанные с ключевыми словами, которые могут использоваться в разных типах сущностей. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Base.Source +{ + /// + /// Классы, связанные с источниками, которые могут использоваться в разных типах сущностей. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Base.Targets +{ + /// + /// Классы, связанные с целями внешних ссылок, которые могут использоваться в разных типах сущностей. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Common +{ + /// + /// Общее классы на которые могут ссылаться разные типа. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Common.I18n +{ + /// + /// Классы, связанные с интернационализацией (i18n) модели. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Enum +{ + /// + /// Описание всех перечислений, использующихся в модели. + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure +{ + /// + /// Основные типы, которые связаны 1-к-1 с . + /// Для указания этой связи используется интерфейс . + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Migrations +{ + /// + /// Миграции БД, генерируются в автомотическом режиме. + /// + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure.AncestryRelation +{ + /// + /// Классы, описывающие связи класса . + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure.ArchetypeRelation +{ + /// + /// Классы, описывающие связи класса . + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure.BackgroundRelation +{ + /// + /// Классы, описывающие связи класса . + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure.ClassRelation +{ + /// + /// Классы, описывающие связи класса . + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure.DeityRelation +{ + /// + /// Классы, описывающие связи класса . + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure.FeatRelation +{ + /// + /// Классы, описывающие связи класса . + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure.RitualRelation +{ + /// + /// Классы, описывающие связи класса . + /// + internal static class NamespaceDoc { } +} + +namespace Pf2eModel.Model.Structure.SpellRelation +{ + /// + /// Классы, описывающие связи класса . + /// + internal static class NamespaceDoc { } +} diff --git a/Pf2eModel/Pf2eDbContext.cs b/Pf2eModel/Pf2eDbContext.cs index c7c5c52..4d314e8 100644 --- a/Pf2eModel/Pf2eDbContext.cs +++ b/Pf2eModel/Pf2eDbContext.cs @@ -181,8 +181,3 @@ public partial class Pf2eDbContext : DbContext ); } } - -/// -/// Набор классов модели ORM EntityFramework. -/// -internal static class NamespaceDoc { } diff --git a/README.md b/README.md index 550b352..8ba9464 100644 --- a/README.md +++ b/README.md @@ -13,57 +13,14 @@ - Перейдите а папку `Pf2eModel` (`cd ../Pf2eModel`) и запустите `dotnet ef database update` для создания структуры БД в командной строке. Если необходимо обновить структуру после изменения модели - используйте скрипт `RebuildDatabase.bat`. - Запустите проект `Pf2eDataScrapper` для парсинга информации. -## Роли проектов +## Роли и документация проектов -- `Pf2eModel` - общая ORM-структура модели. -- `Pf2eDataScrapper` - загрузчик и обработчик данных. +- [`Pf2eModel`](Documentation/Pf2eModel/index.md) - общая ORM-структура модели. +- [`Pf2eDataScrapper`](Documentation/Pf2eDataScrapper/index.md) - загрузчик и обработчик данных. -## Общая структура +По ссылкам можно увидеть полную документацию классов. -### `Pf2eModel` - -Набор классов модели ORM EntityFramework. - -Класс-контекст `Pf2eDbContext` разделён на 2 файла: -- в `Pf2eDbContext.cs` находится набор таблиц и конфигурация БД, -- в `Pf2eDbContextModel.cs` находится Fluent API описание связей в модели. - -Для удобства модель разбита на несколько папок-пространст имён: -- `Base` - здесь описан класс `Entity`, который является корнем всех связей и описаний. В этой таблице находятся общие характеристики объектов: название, описание, тип, а так же ссылки на общие элементы: указание источников и трейтов - всё это в пространстве имён -- `Common` содержит общее классы на которые могут ссылаться разные типа. -- `Enum` содержит описание всех перечислений, использующихся в модели. -- `Structure` содержит все основные типы, которые связаны 1-к-1 с `Entity` (для указания этого используется интерфейс `IEntity`). - -#### `Migrations` - -Миграции БД, генерируются в автомотическом режиме. - -### `Pf2eDataScrapper` - -Основной проект обработки и отображения данных. - -Структура пространст имён: - -- `Helpers` - классы-помощники, которые используются в - -#### `SchemaBuilder` - -Классы обработки вводимых JSON-данных и создание схемы данных для использовании при формировании обработчиков данных. - -Использование: -```csharp -SchemaNode schema = new(); // Создание схемы данных - -data = JObject.Parse(reader.ReadToEnd()); // Загрузка данных - -schema.AddValue(data); // Добавление данных -``` - -#### `Importers` - -Классы загрузки и обработки данных. - -# Прогресс +# Прогресс создания обработчиков ## Aonprd (Архивы Нефиса) From 75eba984b5cfc51f1ed682870596eb2637e82463 Mon Sep 17 00:00:00 2001 From: Ilya Kozhemyakin Date: Sun, 1 Jun 2025 21:38:33 +0400 Subject: [PATCH 32/33] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D1=81=20ERD-=D0=B4=D0=B8=D0=B0=D0=B3=D1=80=D0=B0=D0=BC=D0=BE?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + dbml/schema.pgsql | 637 -------- erd/pf2e.pgerd | 1 + erd/pf2e.pgerd.png | Bin 0 -> 679341 bytes erd/schema.sql | 3598 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 3601 insertions(+), 637 deletions(-) delete mode 100644 dbml/schema.pgsql create mode 100644 erd/pf2e.pgerd create mode 100644 erd/pf2e.pgerd.png create mode 100644 erd/schema.sql diff --git a/.gitignore b/.gitignore index d8c840f..f0924ae 100644 --- a/.gitignore +++ b/.gitignore @@ -367,3 +367,5 @@ FodyWeavers.xsd .env .aider* + +erd/.dump.sh \ No newline at end of file diff --git a/dbml/schema.pgsql b/dbml/schema.pgsql deleted file mode 100644 index 41a6322..0000000 --- a/dbml/schema.pgsql +++ /dev/null @@ -1,637 +0,0 @@ --- Common Enums - -DROP TYPE IF EXISTS "enum_size"; -CREATE TYPE "enum_size" AS ENUM ( - 'med', - 'lanuages', - 'system', - 'tiny' -); - - --- Entity - -CREATE SEQUENCE "entity_sequence"; - -DROP TYPE IF EXISTS "entity_type"; -CREATE TYPE "entity_type" AS ENUM ( - 'action', - 'ancestry', - 'background', - 'backpack', - 'campaign_feature', - 'class', - 'condition', - 'deity', - 'feat', - 'heritage', - 'spell' -); - -DROP TYPE IF EXISTS "entity_rarity"; -CREATE TYPE "entity_rarity" AS ENUM ( - 'common', - 'uncommon', - 'rare', - 'unique' -); - -CREATE TABLE "entity" ( - "id" int DEFAULT nextval('entity_sequence') PRIMARY KEY, - "name" varchar(150) NOT NULL, - "img" varchar(150) NOT NULL, - "description" text NOT NULL, - "description_gm" text, - "type" entity_type NOT NULL, - "slug" varchar(150) NOT NULL, - "rarity" entity_rarity NOT NULL DEFAULT 'common', - "is_translated" bool DEFAULT false, - "is_checked" bool DEFAULT false -); - - - --- Alias - -DROP TYPE IF EXISTS "alias_type"; -CREATE TYPE "alias_type" AS ENUM ( - 'old', - 'english', - 'hobby' -); - -CREATE TABLE "entity_aliases" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "entity_id" int NOT NULL, - "alias" varchar NOT NULL, - "type" alias_type NOT NULL -); -ALTER TABLE "entity_aliases" ADD FOREIGN KEY ("entity_id") REFERENCES "entity" ("id"); - - --- Keywords - -CREATE TABLE "keyword" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "slug" varchar(50) NOT NULL -); - -CREATE TABLE "entity_keywords" ( - "entity_id" int NOT NULL, - "keyword_id" int NOT NULL -); -ALTER TABLE "entity_keywords" ADD FOREIGN KEY ("entity_id") REFERENCES "entity" ("id"); -ALTER TABLE "entity_keywords" ADD FOREIGN KEY ("keyword_id") REFERENCES "keyword" ("id"); - -CREATE TABLE "keyword_aliases" ( - "keyword_id" int, - "type" alias_type, - "value" varchar NOT NULL, - PRIMARY KEY ("keyword_id", "type") -); -ALTER TABLE "keyword_aliases" ADD FOREIGN KEY ("keyword_id") REFERENCES "keyword" ("id"); - - - --- Source - -CREATE TABLE "source" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "name" varchar NOT NULL, - "url" varchar NOT NULL -); - -CREATE TABLE "entity_sources" ( - "entity_id" int NOT NULL, - "source_id" int NOT NULL, - "suffix" varchar NOT NULL DEFAULT '', - "is_remaster" bool NOT NULL -); -ALTER TABLE "entity_sources" ADD FOREIGN KEY ("entity_id") REFERENCES "entity" ("id"); -ALTER TABLE "entity_sources" ADD FOREIGN KEY ("source_id") REFERENCES "source" ("id"); - - --- External - -CREATE TABLE "external_target" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "name" varchar NOT NULL, - "icon" varchar NOT NULL -); - -CREATE TABLE "entity_external" ( - "entity_id" int, - "target_id" int, - "path" varchar NOT NULL, - "suffix" varchar NOT NULL DEFAULT '', - "md5" varchar NOT NULL, - PRIMARY KEY ("entity_id", "target_id") -); -ALTER TABLE "entity_external" ADD FOREIGN KEY ("entity_id") REFERENCES "entity" ("id"); -ALTER TABLE "entity_external" ADD FOREIGN KEY ("target_id") REFERENCES "external_target" ("id"); - - --- Trait - -DROP TYPE IF EXISTS "trait_type"; -CREATE TYPE "trait_type" AS ENUM ( - 'regular', - 'tradition' -); - -CREATE TABLE "trait" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "name" varchar NOT NULL, - "slug" varchar NOT NULL, - "description" text NOT NULL, - "type" trait_type NOT NULL -); - -CREATE TABLE "entity_traits" ( - "entity_id" int, - "trait_id" int, - PRIMARY KEY ("entity_id", "trait_id") -); -ALTER TABLE "entity_traits" ADD FOREIGN KEY ("entity_id") REFERENCES "entity" ("id"); -ALTER TABLE "entity_traits" ADD FOREIGN KEY ("trait_id") REFERENCES "trait" ("id"); - - --- Frequency - -CREATE TABLE "frequency" ( - "id" int PRIMARY KEY, - "max" int NOT NULL, - "per" int NOT NULL, - "value" int -); -ALTER TABLE "frequency" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - - --- Language - -CREATE TABLE "language" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "name" varchar NOT NULL, - "rarity" entity_rarity NOT NULL -); - - --- Training - -CREATE TABLE "trainings" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "key" varchar NOT NULL -); - - ------------------------------------------------------------------------------------------- - --- Spell - -DROP TYPE IF EXISTS "spell_area_type"; -CREATE TYPE "spell_area_type" AS ENUM ( - 'burst', - 'emanation', - 'cone', - 'line', - 'square', - 'cube', - 'cylinder' -); -DROP TYPE IF EXISTS "spell_damage_category"; -CREATE TYPE "spell_damage_category" AS ENUM ( - 'persistent', - 'splash' -); -DROP TYPE IF EXISTS "spell_damage_kinds"; -CREATE TYPE "spell_damage_kinds" AS ENUM ( - 'damage', - 'healing', - 'any' -); - -CREATE TABLE "spell" ( - "id" int PRIMARY KEY, - "level" int NOT NULL, - "cost" int NOT NULL, - "counteraction" bool NOT NULL, - "sustained" bool NOT NULL, - "duration" varchar NOT NULL, - "range" varchar NOT NULL, - "requirements" varchar NOT NULL, - "target" varchar NOT NULL, - "time" varchar NOT NULL, - "can_heightening" bool NOT NULL -); -ALTER TABLE "spell" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - -CREATE TABLE "spell_area" ( - "spell_id" int PRIMARY KEY, - "details" varchar, - "type" spell_area_type NOT NULL, - "value" int NOT NULL -); -ALTER TABLE "spell_area" ADD FOREIGN KEY ("spell_id") REFERENCES "spell" ("id"); - -CREATE TABLE "spell_damage" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "spell_id" int NOT NULL, - "key" varchar NOT NULL, - "apply_mod" bool, - "category" spell_damage_category, - "formula" varchar NOT NULL, - "kinds" spell_damage_kinds NOT NULL, - "materials" varchar, - "type" varchar NOT NULL -); -ALTER TABLE "spell_damage" ADD FOREIGN KEY ("spell_id") REFERENCES "spell" ("id"); - -CREATE TABLE "spell_ritual" ( - "spell_id" int PRIMARY KEY, - "primary_check" varchar NOT NULL, - "secondary_casters" int NOT NULL, - "secondary_checks" varchar NOT NULL -); -ALTER TABLE "spell_ritual" ADD FOREIGN KEY ("spell_id") REFERENCES "spell" ("id"); - -CREATE TABLE "spell_defense" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "spell_id" int NOT NULL, - "is_passive" bool NOT NULL, - "is_basic" bool, - "statistic" varchar NOT NULL -); -ALTER TABLE "spell_defense" ADD FOREIGN KEY ("spell_id") REFERENCES "spell" ("id"); - - --- Action - -DROP TYPE IF EXISTS "action_type"; -CREATE TYPE "action_type" AS ENUM ( - 'action', - 'free', - 'reaction', - 'passive' -); -DROP TYPE IF EXISTS "action_category"; -CREATE TYPE "action_category" AS ENUM ( - 'offensive', - 'defensive', - 'interaction', - 'familiar' -); -DROP TYPE IF EXISTS "ancestry_vision"; -CREATE TYPE "ancestry_vision" AS ENUM ( - 'normal', - 'low_light_vision', - 'darkvision' -); - -CREATE TABLE "action" ( - "id" int PRIMARY KEY, - "action_type" action_type NOT NULL, - "actions" int, - "category" action_category, - "death_note" bool, - "extra" text -); -ALTER TABLE "action" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - - --- Ancestry - -CREATE TABLE "ancestry" ( - "id" int PRIMARY KEY, - "hp" int NOT NULL, - "reach" int NOT NULL, - "size" enum_size NOT NULL, - "speed" int NOT NULL, - "vision" ancestry_vision NOT NULL -); -ALTER TABLE "ancestry" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - -CREATE TABLE "ancestry_languages" ( - "ancestry_id" int, - "language_id" int, - "is_additional" bool NOT NULL, - PRIMARY KEY ("ancestry_id", "language_id") -); -ALTER TABLE "ancestry_languages" ADD FOREIGN KEY ("ancestry_id") REFERENCES "ancestry" ("id"); -ALTER TABLE "ancestry_languages" ADD FOREIGN KEY ("language_id") REFERENCES "language" ("id"); - -CREATE TABLE "ancestry_boosts" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "ancestry_id" int NOT NULL, - "boost" bool NOT NULL DEFAULT true, - "value" varchar(3) -); -ALTER TABLE "ancestry_boosts" ADD FOREIGN KEY ("ancestry_id") REFERENCES "ancestry" ("id"); - -CREATE TABLE "ancestry_items" ( - "ancestry_id" int, - "item_id" int, - "level" int NOT NULL, - PRIMARY KEY ("ancestry_id", "item_id") -); -ALTER TABLE "ancestry_items" ADD FOREIGN KEY ("ancestry_id") REFERENCES "ancestry" ("id"); -ALTER TABLE "ancestry_items" ADD FOREIGN KEY ("item_id") REFERENCES "entity" ("id"); - - --- Background - -CREATE TABLE "background" ( - "id" int PRIMARY KEY -); -ALTER TABLE "background" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - -CREATE TABLE "background_items" ( - "background_id" int, - "entity_id" int, - "level" int, - PRIMARY KEY ("background_id", "entity_id") -); -ALTER TABLE "background_items" ADD FOREIGN KEY ("background_id") REFERENCES "background" ("id"); -ALTER TABLE "background_items" ADD FOREIGN KEY ("entity_id") REFERENCES "entity" ("id"); - -CREATE TABLE "background_skills" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "background_id" int NOT NULL, - "name" varchar NOT NULL, - "is_skill" bool NOT NULL -); -ALTER TABLE "background_skills" ADD FOREIGN KEY ("background_id") REFERENCES "background" ("id"); - -CREATE TABLE "background_boosts" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "background_id" int NOT NULL, - "value" varchar(3) -); -ALTER TABLE "background_boosts" ADD FOREIGN KEY ("background_id") REFERENCES "background" ("id"); - - --- Campaign Feature - -DROP TYPE IF EXISTS "campaign_feature_category"; -CREATE TYPE "campaign_feature_category" AS ENUM ( - 'army_tactic', - 'army_war_action', - 'kingdom_activity', - 'kingdom_feat', - 'kingdom_feature' -); - -CREATE TABLE "campaign_feature" ( - "id" int PRIMARY KEY, - "level" int, - "action_type" action_type NOT NULL, - "actions" int NOT NULL, - "category" campaign_feature_category NOT NULL, - "prerequisites" varchar, - "extra" text -); -ALTER TABLE "campaign_feature" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - - --- Class - -DROP TYPE IF EXISTS "class_progression_type"; -CREATE TYPE "class_progression_type" AS ENUM ( - 'entity', - 'ancestry_feat', - 'class_feat', - 'general_feat', - 'skill_feat', - 'skill_increase' -); - -CREATE TABLE "class" ( - "id" int PRIMARY KEY, - "hp" int NOT NULL, - "perception" int NOT NULL, - "spellcasting" bool NOT NULL, - "additionalTrainedSkills" int NOT NULL -); -ALTER TABLE "class" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - -CREATE TABLE "class_key_abilities" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "class_id" int NOT NULL, - "value" varchar(3) NOT NULL -); -ALTER TABLE "class_key_abilities" ADD FOREIGN KEY ("class_id") REFERENCES "class" ("id"); - -CREATE TABLE "class_progression" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "class_id" int NOT NULL, - "level" int NOT NULL, - "type" class_progression_type NOT NULL, - "entity_id" int -); -ALTER TABLE "class_progression" ADD FOREIGN KEY ("class_id") REFERENCES "class" ("id"); -ALTER TABLE "class_progression" ADD FOREIGN KEY ("entity_id") REFERENCES "entity" ("id"); - -CREATE TABLE "class_trainings" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "class_id" int NOT NULL, - "item" int, - "name" varchar, - "stage" int NOT NULL -); -ALTER TABLE "class_trainings" ADD FOREIGN KEY ("class_id") REFERENCES "class" ("id"); -ALTER TABLE "class_trainings" ADD FOREIGN KEY ("item") REFERENCES "trainings" ("id"); - - --- Condition - -DROP TYPE IF EXISTS "condition_group"; -CREATE TYPE "condition_group" AS ENUM ( - 'senses', - 'abilities', - 'death', - 'attitudes', - 'detection' -); - -CREATE TABLE "condition" ( - "id" int PRIMARY KEY, - "group" condition_group, - "extra" text -); -ALTER TABLE "condition" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - - --- Deity - -DROP TYPE IF EXISTS "deity_category"; -CREATE TYPE "deity_category" AS ENUM ( - 'deity', - 'pantheon', - 'covenant', - 'philosophy' -); -DROP TYPE IF EXISTS "deity_fonts"; -CREATE TYPE "deity_fonts" AS ENUM ( - 'none', - 'heal', - 'harm', - 'any' -); -DROP TYPE IF EXISTS "deity_sanctification"; -CREATE TYPE "deity_sanctification" AS ENUM ( - 'holy', - 'unholy', - 'any' -); - -CREATE TABLE "deity" ( - "id" int PRIMARY KEY, - "category" deity_category NOT NULL, - "font" deity_fonts NOT NULL, - "sanctification_must" bool NOT NULL, - "sanctification_type" deity_sanctification NOT NULL -); -ALTER TABLE "deity" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - -CREATE TABLE "deity_attributes" ( - "deity_id" int, - "attribute" varchar(3), - PRIMARY KEY ("deity_id", "attribute") -); -ALTER TABLE "deity_attributes" ADD FOREIGN KEY ("deity_id") REFERENCES "deity" ("id"); - -CREATE TABLE "deity_skills" ( - "deity_id" int, - "skill_id" int, - PRIMARY KEY ("deity_id", "skill_id") -); -ALTER TABLE "deity_skills" ADD FOREIGN KEY ("deity_id") REFERENCES "deity" ("id"); -ALTER TABLE "deity_skills" ADD FOREIGN KEY ("skill_id") REFERENCES "trainings" ("id"); - -CREATE TABLE "deity_spells" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "deity_id" int NOT NULL, - "level" int NOT NULL, - "spell_id" int NOT NULL -); -ALTER TABLE "deity_spells" ADD FOREIGN KEY ("deity_id") REFERENCES "deity" ("id"); -ALTER TABLE "deity_spells" ADD FOREIGN KEY ("spell_id") REFERENCES "spell" ("id"); - -CREATE TABLE "deity_weapons" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "deity_id" int NOT NULL, - "weapon" varchar NOT NULL -); -ALTER TABLE "deity_weapons" ADD FOREIGN KEY ("deity_id") REFERENCES "deity" ("id"); - -CREATE TABLE "deity_domains" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "deity_id" int NOT NULL, - "is_primary" bool NOT NULL, - "value" varchar NOT NULL -); -ALTER TABLE "deity_domains" ADD FOREIGN KEY ("deity_id") REFERENCES "deity" ("id"); - - --- Feat - -DROP TYPE IF EXISTS "feat_category"; -CREATE TYPE "feat_category" AS ENUM ( - 'ancestryfeature', - 'classfeature', - 'calling', - 'class', - 'skill', - 'general', - 'ancestry', - 'bonus', - 'deityboon', - 'curse', - 'pfsboon' -); - -CREATE TABLE "feat" ( - "id" int PRIMARY KEY, - "action_type" action_type NOT NULL, - "actions" int, - "category" feat_category NOT NULL, - "level" int NOT NULL, - "level_taken" int, - "max_takable" int, - "location" varchar(12), - "only_level_1" bool, - "extra" text -); -ALTER TABLE "feat" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); - -CREATE TABLE "feat_prerequisites" ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "feat_id" int NOT NULL, - "value" varchar NOT NULL -); -ALTER TABLE "feat_prerequisites" ADD FOREIGN KEY ("feat_id") REFERENCES "feat" ("id"); - - --- Heritage - -CREATE TABLE "heritage" ( - "id" int PRIMARY KEY, - "ancestry_id" int -); -ALTER TABLE "heritage" ADD FOREIGN KEY ("id") REFERENCES "entity" ("id"); -ALTER TABLE "heritage" ADD FOREIGN KEY ("ancestry_id") REFERENCES "ancestry" ("id"); - - ----------------------------------------------------------------------------------------------- - -CREATE OR REPLACE FUNCTION f_truncate_tables() - RETURNS void - LANGUAGE plpgsql AS -$func$ -BEGIN - EXECUTE - (SELECT 'TRUNCATE TABLE ' - || string_agg(format('%I.%I', schemaname, tablename), ', ') - || ' CASCADE;' - FROM pg_tables - WHERE schemaname = 'public' - ); -END -$func$; - ----------------------------------------------------------------------------------------------- - - -CREATE OR REPLACE FUNCTION f_drop_tables() - RETURNS void - LANGUAGE plpgsql AS -$func$ -BEGIN - EXECUTE - (SELECT 'DROP TABLE ' - || string_agg(format('%I.%I', schemaname, tablename), ', ') - || ' CASCADE;' - FROM pg_tables - WHERE schemaname = 'public' - ); -END -$func$; - ----------------------------------------------------------------------------------------------- - -CREATE OR REPLACE FUNCTION f_permission_tables(_username text) - RETURNS void - LANGUAGE plpgsql AS -$func$ -DECLARE - _tbl text; - _sch text; -BEGIN - FOR _sch, _tbl IN - SELECT schemaname, tablename - FROM pg_tables - WHERE schemaname = 'public' - LOOP - EXECUTE format('ALTER TABLE %I.%I OWNER TO %I', _sch, _tbl, _username); - END LOOP; -END -$func$; \ No newline at end of file diff --git a/erd/pf2e.pgerd b/erd/pf2e.pgerd new file mode 100644 index 0000000..155b34f --- /dev/null +++ b/erd/pf2e.pgerd @@ -0,0 +1 @@ +{"version":"90200","data":{"id":"b3839ee0-33c2-43f4-afd2-95d988d100f9","offsetX":-4526.1160977949885,"offsetY":-2429.4434308790364,"zoom":167.1594278737135,"gridSize":15,"layers":[{"id":"2da26cfc-1363-4850-a5d3-8bec31076149","type":"diagram-links","isSvg":true,"transformed":true,"models":{"8d6d50c6-db8c-4274-aac5-1a6cd293d2d1":{"id":"8d6d50c6-db8c-4274-aac5-1a6cd293d2d1","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"4aaa1c42-6cb1-4f67-aa76-eb821a808476","targetPort":"f53489a2-72a2-432b-964b-286cc312c046","points":[{"id":"0a776f2b-beee-40fc-bcab-a11283d12a0c","type":"point","x":3039,"y":-175.390625},{"id":"7f0c762f-bc29-4d38-85bf-58f17a3ad84a","type":"point","x":3039,"y":-220.390625},{"id":"3b7c68ea-350b-4387-82b5-bc629d747d85","type":"point","x":3121,"y":-220.390625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"4aaa1c42-6cb1-4f67-aa76-eb821a808476","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"6db4d1e5-4aa5-48c9-998d-def3f027b375":{"id":"6db4d1e5-4aa5-48c9-998d-def3f027b375","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","targetPort":"c8090663-57a2-4151-aaac-03510f75e7fb","points":[{"id":"bffea478-669f-41fb-b40f-02f6dfaa520f","type":"point","x":3039,"y":-175.390625},{"id":"a04d437d-ad92-4cc7-aab1-5d7001f564a8","type":"point","x":3039,"y":4.609375},{"id":"04106839-2857-4230-8aa7-43ddf7ce49e0","type":"point","x":3121,"y":4.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"123ac55b-4993-4a89-a428-d389597adc44":{"id":"123ac55b-4993-4a89-a428-d389597adc44","locked":true,"type":"onetomany","source":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","sourcePort":"cf6808ad-b609-4cf5-ae03-f70253c231c7","target":"e74444e6-b9dc-4133-a959-281105f1481e","targetPort":"e4ab8da0-f988-4208-b68d-4dd8c79e4451","points":[{"id":"2c5aacc6-f7fb-4eee-b16d-319452cccc1c","type":"point","x":3354,"y":4.609375},{"id":"44117e1c-b7f9-4b91-a065-03affca5fd55","type":"point","x":3354,"y":30.65625},{"id":"6f15e78e-324f-4ab3-9259-5c4c36a65fdf","type":"point","x":3391,"y":30.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"e74444e6-b9dc-4133-a959-281105f1481e","local_column_attnum":2,"referenced_table_uid":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","referenced_column_attnum":1}},"99fade7e-8094-4d92-abd3-cad8afeb96b0":{"id":"99fade7e-8094-4d92-abd3-cad8afeb96b0","locked":true,"type":"onetomany","selected":false,"source":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","sourcePort":"cf6808ad-b609-4cf5-ae03-f70253c231c7","target":"b0f18bbe-e381-4068-a1f6-10b10af52863","targetPort":"3990097d-cb34-43ec-8bd4-d02eb1e8a544","points":[{"id":"a3dd6f8b-ac6c-4ad6-a952-f75852b99f40","type":"point","x":3354,"y":4.609375},{"id":"1ab492de-91a9-4bdc-babc-57d2a61fecfe","type":"point","x":3354,"y":108.28498889465186},{"id":"c3f869ec-ea9b-4890-8207-f27861ce7201","type":"point","x":3627.614521479388,"y":108.28498889465186},{"id":"256d0344-f38d-414a-b6e1-0198d7e80d90","type":"point","x":3627.614521479388,"y":4.609375},{"id":"ce2d4c87-0fc3-4324-8163-4125000b88a8","type":"point","x":3661,"y":4.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"b0f18bbe-e381-4068-a1f6-10b10af52863","local_column_attnum":1,"referenced_table_uid":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","referenced_column_attnum":1}},"40e17158-2d5a-43f2-87fc-1e2ae5bf55a9":{"id":"40e17158-2d5a-43f2-87fc-1e2ae5bf55a9","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"b0f18bbe-e381-4068-a1f6-10b10af52863","targetPort":"189ce05d-db64-475b-82d3-c065c7d34bbc","points":[{"id":"a469797c-3c68-458d-a8a3-3e0394590d3e","type":"point","x":3039,"y":-175.390625},{"id":"a24e66d0-1ea4-40b2-aadd-f551af374e96","type":"point","x":3039,"y":134.2057597452838},{"id":"a66c327f-c15f-40d1-a8ec-3f6ac6d38ad4","type":"point","x":3661,"y":134.2057597452838},{"id":"1f3ca4d0-f15c-4dd7-a8f8-881ce126801f","type":"point","x":3661,"y":30.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"b0f18bbe-e381-4068-a1f6-10b10af52863","local_column_attnum":2,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"bd190116-c39f-4051-8aa4-7de09adf004b":{"id":"bd190116-c39f-4051-8aa4-7de09adf004b","locked":true,"type":"onetomany","selected":false,"source":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","sourcePort":"cf6808ad-b609-4cf5-ae03-f70253c231c7","target":"452d646b-65fa-41a7-9c4a-391e337cb25a","targetPort":"c59cdb15-8f1b-49ee-8a09-615eabcb22d4","points":[{"id":"ee057d9f-0dfa-4bc4-bbba-697c2ba19096","type":"point","x":3354,"y":4.609375},{"id":"fc4de79e-0095-414e-bad4-f0ff6d9a41c0","type":"point","x":3354,"y":107.01222393092631},{"id":"b65f0e05-98e1-4745-8afb-c42717192225","type":"point","x":3898.663535594688,"y":107.01222393092631},{"id":"f43106e7-0239-41fc-8d61-5cd23818e3d1","type":"point","x":3898.663535594688,"y":4.609375},{"id":"5ce71f96-89cc-4cf4-bd22-ede7cdce3cb8","type":"point","x":3931,"y":4.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"452d646b-65fa-41a7-9c4a-391e337cb25a","local_column_attnum":1,"referenced_table_uid":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","referenced_column_attnum":1}},"38d3c083-3048-4953-83db-a4d64e93bcdd":{"id":"38d3c083-3048-4953-83db-a4d64e93bcdd","locked":true,"type":"onetomany","selected":false,"source":"51769507-cd47-4825-8561-af1d1d3a3792","sourcePort":"99b4999f-6805-4ab1-914c-62a5098c14c7","target":"452d646b-65fa-41a7-9c4a-391e337cb25a","targetPort":"1c107425-8988-439d-9930-28891d93c14f","points":[{"id":"2d0f1046-dd5b-48aa-b280-f3d7f8ad50dc","type":"point","x":3444,"y":-610.390625},{"id":"96a8397a-fc49-42c0-8a76-5f1183f79479","type":"point","x":3495.813553494459,"y":-610.390625},{"id":"4aa3cf9d-df99-4bd8-9ee3-89473b7b3c10","type":"point","x":3495.813553494459,"y":-115.36122367620573},{"id":"1b56c036-e79c-4c4b-841e-ea272ac9a1e5","type":"point","x":3926.865167121135,"y":-115.36122367620573},{"id":"9949d11b-22fc-4c58-a6bd-2b2d97528e52","type":"point","x":3926.865167121135,"y":30.65625},{"id":"0094ec78-a079-4b0c-804e-7ad3c05e541e","type":"point","x":3931,"y":30.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"452d646b-65fa-41a7-9c4a-391e337cb25a","local_column_attnum":2,"referenced_table_uid":"51769507-cd47-4825-8561-af1d1d3a3792","referenced_column_attnum":1}},"37f29f5a-6288-45b2-97dd-85e1ba9a231b":{"id":"37f29f5a-6288-45b2-97dd-85e1ba9a231b","locked":true,"type":"onetomany","source":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","sourcePort":"7b718e11-0bf2-48da-9a27-1fd92bc86538","target":"be6b1ff4-866e-489c-aa76-ff99117f4121","targetPort":"9aa88db2-5cb4-4d12-850a-83d57fe87969","points":[{"id":"1d724170-ce3b-4302-8e2e-ff838e3ea9ee","type":"point","x":3354,"y":454.609375},{"id":"08e85cfd-36df-4d42-ac1b-468b13023487","type":"point","x":3354,"y":454.609375},{"id":"d2ef2cf4-1082-43a4-92a3-384564ba5bbf","type":"point","x":3391,"y":454.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"be6b1ff4-866e-489c-aa76-ff99117f4121","local_column_attnum":1,"referenced_table_uid":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","referenced_column_attnum":1}},"f80771ff-8543-4bda-8a33-cf88e5f5240c":{"id":"f80771ff-8543-4bda-8a33-cf88e5f5240c","locked":true,"type":"onetomany","selected":false,"source":"6a0445dc-9262-485d-9bfc-73c374cfec00","sourcePort":"a2bc8378-e84d-4222-af3e-4e4a0fd80614","target":"be6b1ff4-866e-489c-aa76-ff99117f4121","targetPort":"ba694fa9-d2b3-4eb3-8a57-8d7290a5ba42","points":[{"id":"5cf87a98-7d27-4df2-8bab-24d5e5821227","type":"point","x":3354,"y":1534.609375},{"id":"eb3b6710-ab34-43fc-bedc-9e2c7e09a38d","type":"point","x":3374.670191861391,"y":1534.609375},{"id":"e32b2b76-4679-42e5-84bc-bd95af34fb72","type":"point","x":3374.670191861391,"y":1439.2307138606254},{"id":"c07bfa22-3da3-4724-b010-81e0d27238cc","type":"point","x":4214.919513286034,"y":1439.2307138606254},{"id":"e47b2562-a2c0-4046-823b-c120d5a81177","type":"point","x":4214.919513286034,"y":480.65625},{"id":"883f3bfd-828e-4eda-93a9-357c95ee7010","type":"point","x":3391,"y":480.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"be6b1ff4-866e-489c-aa76-ff99117f4121","local_column_attnum":2,"referenced_table_uid":"6a0445dc-9262-485d-9bfc-73c374cfec00","referenced_column_attnum":1}},"901d0a82-4c10-45db-9a2f-5ab11ec4fd51":{"id":"901d0a82-4c10-45db-9a2f-5ab11ec4fd51","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","targetPort":"db1eaeaf-e7ed-46cf-ae93-29ee92cfac74","points":[{"id":"a754c4e2-e1eb-4280-8da6-13f2ff42fbc5","type":"point","x":3039,"y":-175.390625},{"id":"e51519a4-9b6b-4ffd-b534-5bcb4c5ae2f3","type":"point","x":3039,"y":242.76673416875823},{"id":"74c2b10c-1eba-45c0-8a8e-7d377ec2773a","type":"point","x":3038.57051365643,"y":242.76673416875823},{"id":"e723e5c5-b856-41e7-8370-80a4d0795e3d","type":"point","x":3038.57051365643,"y":454.609375},{"id":"f079795a-68fc-4ffb-a3fa-cd033929ca6b","type":"point","x":3121,"y":454.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"0e17425e-9a46-419b-9977-5f4327bc6325":{"id":"0e17425e-9a46-419b-9977-5f4327bc6325","locked":true,"type":"onetomany","selected":false,"source":"6a0445dc-9262-485d-9bfc-73c374cfec00","sourcePort":"a2bc8378-e84d-4222-af3e-4e4a0fd80614","target":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","targetPort":"fe6ca2f0-0347-48d9-bbd2-14240255cc22","points":[{"id":"f6f0a0b6-ccae-4a40-a51f-4130186733cd","type":"point","x":3354,"y":1534.609375},{"id":"1ff1f715-5f9c-416e-bb54-0c65c9098de4","type":"point","x":3374.670191861391,"y":1534.609375},{"id":"4b137ae0-9382-4376-8fdd-24296ed059d9","type":"point","x":3374.670191861391,"y":1440.4236598107893},{"id":"01689f52-5a38-4957-a835-2e03585e0755","type":"point","x":4212.799046509639,"y":1440.4236598107893},{"id":"05acdc47-1e54-443a-a18a-bbec26dc7926","type":"point","x":4212.799046509639,"y":506.703125},{"id":"a5d716ed-590e-4eae-913f-269cdf554b3b","type":"point","x":3354,"y":506.703125}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","local_column_attnum":3,"referenced_table_uid":"6a0445dc-9262-485d-9bfc-73c374cfec00","referenced_column_attnum":1}},"faf9a083-347c-4ff4-9359-270c4d40fede":{"id":"faf9a083-347c-4ff4-9359-270c4d40fede","locked":true,"type":"onetomany","selected":false,"source":"3c985425-5b89-48c2-995d-e17e0da94b8a","sourcePort":"b366da8e-22d0-4f5e-9d8f-14a31ff7bf40","target":"b65ec3a2-384f-4e9b-a535-4c3228a4935e","targetPort":"14d8d2b9-9a3d-432b-a248-55c1e0d866e5","points":[{"id":"d17415eb-7b71-4189-9658-a5ccdc503c0d","type":"point","x":3354,"y":679.609375},{"id":"e5bdff2d-c5fe-401a-80e0-59a280f6e35d","type":"point","x":3354,"y":765.7857083814371},{"id":"3924f5cd-9ddf-4d3a-8e07-d8b7667d391e","type":"point","x":3661,"y":765.7857083814371},{"id":"f5dd6c91-a741-46a6-bc90-844f025ab089","type":"point","x":3661,"y":705.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"b65ec3a2-384f-4e9b-a535-4c3228a4935e","local_column_attnum":2,"referenced_table_uid":"3c985425-5b89-48c2-995d-e17e0da94b8a","referenced_column_attnum":1}},"15700432-11ef-4229-91d3-dc8e826a7db4":{"id":"15700432-11ef-4229-91d3-dc8e826a7db4","locked":true,"type":"onetomany","selected":false,"source":"3c985425-5b89-48c2-995d-e17e0da94b8a","sourcePort":"b366da8e-22d0-4f5e-9d8f-14a31ff7bf40","target":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","targetPort":"4da6f1e8-c9ed-4f7f-82b9-3a10e2132fd5","points":[{"id":"9b56fe5e-0f6d-488b-abb6-f7f5d382605f","type":"point","x":3354,"y":679.609375},{"id":"b2953c24-65f3-4c3d-9c69-bf49e54b280e","type":"point","x":3354,"y":679.409366018879},{"id":"70861f3a-20f5-4d5d-b22c-3359cc78c6e3","type":"point","x":3391,"y":679.409366018879},{"id":"4f2d7b95-1791-4e3f-8205-910bfdda22b9","type":"point","x":3391,"y":679.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","local_column_attnum":1,"referenced_table_uid":"3c985425-5b89-48c2-995d-e17e0da94b8a","referenced_column_attnum":1}},"06f76c4f-b3d7-4f32-b300-5246ae1ab14d":{"id":"06f76c4f-b3d7-4f32-b300-5246ae1ab14d","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","targetPort":"f3dc238a-c10e-4c6e-985c-e242baf58eb5","points":[{"id":"dcbaf50c-4d5c-41f2-838e-42954cc854f3","type":"point","x":3039,"y":-175.390625},{"id":"17199f06-211b-437e-85ad-4aa296aca33e","type":"point","x":3039,"y":721.5920358293214},{"id":"e40cf32a-02af-4aca-a6d5-2fd5cdd752bf","type":"point","x":3391,"y":721.5920358293214},{"id":"54c86f71-4aed-4eb8-9045-4d37c3ff84ca","type":"point","x":3391,"y":705.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","local_column_attnum":2,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"c8a7105a-4205-450d-b208-2fb4e0bd0d41":{"id":"c8a7105a-4205-450d-b208-2fb4e0bd0d41","locked":true,"type":"onetomany","selected":false,"source":"3c985425-5b89-48c2-995d-e17e0da94b8a","sourcePort":"b366da8e-22d0-4f5e-9d8f-14a31ff7bf40","target":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","targetPort":"23f20771-e4ee-4da8-85f9-2a8ee5b2509e","points":[{"id":"d67777d9-4557-4b8c-bce9-931392cfdeab","type":"point","x":3354,"y":679.609375},{"id":"c0f12200-524f-4e90-a904-d04a3ecb42cc","type":"point","x":3354,"y":765.7857083814371},{"id":"dd8198c1-3027-4fe0-80d1-44adeef9b2d5","type":"point","x":3931,"y":765.7857083814371},{"id":"110a2209-fc01-4554-85d5-b5285abd6d4e","type":"point","x":3931,"y":705.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","local_column_attnum":2,"referenced_table_uid":"3c985425-5b89-48c2-995d-e17e0da94b8a","referenced_column_attnum":1}},"6981aac6-5e9a-4e34-aea2-1f9fe83e33e8":{"id":"6981aac6-5e9a-4e34-aea2-1f9fe83e33e8","locked":true,"type":"onetomany","source":"a8da1c01-6c92-475b-9088-6449c7a7afaf","sourcePort":"46484358-e975-4399-9363-ca9793e2836e","target":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","targetPort":"7ed057bd-dec9-4f7e-a130-77a97df6a900","points":[{"id":"33905d12-77f6-4ff4-a1ad-59a4de7db75e","type":"point","x":4299,"y":-685.390625},{"id":"443a78b4-1ddf-4c5e-9c63-322dc0d2a29e","type":"point","x":4299,"y":731.703125},{"id":"f2410d8a-3c2b-429b-a165-3cf18756b1f9","type":"point","x":4164,"y":731.703125}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","local_column_attnum":3,"referenced_table_uid":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced_column_attnum":1}},"cccdb52f-1e67-4bdc-99df-eca62e6b9a4b":{"id":"cccdb52f-1e67-4bdc-99df-eca62e6b9a4b","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"3c985425-5b89-48c2-995d-e17e0da94b8a","targetPort":"5227f039-d673-4a8f-a0fa-29258fca9589","points":[{"id":"40c94853-a61c-429e-b067-9870c7a7b974","type":"point","x":3039,"y":-175.390625},{"id":"561f9496-2d73-4723-ad70-4c08adf9e96f","type":"point","x":3039,"y":679.609375},{"id":"a2893dc8-e740-441e-a43d-db72b1f9ed85","type":"point","x":3121,"y":679.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"3c985425-5b89-48c2-995d-e17e0da94b8a","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"8428c95b-a620-4c51-ba6c-b9ca02f3ba1d":{"id":"8428c95b-a620-4c51-ba6c-b9ca02f3ba1d","locked":true,"type":"onetomany","source":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","sourcePort":"9a0b883e-5d34-42d8-bbb1-27111fd34cd5","target":"4c2de672-7c04-4bde-8477-a9eb56d1652a","targetPort":"f023d2fc-71b7-411b-971c-aadce51f6d2e","points":[{"id":"b6bb1948-9d82-4d41-8af7-4726eb0df5fa","type":"point","x":2446.0000213186186,"y":1234.609396054725},{"id":"91ac73e6-5fb7-4b81-9c69-90ff87d7098b","type":"point","x":2446.0000213186186,"y":1095.65625},{"id":"936ffb85-fb9e-4c6e-9508-c9c6dbbba969","type":"point","x":2409,"y":1095.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"4c2de672-7c04-4bde-8477-a9eb56d1652a","local_column_attnum":2,"referenced_table_uid":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","referenced_column_attnum":1}},"c0f8a094-1a68-4a0d-8b05-038b4ed69daf":{"id":"c0f8a094-1a68-4a0d-8b05-038b4ed69daf","locked":true,"type":"onetomany","source":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","sourcePort":"9a0b883e-5d34-42d8-bbb1-27111fd34cd5","target":"43a82fc9-ffd3-45b7-b120-479c764dc03e","targetPort":"9344f216-adaf-4dcb-9de3-347bbc5263b2","points":[{"id":"02ce1b31-02fb-417d-9411-787bd2c532c6","type":"point","x":2446.0000213186186,"y":1234.609396054725},{"id":"5410951d-0bf3-4ae2-9ff3-13831ae77264","type":"point","x":2446.0000213186186,"y":1290.6561595992225},{"id":"8a02a21b-fa9c-4058-b976-349811c38c20","type":"point","x":2408.9998727112816,"y":1290.6561595992225}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"43a82fc9-ffd3-45b7-b120-479c764dc03e","local_column_attnum":2,"referenced_table_uid":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","referenced_column_attnum":1}},"09572470-4f3e-4090-9bb9-f291dfafc413":{"id":"09572470-4f3e-4090-9bb9-f291dfafc413","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"43a82fc9-ffd3-45b7-b120-479c764dc03e","targetPort":"97c90db8-6de0-4cf1-9355-6c7e0c9f1cbb","points":[{"id":"36d9b2ae-de52-4bee-a172-b5490778fac8","type":"point","x":2806,"y":-175.390625},{"id":"499194be-1b8e-41db-bad0-91c1a83e5b4b","type":"point","x":2806,"y":1342.749983902891},{"id":"82b49516-8d8f-4abe-80c6-fd4620c340ac","type":"point","x":2408.9998727112816,"y":1342.749983902891}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"43a82fc9-ffd3-45b7-b120-479c764dc03e","local_column_attnum":4,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"5e4e4f4b-2cea-4964-89af-af8a1d0df3ad":{"id":"5e4e4f4b-2cea-4964-89af-af8a1d0df3ad","locked":true,"type":"onetomany","source":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","sourcePort":"9a0b883e-5d34-42d8-bbb1-27111fd34cd5","target":"7da37f73-173f-4379-842d-816ffe491fd4","targetPort":"0fcc9bf4-29ad-43fb-bced-b99426b2cf13","points":[{"id":"328b787b-f028-4da9-9e36-19dcff9b5200","type":"point","x":2446.0000213186186,"y":1234.609396054725},{"id":"1ae520b0-0b1b-43dd-864b-a319e8a718fd","type":"point","x":2446.0000213186186,"y":1530.65625},{"id":"aa2cb225-bb94-4e7a-9705-d39cc966dbbd","type":"point","x":2409,"y":1530.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"7da37f73-173f-4379-842d-816ffe491fd4","local_column_attnum":2,"referenced_table_uid":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","referenced_column_attnum":1}},"600c552a-40e7-4e31-9057-756b1dfe5379":{"id":"600c552a-40e7-4e31-9057-756b1dfe5379","locked":true,"type":"onetomany","selected":false,"source":"a8da1c01-6c92-475b-9088-6449c7a7afaf","sourcePort":"096e2789-e9b1-4623-a2d8-332700cafd6a","target":"7da37f73-173f-4379-842d-816ffe491fd4","targetPort":"49759582-3f47-4646-81fc-c1f6d9c8e5ce","points":[{"id":"bffae6f1-faf7-4679-ae62-2ca327530121","type":"point","x":4066,"y":-685.390625},{"id":"a4ccd26c-a5a7-4b76-be7c-9cab33f76a25","type":"point","x":4066,"y":-812.2003551680971},{"id":"1a819d58-e0a6-4ce0-9d41-18cd7a77d438","type":"point","x":1846.7391384732614,"y":-812.2003551680971},{"id":"d2cb9b3a-221d-4759-bd21-b6dee6f49217","type":"point","x":1846.7391384732614,"y":1622.834697866837},{"id":"2a8f9d06-e2ca-48ca-992a-839de091eba7","type":"point","x":2411.0895886265707,"y":1622.834697866837},{"id":"c1a6edf9-164e-4e37-8a74-af5b863d2d75","type":"point","x":2411.0895886265707,"y":1556.703125},{"id":"546dfb92-28f7-4696-9f7f-717faa016621","type":"point","x":2409,"y":1556.703125}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"7da37f73-173f-4379-842d-816ffe491fd4","local_column_attnum":3,"referenced_table_uid":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced_column_attnum":1}},"1bbb315c-d4e8-4342-9553-1a7f9d42cd46":{"id":"1bbb315c-d4e8-4342-9553-1a7f9d42cd46","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","targetPort":"c056f935-f663-4dbb-a2d7-da45d73d439c","points":[{"id":"85d6a98b-1e1f-4fe9-90fb-b3dac97e6b90","type":"point","x":2806,"y":-175.390625},{"id":"1fa84d40-7c1c-41a3-99fd-80204fe85bcb","type":"point","x":2806,"y":1234.609396054725},{"id":"d2a3da63-7d5d-4472-8f09-7127f5eb40f3","type":"point","x":2678.9999788593796,"y":1234.609396054725}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"222c6d27-115e-45fa-ac74-824b57cc65b0":{"id":"222c6d27-115e-45fa-ac74-824b57cc65b0","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"2ffbc8c1-67b3-420b-b08e-6a3e7b17e982","targetPort":"4ff66194-701f-44a5-92cd-605c6e401525","points":[{"id":"fbab667c-d8dd-44b8-98e5-3ba87409534d","type":"point","x":3039,"y":-175.390625},{"id":"b980683f-db7f-499a-a4db-e897e0fac2f9","type":"point","x":3039,"y":844.609375},{"id":"f20c7eba-5944-4b5f-b03f-c88c4b2f956a","type":"point","x":3121,"y":844.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"2ffbc8c1-67b3-420b-b08e-6a3e7b17e982","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"33c009d5-2a3a-4b26-804c-5f49ddde03b4":{"id":"33c009d5-2a3a-4b26-804c-5f49ddde03b4","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","targetPort":"b7f7a66f-2d77-448a-a1d0-055412eff3f1","points":[{"id":"8a40e8e6-bb12-48fc-926e-4603948ae0cf","type":"point","x":3039,"y":-175.390625},{"id":"b90708b1-aa32-4ab3-b76d-aea97fd24367","type":"point","x":3039,"y":1084.4727259119807},{"id":"8a5d019f-24b6-4cd9-9ecd-88a17d9c777c","type":"point","x":3120.9998090224235,"y":1084.4727259119807},{"id":"9961ed05-e2ca-483e-89d7-fe8fef824c89","type":"point","x":3120.9998090224235,"y":1084.601514558461}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"2e4a4581-fbcb-4d67-b61e-f17c2d665dae":{"id":"2e4a4581-fbcb-4d67-b61e-f17c2d665dae","locked":true,"type":"onetomany","selected":false,"source":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","sourcePort":"310110c1-e97b-4398-b0f2-6e4ae8fddafa","target":"77e9c57b-29b6-4b0c-add0-644c299b2c65","targetPort":"130df01b-7ae6-4cce-b7a7-5b757a15ce4d","points":[{"id":"a06a0601-0904-415e-9ebd-7246565a75bc","type":"point","x":3353.9997665631854,"y":1084.601514558461},{"id":"c452d0fc-32ca-4f04-b914-02439b9b0583","type":"point","x":3353.9997665631854,"y":1172.3100134463045},{"id":"12f59edf-2cca-4c6d-94f2-d68079655677","type":"point","x":3661,"y":1172.3100134463045},{"id":"0e3d5389-509c-483d-a44e-826635f9b905","type":"point","x":3661,"y":1084.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"77e9c57b-29b6-4b0c-add0-644c299b2c65","local_column_attnum":1,"referenced_table_uid":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced_column_attnum":1}},"36599081-6b6b-4098-8008-9fb0b1f91081":{"id":"36599081-6b6b-4098-8008-9fb0b1f91081","locked":true,"type":"onetomany","selected":false,"source":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","sourcePort":"310110c1-e97b-4398-b0f2-6e4ae8fddafa","target":"27e8d943-bbe3-4137-8922-d593ef389faf","targetPort":"0f7de847-feb6-408c-a07e-a9b886dafff5","points":[{"id":"cea690b5-a1c4-4e5c-bf8f-ed8fea65e379","type":"point","x":3353.9997665631854,"y":1084.601514558461},{"id":"77260a72-8456-4dfc-8d34-f0d55d34e9a5","type":"point","x":3353.9997665631854,"y":1171.162690632932},{"id":"aac7a3da-6c54-4554-bb95-296a276a2624","type":"point","x":3354.535925204229,"y":1171.162690632932},{"id":"105783fb-3463-4c08-a355-f4027ccc04ab","type":"point","x":3354.535925204229,"y":1335.6484957065595},{"id":"98b8d934-c1ae-4f07-abcf-1210cdbde9d7","type":"point","x":3390.9998090224235,"y":1335.6484957065595}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"27e8d943-bbe3-4137-8922-d593ef389faf","local_column_attnum":2,"referenced_table_uid":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced_column_attnum":1}},"5ca1e9c6-7e9f-41e9-9065-55e5c14052cc":{"id":"5ca1e9c6-7e9f-41e9-9065-55e5c14052cc","locked":true,"type":"onetomany","selected":false,"source":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","sourcePort":"310110c1-e97b-4398-b0f2-6e4ae8fddafa","target":"4564ad70-59d5-415d-ab76-6e944aac220f","targetPort":"5117cd62-5bd0-4fbe-8a09-790317043136","points":[{"id":"132447d7-8590-4749-9bb0-e91727d9683b","type":"point","x":3353.9997665631854,"y":1084.601514558461},{"id":"2214d3ee-6a8e-453c-b056-9c8cc4bbc956","type":"point","x":3353.9997665631854,"y":1172.028014178348},{"id":"ff52d256-e8f2-4ad4-8f57-37a61d8f4619","type":"point","x":3661,"y":1172.028014178348},{"id":"60c66f11-b32f-4c17-8b0d-801ee4bceefd","type":"point","x":3661,"y":1335.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"4564ad70-59d5-415d-ab76-6e944aac220f","local_column_attnum":2,"referenced_table_uid":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced_column_attnum":1}},"8274b177-f7c4-45c1-8a60-083c9e7bdde1":{"id":"8274b177-f7c4-45c1-8a60-083c9e7bdde1","locked":true,"type":"onetomany","selected":false,"source":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","sourcePort":"c54dc85c-d4f5-4139-9582-8ac3af3285b7","target":"4564ad70-59d5-415d-ab76-6e944aac220f","targetPort":"fbab53f0-7177-4846-b118-cb3b72f487a3","points":[{"id":"10c0db06-3bbe-4d60-9909-fb6e24b441b4","type":"point","x":3354,"y":2389.609375},{"id":"805ca34e-d249-4b45-b6fd-20bc787cf841","type":"point","x":3354,"y":2268.3524639172133},{"id":"1d18c3c9-1da2-4c0c-ae13-5d318fe99615","type":"point","x":4069.9366308599638,"y":2268.3524639172133},{"id":"3bbe29d6-450e-4198-aefc-4eb850088f73","type":"point","x":4069.9366308599638,"y":1386.8399625142606},{"id":"1f3fe114-7e06-4c43-85cf-697fc8f55e0b","type":"point","x":3951.7324278970736,"y":1386.8399625142606},{"id":"beb72b4b-5df2-42af-a6a2-48cecbfc9095","type":"point","x":3951.7324278970736,"y":1387.75},{"id":"8d194bdf-c84c-4236-858b-474cb115d51d","type":"point","x":3661,"y":1387.75}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"4564ad70-59d5-415d-ab76-6e944aac220f","local_column_attnum":4,"referenced_table_uid":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","referenced_column_attnum":1}},"7ccba789-9cb9-4555-9dc5-62ff4b48258a":{"id":"7ccba789-9cb9-4555-9dc5-62ff4b48258a","locked":true,"type":"onetomany","selected":false,"source":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","sourcePort":"310110c1-e97b-4398-b0f2-6e4ae8fddafa","target":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","targetPort":"bf7a9ff0-9ec5-4524-84a0-86a4146afc9d","points":[{"id":"722557a6-2d63-4508-8287-630ea8f53f84","type":"point","x":3353.9997665631854,"y":1084.601514558461},{"id":"b3f5383a-c61d-4871-868f-b60dd4262daa","type":"point","x":3353.9997665631854,"y":1171.162690632932},{"id":"e93108d8-1615-40b0-a6fe-79a11d1d4c80","type":"point","x":3931,"y":1171.162690632932},{"id":"74a16775-548d-4148-954c-77b48910d1cd","type":"point","x":3931,"y":1084.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","local_column_attnum":1,"referenced_table_uid":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced_column_attnum":1}},"becbd62a-cd2d-43ec-a2f3-db062547a7f9":{"id":"becbd62a-cd2d-43ec-a2f3-db062547a7f9","locked":true,"type":"onetomany","selected":false,"source":"a8da1c01-6c92-475b-9088-6449c7a7afaf","sourcePort":"46484358-e975-4399-9363-ca9793e2836e","target":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","targetPort":"c92deb7a-bc45-48d5-b04b-6f510efcb511","points":[{"id":"7bb63958-a4cf-4209-8f79-6a9eb6632ad3","type":"point","x":4299,"y":-685.390625},{"id":"f56c7aee-bade-4be4-9d1c-e437c767c5c5","type":"point","x":4299,"y":1110.65625},{"id":"fea7a54b-b070-45a7-9589-489bed472613","type":"point","x":4164,"y":1110.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","local_column_attnum":2,"referenced_table_uid":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced_column_attnum":1}},"cea5fca7-ac29-44f9-95a8-327bc43a3a34":{"id":"cea5fca7-ac29-44f9-95a8-327bc43a3a34","locked":true,"type":"onetomany","source":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","sourcePort":"310110c1-e97b-4398-b0f2-6e4ae8fddafa","target":"24283dfe-29c8-4c84-a491-ee873da3910f","targetPort":"6aacc928-0860-4b21-978d-6644da8505c6","points":[{"id":"383f0fb7-8aa6-4791-a642-8643d7ba0295","type":"point","x":3353.9997665631854,"y":1084.601514558461},{"id":"5dd96505-a98a-419c-b1b9-0b5658807745","type":"point","x":3353.9997665631854,"y":1110.65627636213},{"id":"21e7e03e-d236-45a8-8dbd-ca4015d3fa9c","type":"point","x":3391.0000213186195,"y":1110.65627636213}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"24283dfe-29c8-4c84-a491-ee873da3910f","local_column_attnum":2,"referenced_table_uid":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced_column_attnum":1}},"94df77f9-8383-4d21-b400-c79e85e88f57":{"id":"94df77f9-8383-4d21-b400-c79e85e88f57","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"dfdbde67-56d9-43f3-812f-58519c0da853","targetPort":"d236a9f4-98c6-43fb-ad1f-93bf6a42f88e","points":[{"id":"f43696f4-010b-4d2e-835d-6383356e15db","type":"point","x":2806,"y":-175.390625},{"id":"6b136051-da15-4892-9f2e-97c9b7a3e91d","type":"point","x":2806,"y":345.65626574732005},{"id":"80b82a83-1549-4b25-8ee3-754232cc5124","type":"point","x":2678.9999257853287,"y":345.65626574732005}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"dfdbde67-56d9-43f3-812f-58519c0da853","local_column_attnum":2,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"a306c93a-aa14-4d42-9269-4cd74b4a1876":{"id":"a306c93a-aa14-4d42-9269-4cd74b4a1876","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"875068af-e48a-4a77-ac71-f95b636467a6","targetPort":"3db395b8-04cc-48e3-9b55-60df28819a15","points":[{"id":"41ca9c05-b2d8-48cc-bcec-a45e057e94dd","type":"point","x":2806,"y":-175.390625},{"id":"c4b0ec99-1c23-424e-8921-f8a28423afec","type":"point","x":2806,"y":49.601514558461076},{"id":"009bd7c9-aadb-4074-9256-4e3a00b2a65b","type":"point","x":2678.9999257853287,"y":49.601514558461076}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"875068af-e48a-4a77-ac71-f95b636467a6","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"9fe77401-5732-41ae-a219-014760f53f94":{"id":"9fe77401-5732-41ae-a219-014760f53f94","locked":true,"type":"onetomany","source":"bbeca3af-e1d2-40af-a4b9-5bff1ecc2b17","sourcePort":"72600744-28e0-46c7-a353-7e6895425eee","target":"875068af-e48a-4a77-ac71-f95b636467a6","targetPort":"5abcf35e-2096-4cf7-a57d-4f6f58b48512","points":[{"id":"b789f237-cae4-4acf-922b-3225e495d2ed","type":"point","x":2409.0000850074757,"y":49.601562325104936},{"id":"56955177-2300-4df0-b8aa-3ea49a2b2d34","type":"point","x":2409.0000850074757,"y":75.64842671029555},{"id":"49c8898c-29af-49e2-bac1-e9c9aaf3f191","type":"point","x":2445.9999682445687,"y":75.64842671029555}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"875068af-e48a-4a77-ac71-f95b636467a6","local_column_attnum":2,"referenced_table_uid":"bbeca3af-e1d2-40af-a4b9-5bff1ecc2b17","referenced_column_attnum":1}},"b93f97e9-ee47-4ecf-900d-1696fb0a855d":{"id":"b93f97e9-ee47-4ecf-900d-1696fb0a855d","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","targetPort":"e22fa35a-954d-45a0-81b4-f05f0dae8c22","points":[{"id":"b793a811-4bc4-432c-964e-c9f0620393de","type":"point","x":2806,"y":-175.390625},{"id":"6216c54e-5e40-46f7-9384-eeaf02fd2dec","type":"point","x":2806,"y":-175.390625},{"id":"7cfda10f-d2ee-4880-aa45-e4869fd0957e","type":"point","x":2679,"y":-175.390625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"ed5d7103-c18c-40c8-975a-5c2e0256cad1":{"id":"ed5d7103-c18c-40c8-975a-5c2e0256cad1","locked":true,"type":"onetomany","source":"8e20d2d2-bfd0-4f03-924f-80578f960562","sourcePort":"d74048cb-206f-4ba7-8a44-f79fc535ffef","target":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","targetPort":"3616cb87-4b59-4041-a0c6-584db96f9346","points":[{"id":"cc22b2a6-a71c-4b22-b5cc-4c6512f1420a","type":"point","x":2409,"y":-175.390625},{"id":"fcbe9e06-6f09-4cc6-9f6f-7c756ab44f4c","type":"point","x":2409,"y":-149.34375},{"id":"16976a06-19e8-431b-9fe2-8dd00df01aa0","type":"point","x":2446,"y":-149.34375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","local_column_attnum":2,"referenced_table_uid":"8e20d2d2-bfd0-4f03-924f-80578f960562","referenced_column_attnum":1}},"b7b25b27-25c7-49b2-a331-7a816df6c063":{"id":"b7b25b27-25c7-49b2-a331-7a816df6c063","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","targetPort":"0d28db63-a5ca-4793-8b35-b29678672866","points":[{"id":"1b507889-2fb1-4bdb-bce6-bf19aad95d3a","type":"point","x":2806,"y":-175.390625},{"id":"e1ff2bfb-b94d-455b-9129-7a7ae3c00108","type":"point","x":2806,"y":-419.34369179344094},{"id":"0adec2dd-01ac-405d-bad8-c4b61bd219df","type":"point","x":2678.9999788593786,"y":-419.34369179344094}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","local_column_attnum":2,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"85201d1e-de31-40b3-a765-519330982299":{"id":"85201d1e-de31-40b3-a765-519330982299","locked":true,"type":"onetomany","source":"77bdd1d9-21d0-4481-b709-2f29aa01c1dd","sourcePort":"8ab26760-2dc9-4f1c-a4c4-060676890419","target":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","targetPort":"5f09584f-5418-41cd-9d28-f8cd7f3c2e6d","points":[{"id":"4af460b4-5fd0-4d98-a7d6-9d5794f800fa","type":"point","x":2409.0000319334276,"y":-445.3906357897048},{"id":"1a4f1a08-35f7-4304-9e93-4300c4576764","type":"point","x":2409.0000319334276,"y":-393.29685925267995},{"id":"ad6699fc-e244-419c-9bcb-9683d0280b90","type":"point","x":2446.0000213186177,"y":-393.29685925267995}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","local_column_attnum":3,"referenced_table_uid":"77bdd1d9-21d0-4481-b709-2f29aa01c1dd","referenced_column_attnum":1}},"c8e55302-d304-4e73-9e26-79e2480fba7b":{"id":"c8e55302-d304-4e73-9e26-79e2480fba7b","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","targetPort":"22e9a15d-b93c-4713-a5b6-6e25b62de358","points":[{"id":"e351103e-c44f-4650-bb33-5179480db703","type":"point","x":2806,"y":-175.390625},{"id":"0c11a1ac-7984-4627-bd65-47122933d409","type":"point","x":2806,"y":724.5401918443839},{"id":"88ab4c73-557c-44c2-a771-8b946c4c754b","type":"point","x":2679,"y":724.5401918443839},{"id":"42fee918-c7d9-4611-9bb8-c455aff744af","type":"point","x":2679,"y":724.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"4974960f-15c6-4535-bba9-721a89ded051":{"id":"4974960f-15c6-4535-bba9-721a89ded051","locked":true,"type":"onetomany","source":"3f2a50f8-9894-4730-b590-6f02ddca6546","sourcePort":"0528d5db-0e01-462d-8a62-64d48ed30b80","target":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","targetPort":"59db02c5-0ce9-4b8d-ac4e-a22aec288533","points":[{"id":"15b2e6df-1170-420f-a916-f23b4df00443","type":"point","x":2424,"y":724.609375},{"id":"17f2b2db-875c-4b5d-b73f-8871787fb375","type":"point","x":2424,"y":750.65625},{"id":"a719ea09-1b8e-458d-8705-7169a11a8f4d","type":"point","x":2446,"y":750.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","local_column_attnum":2,"referenced_table_uid":"3f2a50f8-9894-4730-b590-6f02ddca6546","referenced_column_attnum":1}},"7bda8586-baf0-4e5a-979d-9091c9a0b9b2":{"id":"7bda8586-baf0-4e5a-979d-9091c9a0b9b2","locked":true,"type":"onetomany","selected":false,"source":"6a0445dc-9262-485d-9bfc-73c374cfec00","sourcePort":"a2bc8378-e84d-4222-af3e-4e4a0fd80614","target":"58db1892-a838-48fb-961d-75e31e218b99","targetPort":"bb2f24cb-7886-4a23-9806-2683beb913f9","points":[{"id":"64e7efcb-24d3-4a11-ab0d-638f6fdb94f7","type":"point","x":3354,"y":1534.609375},{"id":"f9429ff0-1726-4a05-8bdc-a24d88bf9a2a","type":"point","x":3354,"y":1534.666389873735},{"id":"f16e212e-b00a-406c-a9e5-c9682e1df132","type":"point","x":3374.670191861391,"y":1534.666389873735},{"id":"2f1bb900-c45e-4b72-9651-47dc7091eabf","type":"point","x":3374.670191861391,"y":1605.65627636213},{"id":"ef413c58-fd4f-452d-b8a6-8d098471e6cd","type":"point","x":3391.0000213186195,"y":1605.65627636213}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"58db1892-a838-48fb-961d-75e31e218b99","local_column_attnum":2,"referenced_table_uid":"6a0445dc-9262-485d-9bfc-73c374cfec00","referenced_column_attnum":1}},"a2132da1-931c-463e-b001-7d74cd4d527f":{"id":"a2132da1-931c-463e-b001-7d74cd4d527f","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"6a0445dc-9262-485d-9bfc-73c374cfec00","targetPort":"c213b620-b26f-4e87-8eec-4471863de573","points":[{"id":"005515f5-9923-4d53-bd23-9c2962448295","type":"point","x":3039,"y":-175.390625},{"id":"adf470ed-5e64-4da4-a5d9-7c8ce5744d55","type":"point","x":3039,"y":1246.0447861379969},{"id":"806d8e4c-3483-40f1-8fd8-bf7401d1b59a","type":"point","x":3039.348729475587,"y":1246.0447861379969},{"id":"01daf95b-734c-47ad-b719-d0836e91339c","type":"point","x":3039.348729475587,"y":1534.609375},{"id":"bddb35e5-5870-4d41-ac94-60201b2d761a","type":"point","x":3121,"y":1534.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"6a0445dc-9262-485d-9bfc-73c374cfec00","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"ea0295f5-1f5e-4be7-9250-80d890f3e1d4":{"id":"ea0295f5-1f5e-4be7-9250-80d890f3e1d4","locked":true,"type":"onetomany","selected":false,"source":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","sourcePort":"cf6808ad-b609-4cf5-ae03-f70253c231c7","target":"d4fc2096-8445-4ec9-b636-f32b55ed9325","targetPort":"54c0962a-d379-4bea-868d-2414c57936aa","points":[{"id":"4996fced-e9a4-4e19-bc5f-dca3360f3e9b","type":"point","x":3354,"y":4.609375},{"id":"3c8bf2e5-1bac-43ce-a5ff-2cf28231a22b","type":"point","x":3353.431992750038,"y":4.609375},{"id":"60e61669-d978-4bbf-971d-56d71ae7067c","type":"point","x":3353.431992750038,"y":300.65625},{"id":"0bc45e69-e001-433d-9274-87d6789e0e33","type":"point","x":3354,"y":300.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"d4fc2096-8445-4ec9-b636-f32b55ed9325","local_column_attnum":2,"referenced_table_uid":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","referenced_column_attnum":1}},"833f096f-e69d-432e-8900-708118cd1cdc":{"id":"833f096f-e69d-432e-8900-708118cd1cdc","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"d4fc2096-8445-4ec9-b636-f32b55ed9325","targetPort":"693d18aa-12f4-42a5-b074-75ebe35026b9","points":[{"id":"5a6b49cd-6ff8-4bc8-aa64-27cbe29db12c","type":"point","x":3039,"y":-175.390625},{"id":"35cbfbc6-a223-4e32-a4b1-e4e5a18799cb","type":"point","x":3039,"y":274.609375},{"id":"08d20180-5ca8-4827-a04f-05b79f75316e","type":"point","x":3121,"y":274.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"d4fc2096-8445-4ec9-b636-f32b55ed9325","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"7e7e7aa4-e812-4f5d-bbd4-c944b1584731":{"id":"7e7e7aa4-e812-4f5d-bbd4-c944b1584731","locked":true,"type":"onetomany","source":"8e20d2d2-bfd0-4f03-924f-80578f960562","sourcePort":"75ae2e3f-1158-4bb9-9124-3a9002323010","target":"5b6d2934-95ab-4525-8d85-c5b5e58c357b","targetPort":"d8e4b23e-ea2b-4b61-ab9b-4762b95b81a4","points":[{"id":"93a84b08-40d3-4be2-94b1-59483824642f","type":"point","x":2176,"y":-175.390625},{"id":"cc9e3315-89c7-4c1e-bd76-48f72c917901","type":"point","x":2176,"y":-149.34373425267995},{"id":"fd5fb28a-9ef4-46c5-aaf1-28ebd1f1e704","type":"point","x":2138.9999788593786,"y":-149.34373425267995}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"5b6d2934-95ab-4525-8d85-c5b5e58c357b","local_column_attnum":2,"referenced_table_uid":"8e20d2d2-bfd0-4f03-924f-80578f960562","referenced_column_attnum":1}},"a91cb14e-8c71-4e91-be92-8d162078e309":{"id":"a91cb14e-8c71-4e91-be92-8d162078e309","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"51769507-cd47-4825-8561-af1d1d3a3792","targetPort":"fe381b5b-df2c-41d5-affd-af7d89954685","points":[{"id":"84342bec-d9fc-4563-95eb-0987b482d8ad","type":"point","x":3039,"y":-175.390625},{"id":"36a4a1e8-302c-478e-977d-2ac3391f21be","type":"point","x":3039,"y":-584.34375},{"id":"bc77963b-b461-4a13-bbe9-1dbbcd902d22","type":"point","x":3211,"y":-584.34375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"51769507-cd47-4825-8561-af1d1d3a3792","local_column_attnum":2,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"2db94f0f-f999-4a56-8643-6786f2685ea9":{"id":"2db94f0f-f999-4a56-8643-6786f2685ea9","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"1229197d-6829-4998-9eac-de70486a13dd","targetPort":"fbd72068-9da3-4430-a2cd-f3c877facd8b","points":[{"id":"65d55ee6-4036-4fe0-b4d6-18ae39ff92de","type":"point","x":2806,"y":-175.390625},{"id":"2fe12e88-e501-47bf-80a6-77037400615f","type":"point","x":2806,"y":544.609375},{"id":"dacc7315-6593-434e-8974-36c52c3b5cc2","type":"point","x":2679,"y":544.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"1229197d-6829-4998-9eac-de70486a13dd","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"9f5e570c-8929-413a-8964-dbb5b04804e2":{"id":"9f5e570c-8929-413a-8964-dbb5b04804e2","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"1229197d-6829-4998-9eac-de70486a13dd","targetPort":"8b7a90ea-56be-47cc-ae8c-ecde2b8548aa","points":[{"id":"662504af-f295-4f3c-b49d-2cab168e5376","type":"point","x":2806,"y":-175.390625},{"id":"d475dd5f-87fb-4961-a118-7c8768ba3ac3","type":"point","x":2806,"y":570.65625},{"id":"8d689fc7-1626-4fac-aee0-5bda7560a8a4","type":"point","x":2679,"y":570.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"1229197d-6829-4998-9eac-de70486a13dd","local_column_attnum":2,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"eb145a26-8512-4ebb-b547-a7da2afc08b9":{"id":"eb145a26-8512-4ebb-b547-a7da2afc08b9","locked":true,"type":"onetomany","source":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","sourcePort":"83ceeab9-0693-4ca1-a62d-21fee6298957","target":"a242e399-4642-4640-b31a-78e0e8363538","targetPort":"28c125ae-1071-4b3a-b4d8-72153369e60a","points":[{"id":"e2058935-ec8b-44b9-b160-f57caee16d5e","type":"point","x":3354.0001911555773,"y":1849.6093695177005},{"id":"5b447593-f06a-46d7-a164-0d097aa41aee","type":"point","x":3354.0001911555773,"y":1849.609375},{"id":"faa30dbc-5b18-4b4c-bf11-3af89bca0642","type":"point","x":3391,"y":1849.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"a242e399-4642-4640-b31a-78e0e8363538","local_column_attnum":1,"referenced_table_uid":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","referenced_column_attnum":1}},"8985c8ba-711f-4dce-a582-e23b8ccaa622":{"id":"8985c8ba-711f-4dce-a582-e23b8ccaa622","locked":true,"type":"onetomany","selected":false,"source":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","sourcePort":"83ceeab9-0693-4ca1-a62d-21fee6298957","target":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","targetPort":"3cf969e0-4d33-4611-bd7a-28d4c0adcf84","points":[{"id":"70f1ce81-d9ef-4fd3-9c16-7b7ccc5c1bfd","type":"point","x":3354.0001911555773,"y":1849.6093695177005},{"id":"c25f3353-1853-475d-8905-f2b6d8db02b4","type":"point","x":3392.455496663886,"y":1849.6093695177005},{"id":"f8feb101-8db0-4231-ad3e-a6512304b08d","type":"point","x":3392.455496663886,"y":1963.2356308288659},{"id":"ccb84058-5504-4db0-9f36-e5ee3d9ee907","type":"point","x":3661,"y":1963.2356308288659},{"id":"c6d6ac37-ad54-4957-b21d-b85ce38b9b93","type":"point","x":3661,"y":1875.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","local_column_attnum":2,"referenced_table_uid":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","referenced_column_attnum":1}},"3304af0e-1bf4-42fa-bef1-39d7b300ed53":{"id":"3304af0e-1bf4-42fa-bef1-39d7b300ed53","locked":true,"type":"onetomany","selected":false,"source":"a8da1c01-6c92-475b-9088-6449c7a7afaf","sourcePort":"096e2789-e9b1-4623-a2d8-332700cafd6a","target":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","targetPort":"723a914b-c127-4bb4-978e-a7a2bda8a17f","points":[{"id":"1afd9c2e-3729-4d30-b3cf-abb89e82ff71","type":"point","x":4066,"y":-685.390625},{"id":"ed6f42e2-642f-430d-af99-b41fa0b3ae5a","type":"point","x":4380.3159218448345,"y":-685.390625},{"id":"05275453-514a-4047-94b8-79869329e946","type":"point","x":4380.3159218448345,"y":1901.703125},{"id":"4c65accc-34fd-4b2f-933a-7cb86d2999c9","type":"point","x":3894,"y":1901.703125}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","local_column_attnum":3,"referenced_table_uid":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced_column_attnum":1}},"590c0185-f2ac-4c91-ad57-a202816f78ad":{"id":"590c0185-f2ac-4c91-ad57-a202816f78ad","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","targetPort":"fffc8052-8a23-4faf-ac07-b113624d7562","points":[{"id":"4f18fe0c-f511-4ad3-a947-1097d9638f01","type":"point","x":3039,"y":-175.390625},{"id":"5dc8fb52-eb6d-4cfa-8201-bc395cc27b70","type":"point","x":3039,"y":899.0486500671375},{"id":"1140aef4-af50-4535-a9e5-e7e564783b80","type":"point","x":3039.4011629221986,"y":899.0486500671375},{"id":"a6549642-0463-408f-b979-cabe7509a89c","type":"point","x":3039.4011629221986,"y":1850.000756195995},{"id":"8fc8a654-ec47-412f-864e-3d2063062382","type":"point","x":3120.9995967262294,"y":1850.000756195995},{"id":"e8cf688e-6b3a-4f05-885d-dfa449357edf","type":"point","x":3120.9995967262294,"y":1849.6093695177005}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"f2d1f789-fec6-44bf-a6f3-ffb958c19920":{"id":"f2d1f789-fec6-44bf-a6f3-ffb958c19920","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","targetPort":"e51832ba-be5e-4083-8228-e98f20b523b9","points":[{"id":"fe280b97-fecd-4843-a3a8-ca5313a43448","type":"point","x":2806,"y":-175.390625},{"id":"2aeab61a-6a78-42ae-8e96-966d0e61a51c","type":"point","x":2806,"y":904.609375},{"id":"19559634-8243-489b-8499-b197c6246509","type":"point","x":2694,"y":904.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"947ff200-256e-4654-a91e-1cbcea306050":{"id":"947ff200-256e-4654-a91e-1cbcea306050","locked":true,"type":"onetomany","source":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","sourcePort":"e51832ba-be5e-4083-8228-e98f20b523b9","target":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","targetPort":"81484ffd-5d53-4515-abff-af207b4f9c9f","points":[{"id":"e9f70cb5-6fb9-4404-b71a-76325930ab7c","type":"point","x":2694,"y":904.609375},{"id":"01a699e9-ad43-4cc1-87d4-b0ebaee91ae9","type":"point","x":2694,"y":956.703125}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","local_column_attnum":3,"referenced_table_uid":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","referenced_column_attnum":1}},"214e3221-187b-4711-ae7e-d00d6a2152af":{"id":"214e3221-187b-4711-ae7e-d00d6a2152af","locked":true,"type":"onetomany","source":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","sourcePort":"c54dc85c-d4f5-4139-9582-8ac3af3285b7","target":"3ee55e19-cad2-4d9c-a39b-93886e3797a6","targetPort":"ea209146-72fc-4578-9a70-d1cf6f452346","points":[{"id":"bccb346f-eaca-4bc2-8cfc-c22d8bf474b9","type":"point","x":3354,"y":2389.609375},{"id":"6cf588e5-10ad-45e7-9581-9da7ddb768d2","type":"point","x":3354,"y":2415.65625},{"id":"be7f7468-58a6-401b-bec9-475b66b63ae0","type":"point","x":3391,"y":2415.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"3ee55e19-cad2-4d9c-a39b-93886e3797a6","local_column_attnum":2,"referenced_table_uid":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","referenced_column_attnum":1}},"4abd9b5b-60c2-4610-9519-0e47a33baaf6":{"id":"4abd9b5b-60c2-4610-9519-0e47a33baaf6","locked":true,"type":"onetomany","selected":false,"source":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","sourcePort":"c54dc85c-d4f5-4139-9582-8ac3af3285b7","target":"17f97e21-d3df-4ca8-98bc-8804869e1642","targetPort":"54cef317-da88-4da7-b7ec-4b7d4cc588fa","points":[{"id":"c346a98a-6c40-473a-a413-69a47d02f9db","type":"point","x":3354,"y":2389.609375},{"id":"0f2dae7a-b8a7-4061-a6e0-f80991cbbfe5","type":"point","x":3354,"y":2556.0340494005127},{"id":"97775126-bcb7-450c-9d23-007343f82acc","type":"point","x":3931.0000213186213,"y":2556.0340494005127},{"id":"18762561-ef47-4a5c-9dbf-df673dfa9598","type":"point","x":3931.0000213186213,"y":2415.6562445177005}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"17f97e21-d3df-4ca8-98bc-8804869e1642","local_column_attnum":2,"referenced_table_uid":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","referenced_column_attnum":1}},"240ba017-058e-41eb-a5bf-c6cb3a4e4451":{"id":"240ba017-058e-41eb-a5bf-c6cb3a4e4451","locked":true,"type":"onetomany","selected":false,"source":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","sourcePort":"c54dc85c-d4f5-4139-9582-8ac3af3285b7","target":"901fffac-2b20-4177-af3d-767650c789ba","targetPort":"c0cd5be3-5f6b-42a7-ba69-b006cb9fb0ed","points":[{"id":"6beda9ce-6788-468f-aea2-822f4aa66e3c","type":"point","x":3354,"y":2389.609375},{"id":"ee25bee8-c5b2-4c5d-8f55-9c016fa791ba","type":"point","x":3354,"y":2557.4869867009334},{"id":"9f32f81f-9e19-4f5c-9047-4f117d7ab82f","type":"point","x":3644.8791418596566,"y":2557.4869867009334},{"id":"e5c0ce38-c77e-4809-ace3-71f1fd5ecaf1","type":"point","x":3644.8791418596566,"y":2415.6561595992225},{"id":"9b7bbec5-6406-44e9-8c29-d9ca36a928db","type":"point","x":3661.0000213186213,"y":2415.6561595992225}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"901fffac-2b20-4177-af3d-767650c789ba","local_column_attnum":2,"referenced_table_uid":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","referenced_column_attnum":1}},"7c43d283-c3d2-494d-95cf-ac92a8977a15":{"id":"7c43d283-c3d2-494d-95cf-ac92a8977a15","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","target":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","targetPort":"acc6924a-e054-467c-a0e4-346150c6a194","points":[{"id":"6bc1d775-9491-4a4f-834d-fc6055ce1b06","type":"point","x":3039,"y":-175.390625},{"id":"2ac42154-7b09-4aac-bc22-5c36055b0457","type":"point","x":3039,"y":934.5132844096221},{"id":"399f9dac-0ee6-4ba1-afc9-af760ad41f0a","type":"point","x":3039.3024329743585,"y":934.5132844096221},{"id":"23bc1245-95d7-42f4-b2e4-9f84f4cc95d6","type":"point","x":3039.3024329743585,"y":2389.609375},{"id":"9014f64d-7559-4811-8084-3f2013eb284f","type":"point","x":3121,"y":2389.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"a7bc8cd0-ed06-4ada-b938-05ebbb00b366":{"id":"a7bc8cd0-ed06-4ada-b938-05ebbb00b366","locked":true,"type":"onetomany","selected":false,"source":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","sourcePort":"c056f935-f663-4dbb-a2d7-da45d73d439c","target":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","targetPort":"8bd76640-7b82-4d78-a68e-2c44e7ed6d3c","points":[{"id":"7bcba3a8-849d-457c-81c3-290f3e9c682e","type":"point","x":2678.9999788593796,"y":1234.609396054725},{"id":"919303d6-ec33-4852-b87f-9ce6dd450560","type":"point","x":2717.37343007715,"y":1234.609396054725},{"id":"cb2f98b3-a89f-4609-9716-afe6cb210fb6","type":"point","x":2717.37343007715,"y":1695.65625},{"id":"1d533c98-2070-4928-bd22-38bbd2eccad1","type":"point","x":2679,"y":1695.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","local_column_attnum":2,"referenced_table_uid":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","referenced_column_attnum":1}},"caa8d582-a73e-472e-b125-1b03bd2bd27c":{"id":"caa8d582-a73e-472e-b125-1b03bd2bd27c","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","targetPort":"d4cdb3ee-7aa4-457d-9b15-acb8b4649515","points":[{"id":"3dec697a-f9fa-4ae9-a129-fe5d92f0f919","type":"point","x":2806,"y":-175.390625},{"id":"03aea96d-9444-44f0-a858-b79772407e06","type":"point","x":2806,"y":1669.609375},{"id":"6ee4cfb0-809a-4b3d-8465-76581e07c48d","type":"point","x":2679,"y":1669.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"299ec798-c3c2-4c2e-b926-e7874e72c0f1":{"id":"299ec798-c3c2-4c2e-b926-e7874e72c0f1","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","targetPort":"965efe94-3cf6-481a-b38d-ca1e3cdd786f","points":[{"id":"4ae08ad9-b491-4109-8e69-16393e63dafd","type":"point","x":2806,"y":-175.390625},{"id":"d2a38e2a-1d6d-4739-9a94-0adc4d221187","type":"point","x":2806,"y":2040.65625},{"id":"812226c9-63a3-498b-91f4-144ff1f080b5","type":"point","x":2454,"y":2040.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","local_column_attnum":2,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"8de5d890-9656-42de-bc97-a18c30f48918":{"id":"8de5d890-9656-42de-bc97-a18c30f48918","locked":true,"type":"onetomany","selected":false,"source":"05ccf9be-5907-4e89-b55f-6f328890bdd0","sourcePort":"a061d917-ed8d-4603-932b-2842217d8cd1","target":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","targetPort":"3fccb262-fa3c-44c2-b32c-34471deb2b5d","points":[{"id":"fbfa284b-c655-45e7-86d7-83863c665e71","type":"point","x":2694,"y":1864.609375},{"id":"206bc5e6-585d-4376-ac0f-cf6d0f396e58","type":"point","x":2722.7915685076096,"y":1864.609375},{"id":"6ed535e5-056f-4feb-bf62-45d876625d7c","type":"point","x":2722.7915685076096,"y":2014.609375},{"id":"82ffe3f3-2e76-4304-899c-2ad27536c5f9","type":"point","x":2454,"y":2014.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","local_column_attnum":1,"referenced_table_uid":"05ccf9be-5907-4e89-b55f-6f328890bdd0","referenced_column_attnum":1}},"c59988fa-9734-4c71-bca8-7388db7f4e2e":{"id":"c59988fa-9734-4c71-bca8-7388db7f4e2e","locked":true,"type":"onetomany","selected":false,"source":"05ccf9be-5907-4e89-b55f-6f328890bdd0","sourcePort":"64c53b8c-e951-4576-bfb3-f591ae6f3230","target":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","targetPort":"12e28ff8-62dc-46dc-aa34-e9bf13516da5","points":[{"id":"1911f7e4-4208-46f4-b7b6-64798ea1dc64","type":"point","x":2461,"y":1864.609375},{"id":"3b0e2e3d-ef48-4a1c-8a02-f3dd1684d34f","type":"point","x":2461,"y":1864.3467589671393},{"id":"06710cf7-2a21-4e48-b92a-ac44d81bfcd0","type":"point","x":2379,"y":1864.3467589671393},{"id":"e3068b26-a6bc-4043-be97-7bcec75bc86d","type":"point","x":2379,"y":1770.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","local_column_attnum":2,"referenced_table_uid":"05ccf9be-5907-4e89-b55f-6f328890bdd0","referenced_column_attnum":1}},"b15e8c80-b28d-44e6-9083-8c8425afab67":{"id":"b15e8c80-b28d-44e6-9083-8c8425afab67","locked":true,"type":"onetomany","selected":false,"source":"a8da1c01-6c92-475b-9088-6449c7a7afaf","sourcePort":"096e2789-e9b1-4623-a2d8-332700cafd6a","target":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","targetPort":"329b235c-b630-403b-a6a4-2c92b61d7452","points":[{"id":"9e6b74cc-9555-489c-9e3a-e7a6cffe4f92","type":"point","x":4066,"y":-685.390625},{"id":"890e1d52-360a-4305-80b1-0128b9e480bd","type":"point","x":4066,"y":-811.7357173000626},{"id":"d46322bf-c38d-46cb-ba2e-d1534ec4dc78","type":"point","x":1847.2782430687682,"y":-811.7357173000626},{"id":"a15ba119-e85c-4f3f-a421-9c4037733e49","type":"point","x":1847.2782430687682,"y":1622.2941810557577},{"id":"b29f2129-f22d-4b27-8493-c7f52e1f7957","type":"point","x":2412.0596027252436,"y":1622.2941810557577},{"id":"60ac30bd-82ed-4b74-8384-9e2c476a3247","type":"point","x":2412.0596027252436,"y":1796.703125},{"id":"8e2fa60b-fa4f-4bdf-be57-b9dd31e6b873","type":"point","x":2379,"y":1796.703125}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","local_column_attnum":3,"referenced_table_uid":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced_column_attnum":1}},"8d50def6-1bb0-478a-aeaa-c97ad2d38d7f":{"id":"8d50def6-1bb0-478a-aeaa-c97ad2d38d7f","locked":true,"type":"onetomany","source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"05ccf9be-5907-4e89-b55f-6f328890bdd0","targetPort":"a061d917-ed8d-4603-932b-2842217d8cd1","points":[{"id":"18577b98-912c-4cac-a1f5-4a36425ddb3a","type":"point","x":2806,"y":-175.390625},{"id":"2e4db865-e055-4cbd-ac32-359bc97efd3f","type":"point","x":2806,"y":1864.609375},{"id":"c4b51adc-a329-41b9-8072-077615f62f87","type":"point","x":2694,"y":1864.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"05ccf9be-5907-4e89-b55f-6f328890bdd0","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}},"4294a055-08ed-4b67-8456-a97e52c562b6":{"id":"4294a055-08ed-4b67-8456-a97e52c562b6","locked":true,"type":"onetomany","selected":false,"source":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","sourcePort":"d4cdb3ee-7aa4-457d-9b15-acb8b4649515","target":"05ccf9be-5907-4e89-b55f-6f328890bdd0","targetPort":"adc4fbe2-85bb-42e4-8309-bca19810d9e0","points":[{"id":"d09e670a-4f7a-49dd-bd5d-819b55a5bd44","type":"point","x":2679,"y":1669.609375},{"id":"cc1c13aa-fa3e-4429-9cb0-78c64484fbda","type":"point","x":2768.8857378469884,"y":1669.609375},{"id":"580f9155-3166-4492-aaaa-5bfc587a4add","type":"point","x":2768.8857378469884,"y":1890.65625},{"id":"c5bc67aa-70d2-4cf2-a54f-170a91aed461","type":"point","x":2694,"y":1890.65625}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"05ccf9be-5907-4e89-b55f-6f328890bdd0","local_column_attnum":2,"referenced_table_uid":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","referenced_column_attnum":1}},"55ee0e0a-6cef-4d72-89c8-8044e4841c9e":{"id":"55ee0e0a-6cef-4d72-89c8-8044e4841c9e","locked":true,"type":"onetomany","selected":false,"source":"a2068488-3358-4fdb-a4d6-e747d8711d93","sourcePort":"2bc07930-2808-4dbd-aac3-a857c96f491c","target":"3f2a50f8-9894-4730-b590-6f02ddca6546","targetPort":"0528d5db-0e01-462d-8a62-64d48ed30b80","points":[{"id":"3b9f0c80-1918-4f56-87a8-d5e382091537","type":"point","x":2806,"y":-175.390625},{"id":"fc4e8adc-c9f1-4e39-a25d-4ee20dda31d4","type":"point","x":2806,"y":614.6475722344039},{"id":"a64f4d15-2db8-4ae0-9bb3-ca7da80bbf49","type":"point","x":2424,"y":614.6475722344039},{"id":"e8ceda3c-c7d9-4963-beb1-c49b1c042d44","type":"point","x":2424,"y":724.609375}],"labels":[],"width":1,"color":"gray","curvyness":50,"selectedColor":"rgb(0,192,255)","data":{"local_table_uid":"3f2a50f8-9894-4730-b590-6f02ddca6546","local_column_attnum":1,"referenced_table_uid":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced_column_attnum":1}}}},{"id":"268d69e7-7267-447e-ae8d-8c8a4ac27854","type":"diagram-nodes","isSvg":false,"transformed":true,"models":{"ac77f15d-7c86-4420-b348-416a7eb0a59d":{"id":"ac77f15d-7c86-4420-b348-416a7eb0a59d","type":"table","selected":false,"x":2835,"y":-720,"ports":[],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":[],"otherInfo":{"data":{"columns":[{"name":"migration_id","atttypid":1043,"attlen":"150","attnum":1,"attndims":0,"atttypmod":154,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(150)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"__EFMigrationsHistory","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"product_version","atttypid":1043,"attlen":"32","attnum":2,"attndims":0,"atttypmod":36,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(32)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"__EFMigrationsHistory","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"__EFMigrationsHistory","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147461,"name":"pk___ef_migrations_history","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"migration_id"}],"include":[]}],"unique_constraint":[],"foreign_key":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"4aaa1c42-6cb1-4f67-aa76-eb821a808476":{"id":"4aaa1c42-6cb1-4f67-aa76-eb821a808476","type":"table","selected":false,"x":3150,"y":-315,"ports":[{"id":"f53489a2-72a2-432b-964b-286cc312c046","type":"onetomany","x":3151,"y":-220.390625,"name":"coll-port-1-left","alignment":"left","parentNode":"4aaa1c42-6cb1-4f67-aa76-eb821a808476","links":["8d6d50c6-db8c-4274-aac5-1a6cd293d2d1"]},{"id":"61cdec86-3a6f-4d6b-9bd1-5e59988abe33","type":"onetomany","x":3324,"y":-220.390625,"name":"coll-port-1-right","alignment":"right","parentNode":"4aaa1c42-6cb1-4f67-aa76-eb821a808476","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["f53489a2-72a2-432b-964b-286cc312c046","61cdec86-3a6f-4d6b-9bd1-5e59988abe33"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"actions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"actions","atttypid":147498,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"action_enum","displaytypname":"action_enum","cltype":"action_enum","inheritedfrom":null,"inheritedid":null,"elemoid":147498,"typnspname":"public","defaultstorage":"p","description":"Количество действий","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"actions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["action_enum"]},{"name":"category","atttypid":147468,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"action_category","displaytypname":"action_category","cltype":"action_category","inheritedfrom":null,"inheritedid":null,"elemoid":147468,"typnspname":"public","defaultstorage":"p","description":"Категория действия","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"actions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["action_category"]},{"name":"extra","atttypid":25,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"text","displaytypname":"text","cltype":"text","inheritedfrom":null,"inheritedid":null,"elemoid":25,"typnspname":"pg_catalog","defaultstorage":"x","description":"Дополнительная информация","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"actions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","name","regclass","text"]}],"name":"actions","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147888,"name":"pk_actions","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_actions_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"actions","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_actions","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe":{"id":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","type":"table","selected":false,"x":3150,"y":-90,"ports":[{"id":"c8090663-57a2-4151-aaac-03510f75e7fb","type":"onetomany","x":3151,"y":4.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","links":["6db4d1e5-4aa5-48c9-998d-def3f027b375"]},{"id":"cf6808ad-b609-4cf5-ae03-f70253c231c7","type":"onetomany","x":3324,"y":4.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","links":["ea0295f5-1f5e-4be7-9250-80d890f3e1d4","123ac55b-4993-4a89-a428-d389597adc44","99fade7e-8094-4d92-abd3-cad8afeb96b0","bd190116-c39f-4051-8aa4-7de09adf004b"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["c8090663-57a2-4151-aaac-03510f75e7fb","cf6808ad-b609-4cf5-ae03-f70253c231c7"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestries","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"size","atttypid":147725,"attlen":null,"attnum":2,"attndims":1,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"size_enum[]","displaytypname":"size_enum[]","cltype":"size_enum[]","inheritedfrom":null,"inheritedid":null,"elemoid":147726,"typnspname":"public","defaultstorage":"x","description":"Размеры родословной","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestries","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["size_enum[]"]},{"name":"hp","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Количество очков здоровья","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestries","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"speed","atttypid":23,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Скорость передвижения","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestries","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"vision_enum","atttypid":147832,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"vision_enum","displaytypname":"vision_enum","cltype":"vision_enum","inheritedfrom":null,"inheritedid":null,"elemoid":147832,"typnspname":"public","defaultstorage":"p","description":"Тип зрения","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestries","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["vision_enum"]}],"name":"ancestries","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147900,"name":"pk_ancestries","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_ancestries_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"ancestries","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_ancestries","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"e74444e6-b9dc-4133-a959-281105f1481e":{"id":"e74444e6-b9dc-4133-a959-281105f1481e","type":"table","selected":false,"x":3420,"y":-90,"ports":[{"id":"e4ab8da0-f988-4208-b68d-4dd8c79e4451","type":"onetomany","x":3421,"y":30.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"e74444e6-b9dc-4133-a959-281105f1481e","links":["123ac55b-4993-4a89-a428-d389597adc44"]},{"id":"f0fd7a43-3a65-4026-8e55-14133519999f","type":"onetomany","x":3594,"y":30.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"e74444e6-b9dc-4133-a959-281105f1481e","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["e4ab8da0-f988-4208-b68d-4dd8c79e4451","f0fd7a43-3a65-4026-8e55-14133519999f"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"ancestry_boosts","is_view_only":false,"attcompression":null,"seqrelid":148107,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"ancestry_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_boosts","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"boost","atttypid":16,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_boosts","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]},{"name":"value","atttypid":147542,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"attribute_values","displaytypname":"attribute_values","cltype":"attribute_values","inheritedfrom":null,"inheritedid":null,"elemoid":147542,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_boosts","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["attribute_values"]}],"name":"ancestry_boosts","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148111,"name":"pk_ancestry_boosts","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_ancestry_boosts_ancestries_ancestry_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147895,"fknsp":"public","fktab":"ancestry_boosts","refnspoid":2200,"refnsp":"public","reftab":"ancestries","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"ancestry_id","references":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","referenced":"id","references_table_name":"public.ancestries"}],"remote_schema":"public","remote_table":"ancestries","coveringindex":"ix_ancestry_boosts_ancestry_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"b0f18bbe-e381-4068-a1f6-10b10af52863":{"id":"b0f18bbe-e381-4068-a1f6-10b10af52863","type":"table","selected":false,"x":3690,"y":-90,"ports":[{"id":"3990097d-cb34-43ec-8bd4-d02eb1e8a544","type":"onetomany","x":3691,"y":4.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"b0f18bbe-e381-4068-a1f6-10b10af52863","links":["99fade7e-8094-4d92-abd3-cad8afeb96b0"]},{"id":"4054882f-4bdc-4b38-9c6c-d63c0a47e319","type":"onetomany","x":3864,"y":4.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"b0f18bbe-e381-4068-a1f6-10b10af52863","links":[]},{"id":"189ce05d-db64-475b-82d3-c065c7d34bbc","type":"onetomany","x":3691,"y":30.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"b0f18bbe-e381-4068-a1f6-10b10af52863","links":["40e17158-2d5a-43f2-87fc-1e2ae5bf55a9"]},{"id":"213dc0c6-27b7-410c-a4a9-5a25ddff4173","type":"onetomany","x":3864,"y":30.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"b0f18bbe-e381-4068-a1f6-10b10af52863","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["3990097d-cb34-43ec-8bd4-d02eb1e8a544","4054882f-4bdc-4b38-9c6c-d63c0a47e319","189ce05d-db64-475b-82d3-c065c7d34bbc","213dc0c6-27b7-410c-a4a9-5a25ddff4173"],"otherInfo":{"data":{"columns":[{"name":"ancestry_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_items","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"item_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_items","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"level","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_items","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"ancestry_items","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148121,"name":"pk_ancestry_items","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"ancestry_id"},{"column":"item_id"}],"include":[]}],"foreign_key":[{"name":"fk_ancestry_items_ancestries_ancestry_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147895,"fknsp":"public","fktab":"ancestry_items","refnspoid":2200,"refnsp":"public","reftab":"ancestries","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"ancestry_id","references":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","referenced":"id","references_table_name":"public.ancestries"}],"remote_schema":"public","remote_table":"ancestries","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_ancestry_items_entities_item_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"ancestry_items","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"item_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"ix_ancestry_items_item_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"452d646b-65fa-41a7-9c4a-391e337cb25a":{"id":"452d646b-65fa-41a7-9c4a-391e337cb25a","type":"table","selected":false,"x":3960,"y":-90,"ports":[{"id":"c59cdb15-8f1b-49ee-8a09-615eabcb22d4","type":"onetomany","x":3961,"y":4.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"452d646b-65fa-41a7-9c4a-391e337cb25a","links":["bd190116-c39f-4051-8aa4-7de09adf004b"]},{"id":"30c10b3d-6b77-4245-9d65-b7e2238bfca7","type":"onetomany","x":4134,"y":4.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"452d646b-65fa-41a7-9c4a-391e337cb25a","links":[]},{"id":"1c107425-8988-439d-9930-28891d93c14f","type":"onetomany","x":3961,"y":30.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"452d646b-65fa-41a7-9c4a-391e337cb25a","links":["38d3c083-3048-4953-83db-a4d64e93bcdd"]},{"id":"311ab279-5893-4b94-8c97-524959c1397c","type":"onetomany","x":4134,"y":30.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"452d646b-65fa-41a7-9c4a-391e337cb25a","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["c59cdb15-8f1b-49ee-8a09-615eabcb22d4","30c10b3d-6b77-4245-9d65-b7e2238bfca7","1c107425-8988-439d-9930-28891d93c14f","311ab279-5893-4b94-8c97-524959c1397c"],"otherInfo":{"data":{"columns":[{"name":"ancestry_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_languages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"language_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_languages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"is_additional","atttypid":16,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ancestry_languages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]}],"name":"ancestry_languages","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148325,"name":"pk_ancestry_languages","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"ancestry_id"},{"column":"language_id"}],"include":[]}],"foreign_key":[{"name":"fk_ancestry_languages_ancestries_ancestry_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147895,"fknsp":"public","fktab":"ancestry_languages","refnspoid":2200,"refnsp":"public","reftab":"ancestries","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"ancestry_id","references":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","referenced":"id","references_table_name":"public.ancestries"}],"remote_schema":"public","remote_table":"ancestries","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_ancestry_languages_languages_language_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147972,"fknsp":"public","fktab":"ancestry_languages","refnspoid":2200,"refnsp":"public","reftab":"languages","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"language_id","references":"51769507-cd47-4825-8561-af1d1d3a3792","referenced":"id","references_table_name":"public.languages"}],"remote_schema":"public","remote_table":"languages","coveringindex":"ix_ancestry_languages_language_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"be6b1ff4-866e-489c-aa76-ff99117f4121":{"id":"be6b1ff4-866e-489c-aa76-ff99117f4121","type":"table","selected":false,"x":3420,"y":360,"ports":[{"id":"9aa88db2-5cb4-4d12-850a-83d57fe87969","type":"onetomany","x":3421,"y":454.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"be6b1ff4-866e-489c-aa76-ff99117f4121","links":["37f29f5a-6288-45b2-97dd-85e1ba9a231b"]},{"id":"eb6c81e9-a751-467d-9586-a4baa55eccb3","type":"onetomany","x":3594,"y":454.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"be6b1ff4-866e-489c-aa76-ff99117f4121","links":[]},{"id":"ba694fa9-d2b3-4eb3-8a57-8d7290a5ba42","type":"onetomany","x":3421,"y":480.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"be6b1ff4-866e-489c-aa76-ff99117f4121","links":["f80771ff-8543-4bda-8a33-cf88e5f5240c"]},{"id":"89d4a670-a3f2-4f35-8b55-72deaa99d770","type":"onetomany","x":3594,"y":480.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"be6b1ff4-866e-489c-aa76-ff99117f4121","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["9aa88db2-5cb4-4d12-850a-83d57fe87969","eb6c81e9-a751-467d-9586-a4baa55eccb3","ba694fa9-d2b3-4eb3-8a57-8d7290a5ba42","89d4a670-a3f2-4f35-8b55-72deaa99d770"],"otherInfo":{"data":{"columns":[{"name":"archetype_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"archetype_feats","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"feat_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"archetype_feats","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"archetype_feats","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148455,"name":"pk_archetype_feats","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"archetype_id"},{"column":"feat_id"}],"include":[]}],"foreign_key":[{"name":"fk_archetype_feats_archetypes_archetype_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":148294,"fknsp":"public","fktab":"archetype_feats","refnspoid":2200,"refnsp":"public","reftab":"archetypes","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"archetype_id","references":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","referenced":"id","references_table_name":"public.archetypes"}],"remote_schema":"public","remote_table":"archetypes","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_archetype_feats_feats_feat_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147960,"fknsp":"public","fktab":"archetype_feats","refnspoid":2200,"refnsp":"public","reftab":"feats","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"feat_id","references":"6a0445dc-9262-485d-9bfc-73c374cfec00","referenced":"id","references_table_name":"public.feats"}],"remote_schema":"public","remote_table":"feats","coveringindex":"ix_archetype_feats_feat_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"8357a1fe-18b1-45d9-aac3-1d10a409ef5c":{"id":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","type":"table","selected":false,"x":3150,"y":360,"ports":[{"id":"db1eaeaf-e7ed-46cf-ae93-29ee92cfac74","type":"onetomany","x":3151,"y":454.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","links":["901d0a82-4c10-45db-9a2f-5ab11ec4fd51"]},{"id":"7b718e11-0bf2-48da-9a27-1fd92bc86538","type":"onetomany","x":3324,"y":454.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","links":["37f29f5a-6288-45b2-97dd-85e1ba9a231b"]},{"id":"0430fa37-d5b2-4993-ac24-864cb5e285cc","type":"onetomany","x":3151,"y":506.703125,"name":"coll-port-3-left","alignment":"left","parentNode":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","links":[]},{"id":"fe6ca2f0-0347-48d9-bbd2-14240255cc22","type":"onetomany","x":3324,"y":506.703125,"name":"coll-port-3-right","alignment":"right","parentNode":"8357a1fe-18b1-45d9-aac3-1d10a409ef5c","links":["0e17425e-9a46-419b-9977-5f4327bc6325"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["db1eaeaf-e7ed-46cf-ae93-29ee92cfac74","7b718e11-0bf2-48da-9a27-1fd92bc86538","0430fa37-d5b2-4993-ac24-864cb5e285cc","fe6ca2f0-0347-48d9-bbd2-14240255cc22"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"archetypes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"level","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Уровень","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"archetypes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"dedication_feat_id","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"ID посвящения","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"archetypes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"extra","atttypid":25,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"text","displaytypname":"text","cltype":"text","inheritedfrom":null,"inheritedid":null,"elemoid":25,"typnspname":"pg_catalog","defaultstorage":"x","description":"Дополнительная информация","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"archetypes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","name","regclass","text"]}],"name":"archetypes","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148299,"name":"pk_archetypes","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_archetypes_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"archetypes","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_archetypes","autoindex":true,"hasindex":true},{"name":"fk_archetypes_feats_dedication_feat_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[3],"confkey":[1],"confrelid":147960,"fknsp":"public","fktab":"archetypes","refnspoid":2200,"refnsp":"public","reftab":"feats","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"dedication_feat_id","references":"6a0445dc-9262-485d-9bfc-73c374cfec00","referenced":"id","references_table_name":"public.feats"}],"remote_schema":"public","remote_table":"feats","coveringindex":"ix_archetypes_dedication_feat_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"b65ec3a2-384f-4e9b-a535-4c3228a4935e":{"id":"b65ec3a2-384f-4e9b-a535-4c3228a4935e","type":"table","selected":false,"x":3690,"y":585,"ports":[{"id":"14d8d2b9-9a3d-432b-a248-55c1e0d866e5","type":"onetomany","x":3691,"y":705.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"b65ec3a2-384f-4e9b-a535-4c3228a4935e","links":["faf9a083-347c-4ff4-9359-270c4d40fede"]},{"id":"7c811178-93c0-4620-867d-f0f7b78ffb5c","type":"onetomany","x":3864,"y":705.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"b65ec3a2-384f-4e9b-a535-4c3228a4935e","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["14d8d2b9-9a3d-432b-a248-55c1e0d866e5","7c811178-93c0-4620-867d-f0f7b78ffb5c"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"background_boosts","is_view_only":false,"attcompression":null,"seqrelid":148148,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"background_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_boosts","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"attribute","atttypid":147542,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"attribute_values","displaytypname":"attribute_values","cltype":"attribute_values","inheritedfrom":null,"inheritedid":null,"elemoid":147542,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_boosts","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["attribute_values"]}],"name":"background_boosts","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148152,"name":"pk_background_boosts","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_background_boosts_backgrounds_background_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147907,"fknsp":"public","fktab":"background_boosts","refnspoid":2200,"refnsp":"public","reftab":"backgrounds","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"background_id","references":"3c985425-5b89-48c2-995d-e17e0da94b8a","referenced":"id","references_table_name":"public.backgrounds"}],"remote_schema":"public","remote_table":"backgrounds","coveringindex":"ix_background_boosts_background_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4":{"id":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","type":"table","selected":false,"x":3420,"y":585,"ports":[{"id":"4da6f1e8-c9ed-4f7f-82b9-3a10e2132fd5","type":"onetomany","x":3421,"y":679.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","links":["15700432-11ef-4229-91d3-dc8e826a7db4"]},{"id":"7ddf5b70-b787-40bc-82e5-d3a5f050df81","type":"onetomany","x":3594,"y":679.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","links":[]},{"id":"f3dc238a-c10e-4c6e-985c-e242baf58eb5","type":"onetomany","x":3421,"y":705.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","links":["06f76c4f-b3d7-4f32-b300-5246ae1ab14d"]},{"id":"c2529454-4f68-418c-9f0a-e67da51433b6","type":"onetomany","x":3594,"y":705.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"77ab8e49-77ce-45e9-a9c6-c7774ffde3c4","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["4da6f1e8-c9ed-4f7f-82b9-3a10e2132fd5","7ddf5b70-b787-40bc-82e5-d3a5f050df81","f3dc238a-c10e-4c6e-985c-e242baf58eb5","c2529454-4f68-418c-9f0a-e67da51433b6"],"otherInfo":{"data":{"columns":[{"name":"background_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_items","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"entity_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_items","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"level","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_items","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"background_items","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148162,"name":"pk_background_items","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"background_id"},{"column":"entity_id"}],"include":[]}],"foreign_key":[{"name":"fk_background_items_backgrounds_background_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147907,"fknsp":"public","fktab":"background_items","refnspoid":2200,"refnsp":"public","reftab":"backgrounds","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"background_id","references":"3c985425-5b89-48c2-995d-e17e0da94b8a","referenced":"id","references_table_name":"public.backgrounds"}],"remote_schema":"public","remote_table":"backgrounds","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_background_items_entities_entity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"background_items","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entity_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"ix_background_items_entity_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"e12ffcd1-3205-4161-b9dd-01c8a080b8d3":{"id":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","type":"table","selected":false,"x":3960,"y":585,"ports":[{"id":"23f20771-e4ee-4da8-85f9-2a8ee5b2509e","type":"onetomany","x":3961,"y":705.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","links":["c8a7105a-4205-450d-b208-2fb4e0bd0d41"]},{"id":"586da595-a7d1-435f-a0e5-c8f50bd9e10f","type":"onetomany","x":4134,"y":705.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","links":[]},{"id":"f1bdaeaa-f06c-4fe0-af04-eeaf2ff55ae6","type":"onetomany","x":3961,"y":731.703125,"name":"coll-port-3-left","alignment":"left","parentNode":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","links":[]},{"id":"7ed057bd-dec9-4f7e-a130-77a97df6a900","type":"onetomany","x":4134,"y":731.703125,"name":"coll-port-3-right","alignment":"right","parentNode":"e12ffcd1-3205-4161-b9dd-01c8a080b8d3","links":["6981aac6-5e9a-4e34-aea2-1f9fe83e33e8"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["23f20771-e4ee-4da8-85f9-2a8ee5b2509e","586da595-a7d1-435f-a0e5-c8f50bd9e10f","f1bdaeaa-f06c-4fe0-af04-eeaf2ff55ae6","7ed057bd-dec9-4f7e-a130-77a97df6a900"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_skills","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"background_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_skills","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"training_id","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_skills","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"rank","atttypid":147810,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"training_rank","displaytypname":"training_rank","cltype":"training_rank","inheritedfrom":null,"inheritedid":null,"elemoid":147810,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"background_skills","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["training_rank"]}],"name":"background_skills","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148177,"name":"pk_background_skills","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_background_skills_backgrounds_background_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147907,"fknsp":"public","fktab":"background_skills","refnspoid":2200,"refnsp":"public","reftab":"backgrounds","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"background_id","references":"3c985425-5b89-48c2-995d-e17e0da94b8a","referenced":"id","references_table_name":"public.backgrounds"}],"remote_schema":"public","remote_table":"backgrounds","coveringindex":"ix_background_skills_background_id","autoindex":true,"hasindex":true},{"name":"fk_background_skills_trainings_training_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[3],"confkey":[1],"confrelid":147876,"fknsp":"public","fktab":"background_skills","refnspoid":2200,"refnsp":"public","reftab":"trainings","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"training_id","references":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced":"id","references_table_name":"public.trainings"}],"remote_schema":"public","remote_table":"trainings","coveringindex":"ix_background_skills_training_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"3c985425-5b89-48c2-995d-e17e0da94b8a":{"id":"3c985425-5b89-48c2-995d-e17e0da94b8a","type":"table","selected":false,"x":3150,"y":585,"ports":[{"id":"5227f039-d673-4a8f-a0fa-29258fca9589","type":"onetomany","x":3151,"y":679.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"3c985425-5b89-48c2-995d-e17e0da94b8a","links":["cccdb52f-1e67-4bdc-99df-eca62e6b9a4b"]},{"id":"b366da8e-22d0-4f5e-9d8f-14a31ff7bf40","type":"onetomany","x":3324,"y":679.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"3c985425-5b89-48c2-995d-e17e0da94b8a","links":["faf9a083-347c-4ff4-9359-270c4d40fede","15700432-11ef-4229-91d3-dc8e826a7db4","c8a7105a-4205-450d-b208-2fb4e0bd0d41"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["5227f039-d673-4a8f-a0fa-29258fca9589","b366da8e-22d0-4f5e-9d8f-14a31ff7bf40"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"backgrounds","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"backgrounds","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147910,"name":"pk_backgrounds","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_backgrounds_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"backgrounds","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_backgrounds","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"4c2de672-7c04-4bde-8477-a9eb56d1652a":{"id":"4c2de672-7c04-4bde-8477-a9eb56d1652a","type":"table","selected":false,"x":2205,"y":975,"ports":[{"id":"aecfea9e-9f4e-4508-aeee-18b41fa5a67c","type":"onetomany","x":2206,"y":1095.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"4c2de672-7c04-4bde-8477-a9eb56d1652a","links":[]},{"id":"f023d2fc-71b7-411b-971c-aadce51f6d2e","type":"onetomany","x":2379,"y":1095.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"4c2de672-7c04-4bde-8477-a9eb56d1652a","links":["8428c95b-a620-4c51-ba6c-b9ca02f3ba1d"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["aecfea9e-9f4e-4508-aeee-18b41fa5a67c","f023d2fc-71b7-411b-971c-aadce51f6d2e"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"class_key_abilities","is_view_only":false,"attcompression":null,"seqrelid":148189,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"class_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_key_abilities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"value","atttypid":147542,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"attribute_values","displaytypname":"attribute_values","cltype":"attribute_values","inheritedfrom":null,"inheritedid":null,"elemoid":147542,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_key_abilities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["attribute_values"]}],"name":"class_key_abilities","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148193,"name":"pk_class_key_abilities","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_class_key_abilities_classes_class_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147917,"fknsp":"public","fktab":"class_key_abilities","refnspoid":2200,"refnsp":"public","reftab":"classes","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"class_id","references":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","referenced":"id","references_table_name":"public.classes"}],"remote_schema":"public","remote_table":"classes","coveringindex":"ix_class_key_abilities_class_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"43a82fc9-ffd3-45b7-b120-479c764dc03e":{"id":"43a82fc9-ffd3-45b7-b120-479c764dc03e","type":"table","selected":false,"x":2205,"y":1170,"ports":[{"id":"7f6cd6cd-70d2-4ce9-b93d-e02e44075de0","type":"onetomany","x":2205.9999151705206,"y":1290.6561595992225,"name":"coll-port-2-left","alignment":"left","parentNode":"43a82fc9-ffd3-45b7-b120-479c764dc03e","links":[]},{"id":"9344f216-adaf-4dcb-9de3-347bbc5263b2","type":"onetomany","x":2378.9998727112816,"y":1290.6561595992225,"name":"coll-port-2-right","alignment":"right","parentNode":"43a82fc9-ffd3-45b7-b120-479c764dc03e","links":["c0f8a094-1a68-4a0d-8b05-038b4ed69daf"]},{"id":"76ce52be-0a26-4619-b7f3-85a9f0805411","type":"onetomany","x":2205.9999151705206,"y":1342.749983902891,"name":"coll-port-4-left","alignment":"left","parentNode":"43a82fc9-ffd3-45b7-b120-479c764dc03e","links":[]},{"id":"97c90db8-6de0-4cf1-9355-6c7e0c9f1cbb","type":"onetomany","x":2378.9998727112816,"y":1342.749983902891,"name":"coll-port-4-right","alignment":"right","parentNode":"43a82fc9-ffd3-45b7-b120-479c764dc03e","links":["09572470-4f3e-4090-9bb9-f291dfafc413"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["7f6cd6cd-70d2-4ce9-b93d-e02e44075de0","9344f216-adaf-4dcb-9de3-347bbc5263b2","76ce52be-0a26-4619-b7f3-85a9f0805411","97c90db8-6de0-4cf1-9355-6c7e0c9f1cbb"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"class_progressions","is_view_only":false,"attcompression":null,"seqrelid":148200,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"class_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_progressions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"type","atttypid":147558,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"class_progression_type","displaytypname":"class_progression_type","cltype":"class_progression_type","inheritedfrom":null,"inheritedid":null,"elemoid":147558,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_progressions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["class_progression_type"]},{"name":"entity_id","atttypid":23,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_progressions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"level","atttypid":23,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_progressions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"class_progressions","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148204,"name":"pk_class_progressions","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_class_progressions_classes_class_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147917,"fknsp":"public","fktab":"class_progressions","refnspoid":2200,"refnsp":"public","reftab":"classes","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"class_id","references":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","referenced":"id","references_table_name":"public.classes"}],"remote_schema":"public","remote_table":"classes","coveringindex":"ix_class_progressions_class_id","autoindex":true,"hasindex":true},{"name":"fk_class_progressions_entities_entity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"n","confmatchtype":false,"conkey":[4],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"class_progressions","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entity_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"ix_class_progressions_entity_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"7da37f73-173f-4379-842d-816ffe491fd4":{"id":"7da37f73-173f-4379-842d-816ffe491fd4","type":"table","selected":false,"x":2205,"y":1410,"ports":[{"id":"0701548c-0ce1-4428-acda-095016da2ed7","type":"onetomany","x":2206,"y":1530.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"7da37f73-173f-4379-842d-816ffe491fd4","links":[]},{"id":"0fcc9bf4-29ad-43fb-bced-b99426b2cf13","type":"onetomany","x":2379,"y":1530.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"7da37f73-173f-4379-842d-816ffe491fd4","links":["5e4e4f4b-2cea-4964-89af-af8a1d0df3ad"]},{"id":"1553be7f-ae60-41c3-b83f-251a4ee356ab","type":"onetomany","x":2206,"y":1556.703125,"name":"coll-port-3-left","alignment":"left","parentNode":"7da37f73-173f-4379-842d-816ffe491fd4","links":[]},{"id":"49759582-3f47-4646-81fc-c1f6d9c8e5ce","type":"onetomany","x":2379,"y":1556.703125,"name":"coll-port-3-right","alignment":"right","parentNode":"7da37f73-173f-4379-842d-816ffe491fd4","links":["600c552a-40e7-4e31-9057-756b1dfe5379"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["0701548c-0ce1-4428-acda-095016da2ed7","0fcc9bf4-29ad-43fb-bced-b99426b2cf13","1553be7f-ae60-41c3-b83f-251a4ee356ab","49759582-3f47-4646-81fc-c1f6d9c8e5ce"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"class_trainings","is_view_only":false,"attcompression":null,"seqrelid":148216,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"class_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_trainings","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"training_id","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_trainings","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"rank","atttypid":147810,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"training_rank","displaytypname":"training_rank","cltype":"training_rank","inheritedfrom":null,"inheritedid":null,"elemoid":147810,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"class_trainings","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["training_rank"]}],"name":"class_trainings","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148220,"name":"pk_class_trainings","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_class_trainings_classes_class_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147917,"fknsp":"public","fktab":"class_trainings","refnspoid":2200,"refnsp":"public","reftab":"classes","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"class_id","references":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","referenced":"id","references_table_name":"public.classes"}],"remote_schema":"public","remote_table":"classes","coveringindex":"ix_class_trainings_class_id","autoindex":true,"hasindex":true},{"name":"fk_class_trainings_trainings_training_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"n","confmatchtype":false,"conkey":[3],"confkey":[1],"confrelid":147876,"fknsp":"public","fktab":"class_trainings","refnspoid":2200,"refnsp":"public","reftab":"trainings","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"training_id","references":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced":"id","references_table_name":"public.trainings"}],"remote_schema":"public","remote_table":"trainings","coveringindex":"ix_class_trainings_training_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"3698aa00-794f-4f84-b1a1-6695f58bf2fb":{"id":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","type":"table","selected":false,"x":2475,"y":1140,"ports":[{"id":"9a0b883e-5d34-42d8-bbb1-27111fd34cd5","type":"onetomany","x":2476.0000213186186,"y":1234.609396054725,"name":"coll-port-1-left","alignment":"left","parentNode":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","links":["8428c95b-a620-4c51-ba6c-b9ca02f3ba1d","c0f8a094-1a68-4a0d-8b05-038b4ed69daf","5e4e4f4b-2cea-4964-89af-af8a1d0df3ad"]},{"id":"c056f935-f663-4dbb-a2d7-da45d73d439c","type":"onetomany","x":2648.9999788593796,"y":1234.609396054725,"name":"coll-port-1-right","alignment":"right","parentNode":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","links":["1bbb315c-d4e8-4342-9553-1a7f9d42cd46","a7bc8cd0-ed06-4ada-b938-05ebbb00b366"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["9a0b883e-5d34-42d8-bbb1-27111fd34cd5","c056f935-f663-4dbb-a2d7-da45d73d439c"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"classes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"hp","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Количество очков здоровья","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"classes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"spellcasting","atttypid":16,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":"Заклинатель?","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"classes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]},{"name":"tradition","atttypid":147794,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"spell_tradition","displaytypname":"spell_tradition","cltype":"spell_tradition","inheritedfrom":null,"inheritedid":null,"elemoid":147794,"typnspname":"public","defaultstorage":"p","description":"Традиция заклинаний","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"classes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["spell_tradition"]},{"name":"additional_trained_skills","atttypid":23,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Дополнительные навыки","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"classes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"classes","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147920,"name":"pk_classes","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_classes_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"classes","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_classes","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"2ffbc8c1-67b3-420b-b08e-6a3e7b17e982":{"id":"2ffbc8c1-67b3-420b-b08e-6a3e7b17e982","type":"table","selected":false,"x":3150,"y":750,"ports":[{"id":"4ff66194-701f-44a5-92cd-605c6e401525","type":"onetomany","x":3151,"y":844.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"2ffbc8c1-67b3-420b-b08e-6a3e7b17e982","links":["222c6d27-115e-45fa-ac74-824b57cc65b0"]},{"id":"cb45af88-8a9e-4a46-b2cb-f1a7410a2dbf","type":"onetomany","x":3324,"y":844.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"2ffbc8c1-67b3-420b-b08e-6a3e7b17e982","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["4ff66194-701f-44a5-92cd-605c6e401525","cb45af88-8a9e-4a46-b2cb-f1a7410a2dbf"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"conditions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"group","atttypid":147574,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"condition_group","displaytypname":"condition_group","cltype":"condition_group","inheritedfrom":null,"inheritedid":null,"elemoid":147574,"typnspname":"public","defaultstorage":"p","description":"Группа состояния","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"conditions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["condition_group"]},{"name":"extra","atttypid":25,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"text","displaytypname":"text","cltype":"text","inheritedfrom":null,"inheritedid":null,"elemoid":25,"typnspname":"pg_catalog","defaultstorage":"x","description":"Дополнительная информация","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"conditions","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","name","regclass","text"]}],"name":"conditions","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147932,"name":"pk_conditions","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_conditions_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"conditions","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_conditions","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"afc62d37-48de-42f7-9d2c-fd0c322f39ad":{"id":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","type":"table","selected":false,"x":3150,"y":990,"ports":[{"id":"b7f7a66f-2d77-448a-a1d0-055412eff3f1","type":"onetomany","x":3150.9998090224235,"y":1084.601514558461,"name":"coll-port-1-left","alignment":"left","parentNode":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","links":["33c009d5-2a3a-4b26-804c-5f49ddde03b4"]},{"id":"310110c1-e97b-4398-b0f2-6e4ae8fddafa","type":"onetomany","x":3323.9997665631854,"y":1084.601514558461,"name":"coll-port-1-right","alignment":"right","parentNode":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","links":["36599081-6b6b-4098-8008-9fb0b1f91081","5ca1e9c6-7e9f-41e9-9065-55e5c14052cc","7ccba789-9cb9-4555-9dc5-62ff4b48258a","cea5fca7-ac29-44f9-95a8-327bc43a3a34","2e4a4581-fbcb-4d67-b61e-f17c2d665dae"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["b7f7a66f-2d77-448a-a1d0-055412eff3f1","310110c1-e97b-4398-b0f2-6e4ae8fddafa"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"category","atttypid":147588,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"deity_category","displaytypname":"deity_category","cltype":"deity_category","inheritedfrom":null,"inheritedid":null,"elemoid":147588,"typnspname":"public","defaultstorage":"p","description":"Тип божества","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["deity_category"]},{"name":"fonts","atttypid":147598,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"deity_fonts","displaytypname":"deity_fonts","cltype":"deity_fonts","inheritedfrom":null,"inheritedid":null,"elemoid":147598,"typnspname":"public","defaultstorage":"p","description":"Тип божественной силы","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["deity_fonts"]},{"name":"sanctification","atttypid":147608,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"deity_sanctification","displaytypname":"deity_sanctification","cltype":"deity_sanctification","inheritedfrom":null,"inheritedid":null,"elemoid":147608,"typnspname":"public","defaultstorage":"p","description":"Тип освящения","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["deity_sanctification"]},{"name":"sanctification_must","atttypid":16,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":"Обязательное освящение","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]}],"name":"deities","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147942,"name":"pk_deities","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_deities_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"deities","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_deities","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"77e9c57b-29b6-4b0c-add0-644c299b2c65":{"id":"77e9c57b-29b6-4b0c-add0-644c299b2c65","type":"table","selected":false,"x":3690,"y":990,"ports":[{"id":"130df01b-7ae6-4cce-b7a7-5b757a15ce4d","type":"onetomany","x":3691,"y":1084.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"77e9c57b-29b6-4b0c-add0-644c299b2c65","links":["2e4a4581-fbcb-4d67-b61e-f17c2d665dae"]},{"id":"dc67af58-d6aa-44fc-9231-62fcf6ce3471","type":"onetomany","x":3864,"y":1084.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"77e9c57b-29b6-4b0c-add0-644c299b2c65","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["130df01b-7ae6-4cce-b7a7-5b757a15ce4d","dc67af58-d6aa-44fc-9231-62fcf6ce3471"],"otherInfo":{"data":{"columns":[{"name":"deity_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_attributes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"attribute","atttypid":147542,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"attribute_values","displaytypname":"attribute_values","cltype":"attribute_values","inheritedfrom":null,"inheritedid":null,"elemoid":147542,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_attributes","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["attribute_values"]}],"name":"deity_attributes","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148250,"name":"pk_deity_attributes","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"deity_id"},{"column":"attribute"}],"include":[]}],"foreign_key":[{"name":"fk_deity_attributes_deities_deity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147939,"fknsp":"public","fktab":"deity_attributes","refnspoid":2200,"refnsp":"public","reftab":"deities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"deity_id","references":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced":"id","references_table_name":"public.deities"}],"remote_schema":"public","remote_table":"deities","coveringindex":null,"autoindex":false,"hasindex":false}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"27e8d943-bbe3-4137-8922-d593ef389faf":{"id":"27e8d943-bbe3-4137-8922-d593ef389faf","type":"table","selected":false,"x":3420,"y":1215,"ports":[{"id":"0f7de847-feb6-408c-a07e-a9b886dafff5","type":"onetomany","x":3420.9998090224235,"y":1335.6484957065595,"name":"coll-port-2-left","alignment":"left","parentNode":"27e8d943-bbe3-4137-8922-d593ef389faf","links":["36599081-6b6b-4098-8008-9fb0b1f91081"]},{"id":"00d7848c-6501-49b1-b533-bba8f4397541","type":"onetomany","x":3593.9997665631854,"y":1335.6484957065595,"name":"coll-port-2-right","alignment":"right","parentNode":"27e8d943-bbe3-4137-8922-d593ef389faf","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["0f7de847-feb6-408c-a07e-a9b886dafff5","00d7848c-6501-49b1-b533-bba8f4397541"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"deity_domains","is_view_only":false,"attcompression":null,"seqrelid":148257,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"deity_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_domains","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"is_primary","atttypid":16,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_domains","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]},{"name":"value","atttypid":1043,"attlen":"255","attnum":4,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_domains","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"deity_domains","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148261,"name":"pk_deity_domains","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_deity_domains_deities_deity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147939,"fknsp":"public","fktab":"deity_domains","refnspoid":2200,"refnsp":"public","reftab":"deities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"deity_id","references":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced":"id","references_table_name":"public.deities"}],"remote_schema":"public","remote_table":"deities","coveringindex":"ix_deity_domains_deity_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"4564ad70-59d5-415d-ab76-6e944aac220f":{"id":"4564ad70-59d5-415d-ab76-6e944aac220f","type":"table","selected":false,"x":3690,"y":1215,"ports":[{"id":"5117cd62-5bd0-4fbe-8a09-790317043136","type":"onetomany","x":3691,"y":1335.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"4564ad70-59d5-415d-ab76-6e944aac220f","links":["5ca1e9c6-7e9f-41e9-9065-55e5c14052cc"]},{"id":"c53a881e-5576-45d3-a992-38e62fb1c9a3","type":"onetomany","x":3864,"y":1335.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"4564ad70-59d5-415d-ab76-6e944aac220f","links":[]},{"id":"fbab53f0-7177-4846-b118-cb3b72f487a3","type":"onetomany","x":3691,"y":1387.75,"name":"coll-port-4-left","alignment":"left","parentNode":"4564ad70-59d5-415d-ab76-6e944aac220f","links":["8274b177-f7c4-45c1-8a60-083c9e7bdde1"]},{"id":"396aefd3-1a8e-4132-b8f9-7e9d58251d57","type":"onetomany","x":3864,"y":1387.75,"name":"coll-port-4-right","alignment":"right","parentNode":"4564ad70-59d5-415d-ab76-6e944aac220f","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["5117cd62-5bd0-4fbe-8a09-790317043136","c53a881e-5576-45d3-a992-38e62fb1c9a3","fbab53f0-7177-4846-b118-cb3b72f487a3","396aefd3-1a8e-4132-b8f9-7e9d58251d57"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"deity_spells","is_view_only":false,"attcompression":null,"seqrelid":148367,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"deity_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"level","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"spell_id","atttypid":23,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"deity_spells","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148371,"name":"pk_deity_spells","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_deity_spells_deities_deity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147939,"fknsp":"public","fktab":"deity_spells","refnspoid":2200,"refnsp":"public","reftab":"deities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"deity_id","references":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced":"id","references_table_name":"public.deities"}],"remote_schema":"public","remote_table":"deities","coveringindex":"ix_deity_spells_deity_id","autoindex":true,"hasindex":true},{"name":"fk_deity_spells_spells_spell_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[4],"confkey":[1],"confrelid":148026,"fknsp":"public","fktab":"deity_spells","refnspoid":2200,"refnsp":"public","reftab":"spells","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"spell_id","references":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","referenced":"id","references_table_name":"public.spells"}],"remote_schema":"public","remote_table":"spells","coveringindex":"ix_deity_spells_spell_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95":{"id":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","type":"table","selected":false,"x":3960,"y":990,"ports":[{"id":"bf7a9ff0-9ec5-4524-84a0-86a4146afc9d","type":"onetomany","x":3961,"y":1084.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","links":["7ccba789-9cb9-4555-9dc5-62ff4b48258a"]},{"id":"ad01c000-7d0a-43be-bf79-bb3d2d1a6871","type":"onetomany","x":4134,"y":1084.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","links":[]},{"id":"10447113-8dfe-4ccc-9a3d-82fd4c5a45e9","type":"onetomany","x":3961,"y":1110.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","links":[]},{"id":"c92deb7a-bc45-48d5-b04b-6f510efcb511","type":"onetomany","x":4134,"y":1110.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"73bab5ce-8433-43a6-bf5f-7e4e1c69ba95","links":["becbd62a-cd2d-43ec-a2f3-db062547a7f9"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["bf7a9ff0-9ec5-4524-84a0-86a4146afc9d","ad01c000-7d0a-43be-bf79-bb3d2d1a6871","10447113-8dfe-4ccc-9a3d-82fd4c5a45e9","c92deb7a-bc45-48d5-b04b-6f510efcb511"],"otherInfo":{"data":{"columns":[{"name":"deity_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_training","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"skills_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_training","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"deity_training","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148271,"name":"pk_deity_training","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"deity_id"},{"column":"skills_id"}],"include":[]}],"foreign_key":[{"name":"fk_deity_training_deities_deity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147939,"fknsp":"public","fktab":"deity_training","refnspoid":2200,"refnsp":"public","reftab":"deities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"deity_id","references":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced":"id","references_table_name":"public.deities"}],"remote_schema":"public","remote_table":"deities","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_deity_training_trainings_skills_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147876,"fknsp":"public","fktab":"deity_training","refnspoid":2200,"refnsp":"public","reftab":"trainings","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"skills_id","references":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced":"id","references_table_name":"public.trainings"}],"remote_schema":"public","remote_table":"trainings","coveringindex":"ix_deity_training_skills_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"24283dfe-29c8-4c84-a491-ee873da3910f":{"id":"24283dfe-29c8-4c84-a491-ee873da3910f","type":"table","selected":false,"x":3420,"y":990,"ports":[{"id":"6aacc928-0860-4b21-978d-6644da8505c6","type":"onetomany","x":3421.0000213186195,"y":1110.65627636213,"name":"coll-port-2-left","alignment":"left","parentNode":"24283dfe-29c8-4c84-a491-ee873da3910f","links":["cea5fca7-ac29-44f9-95a8-327bc43a3a34"]},{"id":"0aa3e634-7a56-42bd-bbbc-ee1952b131fe","type":"onetomany","x":3593.9999788593814,"y":1110.65627636213,"name":"coll-port-2-right","alignment":"right","parentNode":"24283dfe-29c8-4c84-a491-ee873da3910f","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["6aacc928-0860-4b21-978d-6644da8505c6","0aa3e634-7a56-42bd-bbbc-ee1952b131fe"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"deity_weapons","is_view_only":false,"attcompression":null,"seqrelid":148283,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"deity_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_weapons","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"weapon","atttypid":1043,"attlen":"255","attnum":3,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"deity_weapons","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"deity_weapons","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148287,"name":"pk_deity_weapons","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_deity_weapons_deities_deity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147939,"fknsp":"public","fktab":"deity_weapons","refnspoid":2200,"refnsp":"public","reftab":"deities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"deity_id","references":"afc62d37-48de-42f7-9d2c-fd0c322f39ad","referenced":"id","references_table_name":"public.deities"}],"remote_schema":"public","remote_table":"deities","coveringindex":"ix_deity_weapons_deity_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"a2068488-3358-4fdb-a4d6-e747d8711d93":{"id":"a2068488-3358-4fdb-a4d6-e747d8711d93","type":"table","selected":false,"x":2835,"y":-270,"ports":[{"id":"2bc07930-2808-4dbd-aac3-a857c96f491c","type":"onetomany","x":2836,"y":-175.390625,"name":"coll-port-1-left","alignment":"left","parentNode":"a2068488-3358-4fdb-a4d6-e747d8711d93","links":["94df77f9-8383-4d21-b400-c79e85e88f57","a306c93a-aa14-4d42-9269-4cd74b4a1876","b93f97e9-ee47-4ecf-900d-1696fb0a855d","b7b25b27-25c7-49b2-a331-7a816df6c063","09572470-4f3e-4090-9bb9-f291dfafc413","1bbb315c-d4e8-4342-9553-1a7f9d42cd46","2db94f0f-f999-4a56-8643-6786f2685ea9","9f5e570c-8929-413a-8964-dbb5b04804e2","f2d1f789-fec6-44bf-a6f3-ffb958c19920","caa8d582-a73e-472e-b125-1b03bd2bd27c","8d50def6-1bb0-478a-aeaa-c97ad2d38d7f","299ec798-c3c2-4c2e-b926-e7874e72c0f1","c8e55302-d304-4e73-9e26-79e2480fba7b","55ee0e0a-6cef-4d72-89c8-8044e4841c9e"]},{"id":"ae3580ad-3ab3-4b7d-9cea-460c6259b19a","type":"onetomany","x":3009,"y":-175.390625,"name":"coll-port-1-right","alignment":"right","parentNode":"a2068488-3358-4fdb-a4d6-e747d8711d93","links":["33c009d5-2a3a-4b26-804c-5f49ddde03b4","a2132da1-931c-463e-b001-7d74cd4d527f","833f096f-e69d-432e-8900-708118cd1cdc","590c0185-f2ac-4c91-ad57-a202816f78ad","7c43d283-c3d2-494d-95cf-ac92a8977a15","cccdb52f-1e67-4bdc-99df-eca62e6b9a4b","8d6d50c6-db8c-4274-aac5-1a6cd293d2d1","6db4d1e5-4aa5-48c9-998d-def3f027b375","40e17158-2d5a-43f2-87fc-1e2ae5bf55a9","a91cb14e-8c71-4e91-be92-8d162078e309","901d0a82-4c10-45db-9a2f-5ab11ec4fd51","06f76c4f-b3d7-4f32-b300-5246ae1ab14d","222c6d27-115e-45fa-ac74-824b57cc65b0"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["2bc07930-2808-4dbd-aac3-a857c96f491c","ae3580ad-3ab3-4b7d-9cea-460c6259b19a"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":147839,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"name","atttypid":3802,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Отображаемое имя","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"type","atttypid":147642,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"entity_type","displaytypname":"entity_type","cltype":"entity_type","inheritedfrom":null,"inheritedid":null,"elemoid":147642,"typnspname":"public","defaultstorage":"p","description":"Тип сущности","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["entity_type"]},{"name":"slug","atttypid":1043,"attlen":"255","attnum":4,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Часть имени в URL","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"img","atttypid":1043,"attlen":"255","attnum":5,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Изображение объекта","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"description","atttypid":25,"attlen":null,"attnum":6,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"text","displaytypname":"text","cltype":"text","inheritedfrom":null,"inheritedid":null,"elemoid":25,"typnspname":"pg_catalog","defaultstorage":"x","description":"Текстовое описание сущности","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","name","regclass","text"]},{"name":"rarity","atttypid":147632,"attlen":null,"attnum":7,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"entity_rarity","displaytypname":"entity_rarity","cltype":"entity_rarity","inheritedfrom":null,"inheritedid":null,"elemoid":147632,"typnspname":"public","defaultstorage":"p","description":"Редкость сущности","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["entity_rarity"]},{"name":"is_translated","atttypid":16,"attlen":null,"attnum":8,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":"Флаг, указывающий, что у сущности существует перевод","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]},{"name":"is_checked","atttypid":16,"attlen":null,"attnum":9,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":"Флаг, указывающий, что сущность был проверен и не требует автоматической перезаписи","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","boolean","character","character varying","text"]}],"name":"entities","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147845,"name":"pk_entities","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"unique_constraint":[],"foreign_key":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"dfdbde67-56d9-43f3-812f-58519c0da853":{"id":"dfdbde67-56d9-43f3-812f-58519c0da853","type":"table","selected":false,"x":2475,"y":225,"ports":[{"id":"f5bd27fb-56de-4343-b4e6-5ee6327913b6","type":"onetomany","x":2475.9999682445678,"y":345.65626574732005,"name":"coll-port-2-left","alignment":"left","parentNode":"dfdbde67-56d9-43f3-812f-58519c0da853","links":[]},{"id":"d236a9f4-98c6-43fb-ad1f-93bf6a42f88e","type":"onetomany","x":2648.9999257853287,"y":345.65626574732005,"name":"coll-port-2-right","alignment":"right","parentNode":"dfdbde67-56d9-43f3-812f-58519c0da853","links":["94df77f9-8383-4d21-b400-c79e85e88f57"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["f5bd27fb-56de-4343-b4e6-5ee6327913b6","d236a9f4-98c6-43fb-ad1f-93bf6a42f88e"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"entity_aliases","is_view_only":false,"attcompression":null,"seqrelid":147949,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"entity_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Связанная сущность","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_aliases","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"name","atttypid":1043,"attlen":"255","attnum":3,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Алиас","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_aliases","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"entity_aliases","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147953,"name":"pk_entity_aliases","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_entity_aliases_entities_entity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"entity_aliases","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entity_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"ix_entity_aliases_entity_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"875068af-e48a-4a77-ac71-f95b636467a6":{"id":"875068af-e48a-4a77-ac71-f95b636467a6","type":"table","selected":false,"x":2475,"y":-45,"ports":[{"id":"a9a1ddc9-d89f-49bb-bb67-23e67e8d1ed9","type":"onetomany","x":2475.9999682445687,"y":49.601514558461076,"name":"coll-port-1-left","alignment":"left","parentNode":"875068af-e48a-4a77-ac71-f95b636467a6","links":[]},{"id":"3db395b8-04cc-48e3-9b55-60df28819a15","type":"onetomany","x":2648.9999257853287,"y":49.601514558461076,"name":"coll-port-1-right","alignment":"right","parentNode":"875068af-e48a-4a77-ac71-f95b636467a6","links":["a306c93a-aa14-4d42-9269-4cd74b4a1876"]},{"id":"5abcf35e-2096-4cf7-a57d-4f6f58b48512","type":"onetomany","x":2475.9999682445687,"y":75.64842671029555,"name":"coll-port-2-left","alignment":"left","parentNode":"875068af-e48a-4a77-ac71-f95b636467a6","links":["9fe77401-5732-41ae-a219-014760f53f94"]},{"id":"b805508f-15ff-4708-9595-4b6bc6a3ec9b","type":"onetomany","x":2648.9999257853287,"y":75.64842671029555,"name":"coll-port-2-right","alignment":"right","parentNode":"875068af-e48a-4a77-ac71-f95b636467a6","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["a9a1ddc9-d89f-49bb-bb67-23e67e8d1ed9","3db395b8-04cc-48e3-9b55-60df28819a15","5abcf35e-2096-4cf7-a57d-4f6f58b48512","b805508f-15ff-4708-9595-4b6bc6a3ec9b"],"otherInfo":{"data":{"columns":[{"name":"entity_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор сущности","indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_externals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"target_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор цели","indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_externals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"external_id","atttypid":1043,"attlen":"255","attnum":3,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1 2","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_externals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"suffix","atttypid":1043,"attlen":"255","attnum":4,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Часть URL до объекта. Для источников Foundry - полный UUID объекта","indkey":"1 2","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_externals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"md5","atttypid":1043,"attlen":"32","attnum":5,"attndims":0,"atttypmod":36,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(32)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Контрольная сумма объекта","indkey":"1 2","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_externals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"entity_externals","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148053,"name":"pk_entity_externals","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"entity_id"},{"column":"target_id"}],"include":[]}],"foreign_key":[{"name":"fk_entity_externals_entities_entity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"entity_externals","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entity_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_entity_externals_external_targets_target_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147848,"fknsp":"public","fktab":"entity_externals","refnspoid":2200,"refnsp":"public","reftab":"external_targets","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"target_id","references":"bbeca3af-e1d2-40af-a4b9-5bff1ecc2b17","referenced":"id","references_table_name":"public.external_targets"}],"remote_schema":"public","remote_table":"external_targets","coveringindex":"ix_entity_externals_target_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"ace3ef37-8b43-410d-9156-3b2b91fe9e29":{"id":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","type":"table","selected":false,"x":2475,"y":-270,"ports":[{"id":"cc13aae2-46ed-4d4c-aa14-850c5a987e5b","type":"onetomany","x":2476,"y":-175.390625,"name":"coll-port-1-left","alignment":"left","parentNode":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","links":[]},{"id":"e22fa35a-954d-45a0-81b4-f05f0dae8c22","type":"onetomany","x":2649,"y":-175.390625,"name":"coll-port-1-right","alignment":"right","parentNode":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","links":["b93f97e9-ee47-4ecf-900d-1696fb0a855d"]},{"id":"3616cb87-4b59-4041-a0c6-584db96f9346","type":"onetomany","x":2476,"y":-149.34375,"name":"coll-port-2-left","alignment":"left","parentNode":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","links":["ed5d7103-c18c-40c8-975a-5c2e0256cad1"]},{"id":"e1682585-b773-4ccd-a873-4f36d7f5a614","type":"onetomany","x":2649,"y":-149.34375,"name":"coll-port-2-right","alignment":"right","parentNode":"ace3ef37-8b43-410d-9156-3b2b91fe9e29","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["cc13aae2-46ed-4d4c-aa14-850c5a987e5b","e22fa35a-954d-45a0-81b4-f05f0dae8c22","3616cb87-4b59-4041-a0c6-584db96f9346","e1682585-b773-4ccd-a873-4f36d7f5a614"],"otherInfo":{"data":{"columns":[{"name":"entities_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_keyword","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"keywords_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_keyword","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"entity_keyword","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148068,"name":"pk_entity_keyword","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"entities_id"},{"column":"keywords_id"}],"include":[]}],"foreign_key":[{"name":"fk_entity_keyword_entities_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"entity_keyword","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entities_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_entity_keyword_keywords_keywords_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147862,"fknsp":"public","fktab":"entity_keyword","refnspoid":2200,"refnsp":"public","reftab":"keywords","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"keywords_id","references":"8e20d2d2-bfd0-4f03-924f-80578f960562","referenced":"id","references_table_name":"public.keywords"}],"remote_schema":"public","remote_table":"keywords","coveringindex":"ix_entity_keyword_keywords_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386":{"id":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","type":"table","selected":false,"x":2475,"y":-540,"ports":[{"id":"b2d7902c-5bc8-42f4-8491-c0ee05b70bcc","type":"onetomany","x":2476.0000213186177,"y":-419.34369179344094,"name":"coll-port-2-left","alignment":"left","parentNode":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","links":[]},{"id":"0d28db63-a5ca-4793-8b35-b29678672866","type":"onetomany","x":2648.9999788593786,"y":-419.34369179344094,"name":"coll-port-2-right","alignment":"right","parentNode":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","links":["b7b25b27-25c7-49b2-a331-7a816df6c063"]},{"id":"5f09584f-5418-41cd-9d28-f8cd7f3c2e6d","type":"onetomany","x":2476.0000213186177,"y":-393.29685925267995,"name":"coll-port-3-left","alignment":"left","parentNode":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","links":["85201d1e-de31-40b3-a765-519330982299"]},{"id":"744e9300-6fdd-4c0a-ae3c-adfb0dee14b0","type":"onetomany","x":2648.9999788593786,"y":-393.29685925267995,"name":"coll-port-3-right","alignment":"right","parentNode":"672f588b-f9d8-4c1e-9bb7-fb22ef9fb386","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["b2d7902c-5bc8-42f4-8491-c0ee05b70bcc","0d28db63-a5ca-4793-8b35-b29678672866","5f09584f-5418-41cd-9d28-f8cd7f3c2e6d","744e9300-6fdd-4c0a-ae3c-adfb0dee14b0"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"entity_sources","is_view_only":false,"attcompression":null,"seqrelid":148091,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"entity_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор сущности","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_sources","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"source_id","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор источника","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_sources","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"suffix","atttypid":1043,"attlen":"255","attnum":4,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Номер страницы, эррата и т.д.","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_sources","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"is_remaster","atttypid":16,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":"Флаг, указывающий, что объект является частью ремастера","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_sources","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]}],"name":"entity_sources","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148095,"name":"pk_entity_sources","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_entity_sources_entities_entity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"entity_sources","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entity_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"ix_entity_sources_entity_id","autoindex":true,"hasindex":true},{"name":"fk_entity_sources_sources_source_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[3],"confkey":[1],"confrelid":147870,"fknsp":"public","fktab":"entity_sources","refnspoid":2200,"refnsp":"public","reftab":"sources","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"source_id","references":"77bdd1d9-21d0-4481-b709-2f29aa01c1dd","referenced":"id","references_table_name":"public.sources"}],"remote_schema":"public","remote_table":"sources","coveringindex":"ix_entity_sources_source_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"d6309da2-3825-4f2b-b3a6-6e425202f1fa":{"id":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","type":"table","selected":false,"x":2475,"y":630,"ports":[{"id":"4b8c5ac5-16ed-4b80-bc50-79d9a576c43d","type":"onetomany","x":2476,"y":724.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","links":[]},{"id":"22e9a15d-b93c-4713-a5b6-6e25b62de358","type":"onetomany","x":2649,"y":724.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","links":["c8e55302-d304-4e73-9e26-79e2480fba7b"]},{"id":"59db02c5-0ce9-4b8d-ac4e-a22aec288533","type":"onetomany","x":2476,"y":750.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","links":["4974960f-15c6-4535-bba9-721a89ded051"]},{"id":"115c8a40-6d52-41b9-88fc-745ca56fe1e8","type":"onetomany","x":2649,"y":750.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"d6309da2-3825-4f2b-b3a6-6e425202f1fa","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["4b8c5ac5-16ed-4b80-bc50-79d9a576c43d","22e9a15d-b93c-4713-a5b6-6e25b62de358","59db02c5-0ce9-4b8d-ac4e-a22aec288533","115c8a40-6d52-41b9-88fc-745ca56fe1e8"],"otherInfo":{"data":{"columns":[{"name":"entities_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_trait","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"traits_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"entity_trait","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"entity_trait","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148423,"name":"pk_entity_trait","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"entities_id"},{"column":"traits_id"}],"include":[]}],"foreign_key":[{"name":"fk_entity_trait_entities_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"entity_trait","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entities_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_entity_trait_traits_traits_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":148038,"fknsp":"public","fktab":"entity_trait","refnspoid":2200,"refnsp":"public","reftab":"traits","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"traits_id","references":"3f2a50f8-9894-4730-b590-6f02ddca6546","referenced":"id","references_table_name":"public.traits"}],"remote_schema":"public","remote_table":"traits","coveringindex":"ix_entity_trait_traits_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"bbeca3af-e1d2-40af-a4b9-5bff1ecc2b17":{"id":"bbeca3af-e1d2-40af-a4b9-5bff1ecc2b17","type":"table","selected":false,"x":2205,"y":-45,"ports":[{"id":"ea84bd30-5ca9-40a0-9311-458af829e2e6","type":"onetomany","x":2205.9999682445678,"y":49.601562325104936,"name":"coll-port-1-left","alignment":"left","parentNode":"bbeca3af-e1d2-40af-a4b9-5bff1ecc2b17","links":[]},{"id":"72600744-28e0-46c7-a353-7e6895425eee","type":"onetomany","x":2379.0000850074757,"y":49.601562325104936,"name":"coll-port-1-right","alignment":"right","parentNode":"bbeca3af-e1d2-40af-a4b9-5bff1ecc2b17","links":["9fe77401-5732-41ae-a219-014760f53f94"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["ea84bd30-5ca9-40a0-9311-458af829e2e6","72600744-28e0-46c7-a353-7e6895425eee"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"external_targets","is_view_only":false,"attcompression":null,"seqrelid":147847,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"name","atttypid":1043,"attlen":"255","attnum":2,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Отображаемое имя","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"external_targets","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"icon","atttypid":1043,"attlen":"255","attnum":3,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Иконка цели","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"external_targets","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"prefix","atttypid":1043,"attlen":"255","attnum":4,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Префикс для URL-адреса внешней ссылки","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"external_targets","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"external_targets","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147853,"name":"pk_external_targets","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"unique_constraint":[],"foreign_key":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"58db1892-a838-48fb-961d-75e31e218b99":{"id":"58db1892-a838-48fb-961d-75e31e218b99","type":"table","selected":false,"x":3420,"y":1485,"ports":[{"id":"bb2f24cb-7886-4a23-9806-2683beb913f9","type":"onetomany","x":3421.0000213186195,"y":1605.65627636213,"name":"coll-port-2-left","alignment":"left","parentNode":"58db1892-a838-48fb-961d-75e31e218b99","links":["7bda8586-baf0-4e5a-979d-9091c9a0b9b2"]},{"id":"9f2fbb16-75c5-4e17-b044-a1747e6d6269","type":"onetomany","x":3593.9999788593814,"y":1605.65627636213,"name":"coll-port-2-right","alignment":"right","parentNode":"58db1892-a838-48fb-961d-75e31e218b99","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["bb2f24cb-7886-4a23-9806-2683beb913f9","9f2fbb16-75c5-4e17-b044-a1747e6d6269"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"feat_prerequisites","is_view_only":false,"attcompression":null,"seqrelid":148311,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"feat_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"feat_prerequisites","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"value","atttypid":1043,"attlen":"255","attnum":3,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"feat_prerequisites","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"feat_prerequisites","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148315,"name":"pk_feat_prerequisites","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_feat_prerequisites_feats_feat_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147960,"fknsp":"public","fktab":"feat_prerequisites","refnspoid":2200,"refnsp":"public","reftab":"feats","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"feat_id","references":"6a0445dc-9262-485d-9bfc-73c374cfec00","referenced":"id","references_table_name":"public.feats"}],"remote_schema":"public","remote_table":"feats","coveringindex":"ix_feat_prerequisites_feat_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"6a0445dc-9262-485d-9bfc-73c374cfec00":{"id":"6a0445dc-9262-485d-9bfc-73c374cfec00","type":"table","selected":false,"x":3150,"y":1440,"ports":[{"id":"c213b620-b26f-4e87-8eec-4471863de573","type":"onetomany","x":3151,"y":1534.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"6a0445dc-9262-485d-9bfc-73c374cfec00","links":["a2132da1-931c-463e-b001-7d74cd4d527f"]},{"id":"a2bc8378-e84d-4222-af3e-4e4a0fd80614","type":"onetomany","x":3324,"y":1534.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"6a0445dc-9262-485d-9bfc-73c374cfec00","links":["7bda8586-baf0-4e5a-979d-9091c9a0b9b2","f80771ff-8543-4bda-8a33-cf88e5f5240c","0e17425e-9a46-419b-9977-5f4327bc6325"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["c213b620-b26f-4e87-8eec-4471863de573","a2bc8378-e84d-4222-af3e-4e4a0fd80614"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"feats","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"category","atttypid":147682,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"feat_category","displaytypname":"feat_category","cltype":"feat_category","inheritedfrom":null,"inheritedid":null,"elemoid":147682,"typnspname":"public","defaultstorage":"p","description":"Категория способности","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"feats","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["feat_category"]},{"name":"level","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Уровень способности","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"feats","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"actions","atttypid":147498,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"action_enum","displaytypname":"action_enum","cltype":"action_enum","inheritedfrom":null,"inheritedid":null,"elemoid":147498,"typnspname":"public","defaultstorage":"p","description":"Количество действий","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"feats","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["action_enum"]},{"name":"extra","atttypid":25,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"text","displaytypname":"text","cltype":"text","inheritedfrom":null,"inheritedid":null,"elemoid":25,"typnspname":"pg_catalog","defaultstorage":"x","description":"Дополнительная информация","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"feats","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","name","regclass","text"]}],"name":"feats","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147965,"name":"pk_feats","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_feats_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"feats","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_feats","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"c7e0de9d-3baa-4872-b149-d24a723d444f":{"id":"c7e0de9d-3baa-4872-b149-d24a723d444f","type":"table","selected":false,"x":3765,"y":-780,"ports":[],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":[],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"frequencies","is_view_only":false,"attcompression":null,"seqrelid":147855,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"max","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Максимальная продолжительсность","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"frequencies","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"per","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Частота использования","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"frequencies","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"value","atttypid":23,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Значение использования","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"frequencies","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"frequencies","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147859,"name":"pk_frequencies","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"unique_constraint":[],"foreign_key":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"d4fc2096-8445-4ec9-b636-f32b55ed9325":{"id":"d4fc2096-8445-4ec9-b636-f32b55ed9325","type":"table","selected":false,"x":3150,"y":180,"ports":[{"id":"1085db38-6643-4582-a43a-d5cde098f7c9","type":"onetomany","x":3151,"y":300.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"d4fc2096-8445-4ec9-b636-f32b55ed9325","links":[]},{"id":"54c0962a-d379-4bea-868d-2414c57936aa","type":"onetomany","x":3324,"y":300.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"d4fc2096-8445-4ec9-b636-f32b55ed9325","links":["ea0295f5-1f5e-4be7-9250-80d890f3e1d4"]},{"id":"693d18aa-12f4-42a5-b074-75ebe35026b9","type":"onetomany","x":3151,"y":274.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"d4fc2096-8445-4ec9-b636-f32b55ed9325","links":["833f096f-e69d-432e-8900-708118cd1cdc"]},{"id":"d7a5751f-1293-42fc-926c-7f61aa7c83dc","type":"onetomany","x":3324,"y":274.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"d4fc2096-8445-4ec9-b636-f32b55ed9325","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["1085db38-6643-4582-a43a-d5cde098f7c9","54c0962a-d379-4bea-868d-2414c57936aa","693d18aa-12f4-42a5-b074-75ebe35026b9","d7a5751f-1293-42fc-926c-7f61aa7c83dc"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"heritages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"ancestry_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор родословной","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"heritages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"heritages","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148136,"name":"pk_heritages","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_heritages_ancestries_ancestry_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147895,"fknsp":"public","fktab":"heritages","refnspoid":2200,"refnsp":"public","reftab":"ancestries","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"ancestry_id","references":"beb71ec9-b49d-4ced-a72b-0bf30abaf2fe","referenced":"id","references_table_name":"public.ancestries"}],"remote_schema":"public","remote_table":"ancestries","coveringindex":"ix_heritages_ancestry_id","autoindex":true,"hasindex":true},{"name":"fk_heritages_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"heritages","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_heritages","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"5b6d2934-95ab-4525-8d85-c5b5e58c357b":{"id":"5b6d2934-95ab-4525-8d85-c5b5e58c357b","type":"table","selected":false,"x":1935,"y":-270,"ports":[{"id":"5a5e9767-11da-443f-acac-6a36b2804c9a","type":"onetomany","x":1936.0000213186177,"y":-149.34373425267995,"name":"coll-port-2-left","alignment":"left","parentNode":"5b6d2934-95ab-4525-8d85-c5b5e58c357b","links":[]},{"id":"d8e4b23e-ea2b-4b61-ab9b-4762b95b81a4","type":"onetomany","x":2108.9999788593786,"y":-149.34373425267995,"name":"coll-port-2-right","alignment":"right","parentNode":"5b6d2934-95ab-4525-8d85-c5b5e58c357b","links":["7e7e7aa4-e812-4f5d-bbd4-c944b1584731"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["5a5e9767-11da-443f-acac-6a36b2804c9a","d8e4b23e-ea2b-4b61-ab9b-4762b95b81a4"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"keyword_aliases","is_view_only":false,"attcompression":null,"seqrelid":148080,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"keyword_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор ключевого слова","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"keyword_aliases","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"value","atttypid":1043,"attlen":"255","attnum":3,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Синоним","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"keyword_aliases","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"keyword_aliases","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148084,"name":"pk_keyword_aliases","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_keyword_aliases_keywords_keyword_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147862,"fknsp":"public","fktab":"keyword_aliases","refnspoid":2200,"refnsp":"public","reftab":"keywords","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"keyword_id","references":"8e20d2d2-bfd0-4f03-924f-80578f960562","referenced":"id","references_table_name":"public.keywords"}],"remote_schema":"public","remote_table":"keywords","coveringindex":"ix_keyword_aliases_keyword_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"8e20d2d2-bfd0-4f03-924f-80578f960562":{"id":"8e20d2d2-bfd0-4f03-924f-80578f960562","type":"table","selected":false,"x":2205,"y":-270,"ports":[{"id":"75ae2e3f-1158-4bb9-9124-3a9002323010","type":"onetomany","x":2206,"y":-175.390625,"name":"coll-port-1-left","alignment":"left","parentNode":"8e20d2d2-bfd0-4f03-924f-80578f960562","links":["7e7e7aa4-e812-4f5d-bbd4-c944b1584731"]},{"id":"d74048cb-206f-4ba7-8a44-f79fc535ffef","type":"onetomany","x":2379,"y":-175.390625,"name":"coll-port-1-right","alignment":"right","parentNode":"8e20d2d2-bfd0-4f03-924f-80578f960562","links":["ed5d7103-c18c-40c8-975a-5c2e0256cad1"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["75ae2e3f-1158-4bb9-9124-3a9002323010","d74048cb-206f-4ba7-8a44-f79fc535ffef"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"keywords","is_view_only":false,"attcompression":null,"seqrelid":147861,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"name","atttypid":3802,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Отображаемое имя","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"keywords","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"slug","atttypid":1043,"attlen":"255","attnum":3,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Часть имени в URL","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"keywords","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"keywords","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147867,"name":"pk_keywords","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"unique_constraint":[],"foreign_key":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"51769507-cd47-4825-8561-af1d1d3a3792":{"id":"51769507-cd47-4825-8561-af1d1d3a3792","type":"table","selected":false,"x":3240,"y":-705,"ports":[{"id":"10de1f8a-bdf3-4c54-b7db-673a74cdedf8","type":"onetomany","x":3241,"y":-610.390625,"name":"coll-port-1-left","alignment":"left","parentNode":"51769507-cd47-4825-8561-af1d1d3a3792","links":[]},{"id":"99b4999f-6805-4ab1-914c-62a5098c14c7","type":"onetomany","x":3414,"y":-610.390625,"name":"coll-port-1-right","alignment":"right","parentNode":"51769507-cd47-4825-8561-af1d1d3a3792","links":["38d3c083-3048-4953-83db-a4d64e93bcdd"]},{"id":"fe381b5b-df2c-41d5-affd-af7d89954685","type":"onetomany","x":3241,"y":-584.34375,"name":"coll-port-2-left","alignment":"left","parentNode":"51769507-cd47-4825-8561-af1d1d3a3792","links":["a91cb14e-8c71-4e91-be92-8d162078e309"]},{"id":"9edc23bd-86db-4d7b-9895-36bf30347329","type":"onetomany","x":3414,"y":-584.34375,"name":"coll-port-2-right","alignment":"right","parentNode":"51769507-cd47-4825-8561-af1d1d3a3792","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["10de1f8a-bdf3-4c54-b7db-673a74cdedf8","99b4999f-6805-4ab1-914c-62a5098c14c7","fe381b5b-df2c-41d5-affd-af7d89954685","9edc23bd-86db-4d7b-9895-36bf30347329"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"languages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"entity_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"languages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"languages","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147975,"name":"pk_languages","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_languages_entities_entity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"languages","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entity_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"ix_languages_entity_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"1229197d-6829-4998-9eac-de70486a13dd":{"id":"1229197d-6829-4998-9eac-de70486a13dd","type":"table","selected":false,"x":2475,"y":450,"ports":[{"id":"54245560-f251-4613-bff3-1945a17c002b","type":"onetomany","x":2476,"y":544.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"1229197d-6829-4998-9eac-de70486a13dd","links":[]},{"id":"fbd72068-9da3-4430-a2cd-f3c877facd8b","type":"onetomany","x":2649,"y":544.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"1229197d-6829-4998-9eac-de70486a13dd","links":["2db94f0f-f999-4a56-8643-6786f2685ea9"]},{"id":"b4c99180-856f-4a2c-a07f-b5714505ff1d","type":"onetomany","x":2476,"y":570.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"1229197d-6829-4998-9eac-de70486a13dd","links":[]},{"id":"8b7a90ea-56be-47cc-ae8c-ecde2b8548aa","type":"onetomany","x":2649,"y":570.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"1229197d-6829-4998-9eac-de70486a13dd","links":["9f5e570c-8929-413a-8964-dbb5b04804e2"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["54245560-f251-4613-bff3-1945a17c002b","fbd72068-9da3-4430-a2cd-f3c877facd8b","b4c99180-856f-4a2c-a07f-b5714505ff1d","8b7a90ea-56be-47cc-ae8c-ecde2b8548aa"],"otherInfo":{"data":{"columns":[{"name":"legacy_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор сущности","indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"legacy_remasters","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"remaster_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор цели","indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"legacy_remasters","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"legacy_remasters","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147985,"name":"pk_legacy_remasters","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"legacy_id"},{"column":"remaster_id"}],"include":[]}],"foreign_key":[{"name":"fk_legacy_remasters_entities_legacy_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"legacy_remasters","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"legacy_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":null,"autoindex":false,"hasindex":false},{"name":"fk_legacy_remasters_entities_remaster_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"legacy_remasters","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"remaster_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"ix_legacy_remasters_remaster_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"a242e399-4642-4640-b31a-78e0e8363538":{"id":"a242e399-4642-4640-b31a-78e0e8363538","type":"table","selected":false,"x":3420,"y":1755,"ports":[{"id":"28c125ae-1071-4b3a-b4d8-72153369e60a","type":"onetomany","x":3421,"y":1849.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"a242e399-4642-4640-b31a-78e0e8363538","links":["eb145a26-8512-4ebb-b547-a7da2afc08b9"]},{"id":"2aec4645-5f75-4618-8d41-b664bcc09286","type":"onetomany","x":3594,"y":1849.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"a242e399-4642-4640-b31a-78e0e8363538","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["28c125ae-1071-4b3a-b4d8-72153369e60a","2aec4645-5f75-4618-8d41-b664bcc09286"],"otherInfo":{"data":{"columns":[{"name":"ritual_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ritual_areas","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"details","atttypid":3802,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ritual_areas","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"type","atttypid":147754,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"spell_area_type","displaytypname":"spell_area_type","cltype":"spell_area_type","inheritedfrom":null,"inheritedid":null,"elemoid":147754,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ritual_areas","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["spell_area_type"]}],"name":"ritual_areas","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148342,"name":"pk_ritual_areas","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"ritual_id"}],"include":[]}],"foreign_key":[{"name":"fk_ritual_areas_rituals_ritual_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147997,"fknsp":"public","fktab":"ritual_areas","refnspoid":2200,"refnsp":"public","reftab":"rituals","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"ritual_id","references":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","referenced":"id","references_table_name":"public.rituals"}],"remote_schema":"public","remote_table":"rituals","coveringindex":"pk_ritual_areas","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de":{"id":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","type":"table","selected":false,"x":3690,"y":1755,"ports":[{"id":"3cf969e0-4d33-4611-bd7a-28d4c0adcf84","type":"onetomany","x":3691,"y":1875.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","links":["8985c8ba-711f-4dce-a582-e23b8ccaa622"]},{"id":"886711dd-f626-4792-9887-5b516f8653e6","type":"onetomany","x":3864,"y":1875.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","links":[]},{"id":"665ba855-c7a3-48da-9cba-54d2e1074767","type":"onetomany","x":3691,"y":1901.703125,"name":"coll-port-3-left","alignment":"left","parentNode":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","links":[]},{"id":"723a914b-c127-4bb4-978e-a7a2bda8a17f","type":"onetomany","x":3864,"y":1901.703125,"name":"coll-port-3-right","alignment":"right","parentNode":"fac4a1e0-cb2d-4ee2-a3ba-9fd1969809de","links":["3304af0e-1bf4-42fa-bef1-39d7b300ed53"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["3cf969e0-4d33-4611-bd7a-28d4c0adcf84","886711dd-f626-4792-9887-5b516f8653e6","665ba855-c7a3-48da-9cba-54d2e1074767","723a914b-c127-4bb4-978e-a7a2bda8a17f"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"ritual_checks","is_view_only":false,"attcompression":null,"seqrelid":148349,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"ritual_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ritual_checks","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"training_id","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ritual_checks","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"training_string","atttypid":3802,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ritual_checks","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"rank","atttypid":147810,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"training_rank","displaytypname":"training_rank","cltype":"training_rank","inheritedfrom":null,"inheritedid":null,"elemoid":147810,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ritual_checks","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["training_rank"]},{"name":"is_primary","atttypid":16,"attlen":null,"attnum":6,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"ritual_checks","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]}],"name":"ritual_checks","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148355,"name":"pk_ritual_checks","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_ritual_checks_rituals_ritual_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147997,"fknsp":"public","fktab":"ritual_checks","refnspoid":2200,"refnsp":"public","reftab":"rituals","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"ritual_id","references":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","referenced":"id","references_table_name":"public.rituals"}],"remote_schema":"public","remote_table":"rituals","coveringindex":"ix_ritual_checks_ritual_id","autoindex":true,"hasindex":true},{"name":"fk_ritual_checks_trainings_training_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"n","confmatchtype":false,"conkey":[3],"confkey":[1],"confrelid":147876,"fknsp":"public","fktab":"ritual_checks","refnspoid":2200,"refnsp":"public","reftab":"trainings","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"training_id","references":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced":"id","references_table_name":"public.trainings"}],"remote_schema":"public","remote_table":"trainings","coveringindex":"ix_ritual_checks_training_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"4a8bc09d-314a-489d-bf60-d7cc045fbc98":{"id":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","type":"table","selected":false,"x":3150,"y":1755,"ports":[{"id":"fffc8052-8a23-4faf-ac07-b113624d7562","type":"onetomany","x":3150.9995967262294,"y":1849.6093695177005,"name":"coll-port-1-left","alignment":"left","parentNode":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","links":["590c0185-f2ac-4c91-ad57-a202816f78ad"]},{"id":"83ceeab9-0693-4ca1-a62d-21fee6298957","type":"onetomany","x":3324.0001911555773,"y":1849.6093695177005,"name":"coll-port-1-right","alignment":"right","parentNode":"4a8bc09d-314a-489d-bf60-d7cc045fbc98","links":["eb145a26-8512-4ebb-b547-a7da2afc08b9","8985c8ba-711f-4dce-a582-e23b8ccaa622"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["fffc8052-8a23-4faf-ac07-b113624d7562","83ceeab9-0693-4ca1-a62d-21fee6298957"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"level","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Уровень ритуала","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"cast_time","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Продолжительность ритуала в секундах","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"duration","atttypid":23,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Время действия эффекта ритуала в секундах","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"elements","atttypid":147615,"attlen":null,"attnum":5,"attndims":1,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"element_enum[]","displaytypname":"element_enum[]","cltype":"element_enum[]","inheritedfrom":null,"inheritedid":null,"elemoid":147616,"typnspname":"public","defaultstorage":"x","description":"Элементы ритуала","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["element_enum[]"]},{"name":"range","atttypid":23,"attlen":null,"attnum":6,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Дальнность ритуала","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"primary_check_rank","atttypid":147810,"attlen":null,"attnum":7,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"training_rank","displaytypname":"training_rank","cltype":"training_rank","inheritedfrom":null,"inheritedid":null,"elemoid":147810,"typnspname":"public","defaultstorage":"p","description":"Ранг основной проверки","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["training_rank"]},{"name":"primary_check_suffix","atttypid":3802,"attlen":null,"attnum":8,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Дополнительная информация об основной проверки","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"secondary_casters","atttypid":23,"attlen":null,"attnum":9,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Количество дополнительных заклинателей","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"secondary_casters_max","atttypid":23,"attlen":null,"attnum":10,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Максимальное количество дополнительных заклинателей","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","integer","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"secondary_casters_suffix","atttypid":3802,"attlen":null,"attnum":11,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Дополнительная информация о дополнительных заклинателях","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb","jsonb"]},{"name":"range_string","atttypid":3802,"attlen":null,"attnum":12,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Дальность ритуала","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb","jsonb","jsonb"]},{"name":"target","atttypid":3802,"attlen":null,"attnum":13,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Цель ритуала","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb","jsonb","jsonb","jsonb"]},{"name":"cost","atttypid":3802,"attlen":null,"attnum":14,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Стоимость ритуала","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb","jsonb","jsonb","jsonb","jsonb"]},{"name":"requirement","atttypid":3802,"attlen":null,"attnum":15,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Требования к ритуалу","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rituals","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb","jsonb","jsonb","jsonb","jsonb","jsonb"]}],"name":"rituals","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148002,"name":"pk_rituals","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_rituals_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"rituals","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_rituals","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a":{"id":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","type":"table","selected":false,"x":2490,"y":810,"ports":[{"id":"324db44b-d8a2-4062-86b3-e13dc4a390fb","type":"onetomany","x":2491,"y":904.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","links":[]},{"id":"e51832ba-be5e-4083-8228-e98f20b523b9","type":"onetomany","x":2664,"y":904.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","links":["947ff200-256e-4654-a91e-1cbcea306050","f2d1f789-fec6-44bf-a6f3-ffb958c19920"]},{"id":"9deb561b-f3dd-49a7-8782-bee852f02b84","type":"onetomany","x":2491,"y":956.703125,"name":"coll-port-3-left","alignment":"left","parentNode":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","links":[]},{"id":"81484ffd-5d53-4515-abff-af207b4f9c9f","type":"onetomany","x":2664,"y":956.703125,"name":"coll-port-3-right","alignment":"right","parentNode":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","links":["947ff200-256e-4654-a91e-1cbcea306050"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["324db44b-d8a2-4062-86b3-e13dc4a390fb","e51832ba-be5e-4083-8228-e98f20b523b9","9deb561b-f3dd-49a7-8782-bee852f02b84","81484ffd-5d53-4515-abff-af207b4f9c9f"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rules","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"order","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Порядок отображения","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rules","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"parent_id","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Родительского правило","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rules","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"breadcrumbs","atttypid":1009,"attlen":null,"attnum":4,"attndims":1,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"text[]","displaytypname":"text[]","cltype":"text[]","inheritedfrom":null,"inheritedid":null,"elemoid":25,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"rules","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["text[]"]}],"name":"rules","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148014,"name":"pk_rules","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_rules_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"rules","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_rules","autoindex":true,"hasindex":true},{"name":"fk_rules_rules_parent_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"a","confmatchtype":false,"conkey":[3],"confkey":[1],"confrelid":148009,"fknsp":"public","fktab":"rules","refnspoid":2200,"refnsp":"public","reftab":"rules","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"parent_id","references":"e4254e13-eab0-4384-9eb8-7c6ca9acfc1a","referenced":"id","references_table_name":"public.rules"}],"remote_schema":"public","remote_table":"rules","coveringindex":"ix_rules_parent_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"77bdd1d9-21d0-4481-b709-2f29aa01c1dd":{"id":"77bdd1d9-21d0-4481-b709-2f29aa01c1dd","type":"table","selected":false,"x":2205,"y":-540,"ports":[{"id":"bbc0c935-ed1c-4a78-9282-a66fcb5aa86f","type":"onetomany","x":2205.9999151705197,"y":-445.3906357897048,"name":"coll-port-1-left","alignment":"left","parentNode":"77bdd1d9-21d0-4481-b709-2f29aa01c1dd","links":[]},{"id":"8ab26760-2dc9-4f1c-a4c4-060676890419","type":"onetomany","x":2379.0000319334276,"y":-445.3906357897048,"name":"coll-port-1-right","alignment":"right","parentNode":"77bdd1d9-21d0-4481-b709-2f29aa01c1dd","links":["85201d1e-de31-40b3-a765-519330982299"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["bbc0c935-ed1c-4a78-9282-a66fcb5aa86f","8ab26760-2dc9-4f1c-a4c4-060676890419"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"sources","is_view_only":false,"attcompression":null,"seqrelid":147869,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"name","atttypid":1043,"attlen":"255","attnum":2,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":"Отображаемое имя","indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"sources","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"category","atttypid":147738,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"source_category","displaytypname":"source_category","cltype":"source_category","inheritedfrom":null,"inheritedid":null,"elemoid":147738,"typnspname":"public","defaultstorage":"p","description":"Категория источника","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"sources","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["source_category"]},{"name":"release_date","atttypid":1082,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"date","displaytypname":"date","cltype":"date","inheritedfrom":null,"inheritedid":null,"elemoid":1082,"typnspname":"pg_catalog","defaultstorage":"p","description":"Дата выхода источника","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"sources","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["date","timestamp with time zone","timestamp without time zone"]}],"name":"sources","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147873,"name":"pk_sources","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"unique_constraint":[],"foreign_key":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"3ee55e19-cad2-4d9c-a39b-93886e3797a6":{"id":"3ee55e19-cad2-4d9c-a39b-93886e3797a6","type":"table","selected":false,"x":3420,"y":2295,"ports":[{"id":"ea209146-72fc-4578-9a70-d1cf6f452346","type":"onetomany","x":3421,"y":2415.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"3ee55e19-cad2-4d9c-a39b-93886e3797a6","links":["214e3221-187b-4711-ae7e-d00d6a2152af"]},{"id":"3aa41ad2-4072-4f98-86dc-07ae798f813a","type":"onetomany","x":3594,"y":2415.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"3ee55e19-cad2-4d9c-a39b-93886e3797a6","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["ea209146-72fc-4578-9a70-d1cf6f452346","3aa41ad2-4072-4f98-86dc-07ae798f813a"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"spell_areas","is_view_only":false,"attcompression":null,"seqrelid":148383,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"spell_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_areas","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"details","atttypid":3802,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_areas","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"type","atttypid":147754,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"spell_area_type","displaytypname":"spell_area_type","cltype":"spell_area_type","inheritedfrom":null,"inheritedid":null,"elemoid":147754,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_areas","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["spell_area_type"]}],"name":"spell_areas","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148389,"name":"pk_spell_areas","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_spell_areas_spells_spell_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":148026,"fknsp":"public","fktab":"spell_areas","refnspoid":2200,"refnsp":"public","reftab":"spells","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"spell_id","references":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","referenced":"id","references_table_name":"public.spells"}],"remote_schema":"public","remote_table":"spells","coveringindex":"ix_spell_areas_spell_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"17f97e21-d3df-4ca8-98bc-8804869e1642":{"id":"17f97e21-d3df-4ca8-98bc-8804869e1642","type":"table","selected":false,"x":3960,"y":2295,"ports":[{"id":"54cef317-da88-4da7-b7ec-4b7d4cc588fa","type":"onetomany","x":3961.0000213186213,"y":2415.6562445177005,"name":"coll-port-2-left","alignment":"left","parentNode":"17f97e21-d3df-4ca8-98bc-8804869e1642","links":["4abd9b5b-60c2-4610-9519-0e47a33baaf6"]},{"id":"6b607ce7-e550-4cb1-b015-0a4aaef72cf5","type":"onetomany","x":4133.999341970795,"y":2415.6562445177005,"name":"coll-port-2-right","alignment":"right","parentNode":"17f97e21-d3df-4ca8-98bc-8804869e1642","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["54cef317-da88-4da7-b7ec-4b7d4cc588fa","6b607ce7-e550-4cb1-b015-0a4aaef72cf5"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":148396,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"spell_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"key","atttypid":1043,"attlen":"255","attnum":3,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"apply_mod","atttypid":16,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]},{"name":"category","atttypid":147778,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"spell_damage_category","displaytypname":"spell_damage_category","cltype":"spell_damage_category","inheritedfrom":null,"inheritedid":null,"elemoid":147778,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["spell_damage_category"]},{"name":"formula","atttypid":1043,"attlen":"255","attnum":6,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"kinds","atttypid":147786,"attlen":null,"attnum":7,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"spell_damage_kinds","displaytypname":"spell_damage_kinds","cltype":"spell_damage_kinds","inheritedfrom":null,"inheritedid":null,"elemoid":147786,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["spell_damage_kinds"]},{"name":"materials","atttypid":1043,"attlen":"255","attnum":8,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]},{"name":"type","atttypid":1043,"attlen":"255","attnum":9,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_damages","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","character varying","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"spell_damages","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148402,"name":"pk_spell_damages","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_spell_damages_spells_spell_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":148026,"fknsp":"public","fktab":"spell_damages","refnspoid":2200,"refnsp":"public","reftab":"spells","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"spell_id","references":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","referenced":"id","references_table_name":"public.spells"}],"remote_schema":"public","remote_table":"spells","coveringindex":"ix_spell_damages_spell_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"901fffac-2b20-4177-af3d-767650c789ba":{"id":"901fffac-2b20-4177-af3d-767650c789ba","type":"table","selected":false,"x":3690,"y":2295,"ports":[{"id":"c0cd5be3-5f6b-42a7-ba69-b006cb9fb0ed","type":"onetomany","x":3691.0000213186213,"y":2415.6561595992225,"name":"coll-port-2-left","alignment":"left","parentNode":"901fffac-2b20-4177-af3d-767650c789ba","links":["240ba017-058e-41eb-a5bf-c6cb3a4e4451"]},{"id":"bf730742-5a0b-44c6-b289-381fbe4ee233","type":"onetomany","x":3863.9993419707953,"y":2415.6561595992225,"name":"coll-port-2-right","alignment":"right","parentNode":"901fffac-2b20-4177-af3d-767650c789ba","links":[]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["c0cd5be3-5f6b-42a7-ba69-b006cb9fb0ed","bf730742-5a0b-44c6-b289-381fbe4ee233"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"spell_defenses","is_view_only":false,"attcompression":null,"seqrelid":148409,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"spell_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_defenses","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"is_passive","atttypid":16,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_defenses","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]},{"name":"is_basic","atttypid":16,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_defenses","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","boolean","character","character varying","text"]},{"name":"statistic","atttypid":1043,"attlen":"255","attnum":5,"attndims":0,"atttypmod":259,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"character varying","displaytypname":"character varying(255)","cltype":"character varying","inheritedfrom":null,"inheritedid":null,"elemoid":1043,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spell_defenses","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","character varying","information_schema.character_data","information_schema.yes_or_no","name","regclass","text"]}],"name":"spell_defenses","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148413,"name":"pk_spell_defenses","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_spell_defenses_spells_spell_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":148026,"fknsp":"public","fktab":"spell_defenses","refnspoid":2200,"refnsp":"public","reftab":"spells","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"spell_id","references":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","referenced":"id","references_table_name":"public.spells"}],"remote_schema":"public","remote_table":"spells","coveringindex":"ix_spell_defenses_spell_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1":{"id":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","type":"table","selected":false,"x":3150,"y":2295,"ports":[{"id":"acc6924a-e054-467c-a0e4-346150c6a194","type":"onetomany","x":3151,"y":2389.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","links":["7c43d283-c3d2-494d-95cf-ac92a8977a15"]},{"id":"c54dc85c-d4f5-4139-9582-8ac3af3285b7","type":"onetomany","x":3324,"y":2389.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"2ca3f19a-c441-417d-b0aa-a8cc167fd6e1","links":["4abd9b5b-60c2-4610-9519-0e47a33baaf6","240ba017-058e-41eb-a5bf-c6cb3a4e4451","8274b177-f7c4-45c1-8a60-083c9e7bdde1","214e3221-187b-4711-ae7e-d00d6a2152af"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["acc6924a-e054-467c-a0e4-346150c6a194","c54dc85c-d4f5-4139-9582-8ac3af3285b7"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"level","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"is_focus","atttypid":16,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"boolean","displaytypname":"boolean","cltype":"boolean","inheritedfrom":null,"inheritedid":null,"elemoid":16,"typnspname":"pg_catalog","defaultstorage":"p","description":"Фокус?","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["boolean","character","character varying","text"]},{"name":"actions","atttypid":147498,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"action_enum","displaytypname":"action_enum","cltype":"action_enum","inheritedfrom":null,"inheritedid":null,"elemoid":147498,"typnspname":"public","defaultstorage":"p","description":"Количество действий","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["action_enum"]},{"name":"duration","atttypid":23,"attlen":null,"attnum":5,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Время длительности заклинания","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"range","atttypid":23,"attlen":null,"attnum":6,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Дальность заклинания","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"traditions","atttypid":147793,"attlen":null,"attnum":7,"attndims":1,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"spell_tradition[]","displaytypname":"spell_tradition[]","cltype":"spell_tradition[]","inheritedfrom":null,"inheritedid":null,"elemoid":147794,"typnspname":"public","defaultstorage":"x","description":"Традиции заклинания","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["spell_tradition[]"]},{"name":"elements","atttypid":147615,"attlen":null,"attnum":8,"attndims":1,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"element_enum[]","displaytypname":"element_enum[]","cltype":"element_enum[]","inheritedfrom":null,"inheritedid":null,"elemoid":147616,"typnspname":"public","defaultstorage":"x","description":"Элементы заклинания","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["element_enum[]"]},{"name":"trigger","atttypid":3802,"attlen":null,"attnum":9,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Триггер заклинания","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"target","atttypid":3802,"attlen":null,"attnum":10,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Цель заклинания","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb","jsonb"]},{"name":"cost","atttypid":3802,"attlen":null,"attnum":11,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Стоимость заклинания","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb","jsonb","jsonb"]},{"name":"requirement","atttypid":3802,"attlen":null,"attnum":12,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Требования к заклинанию","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"spells","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb","jsonb","jsonb","jsonb"]}],"name":"spells","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148031,"name":"pk_spells","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_spells_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"spells","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_spells","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"e1911550-50d2-4176-b5e3-c1a6fe3b745a":{"id":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","type":"table","selected":false,"x":2475,"y":1575,"ports":[{"id":"86be2202-b74b-49c7-9083-8ff87c7fadc7","type":"onetomany","x":2476,"y":1695.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","links":[]},{"id":"8bd76640-7b82-4d78-a68e-2c44e7ed6d3c","type":"onetomany","x":2649,"y":1695.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","links":["a7bc8cd0-ed06-4ada-b938-05ebbb00b366"]},{"id":"dae3d19f-1b5b-4036-8134-b47c2d1f6d1f","type":"onetomany","x":2476,"y":1669.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","links":[]},{"id":"d4cdb3ee-7aa4-457d-9b15-acb8b4649515","type":"onetomany","x":2649,"y":1669.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","links":["caa8d582-a73e-472e-b125-1b03bd2bd27c","4294a055-08ed-4b67-8456-a97e52c562b6"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["86be2202-b74b-49c7-9083-8ff87c7fadc7","8bd76640-7b82-4d78-a68e-2c44e7ed6d3c","dae3d19f-1b5b-4036-8134-b47c2d1f6d1f","d4cdb3ee-7aa4-457d-9b15-acb8b4649515"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclass_groups","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"class_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":false,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор класса","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclass_groups","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"subclass_groups","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148235,"name":"pk_subclass_groups","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_subclass_groups_classes_class_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147917,"fknsp":"public","fktab":"subclass_groups","refnspoid":2200,"refnsp":"public","reftab":"classes","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"class_id","references":"3698aa00-794f-4f84-b1a1-6695f58bf2fb","referenced":"id","references_table_name":"public.classes"}],"remote_schema":"public","remote_table":"classes","coveringindex":"ix_subclass_groups_class_id","autoindex":true,"hasindex":true},{"name":"fk_subclass_groups_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"subclass_groups","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_subclass_groups","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"d9f61514-38de-415d-9b62-cc1a2af7b4b0":{"id":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","type":"table","selected":false,"x":2250,"y":1920,"ports":[{"id":"c10d26ce-0c5b-4292-8f17-011809fa480b","type":"onetomany","x":2251,"y":2040.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","links":[]},{"id":"965efe94-3cf6-481a-b38d-ca1e3cdd786f","type":"onetomany","x":2424,"y":2040.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","links":["299ec798-c3c2-4c2e-b926-e7874e72c0f1"]},{"id":"84e4008c-fa51-4215-9834-4ac272dc14f3","type":"onetomany","x":2251,"y":2014.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","links":[]},{"id":"3fccb262-fa3c-44c2-b32c-34471deb2b5d","type":"onetomany","x":2424,"y":2014.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"d9f61514-38de-415d-9b62-cc1a2af7b4b0","links":["8de5d890-9656-42de-bc97-a18c30f48918"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["c10d26ce-0c5b-4292-8f17-011809fa480b","965efe94-3cf6-481a-b38d-ca1e3cdd786f","84e4008c-fa51-4215-9834-4ac272dc14f3","3fccb262-fa3c-44c2-b32c-34471deb2b5d"],"otherInfo":{"data":{"columns":[{"name":"subclass_id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclass_provided_entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"entity_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1 2","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclass_provided_entities","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"subclass_provided_entities","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148470,"name":"pk_subclass_provided_entities","col_count":2,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"subclass_id"},{"column":"entity_id"}],"include":[]}],"foreign_key":[{"name":"fk_subclass_provided_entities_entities_entity_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"subclass_provided_entities","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"entity_id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"ix_subclass_provided_entities_entity_id","autoindex":true,"hasindex":true},{"name":"fk_subclass_provided_entities_subclasses_subclass_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":148435,"fknsp":"public","fktab":"subclass_provided_entities","refnspoid":2200,"refnsp":"public","reftab":"subclasses","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"subclass_id","references":"05ccf9be-5907-4e89-b55f-6f328890bdd0","referenced":"id","references_table_name":"public.subclasses"}],"remote_schema":"public","remote_table":"subclasses","coveringindex":null,"autoindex":false,"hasindex":false}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"700c9d5f-3fae-4b6b-b813-c8a04b62035d":{"id":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","type":"table","selected":false,"x":2175,"y":1650,"ports":[{"id":"2e4bd8db-2a4d-41be-912e-7f4beac7604d","type":"onetomany","x":2176,"y":1770.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","links":[]},{"id":"12e28ff8-62dc-46dc-aa34-e9bf13516da5","type":"onetomany","x":2349,"y":1770.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","links":["c59988fa-9734-4c71-bca8-7388db7f4e2e"]},{"id":"4d5e68ee-95cd-47db-acb4-8d28a06fd787","type":"onetomany","x":2176,"y":1796.703125,"name":"coll-port-3-left","alignment":"left","parentNode":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","links":[]},{"id":"329b235c-b630-403b-a6a4-2c92b61d7452","type":"onetomany","x":2349,"y":1796.703125,"name":"coll-port-3-right","alignment":"right","parentNode":"700c9d5f-3fae-4b6b-b813-c8a04b62035d","links":["b15e8c80-b28d-44e6-9083-8c8425afab67"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["2e4bd8db-2a4d-41be-912e-7f4beac7604d","12e28ff8-62dc-46dc-aa34-e9bf13516da5","4d5e68ee-95cd-47db-acb4-8d28a06fd787","329b235c-b630-403b-a6a4-2c92b61d7452"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"subclass_trainings","is_view_only":false,"attcompression":null,"seqrelid":148482,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"],"cid":"c18"},{"name":"subclass_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclass_trainings","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"],"cid":"c19"},{"name":"training_id","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclass_trainings","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"],"cid":"c20"},{"name":"rank","atttypid":147810,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"training_rank","displaytypname":"training_rank","cltype":"training_rank","inheritedfrom":null,"inheritedid":null,"elemoid":147810,"typnspname":"public","defaultstorage":"p","description":null,"indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclass_trainings","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["training_rank"],"cid":"c21"}],"name":"subclass_trainings","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148486,"name":"pk_subclass_trainings","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id","cid":"c13"}],"include":[],"cid":"c12"}],"foreign_key":[{"name":"fk_subclass_trainings_subclasses_subclass_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":148435,"fknsp":"public","fktab":"subclass_trainings","refnspoid":2200,"refnsp":"public","reftab":"subclasses","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"subclass_id","references":"05ccf9be-5907-4e89-b55f-6f328890bdd0","referenced":"id","references_table_name":"public.subclasses","cid":"c15"}],"remote_schema":"public","remote_table":"subclasses","coveringindex":"ix_subclass_trainings_subclass_id","autoindex":true,"hasindex":true,"cid":"c14"},{"name":"fk_subclass_trainings_trainings_training_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[3],"confkey":[1],"confrelid":147876,"fknsp":"public","fktab":"subclass_trainings","refnspoid":2200,"refnsp":"public","reftab":"trainings","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"training_id","references":"a8da1c01-6c92-475b-9088-6449c7a7afaf","referenced":"id","references_table_name":"public.trainings","cid":"c17"}],"remote_schema":"public","remote_table":"trainings","coveringindex":"ix_subclass_trainings_training_id","autoindex":true,"hasindex":true,"cid":"c16"}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"05ccf9be-5907-4e89-b55f-6f328890bdd0":{"id":"05ccf9be-5907-4e89-b55f-6f328890bdd0","type":"table","selected":false,"x":2490,"y":1770,"ports":[{"id":"64c53b8c-e951-4576-bfb3-f591ae6f3230","type":"onetomany","x":2491,"y":1864.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"05ccf9be-5907-4e89-b55f-6f328890bdd0","links":["c59988fa-9734-4c71-bca8-7388db7f4e2e"]},{"id":"a061d917-ed8d-4603-932b-2842217d8cd1","type":"onetomany","x":2664,"y":1864.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"05ccf9be-5907-4e89-b55f-6f328890bdd0","links":["8d50def6-1bb0-478a-aeaa-c97ad2d38d7f","8de5d890-9656-42de-bc97-a18c30f48918"]},{"id":"e1d23cde-4b07-4417-b90b-2ed824b23d1d","type":"onetomany","x":2491,"y":1890.65625,"name":"coll-port-2-left","alignment":"left","parentNode":"05ccf9be-5907-4e89-b55f-6f328890bdd0","links":[]},{"id":"adc4fbe2-85bb-42e4-8309-bca19810d9e0","type":"onetomany","x":2664,"y":1890.65625,"name":"coll-port-2-right","alignment":"right","parentNode":"05ccf9be-5907-4e89-b55f-6f328890bdd0","links":["4294a055-08ed-4b67-8456-a97e52c562b6"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["64c53b8c-e951-4576-bfb3-f591ae6f3230","a061d917-ed8d-4603-932b-2842217d8cd1","e1d23cde-4b07-4417-b90b-2ed824b23d1d","adc4fbe2-85bb-42e4-8309-bca19810d9e0"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclasses","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"group_id","atttypid":23,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Группа подклассов","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclasses","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"additional","atttypid":3802,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Дополнительная информация","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclasses","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"extra","atttypid":25,"attlen":null,"attnum":4,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"text","displaytypname":"text","cltype":"text","inheritedfrom":null,"inheritedid":null,"elemoid":25,"typnspname":"pg_catalog","defaultstorage":"x","description":null,"indkey":"1","isdup":false,"collspcname":"pg_catalog.\"default\"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"subclasses","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["\"char\"","character","character varying","name","regclass","text"]}],"name":"subclasses","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148440,"name":"pk_subclasses","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_subclasses_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"subclasses","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_subclasses","autoindex":true,"hasindex":true},{"name":"fk_subclasses_subclass_groups_group_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[2],"confkey":[1],"confrelid":148232,"fknsp":"public","fktab":"subclasses","refnspoid":2200,"refnsp":"public","reftab":"subclass_groups","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"group_id","references":"e1911550-50d2-4176-b5e3-c1a6fe3b745a","referenced":"id","references_table_name":"public.subclass_groups"}],"remote_schema":"public","remote_table":"subclass_groups","coveringindex":"ix_subclasses_group_id","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"a8da1c01-6c92-475b-9088-6449c7a7afaf":{"id":"a8da1c01-6c92-475b-9088-6449c7a7afaf","type":"table","selected":false,"x":4095,"y":-780,"ports":[{"id":"096e2789-e9b1-4623-a2d8-332700cafd6a","type":"onetomany","x":4096,"y":-685.390625,"name":"coll-port-1-left","alignment":"left","parentNode":"a8da1c01-6c92-475b-9088-6449c7a7afaf","links":["600c552a-40e7-4e31-9057-756b1dfe5379","b15e8c80-b28d-44e6-9083-8c8425afab67","3304af0e-1bf4-42fa-bef1-39d7b300ed53"]},{"id":"46484358-e975-4399-9363-ca9793e2836e","type":"onetomany","x":4269,"y":-685.390625,"name":"coll-port-1-right","alignment":"right","parentNode":"a8da1c01-6c92-475b-9088-6449c7a7afaf","links":["6981aac6-5e9a-4e34-aea2-1f9fe83e33e8","becbd62a-cd2d-43ec-a2f3-db062547a7f9"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["096e2789-e9b1-4623-a2d8-332700cafd6a","46484358-e975-4399-9363-ca9793e2836e"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"d","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"i","genexpr":null,"relname":"trainings","is_view_only":false,"attcompression":null,"seqrelid":147875,"seqtypid":23,"seqstart":"1","seqincrement":"1","seqmax":"2147483647","seqmin":"1","seqcache":"1","seqcycle":false,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"name","atttypid":3802,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"x","attidentity":"","defval":null,"typname":"jsonb","displaytypname":"jsonb","cltype":"jsonb","inheritedfrom":null,"inheritedid":null,"elemoid":3802,"typnspname":"pg_catalog","defaultstorage":"x","description":"Название способности","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"trainings","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["json","jsonb"]},{"name":"type","atttypid":23,"attlen":null,"attnum":3,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Тип способности","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"trainings","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]}],"name":"trainings","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":147881,"name":"pk_trainings","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"unique_constraint":[],"foreign_key":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}},"3f2a50f8-9894-4730-b590-6f02ddca6546":{"id":"3f2a50f8-9894-4730-b590-6f02ddca6546","type":"table","selected":false,"x":2220,"y":630,"ports":[{"id":"5056d911-cab6-487a-977d-1dc40b4c5ef5","type":"onetomany","x":2221,"y":724.609375,"name":"coll-port-1-left","alignment":"left","parentNode":"3f2a50f8-9894-4730-b590-6f02ddca6546","links":[]},{"id":"0528d5db-0e01-462d-8a62-64d48ed30b80","type":"onetomany","x":2394,"y":724.609375,"name":"coll-port-1-right","alignment":"right","parentNode":"3f2a50f8-9894-4730-b590-6f02ddca6546","links":["55ee0e0a-6cef-4d72-89c8-8044e4841c9e","4974960f-15c6-4535-bba9-721a89ded051"]}],"name":"Untitled","color":"rgb(0,192,255)","portsInOrder":[],"portsOutOrder":["5056d911-cab6-487a-977d-1dc40b4c5ef5","0528d5db-0e01-462d-8a62-64d48ed30b80"],"otherInfo":{"data":{"columns":[{"name":"id","atttypid":23,"attlen":null,"attnum":1,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"integer","displaytypname":"integer","cltype":"integer","inheritedfrom":null,"inheritedid":null,"elemoid":23,"typnspname":"pg_catalog","defaultstorage":"p","description":"Идентификатор","indkey":"1","isdup":false,"collspcname":"","is_fk":true,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"traits","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":true,"is_primary_key":true,"attprecision":null,"coloptions":[],"edit_types":["bigint","double precision","information_schema.cardinal_number","integer","money","numeric","oid","real","regclass","regcollation","regconfig","regdictionary","regnamespace","regoper","regoperator","regproc","regprocedure","regrole","regtype","smallint"]},{"name":"type","atttypid":147824,"attlen":null,"attnum":2,"attndims":0,"atttypmod":-1,"attacl":[],"attnotnull":true,"attoptions":null,"attfdwoptions":null,"attstattarget":-1,"attstorage":"p","attidentity":"","defval":null,"typname":"trait_type","displaytypname":"trait_type","cltype":"trait_type","inheritedfrom":null,"inheritedid":null,"elemoid":147824,"typnspname":"public","defaultstorage":"p","description":"Тип признака","indkey":"1","isdup":false,"collspcname":"","is_fk":false,"seclabels":null,"is_sys_column":false,"colconstype":"n","genexpr":null,"relname":"traits","is_view_only":false,"attcompression":null,"seqrelid":null,"seqtypid":null,"seqstart":null,"seqincrement":null,"seqmax":null,"seqmin":null,"seqcache":null,"seqcycle":null,"is_pk":false,"is_primary_key":false,"attprecision":null,"coloptions":[],"edit_types":["trait_type"]}],"name":"traits","schema":"public","description":null,"rlspolicy":false,"forcerlspolicy":false,"fillfactor":null,"toast_tuple_target":null,"parallel_workers":null,"relpersistence":false,"primary_key":[{"oid":148041,"name":"pk_traits","col_count":1,"spcname":"pg_default","comment":null,"condeferrable":false,"condeferred":false,"conislocal":true,"fillfactor":null,"columns":[{"column":"id"}],"include":[]}],"foreign_key":[{"name":"fk_traits_entities_id","condeferrable":false,"condeferred":false,"confupdtype":"a","confdeltype":"c","confmatchtype":false,"conkey":[1],"confkey":[1],"confrelid":147840,"fknsp":"public","fktab":"traits","refnspoid":2200,"refnsp":"public","reftab":"entities","comment":null,"convalidated":true,"conislocal":true,"columns":[{"local_column":"id","references":"a2068488-3358-4fdb-a4d6-e747d8711d93","referenced":"id","references_table_name":"public.entities"}],"remote_schema":"public","remote_table":"entities","coveringindex":"pk_traits","autoindex":true,"hasindex":true}],"unique_constraint":[]},"note":"","metadata":{"data_failed":false,"is_promise":false}}}}}]}} \ No newline at end of file diff --git a/erd/pf2e.pgerd.png b/erd/pf2e.pgerd.png new file mode 100644 index 0000000000000000000000000000000000000000..302575b2c8f25adf8f8a81acc423ce4ee600ebae GIT binary patch literal 679341 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≥yBr(!=fR%bX*uTZyaHyk{4C6@?6(x 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

+|Ec^gNo0Dyo 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

dE* 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 Date: Sun, 1 Jun 2025 22:27:24 +0400 Subject: [PATCH 33/33] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...signer.cs => 20250601182634_1.Designer.cs} | 239 ++++++++++++------ ...0250601111002_1.cs => 20250601182634_1.cs} | 156 ++++++------ .../Migrations/Pf2eDbContextModelSnapshot.cs | 233 +++++++++++------ 3 files changed, 391 insertions(+), 237 deletions(-) rename Pf2eModel/Migrations/{20250601111002_1.Designer.cs => 20250601182634_1.Designer.cs} (91%) rename Pf2eModel/Migrations/{20250601111002_1.cs => 20250601182634_1.cs} (92%) diff --git a/Pf2eModel/Migrations/20250601111002_1.Designer.cs b/Pf2eModel/Migrations/20250601182634_1.Designer.cs similarity index 91% rename from Pf2eModel/Migrations/20250601111002_1.Designer.cs rename to Pf2eModel/Migrations/20250601182634_1.Designer.cs index c4607c3..50fc6eb 100644 --- a/Pf2eModel/Migrations/20250601111002_1.Designer.cs +++ b/Pf2eModel/Migrations/20250601182634_1.Designer.cs @@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum; namespace Pf2eModel.Migrations { [DbContext(typeof(Pf2eDbContext))] - [Migration("20250601111002_1")] + [Migration("20250601182634_1")] partial class _1 { /// @@ -36,7 +36,7 @@ namespace Pf2eModel.Migrations NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "entity_rarity", new[] { "common", "rare", "uncommon", "unique" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "entity_type", new[] { "action", "ancestry", "archetype", "background", "backpack", "campaign_feature", "class", "condition", "deity", "feat", "heritage", "language", "ritual", "rule", "spell", "subclass", "subclass_group", "trait", "unknown" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "feat_category", new[] { "ancestry", "ancestry-feature", "archetype", "bonus", "calling", "campaign-feature", "class", "class-feature", "curse", "deityboon", "general", "pfsboon", "skill", "uncategorized" }); - NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "locale_type", new[] { "english", "hobby", "legacy", "legacy_english", "other", "russian" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "locale_type", new[] { "english", "hobby", "other", "russian" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "size_enum", new[] { "huge", "large", "medium", "small", "tiny" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "source_category", new[] { "adventure-path", "adventures", "blog-posts", "comics", "lost-omens", "rulebook", "society" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "spell_area_type", new[] { "burst", "circle", "cone", "cube", "cuboid", "cylinder", "emanation", "line", "other", "square", "varies" }); @@ -401,7 +401,8 @@ namespace Pf2eModel.Migrations .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("external_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор элемента в ссылке"); b.Property("Md5") .IsRequired() @@ -618,17 +619,20 @@ namespace Pf2eModel.Migrations b.Property("AncestryId") .HasColumnType("integer") .HasColumnName("ancestry_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор родословной"); b.Property("Boost") .HasColumnType("boolean") .HasColumnName("boost") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Флаг повышения/снижения"); b.Property("Value") .HasColumnType("attribute_values") .HasColumnName("value") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Значение атрибута"); b.HasKey("Id") .HasName("pk_ancestry_boosts"); @@ -644,17 +648,20 @@ namespace Pf2eModel.Migrations b.Property("AncestryId") .HasColumnType("integer") .HasColumnName("ancestry_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор родословной"); b.Property("ItemId") .HasColumnType("integer") .HasColumnName("item_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор связанной сущности"); b.Property("Level") .HasColumnType("integer") .HasColumnName("level") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Уровень сущности в родословной"); b.HasKey("AncestryId", "ItemId") .HasName("pk_ancestry_items"); @@ -670,17 +677,20 @@ namespace Pf2eModel.Migrations b.Property("AncestryId") .HasColumnType("integer") .HasColumnName("ancestry_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор родословной"); b.Property("LanguageId") .HasColumnType("integer") .HasColumnName("language_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор связанного языка"); b.Property("IsAdditional") .HasColumnType("boolean") .HasColumnName("is_additional") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Флаг дополнительного"); b.HasKey("AncestryId", "LanguageId") .HasName("pk_ancestry_languages"); @@ -731,11 +741,13 @@ namespace Pf2eModel.Migrations { b.Property("ArchetypeId") .HasColumnType("integer") - .HasColumnName("archetype_id"); + .HasColumnName("archetype_id") + .HasComment("Идентификатор архетипа"); b.Property("FeatId") .HasColumnType("integer") - .HasColumnName("feat_id"); + .HasColumnName("feat_id") + .HasComment("Идентификатор черты"); b.HasKey("ArchetypeId", "FeatId") .HasName("pk_archetype_feats"); @@ -766,19 +778,22 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Attribute") .HasColumnType("attribute_values") .HasColumnName("attribute") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Атрибут"); b.Property("BackgroundId") .HasColumnType("integer") .HasColumnName("background_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор предыстории"); b.HasKey("Id") .HasName("pk_background_boosts"); @@ -794,12 +809,14 @@ namespace Pf2eModel.Migrations b.Property("BackgroundId") .HasColumnType("integer") .HasColumnName("background_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); b.Property("EntityId") .HasColumnType("integer") .HasColumnName("entity_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор сущности"); b.Property("Level") .HasColumnType("integer") @@ -820,22 +837,26 @@ namespace Pf2eModel.Migrations b.Property("Id") .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); b.Property("BackgroundId") .HasColumnType("integer") .HasColumnName("background_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор предыстории"); b.Property("Rank") .HasColumnType("training_rank") .HasColumnName("rank") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Ранг тренировки"); b.Property("TrainingId") .HasColumnType("integer") .HasColumnName("training_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор тренируемого навыка"); b.HasKey("Id") .HasName("pk_background_skills"); @@ -887,25 +908,28 @@ namespace Pf2eModel.Migrations b.ToTable("classes", (string)null); }); - modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.ClassKeyAbility", b => + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.ClassKeyAttribute", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ClassId") .HasColumnType("integer") .HasColumnName("class_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор класса"); b.Property("Value") .HasColumnType("attribute_values") .HasColumnName("value") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Атрибут"); b.HasKey("Id") .HasName("pk_class_key_abilities"); @@ -922,29 +946,34 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ClassId") .HasColumnType("integer") .HasColumnName("class_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор класса"); b.Property("EntityId") .HasColumnType("integer") .HasColumnName("entity_id") - .HasColumnOrder(5); + .HasColumnOrder(5) + .HasComment("Идентификатор сущности"); b.Property("Level") .HasColumnType("integer") .HasColumnName("level") - .HasColumnOrder(31); + .HasColumnOrder(3) + .HasComment("Уровень класса"); b.Property("Type") .HasColumnType("class_progression_type") .HasColumnName("type") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Тип элемента прогрессии класса"); b.HasKey("Id") .HasName("pk_class_progressions"); @@ -964,24 +993,28 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ClassId") .HasColumnType("integer") .HasColumnName("class_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор класса"); b.Property("Rank") .HasColumnType("training_rank") .HasColumnName("rank") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Ранг тренировки"); b.Property("TrainingId") .HasColumnType("integer") .HasColumnName("training_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор тренируемого навыка"); b.HasKey("Id") .HasName("pk_class_trainings"); @@ -1020,24 +1053,28 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор связи"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Rank") .HasColumnType("training_rank") .HasColumnName("rank") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Ранг тренировки"); b.Property("SubclassId") .HasColumnType("integer") .HasColumnName("subclass_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор подкласса"); b.Property("TrainingId") .HasColumnType("integer") .HasColumnName("training_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор тренировки"); b.HasKey("Id") .HasName("pk_subclass_trainings"); @@ -1120,12 +1157,14 @@ namespace Pf2eModel.Migrations b.Property("DeityId") .HasColumnType("integer") .HasColumnName("deity_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор божества"); b.Property("Attribute") .HasColumnType("attribute_values") .HasColumnName("attribute") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Атрибут божества"); b.HasKey("DeityId", "Attribute") .HasName("pk_deity_attributes"); @@ -1139,19 +1178,22 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DeityId") .HasColumnType("integer") .HasColumnName("deity_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор божества"); b.Property("IsPrimary") .HasColumnType("boolean") .HasColumnName("is_primary") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Флаг основного домена"); b.Property("Value") .IsRequired() @@ -1175,24 +1217,28 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DeityId") .HasColumnType("integer") .HasColumnName("deity_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор божества"); b.Property("Level") .HasColumnType("integer") .HasColumnName("level") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Уровень заклинания"); b.Property("SpellId") .HasColumnType("integer") .HasColumnName("spell_id") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Идентификатор заклинания"); b.HasKey("Id") .HasName("pk_deity_spells"); @@ -1212,21 +1258,24 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DeityId") .HasColumnType("integer") .HasColumnName("deity_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор божества"); b.Property("Weapon") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("weapon") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Название оружия"); b.HasKey("Id") .HasName("pk_deity_weapons"); @@ -1281,21 +1330,24 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("FeatId") .HasColumnType("integer") .HasColumnName("feat_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор черты"); b.Property("Value") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("value") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Предварительное требование"); b.HasKey("Id") .HasName("pk_feat_prerequisites"); @@ -1454,18 +1506,21 @@ namespace Pf2eModel.Migrations b.Property("RitualId") .HasColumnType("integer") .HasColumnName("ritual_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор ритуала"); b.Property("Details") .HasMaxLength(255) .HasColumnType("jsonb") .HasColumnName("details") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Описание области действия ритуала"); b.Property("Type") .HasColumnType("spell_area_type") .HasColumnName("type") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Тип области действия ритуала"); b.HasKey("RitualId") .HasName("pk_ritual_areas"); @@ -1479,34 +1534,40 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("IsPrimary") .HasColumnType("boolean") .HasColumnName("is_primary") - .HasColumnOrder(6); + .HasColumnOrder(6) + .HasComment("Флаг основной проверки"); b.Property("Rank") .HasColumnType("training_rank") .HasColumnName("rank") - .HasColumnOrder(5); + .HasColumnOrder(5) + .HasComment("Ранг тренировки"); b.Property("RitualId") .HasColumnType("integer") .HasColumnName("ritual_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор ритуала"); b.Property("TrainingId") .HasColumnType("integer") .HasColumnName("training_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор тренировки"); b.Property("TrainingString") .HasColumnType("jsonb") .HasColumnName("training_string") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Дополнение к проверке"); b.HasKey("Id") .HasName("pk_ritual_checks"); @@ -1642,7 +1703,8 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); @@ -1650,17 +1712,20 @@ namespace Pf2eModel.Migrations .HasMaxLength(255) .HasColumnType("jsonb") .HasColumnName("details") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Описание области заклинания"); b.Property("SpellId") .HasColumnType("integer") .HasColumnName("spell_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор заклинания"); b.Property("Type") .HasColumnType("spell_area_type") .HasColumnName("type") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Тип области заклинания"); b.HasKey("Id") .HasName("pk_spell_areas"); @@ -1677,56 +1742,65 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор урона заклинания"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ApplyMod") .HasColumnType("boolean") .HasColumnName("apply_mod") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Применяется модификатор?"); b.Property("Category") .HasColumnType("spell_damage_category") .HasColumnName("category") - .HasColumnOrder(5); + .HasColumnOrder(5) + .HasComment("Категория урона заклинания"); b.Property("Formula") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("formula") - .HasColumnOrder(6); + .HasColumnOrder(6) + .HasComment("Формула урона заклинания"); b.Property("Key") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("key") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Уровень заклинания"); b.Property("Kinds") .HasColumnType("spell_damage_kinds") .HasColumnName("kinds") - .HasColumnOrder(7); + .HasColumnOrder(7) + .HasComment("Тип урона заклинания"); b.Property("Materials") .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("materials") - .HasColumnOrder(8); + .HasColumnOrder(8) + .HasComment("Материалы для заклинания"); b.Property("SpellId") .HasColumnType("integer") .HasColumnName("spell_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор заклинания"); b.Property("Type") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("type") - .HasColumnOrder(9); + .HasColumnOrder(9) + .HasComment("Тип урона заклинания"); b.HasKey("Id") .HasName("pk_spell_damages"); @@ -1743,14 +1817,16 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("IsBasic") .HasColumnType("boolean") .HasColumnName("is_basic") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Базовый спасборосок?"); b.Property("IsPassive") .HasColumnType("boolean") @@ -1760,7 +1836,8 @@ namespace Pf2eModel.Migrations b.Property("SpellId") .HasColumnType("integer") .HasColumnName("spell_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор заклинания"); b.Property("Statistic") .IsRequired() @@ -2188,7 +2265,7 @@ namespace Pf2eModel.Migrations b.Navigation("Entity"); }); - modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.ClassKeyAbility", b => + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.ClassKeyAttribute", b => { b.HasOne("Pf2eModel.Model.Structure.Class", "Class") .WithMany("ClassKeyAbilities") diff --git a/Pf2eModel/Migrations/20250601111002_1.cs b/Pf2eModel/Migrations/20250601182634_1.cs similarity index 92% rename from Pf2eModel/Migrations/20250601111002_1.cs rename to Pf2eModel/Migrations/20250601182634_1.cs index 2dc5d93..3fec90b 100644 --- a/Pf2eModel/Migrations/20250601111002_1.cs +++ b/Pf2eModel/Migrations/20250601182634_1.cs @@ -26,7 +26,7 @@ namespace Pf2eModel.Migrations .Annotation("Npgsql:Enum:entity_rarity", "common,rare,uncommon,unique") .Annotation("Npgsql:Enum:entity_type", "action,ancestry,archetype,background,backpack,campaign_feature,class,condition,deity,feat,heritage,language,ritual,rule,spell,subclass,subclass_group,trait,unknown") .Annotation("Npgsql:Enum:feat_category", "ancestry,ancestry-feature,archetype,bonus,calling,campaign-feature,class,class-feature,curse,deityboon,general,pfsboon,skill,uncategorized") - .Annotation("Npgsql:Enum:locale_type", "english,hobby,legacy,legacy_english,other,russian") + .Annotation("Npgsql:Enum:locale_type", "english,hobby,other,russian") .Annotation("Npgsql:Enum:size_enum", "huge,large,medium,small,tiny") .Annotation("Npgsql:Enum:source_category", "adventure-path,adventures,blog-posts,comics,lost-omens,rulebook,society") .Annotation("Npgsql:Enum:spell_area_type", "burst,circle,cone,cube,cuboid,cylinder,emanation,line,other,square,varies") @@ -440,7 +440,7 @@ namespace Pf2eModel.Migrations { entity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор сущности"), target_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор цели"), - external_id = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + external_id = table.Column(type: "character varying(255)", maxLength: 255, nullable: true, comment: "Идентификатор элемента в ссылке"), suffix = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Часть URL до объекта. Для источников Foundry - полный UUID объекта"), md5 = table.Column(type: "character varying(32)", maxLength: 32, nullable: false, comment: "Контрольная сумма объекта") }, @@ -539,9 +539,9 @@ namespace Pf2eModel.Migrations { id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ancestry_id = table.Column(type: "integer", nullable: false), - boost = table.Column(type: "boolean", nullable: false), - value = table.Column(type: "attribute_values", nullable: false) + ancestry_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор родословной"), + boost = table.Column(type: "boolean", nullable: false, comment: "Флаг повышения/снижения"), + value = table.Column(type: "attribute_values", nullable: false, comment: "Значение атрибута") }, constraints: table => { @@ -558,9 +558,9 @@ namespace Pf2eModel.Migrations name: "ancestry_items", columns: table => new { - ancestry_id = table.Column(type: "integer", nullable: false), - item_id = table.Column(type: "integer", nullable: false), - level = table.Column(type: "integer", nullable: false) + ancestry_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор родословной"), + item_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор связанной сущности"), + level = table.Column(type: "integer", nullable: false, comment: "Уровень сущности в родословной") }, constraints: table => { @@ -607,10 +607,10 @@ namespace Pf2eModel.Migrations name: "background_boosts", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - background_id = table.Column(type: "integer", nullable: false), - attribute = table.Column(type: "attribute_values", nullable: false) + background_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор предыстории"), + attribute = table.Column(type: "attribute_values", nullable: false, comment: "Атрибут") }, constraints: table => { @@ -627,8 +627,8 @@ namespace Pf2eModel.Migrations name: "background_items", columns: table => new { - background_id = table.Column(type: "integer", nullable: false), - entity_id = table.Column(type: "integer", nullable: false), + background_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор"), + entity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор сущности"), level = table.Column(type: "integer", nullable: true) }, constraints: table => @@ -652,10 +652,10 @@ namespace Pf2eModel.Migrations name: "background_skills", columns: table => new { - id = table.Column(type: "integer", nullable: false), - background_id = table.Column(type: "integer", nullable: false), - training_id = table.Column(type: "integer", nullable: false), - rank = table.Column(type: "training_rank", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор"), + background_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор предыстории"), + training_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор тренируемого навыка"), + rank = table.Column(type: "training_rank", nullable: false, comment: "Ранг тренировки") }, constraints: table => { @@ -678,10 +678,10 @@ namespace Pf2eModel.Migrations name: "class_key_abilities", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - class_id = table.Column(type: "integer", nullable: false), - value = table.Column(type: "attribute_values", nullable: false) + class_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор класса"), + value = table.Column(type: "attribute_values", nullable: false, comment: "Атрибут") }, constraints: table => { @@ -698,12 +698,12 @@ namespace Pf2eModel.Migrations name: "class_progressions", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - class_id = table.Column(type: "integer", nullable: false), - type = table.Column(type: "class_progression_type", nullable: false), - entity_id = table.Column(type: "integer", nullable: true), - level = table.Column(type: "integer", nullable: false) + class_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор класса"), + level = table.Column(type: "integer", nullable: false, comment: "Уровень класса"), + type = table.Column(type: "class_progression_type", nullable: false, comment: "Тип элемента прогрессии класса"), + entity_id = table.Column(type: "integer", nullable: true, comment: "Идентификатор сущности") }, constraints: table => { @@ -726,11 +726,11 @@ namespace Pf2eModel.Migrations name: "class_trainings", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - class_id = table.Column(type: "integer", nullable: false), - training_id = table.Column(type: "integer", nullable: true), - rank = table.Column(type: "training_rank", nullable: false) + class_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор класса"), + training_id = table.Column(type: "integer", nullable: true, comment: "Идентификатор тренируемого навыка"), + rank = table.Column(type: "training_rank", nullable: false, comment: "Ранг тренировки") }, constraints: table => { @@ -777,8 +777,8 @@ namespace Pf2eModel.Migrations name: "deity_attributes", columns: table => new { - deity_id = table.Column(type: "integer", nullable: false), - attribute = table.Column(type: "attribute_values", nullable: false) + deity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор божества"), + attribute = table.Column(type: "attribute_values", nullable: false, comment: "Атрибут божества") }, constraints: table => { @@ -795,10 +795,10 @@ namespace Pf2eModel.Migrations name: "deity_domains", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - deity_id = table.Column(type: "integer", nullable: false), - is_primary = table.Column(type: "boolean", nullable: false), + deity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор божества"), + is_primary = table.Column(type: "boolean", nullable: false, comment: "Флаг основного домена"), value = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) }, constraints: table => @@ -840,10 +840,10 @@ namespace Pf2eModel.Migrations name: "deity_weapons", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - deity_id = table.Column(type: "integer", nullable: false), - weapon = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + deity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор божества"), + weapon = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Название оружия") }, constraints: table => { @@ -886,10 +886,10 @@ namespace Pf2eModel.Migrations name: "feat_prerequisites", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - feat_id = table.Column(type: "integer", nullable: false), - value = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + feat_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор черты"), + value = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Предварительное требование") }, constraints: table => { @@ -906,9 +906,9 @@ namespace Pf2eModel.Migrations name: "ancestry_languages", columns: table => new { - ancestry_id = table.Column(type: "integer", nullable: false), - language_id = table.Column(type: "integer", nullable: false), - is_additional = table.Column(type: "boolean", nullable: false) + ancestry_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор родословной"), + language_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор связанного языка"), + is_additional = table.Column(type: "boolean", nullable: false, comment: "Флаг дополнительного") }, constraints: table => { @@ -931,9 +931,9 @@ namespace Pf2eModel.Migrations name: "ritual_areas", columns: table => new { - ritual_id = table.Column(type: "integer", nullable: false), - details = table.Column(type: "jsonb", maxLength: 255, nullable: true), - type = table.Column(type: "spell_area_type", nullable: false) + ritual_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор ритуала"), + details = table.Column(type: "jsonb", maxLength: 255, nullable: true, comment: "Описание области действия ритуала"), + type = table.Column(type: "spell_area_type", nullable: false, comment: "Тип области действия ритуала") }, constraints: table => { @@ -950,13 +950,13 @@ namespace Pf2eModel.Migrations name: "ritual_checks", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ritual_id = table.Column(type: "integer", nullable: false), - training_id = table.Column(type: "integer", nullable: true), - training_string = table.Column(type: "jsonb", nullable: true), - rank = table.Column(type: "training_rank", nullable: false), - is_primary = table.Column(type: "boolean", nullable: false) + ritual_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор ритуала"), + training_id = table.Column(type: "integer", nullable: true, comment: "Идентификатор тренировки"), + training_string = table.Column(type: "jsonb", nullable: true, comment: "Дополнение к проверке"), + rank = table.Column(type: "training_rank", nullable: false, comment: "Ранг тренировки"), + is_primary = table.Column(type: "boolean", nullable: false, comment: "Флаг основной проверки") }, constraints: table => { @@ -979,11 +979,11 @@ namespace Pf2eModel.Migrations name: "deity_spells", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - deity_id = table.Column(type: "integer", nullable: false), - level = table.Column(type: "integer", nullable: false), - spell_id = table.Column(type: "integer", nullable: false) + deity_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор божества"), + level = table.Column(type: "integer", nullable: false, comment: "Уровень заклинания"), + spell_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор заклинания") }, constraints: table => { @@ -1006,11 +1006,11 @@ namespace Pf2eModel.Migrations name: "spell_areas", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - spell_id = table.Column(type: "integer", nullable: false), - details = table.Column(type: "jsonb", maxLength: 255, nullable: true), - type = table.Column(type: "spell_area_type", nullable: false) + spell_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор заклинания"), + details = table.Column(type: "jsonb", maxLength: 255, nullable: true, comment: "Описание области заклинания"), + type = table.Column(type: "spell_area_type", nullable: false, comment: "Тип области заклинания") }, constraints: table => { @@ -1027,16 +1027,16 @@ namespace Pf2eModel.Migrations name: "spell_damages", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор урона заклинания") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - spell_id = table.Column(type: "integer", nullable: false), - key = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - apply_mod = table.Column(type: "boolean", nullable: true), - category = table.Column(type: "spell_damage_category", nullable: true), - formula = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - kinds = table.Column(type: "spell_damage_kinds", nullable: false), - materials = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - type = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + spell_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор заклинания"), + key = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Уровень заклинания"), + apply_mod = table.Column(type: "boolean", nullable: true, comment: "Применяется модификатор?"), + category = table.Column(type: "spell_damage_category", nullable: true, comment: "Категория урона заклинания"), + formula = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Формула урона заклинания"), + kinds = table.Column(type: "spell_damage_kinds", nullable: false, comment: "Тип урона заклинания"), + materials = table.Column(type: "character varying(255)", maxLength: 255, nullable: true, comment: "Материалы для заклинания"), + type = table.Column(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Тип урона заклинания") }, constraints: table => { @@ -1053,11 +1053,11 @@ namespace Pf2eModel.Migrations name: "spell_defenses", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - spell_id = table.Column(type: "integer", nullable: false), + spell_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор заклинания"), is_passive = table.Column(type: "boolean", nullable: false), - is_basic = table.Column(type: "boolean", nullable: true), + is_basic = table.Column(type: "boolean", nullable: true, comment: "Базовый спасборосок?"), statistic = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) }, constraints: table => @@ -1125,8 +1125,8 @@ namespace Pf2eModel.Migrations name: "archetype_feats", columns: table => new { - archetype_id = table.Column(type: "integer", nullable: false), - feat_id = table.Column(type: "integer", nullable: false) + archetype_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор архетипа"), + feat_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор черты") }, constraints: table => { @@ -1173,11 +1173,11 @@ namespace Pf2eModel.Migrations name: "subclass_trainings", columns: table => new { - id = table.Column(type: "integer", nullable: false) + id = table.Column(type: "integer", nullable: false, comment: "Идентификатор связи") .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - subclass_id = table.Column(type: "integer", nullable: false), - training_id = table.Column(type: "integer", nullable: false), - rank = table.Column(type: "training_rank", nullable: false) + subclass_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор подкласса"), + training_id = table.Column(type: "integer", nullable: false, comment: "Идентификатор тренировки"), + rank = table.Column(type: "training_rank", nullable: false, comment: "Ранг тренировки") }, constraints: table => { diff --git a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs index 0f51b16..8b39b72 100644 --- a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs +++ b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs @@ -33,7 +33,7 @@ namespace Pf2eModel.Migrations NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "entity_rarity", new[] { "common", "rare", "uncommon", "unique" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "entity_type", new[] { "action", "ancestry", "archetype", "background", "backpack", "campaign_feature", "class", "condition", "deity", "feat", "heritage", "language", "ritual", "rule", "spell", "subclass", "subclass_group", "trait", "unknown" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "feat_category", new[] { "ancestry", "ancestry-feature", "archetype", "bonus", "calling", "campaign-feature", "class", "class-feature", "curse", "deityboon", "general", "pfsboon", "skill", "uncategorized" }); - NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "locale_type", new[] { "english", "hobby", "legacy", "legacy_english", "other", "russian" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "locale_type", new[] { "english", "hobby", "other", "russian" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "size_enum", new[] { "huge", "large", "medium", "small", "tiny" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "source_category", new[] { "adventure-path", "adventures", "blog-posts", "comics", "lost-omens", "rulebook", "society" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "spell_area_type", new[] { "burst", "circle", "cone", "cube", "cuboid", "cylinder", "emanation", "line", "other", "square", "varies" }); @@ -398,7 +398,8 @@ namespace Pf2eModel.Migrations .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("external_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор элемента в ссылке"); b.Property("Md5") .IsRequired() @@ -615,17 +616,20 @@ namespace Pf2eModel.Migrations b.Property("AncestryId") .HasColumnType("integer") .HasColumnName("ancestry_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор родословной"); b.Property("Boost") .HasColumnType("boolean") .HasColumnName("boost") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Флаг повышения/снижения"); b.Property("Value") .HasColumnType("attribute_values") .HasColumnName("value") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Значение атрибута"); b.HasKey("Id") .HasName("pk_ancestry_boosts"); @@ -641,17 +645,20 @@ namespace Pf2eModel.Migrations b.Property("AncestryId") .HasColumnType("integer") .HasColumnName("ancestry_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор родословной"); b.Property("ItemId") .HasColumnType("integer") .HasColumnName("item_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор связанной сущности"); b.Property("Level") .HasColumnType("integer") .HasColumnName("level") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Уровень сущности в родословной"); b.HasKey("AncestryId", "ItemId") .HasName("pk_ancestry_items"); @@ -667,17 +674,20 @@ namespace Pf2eModel.Migrations b.Property("AncestryId") .HasColumnType("integer") .HasColumnName("ancestry_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор родословной"); b.Property("LanguageId") .HasColumnType("integer") .HasColumnName("language_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор связанного языка"); b.Property("IsAdditional") .HasColumnType("boolean") .HasColumnName("is_additional") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Флаг дополнительного"); b.HasKey("AncestryId", "LanguageId") .HasName("pk_ancestry_languages"); @@ -728,11 +738,13 @@ namespace Pf2eModel.Migrations { b.Property("ArchetypeId") .HasColumnType("integer") - .HasColumnName("archetype_id"); + .HasColumnName("archetype_id") + .HasComment("Идентификатор архетипа"); b.Property("FeatId") .HasColumnType("integer") - .HasColumnName("feat_id"); + .HasColumnName("feat_id") + .HasComment("Идентификатор черты"); b.HasKey("ArchetypeId", "FeatId") .HasName("pk_archetype_feats"); @@ -763,19 +775,22 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Attribute") .HasColumnType("attribute_values") .HasColumnName("attribute") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Атрибут"); b.Property("BackgroundId") .HasColumnType("integer") .HasColumnName("background_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор предыстории"); b.HasKey("Id") .HasName("pk_background_boosts"); @@ -791,12 +806,14 @@ namespace Pf2eModel.Migrations b.Property("BackgroundId") .HasColumnType("integer") .HasColumnName("background_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); b.Property("EntityId") .HasColumnType("integer") .HasColumnName("entity_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор сущности"); b.Property("Level") .HasColumnType("integer") @@ -817,22 +834,26 @@ namespace Pf2eModel.Migrations b.Property("Id") .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); b.Property("BackgroundId") .HasColumnType("integer") .HasColumnName("background_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор предыстории"); b.Property("Rank") .HasColumnType("training_rank") .HasColumnName("rank") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Ранг тренировки"); b.Property("TrainingId") .HasColumnType("integer") .HasColumnName("training_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор тренируемого навыка"); b.HasKey("Id") .HasName("pk_background_skills"); @@ -890,19 +911,22 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ClassId") .HasColumnType("integer") .HasColumnName("class_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор класса"); b.Property("Value") .HasColumnType("attribute_values") .HasColumnName("value") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Атрибут"); b.HasKey("Id") .HasName("pk_class_key_abilities"); @@ -919,29 +943,34 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ClassId") .HasColumnType("integer") .HasColumnName("class_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор класса"); b.Property("EntityId") .HasColumnType("integer") .HasColumnName("entity_id") - .HasColumnOrder(5); + .HasColumnOrder(5) + .HasComment("Идентификатор сущности"); b.Property("Level") .HasColumnType("integer") .HasColumnName("level") - .HasColumnOrder(31); + .HasColumnOrder(3) + .HasComment("Уровень класса"); b.Property("Type") .HasColumnType("class_progression_type") .HasColumnName("type") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Тип элемента прогрессии класса"); b.HasKey("Id") .HasName("pk_class_progressions"); @@ -961,24 +990,28 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ClassId") .HasColumnType("integer") .HasColumnName("class_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор класса"); b.Property("Rank") .HasColumnType("training_rank") .HasColumnName("rank") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Ранг тренировки"); b.Property("TrainingId") .HasColumnType("integer") .HasColumnName("training_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор тренируемого навыка"); b.HasKey("Id") .HasName("pk_class_trainings"); @@ -1017,24 +1050,28 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор связи"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Rank") .HasColumnType("training_rank") .HasColumnName("rank") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Ранг тренировки"); b.Property("SubclassId") .HasColumnType("integer") .HasColumnName("subclass_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор подкласса"); b.Property("TrainingId") .HasColumnType("integer") .HasColumnName("training_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор тренировки"); b.HasKey("Id") .HasName("pk_subclass_trainings"); @@ -1117,12 +1154,14 @@ namespace Pf2eModel.Migrations b.Property("DeityId") .HasColumnType("integer") .HasColumnName("deity_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор божества"); b.Property("Attribute") .HasColumnType("attribute_values") .HasColumnName("attribute") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Атрибут божества"); b.HasKey("DeityId", "Attribute") .HasName("pk_deity_attributes"); @@ -1136,19 +1175,22 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DeityId") .HasColumnType("integer") .HasColumnName("deity_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор божества"); b.Property("IsPrimary") .HasColumnType("boolean") .HasColumnName("is_primary") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Флаг основного домена"); b.Property("Value") .IsRequired() @@ -1172,24 +1214,28 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DeityId") .HasColumnType("integer") .HasColumnName("deity_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор божества"); b.Property("Level") .HasColumnType("integer") .HasColumnName("level") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Уровень заклинания"); b.Property("SpellId") .HasColumnType("integer") .HasColumnName("spell_id") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Идентификатор заклинания"); b.HasKey("Id") .HasName("pk_deity_spells"); @@ -1209,21 +1255,24 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DeityId") .HasColumnType("integer") .HasColumnName("deity_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор божества"); b.Property("Weapon") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("weapon") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Название оружия"); b.HasKey("Id") .HasName("pk_deity_weapons"); @@ -1278,21 +1327,24 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("FeatId") .HasColumnType("integer") .HasColumnName("feat_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор черты"); b.Property("Value") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("value") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Предварительное требование"); b.HasKey("Id") .HasName("pk_feat_prerequisites"); @@ -1451,18 +1503,21 @@ namespace Pf2eModel.Migrations b.Property("RitualId") .HasColumnType("integer") .HasColumnName("ritual_id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор ритуала"); b.Property("Details") .HasMaxLength(255) .HasColumnType("jsonb") .HasColumnName("details") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Описание области действия ритуала"); b.Property("Type") .HasColumnType("spell_area_type") .HasColumnName("type") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Тип области действия ритуала"); b.HasKey("RitualId") .HasName("pk_ritual_areas"); @@ -1476,34 +1531,40 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("IsPrimary") .HasColumnType("boolean") .HasColumnName("is_primary") - .HasColumnOrder(6); + .HasColumnOrder(6) + .HasComment("Флаг основной проверки"); b.Property("Rank") .HasColumnType("training_rank") .HasColumnName("rank") - .HasColumnOrder(5); + .HasColumnOrder(5) + .HasComment("Ранг тренировки"); b.Property("RitualId") .HasColumnType("integer") .HasColumnName("ritual_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор ритуала"); b.Property("TrainingId") .HasColumnType("integer") .HasColumnName("training_id") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Идентификатор тренировки"); b.Property("TrainingString") .HasColumnType("jsonb") .HasColumnName("training_string") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Дополнение к проверке"); b.HasKey("Id") .HasName("pk_ritual_checks"); @@ -1639,7 +1700,8 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); @@ -1647,17 +1709,20 @@ namespace Pf2eModel.Migrations .HasMaxLength(255) .HasColumnType("jsonb") .HasColumnName("details") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Описание области заклинания"); b.Property("SpellId") .HasColumnType("integer") .HasColumnName("spell_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор заклинания"); b.Property("Type") .HasColumnType("spell_area_type") .HasColumnName("type") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Тип области заклинания"); b.HasKey("Id") .HasName("pk_spell_areas"); @@ -1674,56 +1739,65 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор урона заклинания"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ApplyMod") .HasColumnType("boolean") .HasColumnName("apply_mod") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Применяется модификатор?"); b.Property("Category") .HasColumnType("spell_damage_category") .HasColumnName("category") - .HasColumnOrder(5); + .HasColumnOrder(5) + .HasComment("Категория урона заклинания"); b.Property("Formula") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("formula") - .HasColumnOrder(6); + .HasColumnOrder(6) + .HasComment("Формула урона заклинания"); b.Property("Key") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("key") - .HasColumnOrder(3); + .HasColumnOrder(3) + .HasComment("Уровень заклинания"); b.Property("Kinds") .HasColumnType("spell_damage_kinds") .HasColumnName("kinds") - .HasColumnOrder(7); + .HasColumnOrder(7) + .HasComment("Тип урона заклинания"); b.Property("Materials") .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("materials") - .HasColumnOrder(8); + .HasColumnOrder(8) + .HasComment("Материалы для заклинания"); b.Property("SpellId") .HasColumnType("integer") .HasColumnName("spell_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор заклинания"); b.Property("Type") .IsRequired() .HasMaxLength(255) .HasColumnType("character varying(255)") .HasColumnName("type") - .HasColumnOrder(9); + .HasColumnOrder(9) + .HasComment("Тип урона заклинания"); b.HasKey("Id") .HasName("pk_spell_damages"); @@ -1740,14 +1814,16 @@ namespace Pf2eModel.Migrations .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id") - .HasColumnOrder(1); + .HasColumnOrder(1) + .HasComment("Идентификатор"); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("IsBasic") .HasColumnType("boolean") .HasColumnName("is_basic") - .HasColumnOrder(4); + .HasColumnOrder(4) + .HasComment("Базовый спасборосок?"); b.Property("IsPassive") .HasColumnType("boolean") @@ -1757,7 +1833,8 @@ namespace Pf2eModel.Migrations b.Property("SpellId") .HasColumnType("integer") .HasColumnName("spell_id") - .HasColumnOrder(2); + .HasColumnOrder(2) + .HasComment("Идентификатор заклинания"); b.Property("Statistic") .IsRequired()