From cf2c0d80685f1ca37b0846949d9cf02ac3e380dd Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Fri, 9 Jan 2026 10:30:53 -0500 Subject: [PATCH] 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. --- Core/Models/EventOccurrenceParseResult.cs | 6 +++ Core/Parsers/EventOccurrenceParser.cs | 54 +++++++++++++------ Core/Services/EventOccurrenceParserService.cs | 6 +++ 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/Core/Models/EventOccurrenceParseResult.cs b/Core/Models/EventOccurrenceParseResult.cs index 6eb5c1d..f7f4edd 100644 --- a/Core/Models/EventOccurrenceParseResult.cs +++ b/Core/Models/EventOccurrenceParseResult.cs @@ -52,6 +52,12 @@ public class EventOccurrenceParseResult /// public int SkippedHSEventCount { get; set; } + /// + /// 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. + /// + public IDictionary Footnotes { get; set; } = new Dictionary(); + /// /// Total number of event occurrences successfully parsed. /// diff --git a/Core/Parsers/EventOccurrenceParser.cs b/Core/Parsers/EventOccurrenceParser.cs index e10db52..e256f99 100644 --- a/Core/Parsers/EventOccurrenceParser.cs +++ b/Core/Parsers/EventOccurrenceParser.cs @@ -18,6 +18,11 @@ public class EventOccurrenceParserResult public List SkippedMSSectionHeaders { get; set; } = new(); public int SkippedMSEventCount { get; set; } public int SkippedHSEventCount { get; set; } + /// + /// Footnotes captured for each event definition. Footnotes are lines that start with "*" or are parenthetical notes. + /// Multiple footnotes are concatenated into a single string. + /// + public IDictionary Footnotes { get; set; } = new Dictionary(); } 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)) diff --git a/Core/Services/EventOccurrenceParserService.cs b/Core/Services/EventOccurrenceParserService.cs index 12d0532..ced5090 100644 --- a/Core/Services/EventOccurrenceParserService.cs +++ b/Core/Services/EventOccurrenceParserService.cs @@ -114,6 +114,12 @@ public class EventOccurrenceParserService : IEventOccurrenceParserService result.SkippedMSSectionHeaders.AddRange(parserResult.SkippedMSSectionHeaders); 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)