Продолжение работы
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Тип документов компендиума.
|
||||
/// </summary>
|
||||
internal enum CompendiumDocumentType
|
||||
{
|
||||
Actor,
|
||||
Adventure,
|
||||
Cards,
|
||||
Item,
|
||||
JournalEntry,
|
||||
Macro,
|
||||
Playlist,
|
||||
RollTable,
|
||||
Scene,
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Папка компендиума.
|
||||
/// </summary>
|
||||
internal class CompendiumFolder
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор папки.
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Название папки.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Путь к папке.
|
||||
/// </summary>
|
||||
public string? Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Указатель, что папка содержится в других папках.
|
||||
/// </summary>
|
||||
public bool IsContained { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Тип документов в папке.
|
||||
/// </summary>
|
||||
public CompendiumDocumentType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Родительская папка.
|
||||
/// </summary>
|
||||
public CompendiumFolder? Parent { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор папки компендиума из JSON-объекта.
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
public CompendiumFolder(JObject json)
|
||||
{
|
||||
if (json.TryGetValue("_id", out JToken? id))
|
||||
{
|
||||
// Вложенная папка через _folders.json
|
||||
Id = id.ToString();
|
||||
Name = json["name"]!.ToString();
|
||||
Path = null;
|
||||
IsContained = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Корневой пак в system.json
|
||||
Id = json["name"]!.ToString();
|
||||
Name = json["label"]!.ToString();
|
||||
Path = json["path"]!.ToString();
|
||||
IsContained = false;
|
||||
}
|
||||
|
||||
Type = Enum.Parse<CompendiumDocumentType>(json["type"]!.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор папки компендиума.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор папки.</param>
|
||||
/// <param name="name">Название папки.</param>
|
||||
public CompendiumFolder(string id, string name)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Path = null;
|
||||
IsContained = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Строковое представление данных о папки компендиума.
|
||||
/// </summary>
|
||||
/// <returns>Строковое представление данных о папки компендиума.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"CompendiumFolder([Id={Id}][Type={Type}][Group={Name}][Path={Path}][Parent={Parent?.Name ?? "null"}])";
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция папок компендиума
|
||||
/// </summary>
|
||||
internal class CompendiumFolderCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Список загруженных папок.
|
||||
/// </summary>
|
||||
public List<CompendiumFolder> Items { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Добавление информации о папке.
|
||||
/// </summary>
|
||||
/// <param name="folder">Добавляемая папка.</param>
|
||||
public void Add(CompendiumFolder folder)
|
||||
{
|
||||
Items.Add(folder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение папки по идентификатору
|
||||
/// </summary>
|
||||
/// <param name="id">Искомый идентификатор</param>
|
||||
/// <returns>Искомая папка</returns>
|
||||
public CompendiumFolder? GetById(string id)
|
||||
{
|
||||
return Items.FirstOrDefault(x => x.Id == id);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на сущность с указанием файла, в котором она хранится.
|
||||
/// </summary>
|
||||
/// <param name="id">Идентификатор сущности.</param>
|
||||
/// <param name="type">Тип сущности.</param>
|
||||
/// <param name="file">Имя файла, в котором хранится сущность.</param>
|
||||
internal class FoundryEnCompendiumItem(string id, string type, string file) : AbstractCompendiumItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор сущности.
|
||||
/// </summary>
|
||||
public override string Id { get; } = id;
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности.
|
||||
/// </summary>
|
||||
public override string Type { get; } = type;
|
||||
|
||||
/// <summary>
|
||||
/// Имя файла, в котором хранится сущность.
|
||||
/// </summary>
|
||||
public string File { get; } = file;
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Helpers;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
using Pf2eModel.Model.Enum;
|
||||
using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.DataBuilder;
|
||||
|
||||
internal abstract class FoundryEnAbstractProcessor(
|
||||
DatabaseHelper databaseHelper,
|
||||
Pf2eDbContext context,
|
||||
ProcessorInput input
|
||||
) : AbstractInputProcessor(input)
|
||||
{
|
||||
protected DatabaseHelper Helper { get; } = databaseHelper;
|
||||
|
||||
public Pf2eDbContext Context { get; } = context;
|
||||
|
||||
public Entity Entity { get; set; } = new();
|
||||
|
||||
public abstract EntityType Type { get; }
|
||||
|
||||
public abstract IEntity Object { get; }
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
Entity.Id = Helper.NextEntityId;
|
||||
Entity.Type = Type;
|
||||
|
||||
Entity.Name[LocaleType.English] = Input.GetRequired<string>("name");
|
||||
Entity.Img = Input.GetRequired<string>("img");
|
||||
Entity.Description = Input.GetRequired<string>("system.description.value");
|
||||
|
||||
Entity.IsTranslated = false;
|
||||
Entity.IsChecked = false;
|
||||
|
||||
Entity.Slug = Helper.GenerateSlug(Entity.Name[LocaleType.English]);
|
||||
Entity.Rarity = Input.EnumOptional("system.traits.rarity", EntityRarity.Common);
|
||||
|
||||
Object.Id = Entity.Id;
|
||||
|
||||
Context.Entities.Add(Entity);
|
||||
|
||||
// Alias
|
||||
/*
|
||||
EntityAlias alias = new()
|
||||
{
|
||||
Type = LocaleType.English,
|
||||
Alias = Entity.Group,
|
||||
EntityId = Entity.Id,
|
||||
Entity = Entity
|
||||
};
|
||||
Context.EntityAliases.Add(alias);
|
||||
*/
|
||||
// Source
|
||||
EntitySource source = new()
|
||||
{
|
||||
Entity = Entity,
|
||||
|
||||
Source = GetSource(Input.GetRequired<string>("system.publication.title")),
|
||||
|
||||
Suffix = "",
|
||||
IsRemaster = Input.GetRequired<bool>("system.publication.remaster"),
|
||||
};
|
||||
Context.EntitySources.Add(source);
|
||||
|
||||
// External
|
||||
EntityExternal external = new()
|
||||
{
|
||||
Entity = Entity,
|
||||
Target = GetTarget(),
|
||||
Suffix = Input.GetRequired<string>("_id"),
|
||||
Md5 = Md5(Input.Input.ToString()),
|
||||
};
|
||||
Context.EntityExternals.Add(external);
|
||||
|
||||
// Traits
|
||||
JToken? traitsJson = Input.Input.SelectToken("system.traits.value");
|
||||
|
||||
if (traitsJson?.Type != JTokenType.Array)
|
||||
{
|
||||
throw new Exception("system.traits.value is expected to be an array");
|
||||
}
|
||||
|
||||
foreach (Trait trait in GetTraits(traitsJson.ToArray().Select(jToken => jToken.ToString())))
|
||||
{
|
||||
Entity.Traits.Add(trait);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
Context.SaveChanges();
|
||||
}
|
||||
|
||||
protected Source GetSource(string name)
|
||||
{
|
||||
Source? source = Context.Sources.FirstOrDefault(s => s.Name == name);
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
source = new() { Name = name };
|
||||
|
||||
Context.Sources.Add(source);
|
||||
return source;
|
||||
}
|
||||
|
||||
protected ExternalTarget GetTarget()
|
||||
{
|
||||
const string name = "pf2e - Foundry module";
|
||||
|
||||
ExternalTarget? target = Context.ExternalTargets.FirstOrDefault(s => s.Name == name);
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
target = new()
|
||||
{
|
||||
Name = "pf2e - Foundry module",
|
||||
Icon = "",
|
||||
Prefix = "https://github.com/foundryvtt/pf2e",
|
||||
};
|
||||
|
||||
Context.ExternalTargets.Add(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
protected IEnumerable<Trait> GetTraits(IEnumerable<string> names)
|
||||
{
|
||||
List<Trait> result = Context
|
||||
.Traits.Include(t => t.Entity)
|
||||
.ThenInclude(e => e.Name)
|
||||
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
|
||||
.Where(t => Pf2eDbContext.LocaleSearchArray(t.Entity.Name, names))
|
||||
.ToList();
|
||||
|
||||
/*
|
||||
foreach (string name in names)
|
||||
{
|
||||
if (result.Any(t => t.Entity.Group.Contains(name)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Trait trait = new()
|
||||
{
|
||||
Group = name,
|
||||
Slug = name,
|
||||
Type = TraitType.Regular,
|
||||
Description = "",
|
||||
};
|
||||
Context.Traits.Add(trait);
|
||||
result.Add(trait);
|
||||
}
|
||||
*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
|
||||
[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;
|
||||
|
||||
public override Pf2eModel.Model.Structure.Action Object { get; } = new();
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
/*
|
||||
Object.Type = EnumRequired<ActionType>("system.actionType.value");
|
||||
Object.Category = EnumOptional<ActionCategory>("system.category", ActionCategory.Unknown);
|
||||
Object.Actions = GetOptional<int>("system.actions.value");
|
||||
Object.DeathNote = GetOptional<bool>("system.deathNote");
|
||||
*/
|
||||
JObject extra = new() { { "selfEffect", Input.Input.SelectToken("system.selfEffect") } };
|
||||
|
||||
Object.Extra = extra.ToString(Newtonsoft.Json.Formatting.None);
|
||||
|
||||
Context.Actions.Add(Object);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
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;
|
||||
|
||||
[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;
|
||||
|
||||
public override Condition Object { get; } = new();
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
JObject extra = new()
|
||||
{
|
||||
{ "duration", Input.Input.SelectToken("system.duration") },
|
||||
{ "overrides", Input.Input.SelectToken("system.overrides") },
|
||||
{ "references", Input.Input.SelectToken("system.references") },
|
||||
{ "value", Input.Input.SelectToken("system.value") },
|
||||
};
|
||||
|
||||
Object.Extra = extra.ToString(Newtonsoft.Json.Formatting.None);
|
||||
Object.Group = Input.EnumOptional("system.group", ConditionGroup.Unknown);
|
||||
|
||||
Context.Conditions.Add(Object);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn;
|
||||
|
||||
/// <summary>
|
||||
/// Загрузчик данных компендиума.
|
||||
/// </summary>
|
||||
/// <param name="repository">Репозиторий данных.</param>
|
||||
internal sealed class FoundryEnCompendium(
|
||||
FoundryEnRepository repository,
|
||||
CompendiumFolderCollection folders
|
||||
) : AbstractCompendium
|
||||
{
|
||||
/// <summary>
|
||||
/// Репозиторий данных.
|
||||
/// </summary>
|
||||
public override FoundryEnRepository Repository { get; } = repository;
|
||||
|
||||
/// <summary>
|
||||
/// Набор папок компендиума
|
||||
/// </summary>
|
||||
public CompendiumFolderCollection Folders { get; } = folders;
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция всех записей компендиума.
|
||||
/// </summary>
|
||||
public List<FoundryEnCompendiumItem> Items { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает количество элементов в компендиуме.
|
||||
/// </summary>
|
||||
/// <returns>Количество элементов.</returns>
|
||||
public override int Count() => Items.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает количество элементов для указанного типа данных.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип данных.</param>
|
||||
/// <returns>Количество элементов.</returns>
|
||||
public override int Count(string type) => Items.Count(i => i.Type == type);
|
||||
|
||||
/// <summary>
|
||||
/// Получение итератора сущностей. Если указан тип <paramref name="type"/> - то только указанного типа.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип сущностей, которые нужно получить.</param>
|
||||
/// <returns>Итератор сущностей.</returns>
|
||||
public override IEnumerable<Tuple<AbstractCompendiumItem, JObject>> Iterate(string? type = null)
|
||||
{
|
||||
IEnumerable<FoundryEnCompendiumItem> 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Импортёр для модуля Pf2e FoundryVTT.
|
||||
/// </summary>
|
||||
internal class FoundryEnImporter(FoundryEnRepository repository, FoundryEnCompendium compendium)
|
||||
: AbstractImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Название внешней ссылки.
|
||||
/// </summary>
|
||||
public const string TargetName = "Pf2e FoundryVTT module";
|
||||
|
||||
/// <summary>
|
||||
/// Отображаемое имя импортёра.
|
||||
/// </summary>
|
||||
public override string Name => TargetName;
|
||||
|
||||
/// <summary>
|
||||
/// Короткое имя импортёра.
|
||||
/// </summary>
|
||||
public override string Slug => "foundry-en";
|
||||
|
||||
public override FoundryEnRepository Repository { get; } = repository;
|
||||
|
||||
/// <summary>
|
||||
/// Загрузчик данных компендиума.
|
||||
/// </summary>
|
||||
public override FoundryEnCompendium Compendium { get; } = compendium;
|
||||
|
||||
#region "Внедрение зависимостей"
|
||||
|
||||
public static void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<FoundryEnImporter>();
|
||||
services.AddSingleton<FoundryEnRepository>();
|
||||
services.AddSingleton<FoundryEnCompendium>();
|
||||
|
||||
services.AddSingleton<CompendiumFolderCollection>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Git-репозиторий данных FoundryVTT-модуля "pf2e".
|
||||
/// </summary>
|
||||
/// <param name="importer"><inheritdoc cref="FoundryEnImporter"/></param>
|
||||
internal class FoundryEnRepository(FoundryEnImporter importer) : GitRepository(importer, GitUrl)
|
||||
{
|
||||
public const string GitUrl = "https://github.com/foundryvtt/pf2e.git";
|
||||
}
|
||||
+123
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Загрузка основных папок.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработка папок компендиума в поле `packFolders`.
|
||||
/// </summary>
|
||||
/// <param name="folders"></param>
|
||||
/// <param name="parent"></param>
|
||||
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() { }
|
||||
}
|
||||
+104
@@ -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<CompendiumFolder> 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() { }
|
||||
}
|
||||
+23
@@ -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() { }
|
||||
}
|
||||
+99
@@ -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
|
||||
{
|
||||
/// <inheritdoc cref="FoundryEnImporter"/>
|
||||
protected FoundryEnImporter Importer { get; } = importer;
|
||||
|
||||
/// <inheritdoc cref="FoundryEnRepository"/>
|
||||
protected FoundryEnRepository Repository { get; } = repository;
|
||||
|
||||
/// <inheritdoc cref="FoundryEnCompendium"/>
|
||||
protected override FoundryEnCompendium Compendium { get; } = compendium;
|
||||
|
||||
/// <inheritdoc cref="ProcessProgress"/>
|
||||
protected override ProcessProgress Progress { get; } = progress;
|
||||
|
||||
/// <inheritdoc cref="ApplicationSettings"/>
|
||||
protected ApplicationSettings Settings { get; } = settings;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string SavePath => Path.Combine(Settings.OutputDirPath, Importer.Slug);
|
||||
|
||||
/// <summary>
|
||||
/// Определитель типов данных.
|
||||
/// </summary>
|
||||
protected override SchemaTypeResolver Resolver { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Список типов, являющихся экипировкой.
|
||||
/// </summary>
|
||||
private readonly string[] _equipmentTypes =
|
||||
[
|
||||
"Item.equipment",
|
||||
"Item.armor",
|
||||
"Item.backpack",
|
||||
"Item.consumable",
|
||||
"Item.kit",
|
||||
"Item.shield",
|
||||
"Item.treasure",
|
||||
"Item.weapon",
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Выбор целевых элементом схемы данных для типа элемента.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип входного элемента.</param>
|
||||
/// <returns>Список целевых элементов схемы данных.</returns>
|
||||
private List<SchemaNode> GetSchemaNodes(string type)
|
||||
{
|
||||
List<SchemaNode> 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<string> GetSchemaNodes(AbstractCompendiumItem item)
|
||||
{
|
||||
List<string> nodes = [item.Type];
|
||||
|
||||
// Нода подкласса
|
||||
if (_equipmentTypes.Contains(item.Type))
|
||||
{
|
||||
nodes.Add("Equipment");
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.SchemaBuilder;
|
||||
using Pf2eDataScrapper.SchemaBuilder.Types;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
|
||||
|
||||
internal class SchemaTypeResolver : TypeResolver
|
||||
{
|
||||
public class FoundryRulesSchemaType(SchemaNode parent) : IgnoreSchemaType(parent)
|
||||
{
|
||||
public override string GetTypeName()
|
||||
{
|
||||
return "ignored (Foundry rules object)";
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly List<string> DictionaryPaths =
|
||||
[
|
||||
"Actor.character.items.[].system.items",
|
||||
"Actor.character.items.[].system.overlays",
|
||||
"Item.ancestry.system.items",
|
||||
"Item.ancestry.system.boosts",
|
||||
"Item.background.system.boosts",
|
||||
"Item.background.system.items",
|
||||
"Item.class.system.items",
|
||||
"Item.consumable.system.spell.system.damage",
|
||||
"Item.consumable.system.spell.system.overlays",
|
||||
"Item.deity.system.spells",
|
||||
"Item.feat.system.subfeatures.proficiencies",
|
||||
"Item.kit.system.items",
|
||||
"Item.kit.system.items.[value].items",
|
||||
"Item.spell.system.damage",
|
||||
"Item.spell.system.heightening.damage",
|
||||
"Item.spell.system.heightening.levels",
|
||||
"Item.spell.system.heightening.levels.[value].damage",
|
||||
"Item.spell.system.overlays",
|
||||
"Item.spell.system.overlays.[value].system.damage",
|
||||
"Item.spell.system.overlays.[value].system.heightening",
|
||||
"Item.spell.system.overlays.[value].system.heightening.[value]",
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Частное определение типа поля.
|
||||
/// </summary>
|
||||
/// <param name="node">Поле схемы данных, для которого создаётся тип.</param>
|
||||
/// <param name="value">Добавляемое значение.</param>
|
||||
/// <returns>Новый тип для схемы данных. `null`, что бы использовать значение по умолчанию</returns>
|
||||
protected override AbstractSchemaType? CustomResolve(SchemaNode node, JToken? value)
|
||||
{
|
||||
return CheckPath(node, DictionaryPaths) ? new DictionarySchemaType(node)
|
||||
: node.Path.EndsWith(".rules") ? new FoundryRulesSchemaType(node)
|
||||
: (AbstractSchemaType?)null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user