Files

238 lines
10 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
public 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>
public 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);
}
}