Compare commits

...
3 Commits
Author SHA1 Message Date
kozhilya 06d154eccc Обновление отображения 2025-04-27 12:22:52 +04:00
kozhilya 76167f1b24 Большой рефактор 2025-04-26 16:56:29 +04:00
kozhilya 9d0683a23f Немного работы 2025-04-26 10:53:50 +04:00
93 changed files with 3296 additions and 1063 deletions
+7 -1
View File
@@ -4,6 +4,11 @@ root = true
# Файлы C#
[*.cs]
#### Resharper ####
resharper_member_can_be_private_global_highlighting = none
#### Основные параметры EditorConfig ####
# Отступы и интервалы
@@ -264,4 +269,5 @@ dotnet_style_namespace_match_folder = true:suggestion
dotnet_code_quality_unused_parameters = all:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
indent_size = 4
dotnet_diagnostic.CA1822.severity = none
+1
View File
@@ -10,6 +10,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pf2eDataScrapper", "Pf2eDat
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Элементы решения", "Элементы решения", "{754FC069-D67B-A9D7-50A1-8D1CA196D8F1}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
README.md = README.md
EndProjectSection
EndProject
+5 -9
View File
@@ -5,21 +5,17 @@ namespace Pf2eDataScrapper.Importers;
public class DatabaseHelper
{
public Pf2eDbContext Context = new();
public readonly Pf2eDbContext Context = new();
public SlugHelper Slugger = new();
public readonly SlugHelper Slugger = new();
private int _id = 0;
private int _id;
public int NextEntityId
{
get => ++_id;
set => _id = value;
}
public int NextEntityId => ++_id;
private DatabaseHelper()
{
NextEntityId = Context.Entities.Any() ? Context.Entities.Max(e => e.Id) : 0;
_id = Context.Entities.Any() ? Context.Entities.Max(e => e.Id) : 0;
}
public string GenerateSlug(string name)
@@ -18,7 +18,7 @@ internal class AonprdLoader(AonprdImporter importer)
foreach (KeyValuePair<string, JToken?> kvp in indexJson)
{
string file = kvp.Key.ToString();
string file = kvp.Key;
JArray items = (JArray)kvp.Value!;
foreach (JToken item in items)
@@ -40,7 +40,7 @@ internal class AonprdLoader(AonprdImporter importer)
public IEnumerable<LoaderEntry> Entries(string[] types)
{
foreach (string? file in Compendium.Files(types))
foreach (string file in Compendium.Files(types))
{
using StreamReader fileReader = new(Path.Combine(Importer.Dir, $"{file}.json"));
JArray array = JArray.Parse(fileReader.ReadToEnd());
@@ -1,13 +1,13 @@
using Konsole;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
using static Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.AbstractProcessor;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
internal class AonprdDataBuilder(AonprdLoader loader)
{
public Pf2eDatabase Database = Pf2eDatabase.Instance;
public readonly Pf2eDatabase Database = Pf2eDatabase.Instance;
/// <summary>
/// Загрузчик данных.
@@ -19,7 +19,7 @@ internal class AonprdDataBuilder(AonprdLoader loader)
/// </summary>
public AonprdCompendium Compendium => Loader.Compendium;
public AonprdProcessorCollection Processors = new();
public readonly AonprdProcessorCollection Processors = new();
public void TruncateTables()
{
@@ -30,64 +30,79 @@ internal class AonprdDataBuilder(AonprdLoader loader)
Console.WriteLine("Truncation completed!");
}
public void Build()
protected ConcurrentWriter Window = null!;
protected Window Output = null!;
protected ProgressBar ProcessProgressBar = null!;
protected ProgressBar SubProcessProgressBar = null!;
protected void ConfigureWindow()
{
Console.WriteLine("Building data...");
Window = new Window(0, 0, Console.WindowWidth, Console.WindowHeight).Concurrent();
ConcurrentWriter window = new Window(
0,
0,
Console.WindowWidth,
Console.WindowHeight
).Concurrent();
IConsole bars = window.OpenBox(
IConsole bars = Window.OpenBox(
"Progress",
Console.WindowWidth,
4,
new BoxStyle { Body = Colors.WhiteOnBlack }
new() { Body = Colors.WhiteOnBlack }
);
Window output = new(0, 4, Console.WindowWidth, Console.WindowHeight - 4, window);
Output = new(0, 4, Console.WindowWidth, Console.WindowHeight - 4, Window);
ProgressBar bar1 = new(bars, Processors.Items.Count, 15);
ProgressBar bar2 = new(bars, 1, 15);
int i = 0;
ProcessProgressBar = new(bars, Processors.Items.Count, 15);
SubProcessProgressBar = new(bars, 1, 15);
}
protected void RunProcessor(AonprdProcessorCollection.ProcessorItem item, JObject json)
{
AonprdProcessorInput input = new(json);
AbstractProcessor processor = (AbstractProcessor)item.Constructor.Invoke([input]);
try
{
processor.Process();
processor.Save();
}
catch (LegacyItemException ex)
{
Output.WriteLine(ex.Message);
}
catch (SkipItemException ex)
{
Output.WriteLine(ex.Message);
}
}
public void Build()
{
ConfigureWindow();
foreach (AonprdProcessorCollection.ProcessorItem item in Processors.Iterate())
{
bar1.Refresh(i++, $"{item.Attribute.Name}");
ProcessProgressBar.Next(item.Attribute.Name);
bar2.Max = Compendium.Count([item.Attribute.Type]);
int j = 0;
if (item.Attribute.Type is null)
{
RunProcessor(item, []);
continue;
}
SubProcessProgressBar.Max = Compendium.Count([item.Attribute.Type]);
foreach (AonprdLoader.LoaderEntry entry in Loader.Entries(item.Attribute.Type))
{
bar2.Refresh(j++, $"{entry.Id}");
AonprdProcessorInput input = new(entry.Json);
AbstractProcessor processor = (AbstractProcessor)item.Constructor.Invoke([input]);
try
{
processor.Process();
processor.Save();
}
catch (LegacyItemException ex)
{
output.WriteLine(ex.Message);
}
catch (SkipItemException ex)
{
output.WriteLine(ex.Message);
}
SubProcessProgressBar.Next($"{entry.Id}");
RunProcessor(item, entry.Json);
}
bar2.Refresh(j, "Processor completed");
SubProcessProgressBar.Next("Ready");
}
bar1.Refresh(i, "Done!");
ProcessProgressBar.Next("Done!");
window.Clear();
Window.Clear();
}
}
@@ -1,11 +1,11 @@
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
[AttributeUsage(AttributeTargets.Class)]
public class AonprdProcessorAttribute(string name, string type, string[] dependencies) : Attribute
public class AonprdProcessorAttribute(string name, string? type, string[] dependencies) : Attribute
{
public readonly string Name = name;
public readonly string Type = type;
public readonly string? Type = type;
public readonly IEnumerable<string> Dependencies = dependencies;
@@ -1,6 +1,7 @@
using System.Reflection;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
internal class AonprdProcessorCollection
{
@@ -41,13 +42,13 @@ internal class AonprdProcessorCollection
);
if (collision is not null)
{
throw new Exception(
throw new(
$"Multiply attributes 'AonprdProcessor' have same name '{attribute.Name}'."
+ $" They are added to classes: '{type.FullName}' and '{collision.Constructor.DeclaringType!.FullName}'"
);
}
Items.Add(new ProcessorItem(attribute, constructor));
Items.Add(new(attribute, constructor));
}
}
@@ -65,7 +66,7 @@ internal class AonprdProcessorCollection
}
IEnumerable<int> dependencyPriorities = item.Attribute.Dependencies.Select(d =>
priorities.TryGetValue(d, out int value) ? value : -1
priorities.GetValueOrDefault(d, -1)
);
if (dependencyPriorities.Any(p => p == -1))
@@ -1,4 +1,5 @@
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
@@ -84,10 +85,10 @@ internal class AonprdProcessorInput(JObject input) : ProcessorInput(input)
public class ActionStruct
{
public int Actions = 0;
public int MaxActions = 0;
public bool IsPassive = false;
public bool IsReaction = false;
public bool IsFreeAction = false;
public int Actions;
public int MaxActions;
public bool IsPassive;
public bool IsReaction;
public bool IsFreeAction;
}
}
@@ -0,0 +1,12 @@
using Pf2eModel.Model.Base.Targets;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
internal class AonprdBuilderGlobals
{
public ExternalTarget AonprdTarget = null!;
private AonprdBuilderGlobals() { }
public static AonprdBuilderGlobals Instance = new AonprdBuilderGlobals();
}
@@ -1,6 +1,6 @@
using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
public static class AonprdEnumResolver
{
@@ -0,0 +1,19 @@
using System.Text.RegularExpressions;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
internal partial class StringHelper
{
private StringHelper() { }
public string ResolveSource(string input)
{
Match match = SourceSuffixRegex().Match(input);
return match.Captures.Count > 0 ? match.Captures[0].Value : "";
}
public static StringHelper Instance { get; } = new();
[GeneratedRegex(@"^\[.*?\]\(.*?\)\s+(.*)$")]
private static partial Regex SourceSuffixRegex();
}
@@ -0,0 +1,223 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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.Aonprd.DataBuilder.Processors;
internal abstract class AbstractEntityProcessor(AonprdProcessorInput input)
: AbstractProcessor(input)
{
public Entity Entity { get; } = new();
public abstract EntityType Type { get; }
public abstract IEntity Object { get; }
public abstract void Add();
public override void Process()
{
if (Input.GetOptional<JArray>("remaster_id") is not null)
{
string id = Input.GetRequired<string>("id");
string name = Input.GetRequired<string>("name");
ProcessLegacy(id, name);
throw new LegacyItemException(name);
}
AddEntity();
AddAlias();
AddSource();
AddExternal();
AddTraits();
}
public override void Save()
{
Add();
Context.SaveChanges();
}
protected string AddedDescription(string path) =>
$"\r\n\r\n========== [{path}] ==========\r\n\r\n{Input.GetOptional<string>(path) ?? ""}";
protected string AddedDescription(string[] paths) =>
string.Join("", paths.Select(AddedDescription));
protected virtual string GetDescription() => Input.GetRequired<string>("markdown");
protected virtual void AddEntity()
{
Entity.Id = Helper.NextEntityId;
Entity.Type = Type;
Entity.Name[LocaleType.English] = Input.GetRequired<string>("name");
Entity.Img = "";
Entity.Description = GetDescription();
Entity.Rarity = Input.EnumOptional("rarity", EntityRarity.Common);
Entity.Slug = Helper.GenerateSlug(Entity.Name[LocaleType.English]);
Entity.IsTranslated = false;
Entity.IsChecked = false;
Object.Entity = Entity;
Object.Id = Entity.Id;
Context.Entities.Add(Entity);
}
protected virtual void AddAlias()
{
JArray? legacyIdArray = Input.GetOptional<JArray>("legacy_id");
if (legacyIdArray is null)
{
return;
}
string legacyId = legacyIdArray[0].ToString();
if (LegacyAliases.TryGetValue(legacyId, out string? legacyName))
{
// if (legacyName != Entity.Name[LocaleType.English])
Entity.Name[LocaleType.LegacyEnglish] = legacyName;
LegacyAliases.Remove(legacyId);
}
else
{
RemasterWithoutLegacy.Add(legacyId, Entity);
}
}
protected virtual void AddSource()
{
string name = Input.GetRequired<string>("source[0]");
Source? source = Context.Sources.FirstOrDefault(s => s.Name == name);
if (source == null)
{
source = new()
{
Name = name,
Category = Input.EnumRequired<SourceCategory>("primary_source_category"),
ReleaseDate = DateOnly.Parse(Input.GetRequired<string>("release_date")),
};
Context.Sources.Add(source);
}
EntitySource entitySource = new()
{
Entity = Entity,
Source = source,
Suffix = StringHelper.ResolveSource(Input.GetRequired<string>("source_markdown")),
IsRemaster = Input.GetOptional<JArray>("legacy_id") is not null,
};
Context.EntitySources.Add(entitySource);
}
protected virtual void AddExternal()
{
EntityExternal external = new()
{
Entity = Entity,
Target = Globals.AonprdTarget,
Suffix = Input.GetRequired<string>("url"),
Md5 = Md5(Input.Input.ToString()),
};
Context.EntityExternals.Add(external);
}
protected virtual void AddTraits()
{
JToken? traitsJson = Input.Input.SelectToken("trait");
if (traitsJson?.Type != JTokenType.Array)
{
return;
}
IEnumerable<string> names = traitsJson.ToArray().Select(jToken => jToken.ToString());
List<Entity> traitEntities = Context
.Traits.Select(t => t.Entity)
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
.Where(e => Pf2eDbContext.LocaleSearchArray(e.Name, names))
.ToList();
List<Trait> traits = Context
.Traits.Include(t => t.Entity)
.Where(t => traitEntities.Contains(t.Entity))
.ToList();
foreach (string name in names)
{
if (!traits.Any(t => t.Entity.Name.Contains(name)))
{
throw new Exception($"Trait '{name}' is not found!!!");
}
}
foreach (Trait trait in traits)
{
Entity.Traits.Add(trait);
}
}
protected void ProcessLegacy(string id, string name)
{
if (RemasterWithoutLegacy.TryGetValue(id, out Entity? remastered))
{
// if (remastered.ToString() != name)
remastered.Name[LocaleType.LegacyEnglish] = name;
RemasterWithoutLegacy.Remove(id);
}
else
{
LegacyAliases.Add(id, name);
}
}
public string GenerateExtra(params string[] args)
{
Dictionary<string, object?> data = [];
foreach (string key in args)
{
data.Add(key, Input.Input.SelectToken(key));
}
return JsonConvert.SerializeObject(data, new JsonSerializerSettings());
}
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;
}
protected static readonly Dictionary<string, string> LegacyAliases = [];
protected static readonly Dictionary<string, Entity> RemasterWithoutLegacy = [];
}
public class LegacyItemException(string name)
: Exception($"Item '{name}' is linked with newer version, skipping.") { }
public class SkipItemException(string name, string reason)
: Exception($"Item '{name}' is skipped, reason: {reason}.") { }
@@ -1,248 +1,21 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
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.Aonprd.DataBuilder.Processors;
internal abstract partial class AbstractProcessor(AonprdProcessorInput input)
internal abstract class AbstractProcessor(AonprdProcessorInput input)
{
protected DatabaseHelper Helper = DatabaseHelper.Instance;
protected readonly DatabaseHelper Helper = DatabaseHelper.Instance;
protected readonly StringHelper StringHelper = StringHelper.Instance;
protected readonly AonprdBuilderGlobals Globals = AonprdBuilderGlobals.Instance;
public readonly Pf2eDbContext Context = DatabaseHelper.DbContext;
public readonly AonprdProcessorInput Input = input;
public Entity Entity { get; set; } = new();
public abstract void Process();
public abstract EntityType Type { get; }
public abstract IEntity Object { get; }
protected abstract void Add();
public virtual void Process()
{
if (Input.GetOptional<JArray>("remaster_id") is not null)
{
string id = Input.GetRequired<string>("id");
string name = Input.GetRequired<string>("name");
ProcessLegacy(id, name);
throw new LegacyItemException(name);
}
AddEntity();
AddAlias();
AddSource();
AddExternal();
AddTraits();
}
public void Save()
{
Add();
Context.SaveChanges();
}
protected string AddedDescription(string path) =>
$"\r\n\r\n========== [{path}] ==========\r\n\r\n{Input.GetOptional<string>(path) ?? ""}";
protected string AddedDescription(string[] paths) =>
string.Join("", paths.Select(AddedDescription));
protected virtual string GetDescription() => Input.GetRequired<string>("markdown");
protected virtual void AddEntity()
{
Entity.Id = Helper.NextEntityId;
Entity.Type = Type;
Entity.Name[LocaleType.English] = Input.GetRequired<string>("name");
Entity.Img = "";
Entity.Description = GetDescription();
Entity.Rarity = Input.EnumOptional<EntityRarity>("rarity", EntityRarity.Common);
Entity.Slug = Helper.GenerateSlug(Entity.Name[LocaleType.English]);
Entity.IsTranslated = false;
Entity.IsChecked = false;
Object.Entity = Entity;
Object.Id = Entity.Id;
Context.Entities.Add(Entity);
}
protected virtual void AddAlias()
{
JArray? legacyIdArray = Input.GetOptional<JArray>("legacy_id");
if (legacyIdArray is null)
{
return;
}
string legacyId = legacyIdArray[0].ToString();
if (LegacyAliases.TryGetValue(legacyId, out string? legacyName))
{
// if (legacyName != Entity.Name[LocaleType.English])
Entity.Name[LocaleType.LegacyEnglish] = legacyName;
LegacyAliases.Remove(legacyId);
}
else
{
RemasterWithoutLegacy.Add(legacyId, Entity);
}
}
protected virtual void AddSource()
{
string name = Input.GetRequired<string>("source[0]");
Source? source = Context.Sources.FirstOrDefault(s => s.Name == name);
if (source == null)
{
source = new()
{
Name = name,
Category = Input.EnumRequired<SourceCategory>("primary_source_category"),
ReleaseDate = DateOnly.Parse(Input.GetRequired<string>("release_date")),
};
Context.Sources.Add(source);
}
string sourceMarkdown = Input.GetRequired<string>("source_markdown");
Match match = SourceSuffixRegex().Match(sourceMarkdown);
EntitySource entitySource = new()
{
Entity = Entity,
Source = source,
Suffix = ((match?.Captures.Count > 0) ? match?.Captures[0]?.Value : "") ?? "",
IsRemaster = Input.GetOptional<JArray>("legacy_id") is not null,
};
Context.EntitySources.Add(entitySource);
}
protected virtual void AddExternal()
{
if (AonprdTarget is null)
{
AonprdTarget = Context.ExternalTargets.FirstOrDefault(t =>
t.Name == AonprdImporter.SourceName
);
AonprdTarget ??= new()
{
Name = AonprdImporter.SourceName,
Icon = "http://www.google.com/s2/favicons?domain=2e.aonprd.com",
Prefix = "https://2e.aonprd.com",
};
}
EntityExternal external = new()
{
Entity = Entity,
Target = AonprdTarget,
Suffix = Input.GetRequired<string>("url"),
Md5 = Md5(Input.Input.ToString()),
};
Context.EntityExternals.Add(external);
}
protected virtual void AddTraits()
{
JToken? traitsJson = Input.Input.SelectToken("trait");
if (traitsJson?.Type != JTokenType.Array)
{
return;
}
IEnumerable<string> names = traitsJson.ToArray().Select(jToken => jToken.ToString());
List<Entity> traitEntities = Context
.Traits.Select(t => t.Entity)
.Where(e => Pf2eDbContext.LocaleSearchArray(e.Name, names))
.ToList();
List<Trait> traits = Context
.Traits.Include(t => t.Entity)
.Where(t => traitEntities.Contains(t.Entity))
.ToList();
foreach (string name in names)
{
if (!traits.Any(t => t.Entity.Name.Contains(name)))
{
throw new Exception($"Trait '{name}' is not found!!!");
}
}
foreach (Trait trait in traits)
{
Entity.Traits.Add(trait);
}
}
protected void ProcessLegacy(string id, string name)
{
if (RemasterWithoutLegacy.TryGetValue(id, out Entity? remastered))
{
// if (remastered.ToString() != name)
remastered.Name[LocaleType.LegacyEnglish] = name;
RemasterWithoutLegacy.Remove(id);
}
else
{
LegacyAliases.Add(id, name);
}
}
public string GenerateExtra(params string[] args)
{
Dictionary<string, object?> data = [];
foreach (string key in args)
{
data.Add(key, Input.Input.SelectToken(key));
}
return JsonConvert.SerializeObject(data, new JsonSerializerSettings() { });
}
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;
}
protected static ExternalTarget? AonprdTarget = null;
protected static Dictionary<string, string> LegacyAliases = [];
protected static Dictionary<string, Entity> RemasterWithoutLegacy = [];
[GeneratedRegex(@"^\[.*?\]\(.*?\)\s+(.*)$")]
private static partial Regex SourceSuffixRegex();
public class LegacyItemException(string name)
: Exception($"Item '{name}' is linked with newer version, skipping.") { }
public class SkipItemException(string name, string reason)
: Exception($"Item '{name}' is skipped, reason: {reason}.") { }
public abstract void Save();
}
@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pf2eModel;
using Pf2eModel.Model;
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum;
@@ -13,7 +7,7 @@ using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
internal abstract class AbstractSubclassProcessor(AonprdProcessorInput input)
: AbstractProcessor(input)
: AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Subclass;
@@ -23,7 +17,7 @@ internal abstract class AbstractSubclassProcessor(AonprdProcessorInput input)
public abstract string GroupName { get; }
protected override void Add() => Context.Subclasses.Add(Object);
public override void Add() => Context.Subclasses.Add(Object);
public override void Process()
{
@@ -37,51 +31,40 @@ internal abstract class AbstractSubclassProcessor(AonprdProcessorInput input)
public SubclassGroup GetGroup()
{
SubclassGroup? group = Context.SubclassGroups.FirstOrDefault(g =>
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
Pf2eDbContext.LocaleSearch(g.Entity.Name, GroupName)
);
if (group is null)
if (group is not null)
{
Class? cl =
(ClassName is not null)
? Context.Classes.FirstOrDefault(c =>
Pf2eDbContext.LocaleSearch(c.Entity.Name, GroupName)
)
: null;
Entity groupEntity = new()
{
Id = Helper.NextEntityId,
Type = EntityType.SubclassGroup,
Img = "",
Description = "",
Rarity = EntityRarity.Common,
IsTranslated = false,
IsChecked = false,
};
groupEntity.Name[LocaleType.English] = GroupName;
groupEntity.Slug = Helper.GenerateSlug(GroupName);
Context.Entities.Add(groupEntity);
/*
Entity.Id = Helper.NextEntityId;
Entity.Type = Type;
Entity.Name[LocaleType.English] = Input.GetRequired<string>("name");
Entity.Img = "";
Entity.Description = GetDescription();
Entity.Rarity = Input.EnumOptional<EntityRarity>("rarity", EntityRarity.Common);
Entity.Slug = Helper.GenerateSlug(Entity.Name[LocaleType.English]);
Entity.IsTranslated = false;
Entity.IsChecked = false;
*/
group = new SubclassGroup() { Entity = groupEntity, Class = cl };
Context.SubclassGroups.Add(group);
return group;
}
Class? cl = ClassName is not null
? Context.Classes.FirstOrDefault(c =>
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
Pf2eDbContext.LocaleSearch(c.Entity.Name, GroupName)
)
: null;
Entity groupEntity = new()
{
Id = Helper.NextEntityId,
Type = EntityType.SubclassGroup,
Img = "",
Description = "",
Rarity = EntityRarity.Common,
IsTranslated = false,
IsChecked = false,
Name = { [LocaleType.English] = GroupName },
Slug = Helper.GenerateSlug(GroupName),
};
Context.Entities.Add(groupEntity);
group = new() { Entity = groupEntity, Class = cl };
Context.SubclassGroups.Add(group);
return group;
}
}
@@ -0,0 +1,26 @@
using Pf2eModel.Model.Base.Targets;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common;
[AonprdProcessor("external", null, [])]
internal class ExternalProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public ExternalTarget AonprdTarget = null!;
public override void Process()
{
AonprdTarget = new()
{
Name = AonprdImporter.SourceName,
Icon = "http://www.google.com/s2/favicons?domain=2e.aonprd.com",
Prefix = "https://2e.aonprd.com",
};
}
public override void Save()
{
Context.ExternalTargets.Add(AonprdTarget);
Globals.AonprdTarget = AonprdTarget;
}
}
@@ -1,29 +1,25 @@
using Pf2eModel.Model;
using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common;
[AonprdProcessor("source", new string[] { })]
[AonprdProcessor("source", [])]
internal class SourceProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Unknown;
public override IEntity? Object => null;
protected override void Add() { }
protected Source Source = null!;
public override void Process()
{
// base.Process();
Source source = new()
Source = new()
{
Name = Input.GetRequired<string>("name"),
Category = Input.EnumRequired<SourceCategory>("primary_source_category"),
ReleaseDate = DateOnly.Parse(Input.GetRequired<string>("release_date")),
};
}
Context.Sources.Add(source);
public override void Save()
{
Context.Sources.Add(Source);
}
}
@@ -0,0 +1,20 @@
using Pf2eModel.Model.Common;
using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common;
[AonprdProcessor("skill", [])]
internal class TrainingProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public Training Training = null!;
public override void Process()
{
Training = new() { Name = Input.GetRequired<string>("name"), Type = TrainingType.Skill };
}
public override void Save()
{
Context.Trainings.Add(Training);
}
}
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Subclasses;
@@ -12,7 +7,7 @@ internal class ApparitionProcessor(AonprdProcessorInput input) : AbstractSubclas
{
public override Subclass Object { get; } = new();
public override string? ClassName => "Animist";
public override string ClassName => "Animist";
public override string GroupName => "Apparition";
@@ -3,13 +3,13 @@
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("action")]
internal class ActionProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
internal class ActionProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Action;
public override Pf2eModel.Model.Structure.Action Object { get; } = new();
protected override void Add() => Context.Actions.Add(Object);
public override void Add() => Context.Actions.Add(Object);
public override void Process()
{
@@ -24,7 +24,7 @@ internal class ActionProcessor(AonprdProcessorInput input) : AbstractProcessor(i
Object.IsFreeAction = action.IsFreeAction;
Object.IsPassive = action.IsPassive;
Object.IsReaction = action.IsReaction;
;
Object.Extra = GenerateExtra("school");
}
@@ -1,19 +1,17 @@
using LinqToDB;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Linq;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure.AncestryRelation;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("ancestry", ["trait", "language"])]
internal class AncestryProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
internal class AncestryProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Ancestry;
public override Ancestry Object { get; } = new();
protected override void Add() => Context.Ancestries.Add(Object);
public override void Add() => Context.Ancestries.Add(Object);
public override void Process()
{
@@ -36,7 +34,7 @@ internal class AncestryProcessor(AonprdProcessorInput input) : AbstractProcessor
Object.Speed = Convert.ToInt32(Input.GetOptional<int>("speed.land"));
Object.Vision = Input.EnumOptional<AncestryVision>("vision", AncestryVision.Normal);
Object.Vision = Input.EnumOptional("vision", AncestryVision.Normal);
AddBoosts("attribute", true);
AddBoosts("attribute_flaw", false);
@@ -59,14 +57,14 @@ internal class AncestryProcessor(AonprdProcessorInput input) : AbstractProcessor
foreach (string boost in boosts)
{
AncestryBoost ancestryBoost = new()
{
Ancestry = Object,
Boost = isBoost,
Value = Input.Enum<AttributeValues>(boost),
};
Context.AncestryBoosts.Add(ancestryBoost);
Context.AncestryBoosts.Add(
new()
{
Ancestry = Object,
Boost = isBoost,
Value = Input.Enum<AttributeValues>(boost),
}
);
}
}
}
@@ -4,13 +4,13 @@ using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("archetype")]
internal class ArchetypeProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
internal class ArchetypeProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Archetype;
public override Archetype Object { get; } = new();
protected override void Add() => Context.Archetypes.Add(Object);
public override void Add() => Context.Archetypes.Add(Object);
public override void Process()
{
@@ -6,13 +6,13 @@ using Pf2eModel.Model.Structure.BackgroundRelation;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("background")]
internal class BackgroundProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
internal class BackgroundProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Background;
public override Background Object { get; } = new();
protected override void Add() => Context.Backgrounds.Add(Object);
public override void Add() => Context.Backgrounds.Add(Object);
public override void Process()
{
@@ -1,6 +1,4 @@
using System.Text.RegularExpressions;
using LinqToDB;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using Pf2eModel.Model.Common;
using Pf2eModel.Model.Enum;
@@ -10,13 +8,13 @@ using Pf2eModel.Model.Structure.ClassRelation;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("class", ["trait", "source", "skill"])]
internal class ClassProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Class;
public override Class Object { get; } = new();
protected override void Add() => Context.Classes.Add(Object);
public override void Add() => Context.Classes.Add(Object);
public override void Process()
{
@@ -156,7 +154,6 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractProcessor(in
.Where(s => s != "")
.Select(s => string.Concat(s[0].ToString().ToUpper(), s.AsSpan(1)))
.ToArray();
;
}
protected ICollection<ClassTraining> GetSaveTrainings()
@@ -6,13 +6,13 @@ using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
// [AonprdProcessor("feat", ["trait", "source", "archetype"])]
internal class FeatProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
internal class FeatProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Feat;
public override Feat Object { get; } = new();
protected override void Add() => Context.Feats.Add(Object);
public override void Add() => Context.Feats.Add(Object);
public override void Process()
{
@@ -47,6 +47,7 @@ internal class FeatProcessor(AonprdProcessorInput input) : AbstractProcessor(inp
Input.GetOptional<JArray>("archetype")?.Select(t => t.ToString()).ToArray() ?? [];
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()))
.ToArray();
@@ -56,7 +57,7 @@ internal class FeatProcessor(AonprdProcessorInput input) : AbstractProcessor(inp
Object.Category = ActionCategory.Archetype;
}
foreach (Archetype? archetype in archetypes)
foreach (Archetype archetype in archetypes)
{
Object.Archetypes.Add(archetype);
@@ -4,16 +4,11 @@ using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("language")]
internal class LanguageProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
internal class LanguageProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Language;
public override Language Object { get; } = new();
protected override void Add() => Context.Languages.Add(Object);
public override void Process()
{
base.Process();
}
public override void Add() => Context.Languages.Add(Object);
}
@@ -1,28 +0,0 @@
using Pf2eModel.Model;
using Pf2eModel.Model.Common;
using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("skill", new string[] { })]
internal class TrainingProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Unknown;
public override IEntity? Object => null;
protected override void Add() { }
public override void Process()
{
// base.Process();
Training training = new()
{
Name = Input.GetRequired<string>("name"),
Type = TrainingType.Skill,
};
Context.Trainings.Add(training);
}
}
@@ -3,14 +3,14 @@ using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("trait", new string[] { })]
internal class TraitProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
[AonprdProcessor("trait", ["external"])]
internal class TraitProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
public override EntityType Type => EntityType.Trait;
public override Trait Object { get; } = new();
protected override void Add() => Context.Traits.Add(Object);
public override void Add() => Context.Traits.Add(Object);
public override void Process()
{
@@ -46,7 +46,7 @@ internal class CompendiumFolder
if (json.TryGetValue("_id", out JToken? id))
{
// Вложенная папка через _folders.json
Id = id!.ToString();
Id = id.ToString();
Name = json["name"]!.ToString();
Path = null;
IsContained = true;
@@ -22,12 +22,12 @@ internal class CompendiumLoader(Pf2EFoundryImporter importer)
/// <summary>
/// Счётчик папок
/// </summary>
private int _folderCount = 0;
private int _folderCount;
/// <summary>
/// Общее количество элементов в компендиуме
/// </summary>
public int ItemCount { get; protected set; } = 0;
public int ItemCount { get; protected set; }
/// <summary>
/// Обработка папок компендиума в поле `packFolders`.
@@ -48,7 +48,7 @@ internal class CompendiumLoader(Pf2EFoundryImporter importer)
if (obj.TryGetValue("packs", out JToken? packs))
{
foreach (JToken jToken in packs!)
foreach (JToken jToken in packs)
{
string packId = jToken.ToString();
CompendiumFolder? child = Collection.GetById(packId);
@@ -62,7 +62,7 @@ internal class CompendiumLoader(Pf2EFoundryImporter importer)
if (obj.TryGetValue("folders", out JToken? innerFolders))
{
ProcessPackFolders((JArray)innerFolders!, folder);
ProcessPackFolders((JArray)innerFolders, folder);
}
}
@@ -90,7 +90,7 @@ internal class CompendiumLoader(Pf2EFoundryImporter importer)
}
// Поиск _folders.json
foreach (CompendiumFolder? parent in Collection.ToList())
foreach (CompendiumFolder parent in Collection.ToList())
{
if (parent.Path is null)
{
@@ -1,5 +1,6 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using Pf2eModel;
using Pf2eModel.Model;
@@ -39,10 +40,7 @@ internal abstract class AbstractProcessor(ProcessorInput input)
Entity.IsChecked = false;
Entity.Slug = Helper.GenerateSlug(Entity.Name[LocaleType.English]);
Entity.Rarity = Input.EnumOptional<EntityRarity>(
"system.traits.rarity",
EntityRarity.Common
);
Entity.Rarity = Input.EnumOptional("system.traits.rarity", EntityRarity.Common);
Object.Id = Entity.Id;
@@ -140,16 +138,19 @@ internal abstract class AbstractProcessor(ProcessorInput input)
protected IEnumerable<Trait> GetTraits(IEnumerable<string> names)
{
List<Trait> result = Context
.Traits.Where(t => names.Contains(t.Entity.ToString()))
.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.Name.Contains(name)))
{
continue;
}
/*
Trait trait = new()
{
Name = name,
@@ -159,8 +160,8 @@ internal abstract class AbstractProcessor(ProcessorInput input)
};
Context.Traits.Add(trait);
result.Add(trait);
*/
}
*/
return result;
}
@@ -24,7 +24,7 @@ internal class ConditionProcessor(ProcessorInput input) : AbstractProcessor(inpu
};
Object.Extra = extra.ToString(Newtonsoft.Json.Formatting.None);
Object.Group = Input.EnumOptional<ConditionGroup>("system.group", ConditionGroup.Unknown);
Object.Group = Input.EnumOptional("system.group", ConditionGroup.Unknown);
Context.Conditions.Add(Object);
}
@@ -2,10 +2,6 @@
namespace Pf2eDataScrapper.Importers.Repositories;
/// <summary>
/// Загрузчик данных из git-репозиториев
/// </summary>
/// <param name="repositoryDirPath"></param>
internal class GitRepository(AbstractImporter importer, string url)
: AbstractImporterRepository(importer)
{
+12 -25
View File
@@ -11,7 +11,7 @@ internal class SchemaNode
/// <summary>
/// Отображаемое имя элемента.
/// </summary>
public string Name = "";
public readonly string Name = "";
/// <summary>
/// Путь к элементу.
@@ -21,12 +21,12 @@ internal class SchemaNode
/// <summary>
/// Родительский элемент.
/// </summary>
public SchemaNode? Parent = null;
public readonly SchemaNode? Parent;
/// <summary>
/// Указание, является ли элемент корневым.
/// </summary>
public bool Root = false;
public readonly bool Root;
/// <summary>
/// Корневой элемент схемы данных.
@@ -36,12 +36,12 @@ internal class SchemaNode
/// <summary>
/// Типы данных, использующиеся в элементе.
/// </summary>
public List<AbstractSchemaType> Types = [];
public readonly List<AbstractSchemaType> Types = [];
/// <summary>
/// Указание, может ли этот элемент отсутсвовать в структуре данных.
/// </summary>
public bool IsNullable = false;
public bool IsNullable;
/// <summary>
/// Объект для определения типа поля в схеме данных.
@@ -51,7 +51,7 @@ internal class SchemaNode
/// <summary>
/// Количество значений, добавленных в тип.
/// </summary>
public int Values { get; protected set; } = 0;
public int Values { get; protected set; }
/// <summary>
/// Указание, является ли тип элемента простым.
@@ -93,13 +93,10 @@ internal class SchemaNode
Values++;
foreach (AbstractSchemaType type in Types)
foreach (AbstractSchemaType type in Types.Where(type => type.IsCompatibleValue(value)))
{
if (type.IsCompatibleValue(value))
{
type.AddValue(value);
return;
}
type.AddValue(value);
return;
}
AbstractSchemaType newType = Resolver.Resolve(this, value);
@@ -161,7 +158,7 @@ internal class SchemaNode
foreach (AbstractSchemaType type in Types)
{
types.Add(Indent(type?.ToString() ?? ""));
types.Add(Indent(type.ToString()));
}
return FormatYaml(result, new() { ["types"] = "- " + string.Join("\r\n- ", types) });
@@ -223,19 +220,9 @@ internal class SchemaNode
{
Dictionary<string, string> result = [];
foreach (Dictionary<string, string> item in input)
foreach (KeyValuePair<string, string> kvp in input.SelectMany(item => item))
{
foreach (KeyValuePair<string, string> kvp in item)
{
if (result.ContainsKey(kvp.Key))
{
result[kvp.Key] = kvp.Value;
}
else
{
result.Add(kvp.Key, kvp.Value);
}
}
result[kvp.Key] = kvp.Value;
}
return result;
@@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Migrations
{
[DbContext(typeof(Pf2eDbContext))]
[Migration("20250425111349_1")]
[Migration("20250426125550_1")]
partial class _1
{
/// <inheritdoc />
@@ -182,11 +182,18 @@ namespace Pf2eModel.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("name")
.HasColumnOrder(2);
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text")
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("slug")
.HasColumnOrder(2);
.HasColumnOrder(3);
b.HasKey("Id")
.HasName("pk_keywords");
@@ -1457,7 +1464,8 @@ namespace Pf2eModel.Migrations
b.Property<string>("Extra")
.IsRequired()
.HasColumnType("text")
.HasColumnName("extra");
.HasColumnName("extra")
.HasColumnOrder(4);
b.Property<int>("GroupId")
.HasColumnType("integer")
@@ -91,7 +91,8 @@ namespace Pf2eModel.Migrations
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false)
name = table.Column<string>(type: "jsonb", nullable: false),
slug = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false)
},
constraints: table =>
{
@@ -179,11 +179,18 @@ namespace Pf2eModel.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("name")
.HasColumnOrder(2);
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text")
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("slug")
.HasColumnOrder(2);
.HasColumnOrder(3);
b.HasKey("Id")
.HasName("pk_keywords");
@@ -1454,7 +1461,8 @@ namespace Pf2eModel.Migrations
b.Property<string>("Extra")
.IsRequired()
.HasColumnType("text")
.HasColumnName("extra");
.HasColumnName("extra")
.HasColumnOrder(4);
b.Property<int>("GroupId")
.HasColumnType("integer")
+3 -2
View File
@@ -12,13 +12,13 @@ namespace Pf2eModel.Model.Base;
public class Entity
{
[System.ComponentModel.DataAnnotations.Key]
[Key]
[Column(Order = 1)]
public int Id { get; set; }
[Column(Order = 2)]
[Comment("Отображаемое имя")]
public LocaleString Name { get; set; } = new();
public LocaleString Name { get; set; } = [];
[Column(Order = 3)]
[Comment("Тип объекта")]
@@ -36,6 +36,7 @@ public class Entity
[Column(Order = 6)]
[Comment("Текстовое описание объекта")]
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
public string Description { get; set; } = null!;
[Column(Order = 7)]
+8 -3
View File
@@ -1,9 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Pf2eModel.Model.Common.I18n;
namespace Pf2eModel.Model.Base.Keywords;
public partial class Keyword
public class Keyword
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -11,10 +12,14 @@ public partial class Keyword
public int Id { get; set; }
[Column(Order = 2)]
public LocaleString Name = [];
[Column(Order = 3)]
[MaxLength(Pf2eDbContext.NameLength)]
public string Slug { get; set; } = null!;
[Newtonsoft.Json.JsonIgnore]
public virtual ICollection<Entity> Entities { get; set; } = [];
public ICollection<Entity> Entities { get; set; } = [];
public virtual ICollection<KeywordAlias> Aliases { get; set; } = [];
public ICollection<KeywordAlias> Aliases { get; set; } = [];
}
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Base.Keywords;
public partial class KeywordAlias
public class KeywordAlias
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -18,5 +18,5 @@ public partial class KeywordAlias
public string Value { get; set; } = null!;
[Newtonsoft.Json.JsonIgnore]
public virtual Keyword Keyword { get; set; } = null!;
public Keyword Keyword { get; set; } = null!;
}
+3 -3
View File
@@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
namespace Pf2eModel.Model.Base.Source;
[PrimaryKey(nameof(EntityId), nameof(SourceId))]
public partial class EntitySource
public class EntitySource
{
[Column(Order = 1)]
public int EntityId { get; set; }
@@ -23,7 +23,7 @@ public partial class EntitySource
public bool IsRemaster { get; set; }
[Newtonsoft.Json.JsonIgnore]
public virtual Entity Entity { get; set; } = null!;
public Entity Entity { get; set; } = null!;
public virtual Source Source { get; set; } = null!;
public Source Source { get; set; } = null!;
}
+1 -1
View File
@@ -4,7 +4,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Base.Source;
public partial class Source
public class Source
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
namespace Pf2eModel.Model.Base.Targets;
[PrimaryKey(nameof(EntityId), nameof(TargetId))]
public partial class EntityExternal
public class EntityExternal
{
[Column(Order = 0)]
public int EntityId { get; set; }
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Base.Targets;
public partial class ExternalTarget
public class ExternalTarget
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -24,5 +24,5 @@ public partial class ExternalTarget
[Column(Order = 4)]
[Newtonsoft.Json.JsonIgnore]
public virtual ICollection<EntityExternal> EntityExternals { get; set; } = [];
public ICollection<EntityExternal> EntityExternals { get; set; } = [];
}
+1 -1
View File
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Common;
public partial class Frequency
public class Frequency
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -18,17 +18,19 @@ public static class LocaleExtension
public static LocaleType GetTypeByString(this string localeType)
{
foreach (System.Reflection.FieldInfo field in typeof(LocaleType).GetFields())
foreach (FieldInfo field in typeof(LocaleType).GetFields())
{
if (
Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))
is DescriptionAttribute descriptionAttribute
is not DescriptionAttribute descriptionAttribute
)
{
if (descriptionAttribute.Description == localeType)
{
return (LocaleType)field.GetValue(null)!;
}
continue;
}
if (descriptionAttribute.Description == localeType)
{
return (LocaleType)field.GetValue(null)!;
}
}
+1 -1
View File
@@ -141,7 +141,7 @@ public class LocaleString : ICollection<LocaleItem>
foreach (KeyValuePair<string, JToken?> item in json)
{
result[item.Key] = item.ToString();
result[item.Key] = item.Value?.ToString() ?? "";
}
return result;
+1 -1
View File
@@ -4,7 +4,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Common;
public partial class Training
public class Training
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+2 -1
View File
@@ -5,7 +5,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Action)]
public partial class Action : IEntity
public class Action : IEntity
{
[System.ComponentModel.DataAnnotations.Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -28,6 +28,7 @@ public partial class Action : IEntity
public bool IsPassive { get; set; } = false;
[Column(Order = 6)]
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
public string? Extra { get; set; }
public Entity Entity { get; set; } = null!;
+5 -5
View File
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Structure.AncestryRelation;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Ancestry)]
public partial class Ancestry : IEntity
public class Ancestry : IEntity
{
[System.ComponentModel.DataAnnotations.Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -27,11 +27,11 @@ public partial class Ancestry : IEntity
[Column(Order = 5)]
public AncestryVision Vision { get; set; } = AncestryVision.Normal;
public virtual ICollection<AncestryBoost> AncestryBoosts { get; set; } = [];
public ICollection<AncestryBoost> AncestryBoosts { get; set; } = [];
public virtual ICollection<AncestryItem> AncestryItems { get; set; } = [];
public ICollection<AncestryItem> AncestryItems { get; set; } = [];
public virtual ICollection<AncestryLanguage> AncestryLanguages { get; set; } = [];
public ICollection<AncestryLanguage> AncestryLanguages { get; set; } = [];
public virtual ICollection<Heritage> Heritages { get; set; } = [];
public ICollection<Heritage> Heritages { get; set; } = [];
}
@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Pf2eModel.Model.Structure.AncestryRelation;
public partial class AncestryBoost
public class AncestryBoost
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -21,5 +21,5 @@ public partial class AncestryBoost
public Enum.AttributeValues Value { get; set; }
[JsonIgnore]
public virtual Ancestry Ancestry { get; set; } = null!;
public Ancestry Ancestry { get; set; } = null!;
}
@@ -5,7 +5,7 @@ using Pf2eModel.Model.Base;
namespace Pf2eModel.Model.Structure.AncestryRelation;
[PrimaryKey(nameof(AncestryId), nameof(ItemId))]
public partial class AncestryItem
public class AncestryItem
{
[Column(Order = 1)]
public int AncestryId { get; set; }
@@ -16,7 +16,7 @@ public partial class AncestryItem
[Column(Order = 3)]
public int Level { get; set; }
public virtual Ancestry Ancestry { get; set; } = null!;
public Ancestry Ancestry { get; set; } = null!;
public virtual Entity Item { get; set; } = null!;
public Entity Item { get; set; } = null!;
}
@@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Pf2eModel.Model.Structure.AncestryRelation;
[PrimaryKey(nameof(AncestryId), nameof(LanguageId))]
public partial class AncestryLanguage
public class AncestryLanguage
{
[Column(Order = 1)]
public int AncestryId { get; set; }
@@ -15,7 +15,7 @@ public partial class AncestryLanguage
[Column(Order = 3)]
public bool IsAdditional { get; set; }
public virtual Ancestry Ancestry { get; set; } = null!;
public Ancestry Ancestry { get; set; } = null!;
public virtual Language Language { get; set; } = null!;
public Language Language { get; set; } = null!;
}
+2 -1
View File
@@ -21,9 +21,10 @@ public class Archetype : IEntity
public int? DedicationFeatId { get; set; } = null;
[Column(Order = 4)]
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
public string? Extra { get; set; }
public Feat DedicationFeat { get; set; } = null!;
public virtual ICollection<Feat> Feats { get; set; } = [];
public ICollection<Feat> Feats { get; set; } = [];
}
+4 -4
View File
@@ -7,7 +7,7 @@ using Pf2eModel.Model.Structure.BackgroundRelation;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Background)]
public partial class Background : IEntity
public class Background : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -16,9 +16,9 @@ public partial class Background : IEntity
public Entity Entity { get; set; } = null!;
public virtual ICollection<BackgroundBoost> BackgroundBoosts { get; set; } = [];
public ICollection<BackgroundBoost> BackgroundBoosts { get; set; } = [];
public virtual ICollection<BackgroundItem> BackgroundItems { get; set; } = [];
public ICollection<BackgroundItem> BackgroundItems { get; set; } = [];
public virtual ICollection<BackgroundTraining> BackgroundSkills { get; set; } = [];
public ICollection<BackgroundTraining> BackgroundSkills { get; set; } = [];
}
@@ -5,7 +5,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure.BackgroundRelation;
public partial class BackgroundBoost
public class BackgroundBoost
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -19,5 +19,5 @@ public partial class BackgroundBoost
public AttributeValues Attribute { get; set; }
[JsonIgnore]
public virtual Background Background { get; set; } = null!;
public Background Background { get; set; } = null!;
}
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Base;
namespace Pf2eModel.Model.Structure.BackgroundRelation;
[PrimaryKey(nameof(BackgroundId), nameof(EntityId))]
public partial class BackgroundItem
public class BackgroundItem
{
[Column(Order = 1)]
public int BackgroundId { get; set; }
@@ -18,7 +18,7 @@ public partial class BackgroundItem
public int? Level { get; set; }
[JsonIgnore]
public virtual Background Background { get; set; } = null!;
public Background Background { get; set; } = null!;
public virtual Entity Entity { get; set; } = null!;
public Entity Entity { get; set; } = null!;
}
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure.BackgroundRelation;
public partial class BackgroundTraining
public class BackgroundTraining
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -23,7 +23,7 @@ public partial class BackgroundTraining
public TrainingStage Level { get; set; }
[JsonIgnore]
public virtual Background Background { get; set; } = null!;
public Background Background { get; set; } = null!;
public virtual Training Training { get; set; } = null!;
public Training Training { get; set; } = null!;
}
+1 -1
View File
@@ -7,7 +7,7 @@ using Pf2eModel.Model.Structure.ClassRelation;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Class)]
public partial class Class : IEntity
public class Class : IEntity
{
[Key]
[Column(Order = 1)]
@@ -5,7 +5,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure.ClassRelation;
public partial class ClassKeyAbility
public class ClassKeyAbility
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -19,5 +19,5 @@ public partial class ClassKeyAbility
public AttributeValues Value { get; set; }
[JsonIgnore]
public virtual Class Class { get; set; } = null!;
public Class Class { get; set; } = null!;
}
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure.ClassRelation;
public partial class ClassProgression
public class ClassProgression
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -26,7 +26,7 @@ public partial class ClassProgression
public int? EntityId { get; set; }
[JsonIgnore]
public virtual Class Class { get; set; } = null!;
public Class Class { get; set; } = null!;
public virtual Entity? Entity { get; set; }
public Entity? Entity { get; set; }
}
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure.ClassRelation;
public partial class ClassTraining
public class ClassTraining
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -27,7 +27,7 @@ public partial class ClassTraining
public TrainingStage Stage { get; set; }
[JsonIgnore]
public virtual Class Class { get; set; } = null!;
public Class Class { get; set; } = null!;
public virtual Training? Training { get; set; }
public Training? Training { get; set; }
}
+2 -1
View File
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Condition)]
public partial class Condition : IEntity
public class Condition : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -19,5 +19,6 @@ public partial class Condition : IEntity
public ConditionGroup? Group { get; set; }
[Column(Order = 3)]
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
public string? Extra { get; set; }
}
+6 -7
View File
@@ -8,7 +8,7 @@ using Pf2eModel.Model.Structure.DeityRelation;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Deity)]
public partial class Deity : IEntity
public class Deity : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -29,14 +29,13 @@ public partial class Deity : IEntity
[Column(Order = 5)]
public bool SanctificationMust { get; set; }
public virtual ICollection<DeityAttribute> DeityAttributes { get; set; } =
new List<DeityAttribute>();
public ICollection<DeityAttribute> DeityAttributes { get; set; } = new List<DeityAttribute>();
public virtual ICollection<DeityDomain> DeityDomains { get; set; } = new List<DeityDomain>();
public ICollection<DeityDomain> DeityDomains { get; set; } = new List<DeityDomain>();
public virtual ICollection<DeitySpell> DeitySpells { get; set; } = new List<DeitySpell>();
public ICollection<DeitySpell> DeitySpells { get; set; } = new List<DeitySpell>();
public virtual ICollection<DeityWeapon> DeityWeapons { get; set; } = new List<DeityWeapon>();
public ICollection<DeityWeapon> DeityWeapons { get; set; } = new List<DeityWeapon>();
public virtual ICollection<Training> Skills { get; set; } = new List<Training>();
public ICollection<Training> Skills { get; set; } = new List<Training>();
}
@@ -5,7 +5,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure.DeityRelation;
[PrimaryKey(nameof(DeityId), nameof(Attribute))]
public partial class DeityAttribute
public class DeityAttribute
{
[Column(Order = 1)]
public int DeityId { get; set; }
@@ -13,5 +13,5 @@ public partial class DeityAttribute
[Column(Order = 2)]
public AttributeValues Attribute { get; set; }
public virtual Deity Deity { get; set; } = null!;
public Deity Deity { get; set; } = null!;
}
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Structure.DeityRelation;
public partial class DeityDomain
public class DeityDomain
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Structure.DeityRelation;
public partial class DeitySpell
public class DeitySpell
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Structure.DeityRelation;
public partial class DeityWeapon
public class DeityWeapon
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+4 -3
View File
@@ -7,7 +7,7 @@ using Pf2eModel.Model.Structure.FeatRelation;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Feat)]
public partial class Feat : IEntity
public class Feat : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -38,9 +38,10 @@ public partial class Feat : IEntity
public bool IsPassive { get; set; } = false;
[Column(Order = 10)]
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
public string? Extra { get; set; }
public virtual ICollection<Archetype> Archetypes { get; set; } = [];
public ICollection<Archetype> Archetypes { get; set; } = [];
public virtual ICollection<FeatPrerequisite> FeatPrerequisites { get; set; } = [];
public ICollection<FeatPrerequisite> FeatPrerequisites { get; set; } = [];
}
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Structure.FeatRelation;
public partial class FeatPrerequisite
public class FeatPrerequisite
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+1 -1
View File
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Heritage)]
public partial class Heritage : IEntity
public class Heritage : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
+1 -1
View File
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Language)]
public partial class Language : IEntity
public class Language : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+1 -1
View File
@@ -7,7 +7,7 @@ using Pf2eModel.Model.Structure.SpellRelation;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Spell)]
public partial class Spell : IEntity
public class Spell : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -4,7 +4,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure.SpellRelation;
public partial class SpellArea
public class SpellArea
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -4,7 +4,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure.SpellRelation;
public partial class SpellDamage
public class SpellDamage
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Structure.SpellRelation;
public partial class SpellDefense
public class SpellDefense
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Pf2eModel.Model.Structure.SpellRelation;
public partial class SpellRitual
public class SpellRitual
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
+3 -1
View File
@@ -21,7 +21,9 @@ public class Subclass : IEntity
public SubclassGroup Group { get; set; } = null!;
[Column(Order = 3)]
public LocaleString Additional { get; set; } = new();
public LocaleString Additional { get; set; } = [];
[Column(Order = 4)]
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
public string Extra { get; set; } = "{}";
}
+2 -8
View File
@@ -1,13 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure.ClassRelation;
namespace Pf2eModel.Model.Structure;
@@ -25,5 +19,5 @@ public class SubclassGroup : IEntity
public Class? Class { get; set; } = null!;
public virtual ICollection<Subclass> Subclasses { get; set; } = [];
public ICollection<Subclass> Subclasses { get; set; } = [];
}
+1 -1
View File
@@ -6,7 +6,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Model.Structure;
[EntityOfType(EntityType.Trait)]
public partial class Trait : IEntity
public class Trait : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
+8 -6
View File
@@ -1,4 +1,5 @@
using System.Linq.Expressions;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
@@ -21,6 +22,7 @@ using Pf2eModel.Model.Structure.SpellRelation;
namespace Pf2eModel;
[SuppressMessage("ReSharper", "UnusedParameter.Local")]
public partial class Pf2eDbContext
{
public const int NameLength = 255;
@@ -39,11 +41,7 @@ public partial class Pf2eDbContext
private void BindEntity<T>(EntityTypeBuilder<T> entity)
where T : class, IEntity
{
entity
.HasOne(entity => entity.Entity)
.WithOne()
.HasForeignKey<T>(entity => entity.Id)
.IsRequired();
entity.HasOne(e => e.Entity).WithOne().HasForeignKey<T>(e => e.Id).IsRequired();
}
private void BindLocaleString<T>(
@@ -144,6 +142,8 @@ public partial class Pf2eDbContext
// Keywords
modelBuilder.Entity<Keyword>(entity =>
{
BindLocaleString(entity, k => k.Name);
entity
.HasMany(e => e.Aliases)
.WithOne(e => e.Keyword)
@@ -483,9 +483,11 @@ public partial class Pf2eDbContext
);
}
// ReSharper disable once InconsistentNaming
public static bool LocaleSearch(LocaleString data, string search_value) =>
throw new InvalidOperationException("Only for LINQ translation");
// ReSharper disable once InconsistentNaming
public static bool LocaleSearchArray(LocaleString data, IEnumerable<string> search_values) =>
throw new InvalidOperationException("Only for LINQ translation");
}
+130
View File
@@ -0,0 +1,130 @@
using System.ComponentModel;
using Newtonsoft.Json;
using Pf2eModel;
using Pf2eModel.Model;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Structure;
namespace Pf2eViewer.Converters;
internal class EntityConverter : JsonConverter
{
private readonly Pf2eDbContext _context = new();
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Entity) || objectType.IsAssignableTo(typeof(IEntity));
}
private Dictionary<string, object?> DictionarySerialization(object source)
{
Dictionary<string, object?> dictionary = [];
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source))
{
if (property.Attributes.OfType<JsonIgnoreAttribute>().Any())
{
continue;
}
object? v = property.GetValue(source);
dictionary.Add(property.Name, v);
}
return dictionary;
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
serializer.Serialize(writer, null);
return;
}
Dictionary<string, object?> dictionary = DictionarySerialization(value);
if (value.GetType() != typeof(Entity))
{
dictionary.Remove("Entity");
Processor? processor = _processors.FirstOrDefault(p => p.Type == value.GetType());
processor?.Process(dictionary, (IEntity)value, this);
}
else
{
dictionary.Remove("Description");
}
serializer.Serialize(writer, dictionary);
}
private abstract class Processor
{
public abstract Type Type { get; }
public abstract void Process(
Dictionary<string, object?> dictionary,
IEntity entity,
EntityConverter converter
);
}
private class Processor<T>(Action<Dictionary<string, object?>, T, EntityConverter> method)
: Processor
where T : IEntity
{
public override Type Type { get; } = typeof(T);
public override void Process(
Dictionary<string, object?> dictionary,
IEntity entity,
EntityConverter converter
)
{
method.Invoke(dictionary, (T)entity, converter);
}
}
private readonly ICollection<Processor> _processors =
[
new Processor<Trait>(
(dictionary, trait, converter) =>
{
LocaleString name =
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
trait.Entity?.Name
?? converter
._context.Entities.Where(e => e.Id == trait.Id)
.Select(e => e.Name)
.FirstOrDefault()!;
dictionary.Remove("Entity");
dictionary.Remove("Entities");
dictionary.Add("Name", name.ToString());
}
),
new Processor<Subclass>(
(dictionary, _, _) =>
{
dictionary.Remove("Group");
}
),
];
public override bool CanRead => false;
public override object ReadJson(
JsonReader reader,
Type objectType,
object? existingValue,
JsonSerializer serializer
)
{
throw new NotImplementedException(
"Unnecessary because CanRead is false. The type will skip the converter."
);
}
}
@@ -0,0 +1,41 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Pf2eModel.Model.Common.I18n;
namespace Pf2eViewer.Converters;
public class LocaleStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(LocaleString);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
JObject result = new();
LocaleString str = (LocaleString)value!;
foreach (LocaleItem item in str)
{
result.Add(item.Locale.GetTypeString(), item.Value);
}
result.WriteTo(writer);
}
public override bool CanRead => false;
public override object ReadJson(
JsonReader reader,
Type objectType,
object? existingValue,
JsonSerializer serializer
)
{
throw new NotImplementedException(
"Unnecessary because CanRead is false. The type will skip the converter."
);
}
}
+19
View File
@@ -0,0 +1,19 @@
namespace Pf2eViewer;
internal sealed class CoreBindingNavigator : BindingNavigator
{
public CoreBindingNavigator()
{
AddStandardItems();
if (DeleteItem != null)
{
DeleteItem.Visible = false;
}
if (AddNewItem != null)
{
AddNewItem.Visible = false;
}
}
}
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
namespace Pf2eViewer;
partial class DatabaseViewerControl
{
/// <summary>
/// Обязательная переменная конструктора.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Освободить все используемые ресурсы.
/// </summary>
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Код, автоматически созданный конструктором компонентов
/// <summary>
/// Требуемый метод для поддержки конструктора — не изменяйте
/// содержимое этого метода с помощью редактора кода.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
BindingNavigator = new CoreBindingNavigator();
SearchTextBox = new TextBox();
DataGridView = new DataGridView();
DbBindingSource = new BindingSource(components);
PagesBindingSource = new BindingSource(components);
( BindingNavigator).BeginInit();
((System.ComponentModel.ISupportInitialize) DataGridView).BeginInit();
((System.ComponentModel.ISupportInitialize) DbBindingSource).BeginInit();
((System.ComponentModel.ISupportInitialize) PagesBindingSource).BeginInit();
SuspendLayout();
//
// BindingNavigator
//
BindingNavigator.AddNewItem = null;
BindingNavigator.DeleteItem = null;
BindingNavigator.ImeMode = ImeMode.NoControl;
BindingNavigator.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow;
BindingNavigator.Location = new Point(0, 0);
BindingNavigator.Name = "BindingNavigator";
BindingNavigator.Size = new Size(345, 25);
BindingNavigator.Stretch = true;
BindingNavigator.TabIndex = 1;
BindingNavigator.Text = "coreBindingNavigator1";
BindingNavigator.RefreshItems += BindingNavigator_RefreshItems;
//
// SearchTextBox
//
SearchTextBox.Dock = DockStyle.Top;
SearchTextBox.Location = new Point(0, 25);
SearchTextBox.Name = "SearchTextBox";
SearchTextBox.PlaceholderText = "Поиск";
SearchTextBox.Size = new Size(345, 23);
SearchTextBox.TabIndex = 3;
//
// DataGridView
//
DataGridView.AllowUserToAddRows = false;
DataGridView.AllowUserToDeleteRows = false;
DataGridView.AutoGenerateColumns = false;
DataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader;
DataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders;
DataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
DataGridView.DataSource = DbBindingSource;
DataGridView.Dock = DockStyle.Fill;
DataGridView.Location = new Point(0, 48);
DataGridView.MultiSelect = false;
DataGridView.Name = "DataGridView";
DataGridView.ReadOnly = true;
DataGridView.RowHeadersVisible = false;
DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
DataGridView.Size = new Size(345, 345);
DataGridView.TabIndex = 4;
DataGridView.RowStateChanged += DataGridView_RowStateChanged;
//
// DatabaseViewerControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
Controls.Add(DataGridView);
Controls.Add(SearchTextBox);
Controls.Add(BindingNavigator);
Name = "DatabaseViewerControl";
Size = new Size(345, 393);
Load += DatabaseViewerControl_Load;
( BindingNavigator).EndInit();
((System.ComponentModel.ISupportInitialize) DataGridView).EndInit();
((System.ComponentModel.ISupportInitialize) DbBindingSource).EndInit();
((System.ComponentModel.ISupportInitialize) PagesBindingSource).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private CoreBindingNavigator BindingNavigator;
private TextBox SearchTextBox;
private DataGridView DataGridView;
private BindingSource DbBindingSource;
private BindingSource PagesBindingSource;
}
+143
View File
@@ -0,0 +1,143 @@
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum;
namespace Pf2eViewer;
public partial class DatabaseViewerControl : UserControl
{
public readonly Pf2eDbContext Context = new();
private int _perPage = 30;
private int _page = 1;
private EntityType _type;
public int PerPage
{
get => _perPage;
set
{
_perPage = value;
UpdateView();
}
}
public int Page
{
get => _page;
set
{
_page = value;
UpdateView();
}
}
public int Count { get; private set; }
public int MaxPage => Convert.ToInt32(Math.Ceiling(1.0 * Count / PerPage));
public EntityView CurrentEntity { get; private set; } = null!;
public EntityType Type
{
get => _type;
set
{
_type = value;
UpdateView();
}
}
internal bool _updating = false;
public DatabaseViewerControl()
{
InitializeComponent();
BindingNavigator.BindingSource = DbBindingSource;
DataGridView.AutoGenerateColumns = true;
DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
}
private void DatabaseViewerControl_Load(object sender, EventArgs e)
{
UpdateView();
}
private void BindingNavigator_RefreshItems(object sender, EventArgs e)
{
if (_updating)
{
return;
}
Page = (int)(BindingNavigator?.BindingSource?.Current ?? 1);
UpdateView();
}
public void UpdateView()
{
_updating = true;
IQueryable<Entity> query = Context.Entities.Where(e =>
e.Type == Type
&& (
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
SearchTextBox.Text == ""
|| Pf2eDbContext.LocaleSearch(e.Name, SearchTextBox.Text)
)
);
Count = query.Count();
PagesBindingSource.DataSource = Enumerable.Range(1, MaxPage).ToList();
BindingNavigator.BindingSource = PagesBindingSource;
DbBindingSource.DataSource = query
.Skip((Page - 1) * PerPage)
.Take(PerPage)
.Select(e => new EntityGridRow(e))
.ToList();
if (DataGridView.Rows.Count > 0)
{
DataGridView.Rows[0].Selected = true;
}
DataGridView.Update();
_updating = false;
}
private void DataGridView_RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e)
{
if (DataGridView.SelectedRows.Count == 0)
{
return;
}
EntityGridRow data = (EntityGridRow)DataGridView.SelectedRows[0].DataBoundItem;
CurrentEntity = new(Context, Type, data.Id);
EntityChange?.Invoke(CurrentEntity);
}
public delegate void EntityChangeHandler(EntityView newEntity);
public event EntityChangeHandler? EntityChange;
internal class EntityGridRow(Entity e)
{
public int Id { get; set; } = e.Id;
public string Name { get; set; } = e.Name[LocaleType.English];
public EntityRarity Rarity { get; set; } = e.Rarity;
private readonly Entity _e = e;
public Entity GetEntity() => _e;
}
}
+126
View File
@@ -0,0 +1,126 @@
<?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>
<metadata name="DbBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="PagesBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>163, 17</value>
</metadata>
</root>
-99
View File
@@ -1,99 +0,0 @@
using System.ComponentModel;
using Newtonsoft.Json;
using Pf2eModel;
using Pf2eModel.Model;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Structure;
namespace Pf2eViewer;
internal class EntityConverter : JsonConverter
{
private readonly Pf2eDbContext _context = new();
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Entity) || objectType.IsAssignableTo(typeof(IEntity));
}
private Dictionary<string, object?> DictionarySerialization(object source)
{
Dictionary<string, object?> dictionary = [];
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source))
{
if (property.Attributes.OfType<JsonIgnoreAttribute>().Any())
{
continue;
}
object? v = property.GetValue(source);
dictionary.Add(property.Name, v);
}
return dictionary;
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
Dictionary<string, object?> dictionary = DictionarySerialization(value!);
/*
if (value.GetType() != typeof(Entity))
{
dictionary.Remove("Entity");
}
*/
switch (value)
{
case Entity entity:
ProcessEntity(dictionary, entity);
break;
case Trait trait:
ProcessTrait(dictionary, trait);
break;
}
writer.WriteStartObject();
foreach (KeyValuePair<string, object> keyValuePair in dictionary)
{
writer.WritePropertyName(keyValuePair.Key, true);
serializer.Serialize(writer, keyValuePair.Value);
}
writer.WriteEndObject();
}
private void ProcessTrait(Dictionary<string, object?> dictionary, Trait trait)
{
LocaleString name =
trait.Entity?.Name
?? _context.Entities.Where(e => e.Id == trait.Id).Select(e => e.Name).FirstOrDefault()!;
dictionary.Remove("Entity");
dictionary.Remove("Entities");
dictionary.Add("Name", name.ToString());
}
private void ProcessEntity(Dictionary<string, object?> dictionary, Entity entity)
{
dictionary.Remove("Description");
}
public override bool CanRead => false;
public override object? ReadJson(
JsonReader reader,
Type objectType,
object? existingValue,
JsonSerializer serializer
)
{
throw new NotImplementedException(
"Unnecessary because CanRead is false. The type will skip the converter."
);
}
}
+96
View File
@@ -0,0 +1,96 @@
using Microsoft.EntityFrameworkCore;
using Pf2eModel;
using Pf2eModel.Model;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
namespace Pf2eViewer;
public class EntityView
{
public Entity? Entity;
public IEntity? SubEntity;
public EntityView(Pf2eDbContext context, EntityType type, int id)
{
Getter? getter = _getters.FirstOrDefault(g => g.Type == type);
Entity fullEntity =
context
.Entities.Where(e => e.Id == id && e.Type == type)
.Include(e => e.Sources)
.ThenInclude(s => s.Source)
.Include(e => e.Keywords)
.Include(e => e.Externals)
.ThenInclude(x => x.Target)
.Include(e => e.Traits)
.ThenInclude(t => t.Entity)
.FirstOrDefault() ?? throw new Exception("Entity is null");
IEntity? subEntity =
(getter != null)
? getter.Get(id, context)
: (IEntity?)context.Find(EntityOfTypeAttribute.Resolve(fullEntity), id);
Entity = fullEntity;
SubEntity = subEntity;
}
private readonly ICollection<Getter> _getters =
[
new Getter<Ancestry>(
EntityType.Ancestry,
context => context.Ancestries.Include(a => a.AncestryBoosts)
),
new Getter<Archetype>(
EntityType.Archetype,
context =>
context
.Archetypes.Include(a => a.DedicationFeat)
.ThenInclude(f => f.Entity)
.Include(a => a.Feats)
.ThenInclude(f => f.Entity)
),
new Getter<Background>(
EntityType.Background,
context =>
context
.Backgrounds.Include(b => b.BackgroundBoosts)
.Include(b => b.BackgroundSkills)
.ThenInclude(t => t.Training)
.Include(b => b.BackgroundItems)
),
new Getter<Class>(
EntityType.Class,
context =>
context
.Classes.Include(c => c.ClassKeyAbilities)
.Include(c => c.ClassTrainings)
.ThenInclude(t => t.Training)
.Include(c => c.ClassProgressions)
),
new Getter<Feat>(
EntityType.Feat,
context =>
context.Feats.Include(f => f.Archetypes).ThenInclude(a => a.Entity).AsQueryable()
),
];
private abstract class Getter(EntityType type)
{
public readonly EntityType Type = type;
public abstract IEntity? Get(int id, Pf2eDbContext context);
}
private class Getter<T>(EntityType type, Func<Pf2eDbContext, IQueryable<T>> method)
: Getter(type)
where T : IEntity
{
public override IEntity? Get(int id, Pf2eDbContext context)
{
return method.Invoke(context).FirstOrDefault(c => c.Id == id);
}
}
}
+26 -127
View File
@@ -31,16 +31,9 @@ partial class MainViewForm
RootSplitContainer = new SplitContainer();
InnerSplitContainer = new SplitContainer();
TypesListBox = new ListBox();
EntityGridView = new DataGridView();
PaginationPagel = new Panel();
PageTextBox = new TextBox();
PaginationLabel = new TextBox();
LastPageButton = new Button();
NextPageButton = new Button();
PrevPageButton = new Button();
FirstPageButton = new Button();
DatabaseViewer = new DatabaseViewerControl();
ResultSplitContainer = new SplitContainer();
EntityTreeView = new TreeView();
JsonWebView = new Microsoft.Web.WebView2.WinForms.WebView2();
DescriptionTextBox = new TextBox();
((System.ComponentModel.ISupportInitialize) RootSplitContainer).BeginInit();
RootSplitContainer.Panel1.SuspendLayout();
@@ -50,12 +43,11 @@ partial class MainViewForm
InnerSplitContainer.Panel1.SuspendLayout();
InnerSplitContainer.Panel2.SuspendLayout();
InnerSplitContainer.SuspendLayout();
((System.ComponentModel.ISupportInitialize) EntityGridView).BeginInit();
PaginationPagel.SuspendLayout();
((System.ComponentModel.ISupportInitialize) ResultSplitContainer).BeginInit();
ResultSplitContainer.Panel1.SuspendLayout();
ResultSplitContainer.Panel2.SuspendLayout();
ResultSplitContainer.SuspendLayout();
((System.ComponentModel.ISupportInitialize) JsonWebView).BeginInit();
SuspendLayout();
//
// RootSplitContainer
@@ -90,8 +82,7 @@ partial class MainViewForm
//
// InnerSplitContainer.Panel2
//
InnerSplitContainer.Panel2.Controls.Add(EntityGridView);
InnerSplitContainer.Panel2.Controls.Add(PaginationPagel);
InnerSplitContainer.Panel2.Controls.Add(DatabaseViewer);
InnerSplitContainer.Size = new Size(254, 688);
InnerSplitContainer.SplitterDistance = 102;
InnerSplitContainer.TabIndex = 1;
@@ -107,102 +98,13 @@ partial class MainViewForm
TypesListBox.TabIndex = 0;
TypesListBox.SelectedIndexChanged += TypesListBox_SelectedIndexChanged;
//
// EntityGridView
// EntityView
//
EntityGridView.AllowUserToAddRows = false;
EntityGridView.AllowUserToDeleteRows = false;
EntityGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
EntityGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
EntityGridView.Dock = DockStyle.Fill;
EntityGridView.Location = new Point(0, 53);
EntityGridView.MultiSelect = false;
EntityGridView.Name = "EntityGridView";
EntityGridView.ReadOnly = true;
EntityGridView.RowHeadersVisible = false;
EntityGridView.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToDisplayedHeaders;
EntityGridView.ShowEditingIcon = false;
EntityGridView.Size = new Size(254, 529);
EntityGridView.TabIndex = 0;
EntityGridView.RowStateChanged += EntityGridView_RowStateChanged;
//
// PaginationPagel
//
PaginationPagel.Controls.Add(PageTextBox);
PaginationPagel.Controls.Add(PaginationLabel);
PaginationPagel.Controls.Add(LastPageButton);
PaginationPagel.Controls.Add(NextPageButton);
PaginationPagel.Controls.Add(PrevPageButton);
PaginationPagel.Controls.Add(FirstPageButton);
PaginationPagel.Dock = DockStyle.Top;
PaginationPagel.Location = new Point(0, 0);
PaginationPagel.Name = "PaginationPagel";
PaginationPagel.Size = new Size(254, 53);
PaginationPagel.TabIndex = 1;
//
// PageTextBox
//
PageTextBox.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
PageTextBox.Location = new Point(81, 4);
PageTextBox.Name = "PageTextBox";
PageTextBox.Size = new Size(92, 23);
PageTextBox.TabIndex = 8;
PageTextBox.Text = "1";
PageTextBox.TextAlign = HorizontalAlignment.Center;
PageTextBox.KeyPress += PageTextBox_KeyPress;
//
// PaginationLabel
//
PaginationLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
PaginationLabel.BorderStyle = BorderStyle.None;
PaginationLabel.Location = new Point(3, 31);
PaginationLabel.Name = "PaginationLabel";
PaginationLabel.ReadOnly = true;
PaginationLabel.Size = new Size(248, 16);
PaginationLabel.TabIndex = 7;
PaginationLabel.Text = "Всего";
PaginationLabel.TextAlign = HorizontalAlignment.Center;
//
// LastPageButton
//
LastPageButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
LastPageButton.Location = new Point(218, 3);
LastPageButton.Name = "LastPageButton";
LastPageButton.Size = new Size(33, 23);
LastPageButton.TabIndex = 3;
LastPageButton.Text = ">>";
LastPageButton.UseVisualStyleBackColor = true;
LastPageButton.Click += LastPageButton_Click;
//
// NextPageButton
//
NextPageButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
NextPageButton.Location = new Point(179, 3);
NextPageButton.Name = "NextPageButton";
NextPageButton.Size = new Size(33, 23);
NextPageButton.TabIndex = 2;
NextPageButton.Text = ">";
NextPageButton.UseVisualStyleBackColor = true;
NextPageButton.Click += NextPageButton_Click;
//
// PrevPageButton
//
PrevPageButton.Location = new Point(42, 3);
PrevPageButton.Name = "PrevPageButton";
PrevPageButton.Size = new Size(33, 23);
PrevPageButton.TabIndex = 1;
PrevPageButton.Text = "<";
PrevPageButton.UseVisualStyleBackColor = true;
PrevPageButton.Click += PrevPageButton_Click;
//
// FirstPageButton
//
FirstPageButton.Location = new Point(3, 3);
FirstPageButton.Name = "FirstPageButton";
FirstPageButton.Size = new Size(33, 23);
FirstPageButton.TabIndex = 0;
FirstPageButton.Text = "<<";
FirstPageButton.UseVisualStyleBackColor = true;
FirstPageButton.Click += FirstPageButton_Click;
DatabaseViewer.Dock = DockStyle.Fill;
DatabaseViewer.Location = new Point(0, 0);
DatabaseViewer.Name = "DatabaseViewer";
DatabaseViewer.Size = new Size(254, 582);
DatabaseViewer.TabIndex = 0;
//
// ResultSplitContainer
//
@@ -212,7 +114,7 @@ partial class MainViewForm
//
// ResultSplitContainer.Panel1
//
ResultSplitContainer.Panel1.Controls.Add(EntityTreeView);
ResultSplitContainer.Panel1.Controls.Add(JsonWebView);
//
// ResultSplitContainer.Panel2
//
@@ -221,13 +123,19 @@ partial class MainViewForm
ResultSplitContainer.SplitterDistance = 467;
ResultSplitContainer.TabIndex = 1;
//
// EntityTreeView
// JsonWebView
//
EntityTreeView.Dock = DockStyle.Fill;
EntityTreeView.Location = new Point(0, 0);
EntityTreeView.Name = "EntityTreeView";
EntityTreeView.Size = new Size(467, 688);
EntityTreeView.TabIndex = 0;
JsonWebView.AllowExternalDrop = true;
JsonWebView.CreationProperties = null;
JsonWebView.DefaultBackgroundColor = Color.White;
JsonWebView.Dock = DockStyle.Fill;
JsonWebView.Location = new Point(0, 0);
JsonWebView.Name = "JsonWebView";
JsonWebView.Size = new Size(467, 688);
JsonWebView.TabIndex = 0;
JsonWebView.Visible = false;
JsonWebView.ZoomFactor = 1D;
JsonWebView.CoreWebView2InitializationCompleted += JsonWebView_CoreWebView2InitializationCompleted;
//
// DescriptionTextBox
//
@@ -256,14 +164,12 @@ partial class MainViewForm
InnerSplitContainer.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize) InnerSplitContainer).EndInit();
InnerSplitContainer.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize) EntityGridView).EndInit();
PaginationPagel.ResumeLayout(false);
PaginationPagel.PerformLayout();
ResultSplitContainer.Panel1.ResumeLayout(false);
ResultSplitContainer.Panel2.ResumeLayout(false);
ResultSplitContainer.Panel2.PerformLayout();
((System.ComponentModel.ISupportInitialize) ResultSplitContainer).EndInit();
ResultSplitContainer.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize) JsonWebView).EndInit();
ResumeLayout(false);
}
@@ -272,15 +178,8 @@ partial class MainViewForm
private SplitContainer RootSplitContainer;
private SplitContainer InnerSplitContainer;
private ListBox TypesListBox;
private DataGridView EntityGridView;
private TreeView EntityTreeView;
private SplitContainer ResultSplitContainer;
private TextBox DescriptionTextBox;
private Panel PaginationPagel;
private Button LastPageButton;
private Button NextPageButton;
private Button PrevPageButton;
private Button FirstPageButton;
private TextBox PageTextBox;
private TextBox PaginationLabel;
private Microsoft.Web.WebView2.WinForms.WebView2 JsonWebView;
private DatabaseViewerControl DatabaseViewer;
}
+49 -249
View File
@@ -1,13 +1,12 @@
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Pf2eModel;
using Pf2eModel.Model;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum;
using Pf2eViewer.Converters;
namespace Pf2eViewer;
@@ -15,12 +14,22 @@ public partial class MainViewForm : Form
{
public Pf2eDbContext Context = new();
public readonly string JsonViewerHtml;
public readonly EntityView Viewer;
public MainViewForm()
{
InitializeComponent();
Reset();
EntityGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
_ = new DarkModeCS(this) { ColorMode = DarkModeCS.DisplayMode.SystemDefault };
using StreamReader reader = new("Resources/json-viewer.html");
JsonViewerHtml = reader.ReadToEnd();
DatabaseViewer.EntityChange += DatabaseViewer_EntityChange;
Reset();
}
public void Reset()
@@ -28,55 +37,41 @@ public partial class MainViewForm : Form
Context.Dispose();
Context = new();
TypesListBox.DataSource = Context.Entities.Select(e => e.Type).Distinct().ToList();
TypesListBox.DataSource = Context
.Entities.Select(e => e.Type)
.Distinct()
.OrderBy(t => t.ToString())
.ToList();
TypesListBox.SelectedIndex = 0;
}
public const int PerPage = 30;
public int Count;
public int Page
private void ToggleFreeze(bool state)
{
get => Convert.ToInt32(PageTextBox.Text);
set
{
PageTextBox.Text = value.ToString();
UpdateTreeView();
}
// EntityGridView.Visible = state;
// JsonWebView.Visible = state;
}
public int MaxPage => Convert.ToInt32(Math.Ceiling(1.0 * Count / PerPage));
private void UpdateTreeView()
private void DatabaseViewer_EntityChange(EntityView newEntity)
{
EntityTreeView.Nodes.Clear();
EntityType type = (EntityType)TypesListBox.SelectedItem!;
Count = Context.Entities.Count(e => e.Type == type);
if (Page < 1 || Page > MaxPage)
JsonSerializerSettings settings = new()
{
Page = Math.Min(MaxPage, Math.Max(1, Page));
return;
}
Converters =
[
new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() },
new EntityConverter(),
new LocaleStringConverter(),
],
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
};
EntityGridView.DataSource = Context
.Entities.Where(e => e.Type == (EntityType)TypesListBox.SelectedItem)
.OrderBy(e => e.Id)
.Select(e => new EntityGridRow(e))
.Skip((Page - 1) * PerPage)
.Take(PerPage)
.ToList();
string result = JsonConvert.SerializeObject(newEntity.Entity, settings);
string subResult = JsonConvert.SerializeObject(newEntity.SubEntity, settings);
EntityGridView.Rows[0].Selected = true;
string json = result[..^1] + $",\"SubEntity\":{subResult}}}";
PaginationLabel.Text = $"Всего: {Count} объектов, {MaxPage} страниц";
RenderJson(json);
FirstPageButton.Enabled = Page > 1;
PrevPageButton.Enabled = Page > 1;
NextPageButton.Enabled = Page < MaxPage;
LastPageButton.Enabled = Page < MaxPage;
DescriptionTextBox.Text = newEntity.Entity!.Description;
}
private void TypesListBox_SelectedIndexChanged(object sender, EventArgs _)
@@ -86,223 +81,28 @@ public partial class MainViewForm : Form
return;
}
Page = 1;
DatabaseViewer.Type = (EntityType)TypesListBox.SelectedItem;
// Viewer.Page = 1;
}
private void FirstPageButton_Click(object sender, EventArgs e)
public void RenderJson(string json)
{
Page = 1;
}
private void PrevPageButton_Click(object sender, EventArgs e)
{
Page--;
}
private void NextPageButton_Click(object sender, EventArgs e)
{
Page++;
}
private void LastPageButton_Click(object sender, EventArgs e)
{
Page = MaxPage;
}
private void PageTextBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar))
if (JsonWebView.CoreWebView2 == null)
{
e.Handled = true;
JsonWebView.EnsureCoreWebView2Async();
return;
}
if (e.KeyChar == (char)Keys.Return)
{
UpdateTreeView();
}
string html = JsonViewerHtml.Replace("{JSON}", json);
JsonWebView.CoreWebView2.NavigateToString(html);
}
private void EntityGridView_RowStateChanged(
private void JsonWebView_CoreWebView2InitializationCompleted(
object sender,
DataGridViewRowStateChangedEventArgs e
Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e
)
{
int rowsCount = EntityGridView.SelectedRows.Count;
if (rowsCount is 0 or > 1)
{
return;
}
DataGridViewRow row = EntityGridView.SelectedRows[0];
EntityGridRow data = (EntityGridRow)row.DataBoundItem;
ViewEntity(data.Id);
}
public void ViewEntity(int id)
{
EntityTreeView.Nodes.Clear();
Entity? entity = Context
.Entities.Where(e => e.Id == id && e.Type == (EntityType)TypesListBox.SelectedItem!)
.Include(e => e.Sources)
.ThenInclude(s => s.Source)
.Include(e => e.Keywords)
.Include(e => e.Externals)
.ThenInclude(x => x.Target)
.Include(e => e.Traits)
.ThenInclude(t => t.Entity)
.FirstOrDefault();
if (entity == null)
{
DescriptionTextBox.Text = "Not found";
return;
}
string json = SerializeEntity(entity);
JsonToTreeview(EntityTreeView, json, "Entity");
DescriptionTextBox.Text = entity.Description.Replace("\\r", "").Replace("\\n", "\\r\\n");
}
public string SerializeEntity(Entity entity)
{
JsonSerializerSettings settings = new()
{
Converters =
[
new EntityConverter(),
new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() },
],
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
};
string result = JsonConvert.SerializeObject(entity, settings);
Type type = EntityOfTypeAttribute.Resolve(entity);
string subResult = JsonConvert.SerializeObject(ResolveSubEntity(entity), settings);
return result[..^1] + $",\"SubEntity\":{subResult}}}";
}
public IEntity? ResolveSubEntity(Entity entity)
{
switch (entity.Type)
{
case EntityType.Ancestry:
return Context
.Ancestries.Include(a => a.AncestryBoosts)
.FirstOrDefault(e => e.Id == entity.Id);
case EntityType.Archetype:
return Context
.Archetypes.Include(a => a.DedicationFeat)
.ThenInclude(f => f.Entity)
.Include(a => a.Feats)
.ThenInclude(f => f.Entity)
.FirstOrDefault(a => a.Id == entity.Id);
case EntityType.Background:
return Context
.Backgrounds.Include(b => b.BackgroundBoosts)
.Include(b => b.BackgroundSkills)
.ThenInclude(t => t.Training)
.Include(b => b.BackgroundItems)
.FirstOrDefault(e => e.Id == entity.Id);
case EntityType.Class:
return Context
.Classes.Include(c => c.ClassKeyAbilities)
.Include(c => c.ClassTrainings)
.ThenInclude(t => t.Training)
.Include(c => c.ClassProgressions)
.FirstOrDefault(c => c.Id == entity.Id);
case EntityType.Feat:
return Context
.Feats.Include(f => f.Archetypes)
.ThenInclude(a => a.Entity)
.FirstOrDefault(f => f.Id == entity.Id);
default:
Type type = EntityOfTypeAttribute.Resolve(entity);
return (IEntity?)Context.Find(type, entity.Id);
}
}
public static void JsonToTreeview(TreeView treeView, string jsonString, string rootName)
{
object json = jsonString.StartsWith('[')
? JArray.Parse(jsonString)
: JObject.Parse(jsonString);
treeView.Nodes.Clear();
foreach (TreeNode node in Element2TreeNode(json, rootName).Nodes)
{
treeView.Nodes.Add(node);
}
treeView.ExpandAll();
}
private static TreeNode Element2TreeNode(object element, string text = "")
{
List<TreeNode> subNodes = [];
switch (element.GetType().Name)
{
case "JObject":
JObject jObject = (JObject)element;
foreach (KeyValuePair<string, JToken> token in jObject)
{
subNodes.Add(Element2TreeNode(token.Value, token.Key));
}
break;
case "JArray":
JArray jArray = (JArray)element;
int i = 0;
subNodes.AddRange(jArray.Select(token => Element2TreeNode(token, $"[{i++}]")));
if (i == 0)
{
text += ": []";
}
break;
case "JValue":
JValue jValue = (JValue)element;
text += $": ({jValue.Type}) {jValue}";
break;
default:
throw new Exception(
"clsJSON2Treeview can't interpret object:" + element.GetType().Name
);
}
TreeNode treeNode = new(text);
if (subNodes.Count > 0)
{
treeNode.Nodes.AddRange(
subNodes.OrderBy(node => node.Nodes.Count > 0 ? 1 : 0).ToArray()
);
}
return treeNode;
}
private class EntityGridRow(Entity e)
{
public int Id { get; set; } = e.Id;
public string Name { get; set; } = e.Name[LocaleType.English];
public EntityRarity Rarity { get; set; } = e.Rarity;
private readonly Entity _e = e;
public Entity GetEntity() => _e;
JsonWebView.Visible = true;
DatabaseViewer_EntityChange(DatabaseViewer.CurrentEntity);
}
}
+6
View File
@@ -7,6 +7,7 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
@@ -15,4 +16,9 @@
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Update="Resources\json-viewer.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+24
View File
@@ -0,0 +1,24 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>JSON Viewer</title>
<script src="https://pfau-software.de/json-viewer/dist/iife/index.js"></script>
<style>
body {
margin: 0;
background: black;
}
</style>
</head>
<body>
<andypf-json-viewer indent="4"
expanded="true"
theme="classic-dark"
expand-icon-type="square"
show-data-types="false"
show-toolbar="false"
show-copy="false"
show-size="true">
{JSON}
</andypf-json-viewer>
</body>
</html>