238 lines
10 KiB
C#
238 lines
10 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using Newtonsoft.Json.Linq;
|
||
using NpgsqlTypes;
|
||
using Pf2e.Database.Model.Common.I18n;
|
||
|
||
namespace Pf2e.Scrapper.Processors;
|
||
|
||
/// <summary>
|
||
/// Класс для работы с JSON-объектом.
|
||
/// </summary>
|
||
/// <param name="name">Название объекта, ассоциированного с вводом.</param>
|
||
/// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param>
|
||
/// <param name="enumSolvers">[DI] Набор интерфейсов, разрешающих значения перечислений.</param>
|
||
internal class ProcessorInput(string name, JObject input, IEnumerable<IEnumSolver> enumSolvers)
|
||
{
|
||
/// <summary>
|
||
/// Название объекта, ассоциированного с вводом.
|
||
/// </summary>
|
||
public string Name { get; } = name;
|
||
|
||
/// <summary>
|
||
/// Входящий JSON-объект, который содержит данные для обработки.
|
||
/// </summary>
|
||
public JObject Input { get; } = input;
|
||
|
||
/// <summary>
|
||
/// Интерфейсы, разрешающие значения перечислений.
|
||
/// </summary>
|
||
public IEnumerable<IEnumSolver> EnumSolvers { get; } = enumSolvers;
|
||
|
||
/// <summary>
|
||
/// Значение из JSON по заданному пути, которое обязательно должно существовать.
|
||
/// </summary>
|
||
/// <param name="path">Путь в JSON.</param>
|
||
/// <returns>Объект типа `T` или `null`, если не удалось найти значение.</returns>
|
||
/// <exception cref="NullReferenceException">Если значение не найдено.</exception>
|
||
public virtual T GetRequired<T>(string path)
|
||
{
|
||
JToken token =
|
||
Input.SelectToken(path) ?? throw new NullReferenceException($"'{path}' is null");
|
||
|
||
return token.ToObject<T>() ?? throw new NullReferenceException($"'{path}' is null");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Значение из JSON по заданному пути, которое может не существовать.
|
||
/// </summary>
|
||
/// <param name="path">Путь в JSON</param>
|
||
/// <returns>Объект типа `T` или `null`, если значение не найдено</returns>
|
||
public virtual T? GetOptional<T>(string path)
|
||
{
|
||
JToken? token = Input.SelectToken(path) ?? null;
|
||
|
||
return (token is null || token.Type == JTokenType.Null)
|
||
? default!
|
||
: token.ToObject<T>() ?? default;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Значение из JSON по заданному пути с возможностью предоставить значение по умолчанию.
|
||
/// </summary>
|
||
/// <param name="path">Путь в JSON.</param>
|
||
/// <param name="defaultValue">Значение, которое возвращается, если значение не найдено.</param>
|
||
/// <returns>Объект типа `T` или `defaultValue`, если значение не найдено.</returns>
|
||
public virtual T GetOptional<T>(string path, T defaultValue)
|
||
{
|
||
JToken? token = Input.SelectToken(path) ?? null;
|
||
if (token is null || token.Type == JTokenType.Null)
|
||
{
|
||
return defaultValue;
|
||
}
|
||
return token.ToObject<T>() ?? defaultValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Преобразование значения из JSON по заданному пути в перечисление с использованием `PgName` атрибута.
|
||
/// </summary>
|
||
/// <param name="path">Путь в JSON.</param>
|
||
/// <returns>Перечисление T или выбрасывает исключение, если значение не соответствует жодному варианту.</returns>
|
||
public virtual T EnumRequired<T>(string path)
|
||
where T : Enum
|
||
{
|
||
string value =
|
||
GetOptional<string>(path)
|
||
?? throw new NullReferenceException($"Value in path {path} null or not exists");
|
||
|
||
return Enum<T>(value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Преобразование значения из JSON по заданному пути в перечисление с возможностью предоставить значение по умолчанию.
|
||
/// </summary>
|
||
/// <param name="path">Путь в JSON</param>
|
||
/// <param name="defaultValue">Значение, которое возвращается, если значение не найдено.</param>
|
||
/// <returns>Значение перечисления T или defaultValue</returns>
|
||
public virtual T EnumOptional<T>(string path, T defaultValue)
|
||
where T : Enum
|
||
{
|
||
string? value = GetOptional<string>(path);
|
||
|
||
if (value is null)
|
||
{
|
||
return defaultValue;
|
||
}
|
||
|
||
try
|
||
{
|
||
return Enum<T>(value);
|
||
}
|
||
catch (InvalidEnumStringException)
|
||
{
|
||
return defaultValue;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Преобразование значения из JSON по заданному пути в строку локализации для заданного типа локализации.
|
||
/// </summary>
|
||
/// <param name="path">Путь в JSON.</param>
|
||
/// <param name="type">Тип локализации.</param>
|
||
/// <returns>Объект <see cref="LocaleString"/> с добавленным значением для заданного типа.</returns>
|
||
public virtual LocaleString GetRequiredLocale(string path, LocaleType type)
|
||
{
|
||
return new() { [type] = GetRequired<string>(path) };
|
||
}
|
||
|
||
/// <summary>
|
||
/// Преобразование значения из JSON по заданному пути в строку локализации для заданного типа локализации.
|
||
/// </summary>
|
||
/// <param name="path">Путь в JSON.</param>
|
||
/// <param name="type">Тип локализации.</param>
|
||
/// <returns>Объект <see cref="LocaleString"/> или null, если значение не найдено.</returns>
|
||
public virtual LocaleString? GetOptionalLocale(string path, LocaleType type)
|
||
{
|
||
string? value = GetOptional<string>(path);
|
||
if (value is null)
|
||
{
|
||
return null;
|
||
}
|
||
return new() { [type] = value };
|
||
}
|
||
|
||
/// <summary>
|
||
/// Преобразование значения из JSON в перечисление.
|
||
/// </summary>
|
||
/// <param name="value">Значение, которое нужно преобразовать</param>
|
||
/// <returns>Объект типа `T`, если преобразование успешно, иначе выбрасывает исключение</returns>
|
||
public virtual T Enum<T>(string value)
|
||
where T : Enum
|
||
{
|
||
IEnumerable<EnumSolver<T>> solvers = EnumSolvers
|
||
.Where(solver => solver.GetType().IsAssignableTo(typeof(EnumSolver<T>)))
|
||
.Cast<EnumSolver<T>>();
|
||
|
||
foreach (EnumSolver<T> 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<string> variants = [field.Name];
|
||
|
||
if (
|
||
Attribute.GetCustomAttribute(field, typeof(PgNameAttribute))
|
||
is PgNameAttribute pgAttribute
|
||
)
|
||
{
|
||
variants.Add(pgAttribute.PgName);
|
||
}
|
||
|
||
if (variants.Contains(value))
|
||
{
|
||
return (T)field.GetValue(null)!;
|
||
}
|
||
}
|
||
|
||
throw new InvalidEnumStringException(typeof(T), value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Создание контрольной суммы объекта.
|
||
/// </summary>
|
||
/// <param name="isLowercase">Использовать шестнадцатеричные цифры в нижнем регистре.</param>
|
||
/// <returns>Контрольная сумма объекта.</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Исключение при неверном значении для перечисленния.
|
||
/// </summary>
|
||
/// <param name="type">Тип перечисленния.</param>
|
||
/// <param name="value">Некорректное значение.</param>
|
||
public class InvalidEnumStringException(Type type, string value)
|
||
: Exception($"Invalid value '{value}' for enum type {type.FullName}.") { }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Интерфейс для решения значений перечислений.
|
||
/// </summary>
|
||
internal interface IEnumSolver { }
|
||
|
||
/// <summary>
|
||
/// Абстрактный класс для разрешения значений перечислений.
|
||
/// </summary>
|
||
/// <typeparam name="TEnum">Тип перечисления, который будет разрешаться.</typeparam>
|
||
public abstract class EnumSolver<TEnum> : IEnumSolver
|
||
where TEnum : Enum
|
||
{
|
||
/// <summary>
|
||
/// Словарь, содержащий данные для разрешения значений перечислений.
|
||
/// </summary>
|
||
public abstract Dictionary<string, TEnum> Data { get; }
|
||
|
||
/// <summary>
|
||
/// Метод для разрешения значения перечисления по строковому вводу.
|
||
/// </summary>
|
||
/// <param name="input">Строка, представляющая значение перечисления.</param>
|
||
/// <returns>Элемент перечисления <typeparamref name="TEnum"/>, если найден; иначе <c>null</c>.</returns>
|
||
public TEnum? SolveEnum(string input)
|
||
{
|
||
return Data.GetValueOrDefault(input);
|
||
}
|
||
}
|