Enhance event occurrence parsing to skip unmatched high school section headers

This commit introduces a new property to track skipped high school section headers in the EventOccurrenceParseResult and EventOccurrenceParserResult classes. The EventOccurrenceParser has been updated to gracefully skip HS section headers that do not match any event definitions, improving the parsing logic. Additionally, the LocationParsingConfiguration has been removed from the EventOccurrenceParser, simplifying its constructor. Unit tests have been updated to reflect these changes and ensure correct behavior during parsing.
This commit is contained in:
2026-01-09 00:14:19 -05:00
parent f916cfad6b
commit 19e5ef0675
10 changed files with 279 additions and 372 deletions
@@ -1,11 +1,10 @@
using System.Text.RegularExpressions;
using Core.Models;
namespace Core.Parsers.EventOccurrence;
/// <summary>
/// Parses time and location from combined strings.
/// Handles time ranges, location extraction, and pattern matching.
/// Extracts time using regex, then uses everything after the time as the location.
/// </summary>
public static class TimeLocationParser
{
@@ -28,21 +27,18 @@ public static class TimeLocationParser
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Parses time and location from the timeAndLocation string using configurable location patterns.
/// Parses time and location from the timeAndLocation string.
/// Extracts time using regex, then uses everything after the time as the location (after cleaning time fragments).
/// </summary>
/// <param name="timeAndLocation">The combined time and location string.</param>
/// <param name="locationConfig">The location parsing configuration with patterns.</param>
/// <param name="time">Output parameter: the parsed time string.</param>
/// <param name="location">Output parameter: the parsed location string.</param>
/// <param name="locationParseSuccess">Output parameter: whether location parsing was successful.</param>
public static void Parse(
string timeAndLocation,
LocationParsingConfiguration? locationConfig,
out string time,
out string location,
out bool locationParseSuccess)
out string location)
{
// Try to separate time from location using the time regex
// Extract time using regex
var timeLocationMatch = TimeLocationRegex.Match(timeAndLocation);
if (!timeLocationMatch.Success)
@@ -50,7 +46,6 @@ public static class TimeLocationParser
// If time regex doesn't match, use the whole string as time
time = timeAndLocation.Trim();
location = string.Empty;
locationParseSuccess = false;
return;
}
@@ -63,61 +58,12 @@ public static class TimeLocationParser
if (string.IsNullOrWhiteSpace(locationPart))
{
location = string.Empty;
locationParseSuccess = true; // Consider it a success since no location is needed
return;
}
// Clean up location part - remove any remaining time components
// Clean location of any remaining time fragments
// (e.g., " 12:15 p.m. Exhibit Hall C" -> "Exhibit Hall C")
locationPart = CleanLocationText(locationPart);
if (string.IsNullOrWhiteSpace(locationPart))
{
location = string.Empty;
locationParseSuccess = true; // No location after cleaning is also valid
return;
}
// Try to match location using configurable patterns
(location, locationParseSuccess) = TryMatchLocation(locationPart, locationConfig);
// If no pattern matched but we have a location, use it anyway
// This allows parsing to continue while still tracking that the location didn't match a pattern
if (!locationParseSuccess)
{
location = locationPart;
}
}
/// <summary>
/// Attempts to match a location string against configured patterns.
/// </summary>
private static (string location, bool success) TryMatchLocation(
string locationPart,
LocationParsingConfiguration? locationConfig)
{
// No patterns configured - can't match
if (locationConfig == null || !locationConfig.LocationPatterns.Any())
{
return (string.Empty, false);
}
// Try initial match
var location = LocationPatternMatcher.Match(locationPart, locationConfig.LocationPatterns);
if (!string.IsNullOrEmpty(location))
{
return (location, true);
}
// Try matching against trimmed version (handles extra whitespace)
var cleanedForMatching = locationPart.Trim();
location = LocationPatternMatcher.Match(cleanedForMatching, locationConfig.LocationPatterns);
if (!string.IsNullOrEmpty(location))
{
return (cleanedForMatching, true);
}
return (string.Empty, false);
location = CleanLocationText(locationPart);
}
/// <summary>