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)