Начало борьбы с дублирующимися slug-ами
This commit is contained in:
@@ -110,6 +110,14 @@ public class EntityView
|
|||||||
.ThenInclude(c => c.Training)
|
.ThenInclude(c => c.Training)
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
),
|
),
|
||||||
|
new Getter<Subclass>(
|
||||||
|
EntityType.Subclass,
|
||||||
|
context =>
|
||||||
|
context
|
||||||
|
.Subclasses.Include(s => s.Trainings)
|
||||||
|
.ThenInclude(t => t.Training)
|
||||||
|
.Include(s => s.Entities)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -27,4 +27,110 @@ internal class AonprdBuilderGlobals
|
|||||||
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
|
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AonprdBuilderGlobals() { }
|
public AonprdBuilderGlobals() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить суффикс для ссылки.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Идентификатор Aounprd.</param>
|
||||||
|
/// <returns>Суффикс для ссылки или пустая строка.</returns>
|
||||||
|
public string GetSlugSuffix(string id)
|
||||||
|
{
|
||||||
|
return SlugSuffixes.TryGetValue(id, out var slug) ? $"-{slug}" : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Словарей суффиксов для сущностей
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> SlugSuffixes { get; } =
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
["feat-5997"] = "legacy",
|
||||||
|
["feat-5730"] = "dragonblood",
|
||||||
|
["feat-6469"] = "general",
|
||||||
|
["feat-4344"] = "archetype",
|
||||||
|
["feat-3549"] = "vampire",
|
||||||
|
["feat-7380"] = "mythic",
|
||||||
|
["feat-5737"] = "dragonblood",
|
||||||
|
["feat-6284"] = "bounty-hunter",
|
||||||
|
["feat-2664"] = "archetype",
|
||||||
|
["feat-6883"] = "kitsune",
|
||||||
|
["feat-6209"] = "investigator",
|
||||||
|
["feat-6477"] = "undead-slayer",
|
||||||
|
["feat-7637"] = "dandy",
|
||||||
|
["feat-7004"] = "yaoguai",
|
||||||
|
["feat-4380"] = "stonebound",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Словарь допольнительных проверок связей.
|
||||||
|
/// Ключ - устаревшая сущность, значение - обновлённая.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> LegacyRemasterExtra { get; } =
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
["archetype-83"] = "archetype-129",
|
||||||
|
["archetype-86"] = "archetype-198",
|
||||||
|
["feat-5997"] = "feat-7108",
|
||||||
|
["feat-4088"] = "feat-5491",
|
||||||
|
["feat-1177"] = "feat-4553",
|
||||||
|
["feat-2986"] = "feat-4340",
|
||||||
|
["feat-1753"] = "feat-6031",
|
||||||
|
["feat-2729"] = "feat-4077",
|
||||||
|
["feat-480"] = "feat-4850",
|
||||||
|
["feat-2989"] = "feat-4343",
|
||||||
|
["feat-2987"] = "feat-4341",
|
||||||
|
["feat-2990"] = "feat-4346",
|
||||||
|
["feat-936"] = "feat-2269",
|
||||||
|
["feat-2992"] = "feat-4350",
|
||||||
|
["feat-2991"] = "feat-4349",
|
||||||
|
["feat-2742"] = "feat-4078",
|
||||||
|
["feat-1169"] = "feat-5055",
|
||||||
|
["feat-2743"] = "feat-4079",
|
||||||
|
["feat-963"] = "feat-4519",
|
||||||
|
["feat-1171"] = "feat-4858",
|
||||||
|
["feat-1167"] = "feat-5880",
|
||||||
|
["feat-455"] = "feat-6007",
|
||||||
|
["feat-2993"] = "feat-4352",
|
||||||
|
["language-91"] = "language-180",
|
||||||
|
["ritual-31"] = "ritual-191",
|
||||||
|
["spell-571"] = "spell-1975",
|
||||||
|
["spell-1052"] = "spell-1309",
|
||||||
|
["spell-1053"] = "spell-1306",
|
||||||
|
["spell-843"] = "spell-2376",
|
||||||
|
["spell-578"] = "spell-1566",
|
||||||
|
["spell-551"] = "spell-2003",
|
||||||
|
["spell-553"] = "spell-2012",
|
||||||
|
["spell-646"] = "spell-1394",
|
||||||
|
["spell-1054"] = "spell-1305",
|
||||||
|
["spell-559"] = "spell-2417",
|
||||||
|
["spell-1055"] = "spell-1311",
|
||||||
|
["spell-1056"] = "spell-1310",
|
||||||
|
["spell-841"] = "spell-1107",
|
||||||
|
["spell-1057"] = "spell-1307",
|
||||||
|
["spell-560"] = "spell-2418",
|
||||||
|
["spell-1058"] = "spell-1304",
|
||||||
|
["spell-641"] = "spell-1413",
|
||||||
|
["spell-1059"] = "spell-1308",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Словарь сущностей, которые нужно слить с другой/другими.
|
||||||
|
/// Ключ - пропускаемая сущность. Значение - целевая сущность.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> MergeSources { get; } = new() { ["spell-654"] = "spell-819" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Проверка уникальности:
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
E.type,
|
||||||
|
E.slug,
|
||||||
|
COUNT(*),
|
||||||
|
string_agg(E.id::text, ', ' ORDER BY E.id),
|
||||||
|
string_agg(EE.external_id::text, ', ' ORDER BY E.id)
|
||||||
|
FROM public.entities AS E
|
||||||
|
INNER JOIN public.entity_externals AS EE ON EE.entity_id = E.id
|
||||||
|
GROUP BY E.type, E.slug
|
||||||
|
HAVING COUNT(*) > 1 AND E.type NOT IN ('rule', 'action')
|
||||||
|
*/
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ internal partial class StringHelper
|
|||||||
{
|
{
|
||||||
MarkdownLinkSplit result = new();
|
MarkdownLinkSplit result = new();
|
||||||
|
|
||||||
Match match = LinkRegex().Match(input);
|
MatchCollection matches = LinkRegex().Matches(input);
|
||||||
if (match.Captures.Count > 0)
|
|
||||||
|
foreach (Match match in matches)
|
||||||
{
|
{
|
||||||
result.Links.Add(
|
result.Links.Add(
|
||||||
new()
|
new()
|
||||||
|
|||||||
+3
-1
@@ -139,7 +139,9 @@ internal abstract class AbstractEntityProcessor(
|
|||||||
Entity.Img = "";
|
Entity.Img = "";
|
||||||
Entity.Description = GetDescription();
|
Entity.Description = GetDescription();
|
||||||
Entity.Rarity = Input.EnumOptional("rarity", EntityRarity.Common);
|
Entity.Rarity = Input.EnumOptional("rarity", EntityRarity.Common);
|
||||||
Entity.Slug = Helper.GenerateSlug(Entity.Name[LocaleType.English]);
|
Entity.Slug =
|
||||||
|
Helper.GenerateSlug(Entity.Name[LocaleType.English])
|
||||||
|
+ Globals.GetSlugSuffix(Input.GetRequired<string>("id"));
|
||||||
Entity.IsTranslated = false;
|
Entity.IsTranslated = false;
|
||||||
Entity.IsChecked = false;
|
Entity.IsChecked = false;
|
||||||
|
|
||||||
|
|||||||
+15
-1
@@ -1,10 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Pf2eDataScrapper.App;
|
using Pf2eDataScrapper.App;
|
||||||
using Pf2eDataScrapper.Processors;
|
using Pf2eDataScrapper.Processors;
|
||||||
using Pf2eModel;
|
using Pf2eModel;
|
||||||
|
using Pf2eModel.Model.Base;
|
||||||
using Pf2eModel.Model.Base.Targets;
|
using Pf2eModel.Model.Base.Targets;
|
||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Finalization;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Finalization;
|
||||||
@@ -28,11 +30,23 @@ internal class LegacyRemasterProcessors(ProcessorInput input) : AbstractInputPro
|
|||||||
Context.EntityExternals.AsNoTracking().FirstOrDefault(x => x.ExternalId == aonprdId)
|
Context.EntityExternals.AsNoTracking().FirstOrDefault(x => x.ExternalId == aonprdId)
|
||||||
?? throw new SilentSkipItemException();
|
?? throw new SilentSkipItemException();
|
||||||
int entityId = entity.EntityId;
|
int entityId = entity.EntityId;
|
||||||
|
Entity legacyEntity =
|
||||||
|
Context.Entities.FirstOrDefault(e => e.Id == entityId)
|
||||||
|
?? throw new Exception($"Неизвестная сущность с ID {entityId}");
|
||||||
|
|
||||||
foreach (int remasterId in GetEntityIds("remaster_id"))
|
foreach (int remasterId in GetEntityIds("remaster_id"))
|
||||||
{
|
{
|
||||||
Context.LegacyRemasters.Add(new() { LegacyId = entityId, RemasterId = remasterId });
|
Context.LegacyRemasters.Add(new() { LegacyId = entityId, RemasterId = remasterId });
|
||||||
|
Entity remasterEntity =
|
||||||
|
Context.Entities.AsNoTracking().FirstOrDefault(e => e.Id == remasterId)
|
||||||
|
?? throw new Exception($"Неизвестная сущность с ID {remasterId}");
|
||||||
|
|
||||||
|
if (legacyEntity.Slug == remasterEntity.Slug)
|
||||||
|
{
|
||||||
|
legacyEntity.Slug += "-legacy";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
foreach (int legacyId in GetEntityIds("legacy_id"))
|
foreach (int legacyId in GetEntityIds("legacy_id"))
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-2
@@ -19,7 +19,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasse
|
|||||||
InnerType = "apparition",
|
InnerType = "apparition",
|
||||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Apparitions",
|
Description = "Apparitions",
|
||||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
Group = ProcessorGroupEnum.AonprdSubclasses,
|
||||||
|
Active = false
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(
|
[ProcessorDependencies(
|
||||||
["aonprd:trait", "aonprd:source", "aonprd:class", "aonprd:spell", "aonprd:skill"]
|
["aonprd:trait", "aonprd:source", "aonprd:class", "aonprd:spell", "aonprd:skill"]
|
||||||
@@ -44,7 +45,7 @@ internal class ApparitionProcessor(
|
|||||||
public override void ProcessObject()
|
public override void ProcessObject()
|
||||||
{
|
{
|
||||||
// Spells
|
// Spells
|
||||||
string spellMd = Input.GetRequired<string>("spells_markdown");
|
string spellMd = Input.GetRequired<string>("spell_markdown");
|
||||||
List<string> spellUrls = StringHelper
|
List<string> spellUrls = StringHelper
|
||||||
.ParseMarkdownLinks(spellMd)
|
.ParseMarkdownLinks(spellMd)
|
||||||
.Links.Select(l => l.Url)
|
.Links.Select(l => l.Url)
|
||||||
|
|||||||
+9
-4
@@ -34,10 +34,7 @@ internal class ActionProcessor(
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Process()
|
public override void Process()
|
||||||
{
|
{
|
||||||
if (Input.GetRequired<string>("markdown").StartsWith("**Activate**"))
|
Check();
|
||||||
{
|
|
||||||
throw new SkipItemException(Input.GetRequired<string>("name"), "activating action");
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Process();
|
base.Process();
|
||||||
|
|
||||||
@@ -45,6 +42,14 @@ internal class ActionProcessor(
|
|||||||
Object.Extra = GenerateJsonString("school");
|
Object.Extra = GenerateJsonString("school");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Check()
|
||||||
|
{
|
||||||
|
if (Input.GetRequired<string>("markdown").StartsWith("**Activate"))
|
||||||
|
{
|
||||||
|
throw new SkipItemException(Input.GetRequired<string>("name"), "activating action");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override string[] AddedDescriptionPaths() =>
|
protected override string[] AddedDescriptionPaths() =>
|
||||||
[
|
[
|
||||||
|
|||||||
+3
-3
@@ -51,7 +51,7 @@ internal class RuleProcessor(
|
|||||||
AddExternal();
|
AddExternal();
|
||||||
AddTraits();
|
AddTraits();
|
||||||
|
|
||||||
RuleTocBook book = Toc[GetSource().Id];
|
RuleTocBook book = Toc[GetSourceId()];
|
||||||
|
|
||||||
string[] breadcrumbs = (Input.GetOptional<JArray>("breadcrumbs") ?? [])
|
string[] breadcrumbs = (Input.GetOptional<JArray>("breadcrumbs") ?? [])
|
||||||
.Select(token => token.ToString())
|
.Select(token => token.ToString())
|
||||||
@@ -119,11 +119,11 @@ internal class RuleProcessor(
|
|||||||
/// Получение источника, к которому относится правило.
|
/// Получение источника, к которому относится правило.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Источник, к которому относится правило.</returns>
|
/// <returns>Источник, к которому относится правило.</returns>
|
||||||
private Source GetSource()
|
private int GetSourceId()
|
||||||
{
|
{
|
||||||
foreach (EntitySource entitySource in Entity.Sources)
|
foreach (EntitySource entitySource in Entity.Sources)
|
||||||
{
|
{
|
||||||
return entitySource.Source;
|
return entitySource.SourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new("Source not found");
|
throw new("Source not found");
|
||||||
|
|||||||
+16
-2
@@ -1,4 +1,5 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using AngleSharp;
|
using AngleSharp;
|
||||||
@@ -116,7 +117,20 @@ internal partial class RulesTocProcessor(Pf2eDbContext context) : IProcessor
|
|||||||
{
|
{
|
||||||
IConfiguration config = Configuration.Default.WithDefaultLoader();
|
IConfiguration config = Configuration.Default.WithDefaultLoader();
|
||||||
IBrowsingContext context = BrowsingContext.New(config);
|
IBrowsingContext context = BrowsingContext.New(config);
|
||||||
IDocument document = context.OpenAsync(Url).Result;
|
IDocument document = null!;
|
||||||
|
bool completed = false;
|
||||||
|
|
||||||
|
while (!completed){
|
||||||
|
try
|
||||||
|
{
|
||||||
|
document = context.OpenAsync(Url).Result;
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
catch (AggregateException)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IElement root = document.QuerySelector(Selector) ?? throw new("Broken page download");
|
IElement root = document.QuerySelector(Selector) ?? throw new("Broken page download");
|
||||||
|
|
||||||
|
|||||||
@@ -140,17 +140,19 @@
|
|||||||
</MultiBinding>
|
</MultiBinding>
|
||||||
</TextBlock.Foreground>
|
</TextBlock.Foreground>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock
|
<TextBox
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{Binding Value}"
|
Background="Transparent"
|
||||||
TextTrimming="CharacterEllipsis">
|
BorderThickness="0"
|
||||||
<TextBlock.Foreground>
|
IsReadOnly="True"
|
||||||
|
Text="{Binding Value, Mode=OneWay}">
|
||||||
|
<TextBox.Foreground>
|
||||||
<MultiBinding Converter="{StaticResource ColorAdapterConverter}">
|
<MultiBinding Converter="{StaticResource ColorAdapterConverter}">
|
||||||
<Binding Path="AccentColor" />
|
<Binding Path="AccentColor" />
|
||||||
</MultiBinding>
|
</MultiBinding>
|
||||||
</TextBlock.Foreground>
|
</TextBox.Foreground>
|
||||||
</TextBlock>
|
</TextBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
ResizeDirection="Rows" />
|
ResizeDirection="Rows" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
Margin="3,4,0,0"
|
Margin="3,4,3,0"
|
||||||
Click="Button_Click"
|
Click="Button_Click"
|
||||||
Content="<<< Вернуться в начало" />
|
Content="<<< Вернуться в начало" />
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ internal partial class ProgressForm : Window
|
|||||||
}
|
}
|
||||||
private void Progress_ProgressComplete(ProcessProgress sender)
|
private void Progress_ProgressComplete(ProcessProgress sender)
|
||||||
{
|
{
|
||||||
//(Application.Current as ImportApplication)!.CurrentStage = ApplicationStage.Viewer;
|
(Application.Current as ImportApplication)!.CurrentStage = ApplicationStage.Viewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
|
public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
|
||||||
|
|||||||
Reference in New Issue
Block a user