Начало переработки программы (не рабочий промежуточный коммит).

This commit is contained in:
2025-05-12 19:17:40 +04:00
parent a9f9ae4b93
commit f3d710d7d7
32 changed files with 914 additions and 107 deletions
+62
View File
@@ -0,0 +1,62 @@
namespace Pf2eDataScrapper.App;
/// <summary>
/// Общий аттрибут обработчика.
/// </summary>
/// <param name="name">Название обработчика.</param>
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
[AttributeUsage(AttributeTargets.Class)]
public class ProcessAttribute(string name, ICollection<string> dependencies) : Attribute
{
/// <summary>
/// Название обработчика.
/// </summary>
public string Name { get; init; } = name;
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public ICollection<string> Dependencies { get; init; } = dependencies;
public ProcessAttribute(string name)
: this(name, []) { }
}
/// <summary>
/// Аттрибут обработчика для уточнения группы.
/// </summary>
/// <param group="group">Группа обработчиков, к которому относится обработчик.</param>
[AttributeUsage(AttributeTargets.Class)]
internal class ProcessorGroupAttribute(ProcessorGroupEnum group) : Attribute
{
/// <summary>
/// Название обработчика, к которому относится обработчик.
/// </summary>
public ProcessorGroupEnum Group { get; init; } = group;
}
/// <summary>
/// Аттрибут обработчика для уточнения списка зависимостей.
/// </summary>
/// <param name="dependencies">Список названий зависимостей.</param>
[AttributeUsage(AttributeTargets.Class)]
internal class ProcessorDependenciesAttribute(ICollection<string> dependencies) : Attribute
{
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public ICollection<string> Dependencies { get; init; } = dependencies;
}
/// <summary>
/// Аттрибут обработчика для внутреннего типа элемента.
/// </summary>
/// <param name="innerType">Внутренний типа элемента.</param>
[AttributeUsage(AttributeTargets.Class)]
internal class ProcessorInnerTypeAttribute(string innerType) : Attribute
{
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public string InnerType { get; init; } = innerType;
}
+229
View File
@@ -0,0 +1,229 @@
using System.ComponentModel;
using System.Reflection;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.App;
/// <summary>
/// Класс с коллекцией обработчиков.
/// </summary>
internal class ProcessorCollection
{
/// <summary>
/// Коллекция всех доступных обработчиков.
/// </summary>
public ICollection<ProcessorCollectionItem> AllItems { get; } = [];
/// <summary>
/// Коллекция обработчиков, которые будут обрабатываться.
/// Для изменения необходимо использовать <see cref="FilterProcessors"/>.
/// </summary>
public List<ProcessorCollectionItem> Items { get; private set; } = [];
/// <summary>
/// Конструктор класса <see cref="ProcessorCollection"/>.
/// </summary>
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([]);
}
/// <summary>
/// Создание элемента коллекции обработчиков.
/// </summary>
/// <param name="type">Тип обработчика.</param>
/// <returns></returns>
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<ProcessAttribute>()!;
DescriptionAttribute? descriptionAttribute =
type.GetCustomAttribute<DescriptionAttribute>();
ProcessorGroupAttribute? groupAttribute =
type.GetCustomAttribute<ProcessorGroupAttribute>();
ProcessorDependenciesAttribute? dependenciesAttribute =
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
ProcessorInnerTypeAttribute? innerTypeAttribute =
type.GetCustomAttribute<ProcessorInnerTypeAttribute>();
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,
};
}
/// <summary>
/// Проверка, что все зависимости всех обработчиков могут быть удовлетворены.
/// </summary>
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.");
}
}
}
}
/// <summary>
/// Сортировать обработчики в соответсвии со всеми зависимостями.
/// </summary>
public void SortItems()
{
Dictionary<string, int> priorities = [];
while (priorities.Count < Items.Count)
{
foreach (ProcessorCollectionItem item in Items)
{
if (item.Priority > 0)
{
continue;
}
IEnumerable<int> 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);
}
/// <summary>
/// Ограничить список используемых обработчиков по названию.
/// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики.
/// </summary>
/// <param name="processorNames">Коллекция названиий требуемых обработчиков.</param>
public void FilterProcessors(ICollection<string> processorNames)
{
if (processorNames.Count == 0)
{
Items = [.. AllItems];
SortItems();
return;
}
Dictionary<string, bool> nameFlags = processorNames.ToDictionary(name => name, _ => false);
while (nameFlags.Values.Any(v => !v))
{
foreach (
KeyValuePair<string, bool> 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();
}
}
/// <summary>
/// Элемент коллекции обработчиков.
/// </summary>
internal class ProcessorCollectionItem
{
/// <summary>
/// Название обработчика.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Тип класса обработчика.
/// </summary>
public required Type Type { get; init; }
/// <summary>
/// Описание обработчика.
/// </summary>
public string Description { get; init; } = "";
/// <summary>
/// Группа обработчика.
/// </summary>
public required ProcessorGroupEnum Group { get; init; }
/// <summary>
/// Коллекция названий обработчиков, после которых должен выполняться обработчик.
/// </summary>
public ICollection<string> Dependencies { get; init; } = [];
/// <summary>
/// Приоритет выполнения обработчика.
/// </summary>
public int Priority { get; set; } = -1;
/// <summary>
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
/// Внутренний тип элементов указывается в <see cref="InnerType"/>.
/// </summary>
public required bool IsCollectionProcessor { get; init; }
/// <summary>
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
/// </summary>
public string? InnerType { get; init; }
/// <summary>
/// Конструктор обработчика.
/// </summary>
public required ConstructorInfo Constructor { get; init; }
}
@@ -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,
}
+80
View File
@@ -0,0 +1,80 @@
using System.ComponentModel;
using dotenv.net;
using dotenv.net.Utilities;
namespace Pf2eDataScrapper;
/// <summary>
/// Настройки программы.
/// </summary>
internal class ApplicationSettings
{
/// <summary>
/// Конструктор класса <see cref="ApplicationSettings"/>.
/// </summary>
public ApplicationSettings()
{
DotEnv.Load();
}
#region Настройки из .env
/// <summary>
/// Путь к папке с входными данными.
/// </summary>
[Browsable(true)]
[ReadOnly(true)]
[DisplayName("Путь к папке с входными данными.")]
[Description("Редактирование через .env-файл.")]
public string DataDirPath { get; } = EnvReader.GetStringValue("DATA_DIR");
/// <summary>
/// Папка в входными данными.
/// </summary>
[Browsable(false)]
public DirectoryInfo DataDir => new(DataDirPath);
/// <summary>
/// Путь к папке для сохранения результатов.
/// </summary>
[Browsable(true)]
[ReadOnly(true)]
[DisplayName("Путь к папке для сохранения результатов.")]
[Description("Редактирование через .env-файл.")]
public string OutputDirPath { get; } = EnvReader.GetStringValue("OUTPUT_DIR");
/// <summary>
/// Папка для сохранения результатов.
/// </summary>
[Browsable(false)]
public string OutputDir => new(OutputDirPath);
/// <summary>
/// Строка подключения к базе данных.
/// </summary>
[Browsable(true)]
[ReadOnly(true)]
[DisplayName("Строка подключения к базе данных.")]
[Description("Редактирование через .env-файл.")]
public string DatabaseConnection { get; } = EnvReader.GetStringValue("DATABASE_CONNECTION");
#endregion
#region Основные настройки
/// <summary>
/// Флаг, указывающий, что все старые записи необходимо удалить.
/// </summary>
[Browsable(true)]
[DisplayName("Удаление всех записей перед запуском.")]
[Description("Редактирование через .env-файл.")]
public bool TruncateAllTables { get; set; } = true;
/// <summary>
/// Список обработчиков, которые должны быть выполнены.
/// Пустой список означает, что должны быть выполнены все обработчики.
/// </summary>
public ICollection<string> ProcessorNames { get; } = new List<string>();
#endregion
}
+166
View File
@@ -0,0 +1,166 @@
namespace Pf2eDataScrapper.Forms;
partial class StartForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
+9
View File
@@ -0,0 +1,9 @@
namespace Pf2eDataScrapper.Forms;
public partial class StartForm : Form
{
public StartForm()
{
InitializeComponent();
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+58
View File
@@ -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<ProcessorCollection>();
Services.AddDbContext<Pf2eDbContext>();
}
#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
}
@@ -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;
@@ -1,43 +1,22 @@
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
using Pf2eDataScrapper.App.Attributes;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
/// <summary>
/// Атрибут обработчика данных Архивов Нефиса.
/// </summary>
/// <param name="name">Название обработчика.</param>
/// <param name="type">Тип данных, которые обработчик обрабатывает.</param>
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
/// <param name="includeDefaults">Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка.</param>
[AttributeUsage(AttributeTargets.Class)]
public class AonprdProcessorAttribute(
string name,
string? type,
string[] dependencies,
bool includeDefaults = true
) : Attribute
public class AonprdProcessorAttribute : Attributes
{
/// <summary>
/// Важные общие зависимости для обработчка.
/// </summary>
private static readonly string[] DefaultDependencies = ["trait", "source"];
/// <summary>
/// Название обработчика.
/// </summary>
public readonly string Name = name;
private static readonly string[] DefaultDependencies = ["aonprd:trait", "aonprd:source"];
/// <summary>
/// Тип данных, которые обработчик обрабатывает.
/// </summary>
public readonly string? Type = type;
/// <summary>
/// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.
/// </summary>
public readonly IEnumerable<string> Dependencies =
[
.. includeDefaults ? DefaultDependencies : [],
.. dependencies,
];
public string Type { get; init; }
/// <summary>
/// Конструктор класса <see cref="AonprdProcessorAttribute"/> со стандартными общими зависимости для обработчка.
@@ -62,4 +41,22 @@ public class AonprdProcessorAttribute(
/// <param name="type">Тип данных, которые обработчик обрабатывает.</param>
public AonprdProcessorAttribute(string name, string type)
: this(name, type, DefaultDependencies) { }
/// <summary>
/// Основной конструктор класса <see cref="AonprdProcessorAttribute"/>.
/// </summary>
/// <param name="name">Название обработчика.</param>
/// <param name="type">Тип данных, которые обработчик обрабатывает.</param>
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
/// <param name="includeDefaults">Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка.</param>
public AonprdProcessorAttribute(
string name,
string? type,
ICollection<string> dependencies,
bool includeDefaults = true
)
: base($"aonprd:{name}", [.. includeDefaults ? DefaultDependencies : [], .. dependencies])
{
Type = type;
}
}
@@ -1,5 +1,5 @@
using System.Reflection;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
@@ -1,5 +1,6 @@
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
@@ -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;
@@ -1,49 +0,0 @@
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eModel;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
/// <summary>
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
/// Класс предназначен для обработки данных с помощью различных обработчиков,
/// для которых указан атрибут <see cref="AonprdProcessorAttribute"/>.
/// </summary>
/// <param name="input">Объект обработки вводимого JSON.</param>
/// <seealso cref="AonprdProcessorAttribute"/>
internal abstract class AbstractProcessor(AonprdProcessorInput input)
{
/// <summary>
/// Вспомогательный класс для работы с базой данных.
/// </summary>
protected readonly DatabaseHelper Helper = DatabaseHelper.Instance;
/// <summary>
/// Вспомогательный класс для работы со строками и форматированием.
/// </summary>
protected readonly StringHelper StringHelper = StringHelper.Instance;
/// <summary>
/// Вспомогательный класс для работы с глобальными настройками.
/// </summary>
protected readonly AonprdBuilderGlobals Globals = AonprdBuilderGlobals.Instance;
/// <summary>
/// Контекст базы данных для работы с данными.
/// </summary>
public readonly Pf2eDbContext Context = DatabaseHelper.DbContext;
/// <summary>
/// Объект обработки вводимого JSON.
/// </summary>
public readonly AonprdProcessorInput Input = input;
/// <summary>
/// Основной метод обработки данных.
/// </summary>
public abstract void Process();
/// <summary>
/// Метод для сохранения изменений в базе данных.
/// </summary>
public abstract void Save();
}
@@ -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;
@@ -1,4 +1,5 @@
using Pf2eModel.Model.Common;
using Pf2eDataScrapper.Processors;
using Pf2eModel.Model.Common;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum;
@@ -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;
@@ -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)
@@ -6,6 +6,7 @@
// Ну а пока просто расставляем родительские и дочерние элементы.
using System.Data;
using Pf2eDataScrapper.Processors;
using Pf2eModel.Model.Structure;
using Rule = Pf2eModel.Model.Structure.Rule;
@@ -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;
@@ -83,6 +83,6 @@ internal class CompendiumFolder
/// <returns>Строковое представление данных о папки компендиума.</returns>
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"}])";
}
}
@@ -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 = "",
@@ -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;
@@ -1,4 +1,5 @@
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Processors;
using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors;
@@ -1,4 +1,5 @@
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Processors;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
+4 -4
View File
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>13.0</LangVersion>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="1.3.0" />
@@ -0,0 +1,44 @@
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.App;
using Pf2eModel;
namespace Pf2eDataScrapper.Processors;
/// <summary>
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
/// Класс предназначен для обработки данных с помощью различных обработчиков,
/// для которых указан атрибут <see cref="ProcessAttribute"/>.
/// </summary>
/// <param name="serviceProvider">Провайдер сервисов.</param>
/// <param name="input">Провайдер сервисов.</param>
/// <seealso cref="ProcessAttribute"/>
internal abstract class AbstractInputProcessor(
IServiceProvider serviceProvider,
ProcessorInput input
) : IProcessor
{
/// <summary>
/// Провайдер сервисов.
/// </summary>
public IServiceProvider ServiceProvider { get; } = serviceProvider;
/// <summary>
/// Контекст базы данных.
/// </summary>
public Pf2eDbContext Context { get; } = serviceProvider.GetService<Pf2eDbContext>()!;
/// <summary>
/// Ввод.
/// </summary>
public ProcessorInput Input { get; } = input;
/// <summary>
/// Основной метод обработки данных.
/// </summary>
public abstract void Process();
/// <summary>
/// Метод для сохранения изменений в базе данных.
/// </summary>
public abstract void Save();
}
@@ -0,0 +1,35 @@
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.App;
using Pf2eModel;
namespace Pf2eDataScrapper.Processors;
/// <summary>
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
/// Класс предназначен для обработки данных с помощью различных обработчиков,
/// для которых указан атрибут <see cref="ProcessAttribute"/>.
/// </summary>
/// <param name="serviceProvider">Провайдер сервисов.</param>
/// <seealso cref="ProcessAttribute"/>
internal abstract class AbstractProcessor(IServiceProvider serviceProvider) : IProcessor
{
/// <summary>
/// Провайдер сервисов.
/// </summary>
public IServiceProvider ServiceProvider { get; } = serviceProvider;
/// <summary>
/// Контекст базы данных.
/// </summary>
public Pf2eDbContext Context { get; } = serviceProvider.GetService<Pf2eDbContext>()!;
/// <summary>
/// Основной метод обработки данных.
/// </summary>
public abstract void Process();
/// <summary>
/// Метод для сохранения изменений в базе данных.
/// </summary>
public abstract void Save();
}
+21
View File
@@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
namespace Pf2eDataScrapper.Processors;
public interface IProcessor
{
/// <summary>
/// Провайдер сервисов.
/// </summary>
IServiceProvider ServiceProvider { get; }
/// <summary>
/// Основной метод обработки данных.
/// </summary>
void Process();
/// <summary>
/// Метод для сохранения изменений в базе данных.
/// </summary>
void Save();
}
@@ -2,7 +2,7 @@
using NpgsqlTypes;
using Pf2eModel.Model.Common.I18n;
namespace Pf2eDataScrapper.Importers;
namespace Pf2eDataScrapper.Processors;
/// <summary>
/// Класс для работы с JSON-объектом.
+19 -14
View File
@@ -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
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[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());
}
}