Fix puncture prose boundary splitting
This commit is contained in:
Binary file not shown.
@@ -136,6 +136,25 @@ public sealed class StandardCriticalTableParserIntegrationTests
|
|||||||
Assert.Equal(1, bleedEffects.Single().PerRound);
|
Assert.Equal(1, bleedEffects.Single().PerRound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Puncture_91_95_keeps_a_and_b_prose_in_separate_columns()
|
||||||
|
{
|
||||||
|
var entry = LoadManifest().Tables.Single(item => string.Equals(item.Slug, "puncture", StringComparison.Ordinal));
|
||||||
|
var parseResult = await LoadParseResultAsync(entry);
|
||||||
|
var row91A = parseResult.Table.Results.Single(item =>
|
||||||
|
item.GroupKey is null &&
|
||||||
|
string.Equals(item.RollBandLabel, "91-95", StringComparison.Ordinal) &&
|
||||||
|
string.Equals(item.ColumnKey, "A", StringComparison.Ordinal));
|
||||||
|
var row91B = parseResult.Table.Results.Single(item =>
|
||||||
|
item.GroupKey is null &&
|
||||||
|
string.Equals(item.RollBandLabel, "91-95", StringComparison.Ordinal) &&
|
||||||
|
string.Equals(item.ColumnKey, "B", StringComparison.Ordinal));
|
||||||
|
|
||||||
|
Assert.Equal("Strike to foe's ear. Foe hears at -50.", row91A.DescriptionText);
|
||||||
|
Assert.DoesNotContain("Strike to foe's hip.", row91A.DescriptionText, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("Strike to foe's hip.", row91B.DescriptionText, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Slash_boundary_repair_keeps_56_60_a_prose_first()
|
public async Task Slash_boundary_repair_keeps_56_60_a_prose_first()
|
||||||
{
|
{
|
||||||
@@ -460,6 +479,30 @@ public sealed class StandardCriticalTableParserIntegrationTests
|
|||||||
Assert.Contains(result.Branches.SelectMany(item => item.Effects), item => item.EffectCode == CriticalEffectCodes.BleedPerRound);
|
Assert.Contains(result.Branches.SelectMany(item => item.Effects), item => item.EffectCode == CriticalEffectCodes.BleedPerRound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Loader_persists_puncture_b_91_95_description_text()
|
||||||
|
{
|
||||||
|
var entry = LoadManifest().Tables.Single(item => string.Equals(item.Slug, "puncture", StringComparison.Ordinal));
|
||||||
|
var parseResult = await LoadParseResultAsync(entry);
|
||||||
|
var databasePath = CreateTemporaryDatabaseCopy();
|
||||||
|
var loader = new CriticalImportLoader(databasePath);
|
||||||
|
|
||||||
|
await loader.LoadAsync(parseResult.Table);
|
||||||
|
|
||||||
|
await using var dbContext = CreateDbContext(databasePath);
|
||||||
|
var result = await dbContext.CriticalResults
|
||||||
|
.Include(item => item.CriticalTable)
|
||||||
|
.Include(item => item.CriticalColumn)
|
||||||
|
.Include(item => item.CriticalRollBand)
|
||||||
|
.SingleAsync(item =>
|
||||||
|
item.CriticalTable.Slug == "puncture" &&
|
||||||
|
item.CriticalColumn.ColumnKey == "B" &&
|
||||||
|
item.CriticalRollBand.Label == "91-95");
|
||||||
|
|
||||||
|
Assert.Contains("Strike to foe's hip.", result.DescriptionText, StringComparison.Ordinal);
|
||||||
|
Assert.StartsWith("Strike to foe's hip.", result.RawCellText, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Lookup_service_returns_effects_for_results_and_branches()
|
public async Task Lookup_service_returns_effects_for_results_and_branches()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ internal static class CriticalTableParserSupport
|
|||||||
internal const int TopGroupingTolerance = 2;
|
internal const int TopGroupingTolerance = 2;
|
||||||
|
|
||||||
private static readonly Regex MultiFragmentSplitRegex = new(@"\S(?:.*?\S)?(?=(?:\s{2,}|$))", RegexOptions.Compiled);
|
private static readonly Regex MultiFragmentSplitRegex = new(@"\S(?:.*?\S)?(?=(?:\s{2,}|$))", RegexOptions.Compiled);
|
||||||
|
private static readonly Regex SentenceFragmentSplitRegex = new(@"\S.*?(?:[.!?](?:['"")\]]*)|$)", RegexOptions.Compiled);
|
||||||
private static readonly Regex NumericAffixLineRegex = new(@"^\d+(?:H|∑|∏|π|∫|\s*[–-])", RegexOptions.Compiled);
|
private static readonly Regex NumericAffixLineRegex = new(@"^\d+(?:H|∑|∏|π|∫|\s*[–-])", RegexOptions.Compiled);
|
||||||
private static readonly Regex StandaloneModifierAffixLineRegex = new(@"^(?:\d+)?\((?:\+|-|–)\d+\)$", RegexOptions.Compiled);
|
private static readonly Regex StandaloneModifierAffixLineRegex = new(@"^(?:\d+)?\((?:\+|-|–)\d+\)$", RegexOptions.Compiled);
|
||||||
|
|
||||||
@@ -373,7 +374,7 @@ internal static class CriticalTableParserSupport
|
|||||||
supportsPowerPointModifier: footerText.Contains("powerpoint modification", StringComparison.OrdinalIgnoreCase));
|
supportsPowerPointModifier: footerText.Contains("powerpoint modification", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static List<XmlTextFragment> SplitBoundaryCrossingAffixFragments(
|
internal static List<XmlTextFragment> SplitBoundaryCrossingFragments(
|
||||||
IReadOnlyList<XmlTextFragment> bodyFragments,
|
IReadOnlyList<XmlTextFragment> bodyFragments,
|
||||||
IReadOnlyList<(string Key, double CenterX)> columnCenters,
|
IReadOnlyList<(string Key, double CenterX)> columnCenters,
|
||||||
IReadOnlySet<string> affixLegendSymbols)
|
IReadOnlySet<string> affixLegendSymbols)
|
||||||
@@ -382,7 +383,7 @@ internal static class CriticalTableParserSupport
|
|||||||
|
|
||||||
foreach (var fragment in bodyFragments)
|
foreach (var fragment in bodyFragments)
|
||||||
{
|
{
|
||||||
splitFragments.AddRange(SplitBoundaryCrossingAffixFragment(fragment, columnCenters, affixLegendSymbols));
|
splitFragments.AddRange(SplitBoundaryCrossingFragment(fragment, columnCenters, affixLegendSymbols));
|
||||||
}
|
}
|
||||||
|
|
||||||
return splitFragments;
|
return splitFragments;
|
||||||
@@ -467,7 +468,7 @@ internal static class CriticalTableParserSupport
|
|||||||
!excludedFragments.Contains(item))
|
!excludedFragments.Contains(item))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return SplitBoundaryCrossingAffixFragments(bodyFragments, columnCenters, affixLegendSymbols);
|
return SplitBoundaryCrossingFragments(bodyFragments, columnCenters, affixLegendSymbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RepairLeadingAffixLeakage(List<ColumnarCellEntry> cellEntries, IReadOnlySet<string> affixLegendSymbols)
|
internal static void RepairLeadingAffixLeakage(List<ColumnarCellEntry> cellEntries, IReadOnlySet<string> affixLegendSymbols)
|
||||||
@@ -612,17 +613,17 @@ internal static class CriticalTableParserSupport
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<XmlTextFragment> SplitBoundaryCrossingAffixFragment(
|
private static IReadOnlyList<XmlTextFragment> SplitBoundaryCrossingFragment(
|
||||||
XmlTextFragment fragment,
|
XmlTextFragment fragment,
|
||||||
IReadOnlyList<(string Key, double CenterX)> columnCenters,
|
IReadOnlyList<(string Key, double CenterX)> columnCenters,
|
||||||
IReadOnlySet<string> affixLegendSymbols)
|
IReadOnlySet<string> affixLegendSymbols)
|
||||||
{
|
{
|
||||||
if (!LooksLikeBoundaryCrossingAffixFragment(fragment, columnCenters, affixLegendSymbols))
|
if (!TryGetBoundaryCrossingPattern(fragment, columnCenters, affixLegendSymbols, out var splitPattern))
|
||||||
{
|
{
|
||||||
return [fragment];
|
return [fragment];
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches = MultiFragmentSplitRegex.Matches(fragment.Text);
|
var matches = splitPattern.Matches(fragment.Text);
|
||||||
if (matches.Count < 2)
|
if (matches.Count < 2)
|
||||||
{
|
{
|
||||||
return [fragment];
|
return [fragment];
|
||||||
@@ -667,17 +668,40 @@ internal static class CriticalTableParserSupport
|
|||||||
: [fragment];
|
: [fragment];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool LooksLikeBoundaryCrossingAffixFragment(
|
private static bool TryGetBoundaryCrossingPattern(
|
||||||
XmlTextFragment fragment,
|
XmlTextFragment fragment,
|
||||||
IReadOnlyList<(string Key, double CenterX)> columnCenters,
|
IReadOnlyList<(string Key, double CenterX)> columnCenters,
|
||||||
IReadOnlySet<string> affixLegendSymbols)
|
IReadOnlySet<string> affixLegendSymbols,
|
||||||
|
out Regex splitPattern)
|
||||||
{
|
{
|
||||||
if (!IsAffixLikeLine(fragment.Text, affixLegendSymbols) ||
|
splitPattern = null!;
|
||||||
!fragment.Text.Contains(" ", StringComparison.Ordinal))
|
|
||||||
|
if (!CrossesColumnBoundary(fragment, columnCenters))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsAffixLikeLine(fragment.Text, affixLegendSymbols) &&
|
||||||
|
fragment.Text.Contains(" ", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
splitPattern = MultiFragmentSplitRegex;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsAffixLikeLine(fragment.Text, affixLegendSymbols) &&
|
||||||
|
CountSentenceLikeSegments(fragment.Text) >= 2)
|
||||||
|
{
|
||||||
|
splitPattern = SentenceFragmentSplitRegex;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CrossesColumnBoundary(
|
||||||
|
XmlTextFragment fragment,
|
||||||
|
IReadOnlyList<(string Key, double CenterX)> columnCenters)
|
||||||
|
{
|
||||||
var fragmentRight = fragment.Left + fragment.Width;
|
var fragmentRight = fragment.Left + fragment.Width;
|
||||||
|
|
||||||
for (var index = 0; index < columnCenters.Count - 1; index++)
|
for (var index = 0; index < columnCenters.Count - 1; index++)
|
||||||
@@ -692,6 +716,11 @@ internal static class CriticalTableParserSupport
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int CountSentenceLikeSegments(string text) =>
|
||||||
|
SentenceFragmentSplitRegex.Matches(text)
|
||||||
|
.Select(match => CollapseWhitespace(match.Value))
|
||||||
|
.Count(value => !string.IsNullOrWhiteSpace(value));
|
||||||
|
|
||||||
private static void AddLegendMatch(
|
private static void AddLegendMatch(
|
||||||
IDictionary<string, string> symbolEffects,
|
IDictionary<string, string> symbolEffects,
|
||||||
string value,
|
string value,
|
||||||
|
|||||||
Reference in New Issue
Block a user