Add footnotes support to event occurrence parsing

This commit introduces a new property for capturing footnotes in both the EventOccurrenceParseResult and EventOccurrenceParserResult classes. The EventOccurrenceParser has been updated to handle footnotes, which are identified by lines starting with "*" or as parenthetical notes. The logic for processing these footnotes has been integrated into the parsing flow, ensuring that they are correctly associated with their respective event definitions. Additionally, the EventOccurrenceParserService has been modified to copy footnotes from the parser result, enhancing the overall event parsing functionality.
This commit is contained in:
2026-01-09 10:30:53 -05:00
parent bcd0acb480
commit cf2c0d8068
3 changed files with 49 additions and 17 deletions
@@ -52,6 +52,12 @@ public class EventOccurrenceParseResult
/// </summary>
public int SkippedHSEventCount { get; set; }
/// <summary>
/// Footnotes captured for each event definition. Footnotes are lines that start with "*" or are parenthetical notes.
/// Multiple footnotes are concatenated into a single string. These provide additional context or information about the event occurrences.
/// </summary>
public IDictionary<EventDefinition, string> Footnotes { get; set; } = new Dictionary<EventDefinition, string>();
/// <summary>
/// Total number of event occurrences successfully parsed.
/// </summary>
+37 -17
View File
@@ -18,6 +18,11 @@ public class EventOccurrenceParserResult
public List<string> SkippedMSSectionHeaders { get; set; } = new();
public int SkippedMSEventCount { get; set; }
public int SkippedHSEventCount { get; set; }
/// <summary>
/// Footnotes captured for each event definition. Footnotes are lines that start with "*" or are parenthetical notes.
/// Multiple footnotes are concatenated into a single string.
/// </summary>
public IDictionary<EventDefinition, string> Footnotes { get; set; } = new Dictionary<EventDefinition, string>();
}
public class EventOccurrenceParser
@@ -38,8 +43,9 @@ public class EventOccurrenceParser
var result = new EventOccurrenceParserResult();
var occurrences = result.Occurrences;
var issues = result.Issues;
var footnotes = result.Footnotes;
EventDefinition? currentEventDefinition = null;
bool inContinuationMode = false;
bool inFootnoteMode = false;
SchoolLevel? currentSectionLevel = null;
var lines = File.ReadLines(_txtFile.FullName);
@@ -52,16 +58,16 @@ public class EventOccurrenceParser
// Skip empty lines
if (EventOccurrenceParsers.LineClassifier.IsEmptyLine(normalizedLine))
{
// Empty lines break continuation mode
inContinuationMode = false;
// Empty lines break footnote mode
inFootnoteMode = false;
continue;
}
// Skip comment lines (starting with "#") - use grammar parser
if (EventOccurrenceParsers.LineClassifier.IsCommentLine(normalizedLine))
{
// Comment lines break continuation mode
inContinuationMode = false;
// Comment lines break footnote mode
inFootnoteMode = false;
continue;
}
@@ -76,8 +82,8 @@ public class EventOccurrenceParser
{
var (eventNamePart, schoolLevel) = sectionHeader.Value;
// Section headers break continuation mode
inContinuationMode = false;
// Section headers break footnote mode
inFootnoteMode = false;
// Convert string school level to enum
var sectionSchoolLevel = SetCurrentSectionLevel(schoolLevel);
@@ -115,8 +121,8 @@ public class EventOccurrenceParser
// Check for General Schedule/Session using grammar parser
if (EventOccurrenceParsers.SectionHeaderMatcher.IsGeneralSchedule(normalizedLine))
{
// General schedule breaks continuation mode
inContinuationMode = false;
// General schedule breaks footnote mode
inFootnoteMode = false;
currentSectionLevel = null; // Reset section level
currentEventDefinition = EventDefinition.GeneralSchedule;
continue;
@@ -125,8 +131,8 @@ public class EventOccurrenceParser
// Also check for simple "MS" or "HS" in line (backward compatibility)
if (EventOccurrenceParsers.SectionHeaderMatcher.HasSchoolLevel(normalizedLine))
{
// Section headers break continuation mode
inContinuationMode = false;
// Section headers break footnote mode
inFootnoteMode = false;
// Extract school level from line
SchoolLevel? sectionSchoolLevel = null;
@@ -165,15 +171,29 @@ public class EventOccurrenceParser
continue;
}
// Check if line starts with "*" to enter continuation mode
// Check if line starts with "*" to enter footnote mode
if (normalizedLine.TrimStart().StartsWith("*", StringComparison.Ordinal))
{
inContinuationMode = true;
inFootnoteMode = true;
}
// Skip continuation lines (in continuation mode OR line starts with "*" or is parenthetical)
if (inContinuationMode || EventOccurrenceParsers.LineClassifier.IsContinuationLine(normalizedLine))
// Capture footnote lines (in footnote mode OR line starts with "*" or is parenthetical)
if (inFootnoteMode || EventOccurrenceParsers.LineClassifier.IsContinuationLine(normalizedLine))
{
// Capture footnote for current event definition if we have one
if (currentEventDefinition != null)
{
if (!footnotes.ContainsKey(currentEventDefinition))
{
footnotes[currentEventDefinition] = string.Empty;
}
// Append footnote, adding a space if there's already content
if (!string.IsNullOrEmpty(footnotes[currentEventDefinition]))
{
footnotes[currentEventDefinition] += " ";
}
footnotes[currentEventDefinition] += normalizedLine;
}
continue;
}
@@ -192,8 +212,8 @@ public class EventOccurrenceParser
continue;
}
// Occurrence lines break continuation mode
inContinuationMode = false;
// Occurrence lines break footnote mode
inFootnoteMode = false;
// Skip occurrences under sections that don't match the school level setting
if (ShouldSkipOccurrence(currentSectionLevel, result))
@@ -115,6 +115,12 @@ public class EventOccurrenceParserService : IEventOccurrenceParserService
result.SkippedMSEventCount = parserResult.SkippedMSEventCount;
result.SkippedHSEventCount = parserResult.SkippedHSEventCount;
// Copy footnotes from parser result
foreach (var kvp in parserResult.Footnotes)
{
result.Footnotes[kvp.Key] = kvp.Value;
}
// Add informational messages about skipped events
if (parserResult.SkippedMSEventCount > 0)
{