Глобальный рефакторинг пространств имён
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс-помощник, хранящий необходимые глобальные данные.
|
||||
/// </summary>
|
||||
internal class AonprdBuilderGlobals
|
||||
{
|
||||
/// <summary>
|
||||
/// Цель внешних ссылок Архивов Нефисов.
|
||||
/// </summary>
|
||||
public ExternalTarget AonprdTarget { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Словарь идентификаторов источников.
|
||||
/// </summary>
|
||||
public Dictionary<string, int> SourceIds { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь идентификаторов признаков.
|
||||
/// </summary>
|
||||
public Dictionary<string, int> TraitIds { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Список идентификаторов сущностей, которые нужно игнорировать при обработке.
|
||||
/// </summary>
|
||||
public List<string> Ignore { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарей суффиксов для сущностей
|
||||
/// </summary>
|
||||
public Dictionary<string, string> SlugSuffixes { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь допольнительных проверок связей.
|
||||
/// Ключ - устаревшая сущность, значение - обновлённая.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> LegacyRemasterExtra { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь сущностей, которые нужно слить с другой/другими.
|
||||
/// Ключ - пропускаемая сущность. Значение - целевая сущность.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> MergeSources { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Словарь сущностей, которые нужно слить с другой/другими.
|
||||
/// Ключ - пропускаемая сущность. Значение - целевая сущность.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> SubclassGroups { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
|
||||
/// </summary>
|
||||
public AonprdBuilderGlobals()
|
||||
{
|
||||
using StreamReader fileReader = new("Resources\\ImportAdaptation.json");
|
||||
|
||||
JObject? json = JObject
|
||||
.Parse(fileReader.ReadToEnd())
|
||||
.SelectToken("Aonprd")
|
||||
?.ToObject<JObject>();
|
||||
|
||||
if (json is not null)
|
||||
{
|
||||
Ignore = json.SelectToken("Ignore")?.ToObject<List<string>>() ?? [];
|
||||
|
||||
SlugSuffixes = JsonToDictionary(json, "SlugSuffixes");
|
||||
LegacyRemasterExtra = JsonToDictionary(json, "LegacyRemasterExtra");
|
||||
MergeSources = JsonToDictionary(json, "MergeSources");
|
||||
SubclassGroups = JsonToDictionary(json, "SubclassGroups");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить суффикс для ссылки.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор Aonprd.</param>
|
||||
/// <returns>Суффикс для ссылки или пустая строка.</returns>
|
||||
public string GetSlugSuffix(string id)
|
||||
{
|
||||
return SlugSuffixes.TryGetValue(id, out string? slug) ? $"-{slug}" : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразовать JSON-объект в словарь.
|
||||
/// </summary>
|
||||
/// <param name="json">Исходный JSON-объект.</param>
|
||||
/// <param name="tokenPath">Путь к токену в JSON.</param>
|
||||
/// <returns>Результирующий словарь.</returns>
|
||||
public static Dictionary<string, string> JsonToDictionary(JObject json, string tokenPath)
|
||||
{
|
||||
return json.SelectToken(tokenPath)?.ToObject<Dictionary<string, string>>() ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Проверка уникальности:
|
||||
|
||||
SELECT
|
||||
E.type,
|
||||
E.slug,
|
||||
COUNT(*),
|
||||
string_agg(E.id::text, ', ' ORDER BY E.id),
|
||||
string_agg(EE.external_id::text, ', ' ORDER BY E.id)
|
||||
FROM public.entities AS E
|
||||
INNER JOIN public.entity_externals AS EE ON EE.entity_id = E.id
|
||||
GROUP BY E.type, E.slug
|
||||
HAVING COUNT(*) > 1 AND E.type NOT IN ('rule', 'action')
|
||||
*/
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2e.Scrapper.Processors;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
internal static class AonprdProcessorInputExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработать строку, содержащую информацию о количестве действий.
|
||||
/// </summary>
|
||||
/// <param name="_"></param>
|
||||
/// <param name="input">Входная строка с данными.</param>
|
||||
/// <returns>Структура, хранящая информацию о количетсве действий.</returns>
|
||||
public static ActionStruct ResolveActionString(this ProcessorInput _, string input)
|
||||
{
|
||||
ActionStruct result = new();
|
||||
|
||||
switch (input)
|
||||
{
|
||||
case "Single Action":
|
||||
result.Actions = result.MaxActions = 1;
|
||||
break;
|
||||
|
||||
case "Two Actions":
|
||||
result.Actions = result.MaxActions = 2;
|
||||
break;
|
||||
|
||||
case "Three Actions":
|
||||
result.Actions = result.MaxActions = 3;
|
||||
break;
|
||||
|
||||
case "Reaction":
|
||||
result.IsReaction = true;
|
||||
break;
|
||||
|
||||
case "Free Action":
|
||||
result.IsFreeAction = false;
|
||||
break;
|
||||
|
||||
case "Single Action to Three Actions":
|
||||
case "Single Action or more Actions":
|
||||
result.Actions = 1;
|
||||
result.MaxActions = 3;
|
||||
break;
|
||||
|
||||
case "Single Action or Two Actions":
|
||||
case "Single Action to Two Actions":
|
||||
result.Actions = 1;
|
||||
result.MaxActions = 2;
|
||||
break;
|
||||
|
||||
case "Two Actions or Three Actions":
|
||||
result.Actions = 2;
|
||||
result.MaxActions = 3;
|
||||
break;
|
||||
|
||||
case "Two Actions to 2 rounds":
|
||||
result.Actions = 2;
|
||||
result.MaxActions = 3;
|
||||
result.TwoRounds = true;
|
||||
break;
|
||||
|
||||
case "Reaction or One Action":
|
||||
result.IsReaction = true;
|
||||
result.Actions = result.MaxActions = 1;
|
||||
break;
|
||||
|
||||
case "Reaction or Two Actions":
|
||||
result.IsReaction = true;
|
||||
result.Actions = result.MaxActions = 2;
|
||||
break;
|
||||
|
||||
case "Free Action or One Action":
|
||||
result.IsFreeAction = false;
|
||||
result.Actions = result.MaxActions = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
result.IsPassive = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс структуры, хранящей информацию о количетсве действий.
|
||||
/// </summary>
|
||||
public class ActionStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// Количество действий
|
||||
/// </summary>
|
||||
public int Actions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Максимальное количество действий
|
||||
/// </summary>
|
||||
public int MaxActions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что действия не требуются.
|
||||
/// </summary>
|
||||
public bool IsPassive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что действие может быть использовано как реакция.
|
||||
/// </summary>
|
||||
public bool IsReaction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что можно использовать свободное действие.
|
||||
/// </summary>
|
||||
public bool IsFreeAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что действие может быть продлено до 2 раундов.
|
||||
/// </summary>
|
||||
public bool TwoRounds { get; set; }
|
||||
}
|
||||
|
||||
public class SourceCategorySolver : EnumSolver<SourceCategory>
|
||||
{
|
||||
public override Dictionary<string, SourceCategory> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Rulebooks", SourceCategory.Rulebooks },
|
||||
{ "Adventure Paths", SourceCategory.AdventurePaths },
|
||||
{ "Lost Omens", SourceCategory.LostOmens },
|
||||
{ "Adventures", SourceCategory.Adventures },
|
||||
{ "Comics", SourceCategory.Comics },
|
||||
{ "Blog Posts", SourceCategory.BlogPosts },
|
||||
{ "Society", SourceCategory.Society },
|
||||
};
|
||||
}
|
||||
|
||||
public class VisionEnumSolver : EnumSolver<VisionEnum>
|
||||
{
|
||||
public override Dictionary<string, VisionEnum> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Low-Light VisionEnum", VisionEnum.LowLightVision },
|
||||
{ "Darkvision", VisionEnum.DarkVision },
|
||||
};
|
||||
}
|
||||
|
||||
public class AttributeValuesSolver : EnumSolver<AttributeValues>
|
||||
{
|
||||
public override Dictionary<string, AttributeValues> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Consitution", AttributeValues.Constitution },
|
||||
{ "Other", AttributeValues.Free },
|
||||
{ "other", AttributeValues.Free },
|
||||
};
|
||||
}
|
||||
|
||||
public class ActionEnumSolver : EnumSolver<ActionEnum>
|
||||
{
|
||||
public override Dictionary<string, ActionEnum> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Single Action", ActionEnum.SingleAction },
|
||||
{ "Two Actions", ActionEnum.TwoActions },
|
||||
{ "Three Actions", ActionEnum.ThreeActions },
|
||||
{ "Reaction", ActionEnum.Reaction },
|
||||
{ "Free Action", ActionEnum.FreeAction },
|
||||
{ "Single Action to Three Actions", ActionEnum.SingleActionToThreeActions },
|
||||
{ "Single Action or more Actions", ActionEnum.SingleActionToThreeActions },
|
||||
{ "Single Action or Two Actions", ActionEnum.SingleActionToTwoActions },
|
||||
{ "Single Action to Two Actions", ActionEnum.SingleActionToTwoActions },
|
||||
{ "Two Actions or Three Actions", ActionEnum.TwoActionsToThreeActions },
|
||||
{ "Two Actions to 2 rounds", ActionEnum.TwoActionsToTwoRounds },
|
||||
{ "Reaction or One Action", ActionEnum.ReactionOrOneAction },
|
||||
{ "Reaction or Two Actions", ActionEnum.ReactionOrTwoAction },
|
||||
{ "Reaction or Three Actions", ActionEnum.ReactionOrThreeAction },
|
||||
{ "Free Action or One Action", ActionEnum.FreeActionOrOneAction },
|
||||
{ "1 Minute", ActionEnum.OneMinute },
|
||||
{ "1 minute", ActionEnum.OneMinute },
|
||||
{ "5 minutes", ActionEnum.FiveMinutes },
|
||||
{ "10 minutes", ActionEnum.TenMinutes },
|
||||
{ "30 minutes", ActionEnum.ThirtyMinutes },
|
||||
{ "1 hour", ActionEnum.OneHour },
|
||||
{ "<i>Nethys Note: No cast time was printed</i>", ActionEnum.Unknown },
|
||||
};
|
||||
}
|
||||
|
||||
public static void RegisterServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddTransient<IEnumSolver, SourceCategorySolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, VisionEnumSolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, AttributeValuesSolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, ActionEnumSolver>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс коллекции книг, содержащих правила.
|
||||
/// Реализует шаблон <c>Singleton</c>.
|
||||
/// </summary>
|
||||
internal class RuleTocCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Список книг, содержащих правила.
|
||||
/// </summary>
|
||||
public List<RuleTocBook> Books { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Поиск книги по числовому идентификатору.
|
||||
/// </summary>
|
||||
public RuleTocBook this[int id] =>
|
||||
Books.FirstOrDefault(i => i.SourceId == id) ?? throw new($"Book with id {id} not found");
|
||||
|
||||
/// <summary>
|
||||
/// Поиск книги по CLR-представлению источника.
|
||||
/// </summary>
|
||||
public RuleTocBook this[Source source]
|
||||
{
|
||||
get
|
||||
{
|
||||
RuleTocBook? result = Books.FirstOrDefault(i => i.SourceId == source.Id);
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = new(source);
|
||||
Books.Add(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск книги по имени.
|
||||
/// </summary>
|
||||
public RuleTocBook? FindName(string name)
|
||||
{
|
||||
foreach (RuleTocBook book in Books)
|
||||
{
|
||||
if (book.IsNameExists(name))
|
||||
{
|
||||
return book;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск правила по идентификатору.
|
||||
/// </summary>
|
||||
public RuleTocItem? FindEntityId(int id)
|
||||
{
|
||||
foreach (RuleTocBook book in Books)
|
||||
{
|
||||
RuleTocItem? result = book.GetByEntityId(id);
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает строковое представление коллекции в файле YAML.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join("\n", Books.Select(book => book.ToString()).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сохраняет коллекцию в файле YAML.
|
||||
/// </summary>
|
||||
public void SaveFile()
|
||||
{
|
||||
string fileName =
|
||||
$"rules-toc.{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()}.debug.yaml";
|
||||
|
||||
using FileStream fileStream = File.Create(fileName);
|
||||
using StreamWriter writer = new(fileStream);
|
||||
|
||||
writer.Write(ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Экземпляр класса <see cref="RuleTocCollection"/>.
|
||||
/// </summary>
|
||||
public static RuleTocCollection Instance { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс представляет собой книгу, содержащую правила.
|
||||
/// </summary>
|
||||
internal class RuleTocBook(Source source)
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор источника.
|
||||
/// </summary>
|
||||
public int SourceId { get; } = source.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Названия источника.
|
||||
/// </summary>
|
||||
public string SourceName { get; } = source.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Список дочерних правил.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Список дочерних элементов.
|
||||
/// </summary>
|
||||
public List<RuleTocItem> Children { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Находит элемент по хлебным крошкам.
|
||||
/// </summary>
|
||||
/// <param name="breadcrumbs">Массив хлебных крошек, указывающих на путь в дереве.</param>
|
||||
/// <returns><see cref="RuleTocItem"/> или <c>null</c> если элемент не найден.</returns>
|
||||
public RuleTocItem Get(string[] breadcrumbs)
|
||||
{
|
||||
RuleTocItem? node = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
List<RuleTocItem> children = node?.Children ?? Children;
|
||||
|
||||
if (breadcrumbs.Length == 0)
|
||||
{
|
||||
return node!;
|
||||
}
|
||||
|
||||
string root = breadcrumbs[0];
|
||||
RuleTocItem? item = children.FirstOrDefault(i => i.Name == root);
|
||||
|
||||
if (item is null)
|
||||
{
|
||||
item = new(root, node);
|
||||
children.Add(item);
|
||||
}
|
||||
|
||||
breadcrumbs = breadcrumbs[1..];
|
||||
node = item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, существует ли имя в текущей книге или дочерних элементах.
|
||||
/// </summary>
|
||||
/// <param name="name">Имя для проверки.</param>
|
||||
/// <param name="node">Начальный элемент для поиска.</param>
|
||||
/// <returns>true если имя.</returns>
|
||||
public bool IsNameExists(string name, RuleTocItem? node = null)
|
||||
{
|
||||
foreach (RuleTocItem item in node?.Children ?? Children)
|
||||
{
|
||||
if (item.Name == name || IsNameExists(name, item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Находит элемент по идентификатору сущности.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <param name="node">Начальный узел для поиска.</param>
|
||||
/// <returns><see cref="RuleTocItem"/> или <c>null</c> если элемент не найден.</returns>
|
||||
public RuleTocItem? GetByEntityId(int id, RuleTocItem? node = null)
|
||||
{
|
||||
if (node?.RuleId == id)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
foreach (RuleTocItem item in node?.Children ?? Children)
|
||||
{
|
||||
RuleTocItem? result = GetByEntityId(id, item);
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает строковое представление книги в файле YAML.
|
||||
/// </summary>
|
||||
/// <returns>Строковое представление книги.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"\"{SourceName}\":\n"
|
||||
+ string.Join(
|
||||
"\n",
|
||||
Children.Select(child => RuleTocItem.Indent(child.ToString())).ToList()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс представляет собой элемент дерева правил.
|
||||
/// </summary>
|
||||
internal class RuleTocItem(string name, RuleTocItem? parent)
|
||||
{
|
||||
public string? Id { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Имя элемента.
|
||||
/// </summary>
|
||||
public string Name { get; } = name;
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор правила.
|
||||
/// </summary>
|
||||
public int? RuleId { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Указывает, что элемент обрабатывается.
|
||||
/// </summary>
|
||||
public bool IsHandled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Родительский элемент дерева.
|
||||
/// </summary>
|
||||
public RuleTocItem? Parent { get; } = parent;
|
||||
|
||||
/// <summary>
|
||||
/// Порядок дочерних элементов.
|
||||
/// </summary>
|
||||
public List<string>? ChildrenOrder { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция дочерних элементов.
|
||||
/// </summary>
|
||||
public List<RuleTocItem> Children { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Предыдущий идентификатор.
|
||||
/// </summary>
|
||||
public string? PreviousId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Следующий идентификатор.
|
||||
/// </summary>
|
||||
public string? NextId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор первого дочернего элемента.
|
||||
/// </summary>
|
||||
public string? FirstChildId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор последнего дочернего элемента.
|
||||
/// </summary>
|
||||
public string? LastChildId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает строковое представление элемента содержания в файле YAML.
|
||||
/// </summary>
|
||||
/// <returns>Строковое представление элемента содержания.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
List<string> rows =
|
||||
[
|
||||
$"{Name}:",
|
||||
$"{Tab}aonprd: {Id ?? "null"}",
|
||||
$"{Tab}entity: {RuleId?.ToString() ?? "null"}",
|
||||
];
|
||||
|
||||
if (Children.Count == 0)
|
||||
{
|
||||
// rows.Add($"{TAB}children: None");
|
||||
return string.Join("\n", rows);
|
||||
}
|
||||
|
||||
rows.Add($"{Tab}children:");
|
||||
|
||||
foreach (RuleTocItem child in Children)
|
||||
{
|
||||
rows.Add(child.Indent(2));
|
||||
}
|
||||
|
||||
return string.Join("\n", rows);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Увеличение отступа строкового представления элемента содержания в файле YAML.
|
||||
/// </summary>
|
||||
/// <param name="amount">Количество отступов.</param>
|
||||
/// <returns>Строковое представление элемента содержания с отступом.</returns>
|
||||
/// <seealso cref="Tab"/>
|
||||
public string Indent(int amount = 1)
|
||||
{
|
||||
string result = ToString();
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
result = Indent(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Увеличение отступа строки.
|
||||
/// </summary>
|
||||
/// <param name="str">Входная строка.</param>
|
||||
/// <returns>Строка с отступом.</returns>
|
||||
/// <seealso cref="Tab"/>
|
||||
public static string Indent(string str)
|
||||
{
|
||||
return Tab + str.Replace("\n", $"\n{Tab}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Строка отступа строки.
|
||||
/// </summary>
|
||||
/// <seealso cref="Indent(int)"/>
|
||||
/// <seealso cref="Indent(string)"/>
|
||||
public const string Tab = " ";
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Pf2e.Scrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс-помощник, для обработки строкового ввода с Архивов Нефиса.
|
||||
/// Реализует шаблон <c>Singleton</c>.
|
||||
/// </summary>
|
||||
internal partial class StringHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="StringHelper"/>
|
||||
/// </summary>
|
||||
public StringHelper() { }
|
||||
|
||||
/// <summary>
|
||||
/// Разбиение строки по нестабильным строковым разделителям (например, <c>", "</c>, <c>" or "</c> и т.д.).
|
||||
/// </summary>
|
||||
/// <param name="value">Исходная строка.</param>
|
||||
/// <returns>Коллекция строк.</returns>
|
||||
public List<string> RowSplit(string value)
|
||||
{
|
||||
return value
|
||||
.Replace(", or", ",")
|
||||
.Replace(" or ", ", ")
|
||||
.Replace(";", ",")
|
||||
.Split(", ")
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Разбор списка ссылок в исходной строке.
|
||||
/// </summary>
|
||||
/// <param name="input">Исходная строка.</param>
|
||||
/// <returns>Структура, содеражащая информацию разбиении строки по ссылкам.</returns>
|
||||
public MarkdownLinkSplit ParseMarkdownLinks(string input)
|
||||
{
|
||||
MarkdownLinkSplit result = new();
|
||||
|
||||
MatchCollection matches = LinkRegex().Matches(input);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
result.Links.Add(
|
||||
new()
|
||||
{
|
||||
Prefix = match.Groups[1].Value.Trim(),
|
||||
Text = match.Groups[2].Value.Trim(),
|
||||
Url = match.Groups[3].Value.Trim(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
result.Suffix = LinkRegex().Replace(input, "");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Извлечение суффикса из описания источника.
|
||||
/// </summary>
|
||||
/// <param name="input">Исходная строка.</param>
|
||||
/// <returns>Суффикс.</returns>
|
||||
public string ResolveSourceSuffix(string input)
|
||||
{
|
||||
Match match = SourceSuffixRegex().Match(input);
|
||||
return match.Captures.Count > 0 ? match.Groups[1].Value : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Регулярное выражение для извлечения источника из строки с Markdown-ссылкой.
|
||||
/// </summary>
|
||||
/// <returns>Регулярное выражение.</returns>
|
||||
[GeneratedRegex(@"^\[.*?\]\(.*?\)\s+(.*)$")]
|
||||
public static partial Regex SourceSuffixRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Регулярное выражение для выявления Markdown-ссылок в строке.
|
||||
/// </summary>
|
||||
/// <returns>Регулярное выражение.</returns>
|
||||
[GeneratedRegex(@"(.*?)\[(.*?)\]\((.*?)\)")]
|
||||
public static partial Regex LinkRegex();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс структуры, содеражащая информацию разбиении строки по ссылкам.
|
||||
/// </summary>
|
||||
internal class MarkdownLinkSplit
|
||||
{
|
||||
/// <summary>
|
||||
/// Список содержащихся ссылок.
|
||||
/// </summary>
|
||||
public List<MarkdownLink> Links { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Завершающая часть ссылки.
|
||||
/// </summary>
|
||||
public string Suffix { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Информация о ссылке в строке.
|
||||
/// </summary>
|
||||
internal class MarkdownLink
|
||||
{
|
||||
/// <summary>
|
||||
/// Данные, содержащиеся до ссылки.
|
||||
/// </summary>
|
||||
public string Prefix { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Текст ссылки.
|
||||
/// </summary>
|
||||
public string Text { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// URL ссылки.
|
||||
/// </summary>
|
||||
public string Url { get; init; } = "";
|
||||
}
|
||||
Reference in New Issue
Block a user