Files
chapter-organizer/Core/Parsers/EventOccurrenceParser.cs
T
poprhythm c462ed4561 Enhance event definitions and parsing logic for new event types
Added new event definitions for "Meet the Candidates", "Chapter Officer Meeting", "Voting Delegate Meeting", and "Social Gathering". Updated the EventOccurrenceParser to handle these new event types and modified related services and views to accommodate the changes. Improved test coverage for the new event definitions and ensured proper parsing and display in the calendar components.
2025-12-27 19:32:54 -05:00

209 lines
6.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text.RegularExpressions;
using Core.Entities;
using FuzzySharp;
namespace Core.Parsers;
public class EventOccurrenceParser
{
private FileSystemInfo _txtFile;
private ICollection<EventDefinition> _events;
public EventOccurrenceParser(FileSystemInfo txtFile, ICollection<EventDefinition> events)
{
_events = events;
_txtFile = txtFile;
}
private Regex _re =
new (
@"" + //
@"(?<Name>^[^#].*)\s" +
@"(?<Month>February|March|April|May|June|July)\s" +
@"(?<DayOfMonth>\d{1,2});?\s" +
@"(?<TimeAndLocation>.*)"
);
private readonly Regex _timeRe = new(@"(?<Hour>\d{1,2}):?(?<Minute>\d{2})?\s?(?<APM>(?:a|p)\.?m\.?)");
private readonly Regex _timeLocationRegex = new(@"(?<Time>.*(?>[AaPp]\.?[Mm]\.?))(?<Location>[\s\t].*)?");
public IDictionary<EventDefinition, List<EventOccurrence>> Parse()
{
var occurrences = new Dictionary<EventDefinition, List<EventOccurrence>>();
EventDefinition? currentEventDefinition = null;
var lines = File.ReadLines(_txtFile.FullName);
foreach (var line in lines)
{
var match = _re.Match(line);
if (!match.Success)
{
if (line.Contains("MS"))
{
var evt =
(from e in _events
let rat = Fuzz.Ratio(e.Name, line.Trim())
where rat > 50
orderby rat descending
select e).FirstOrDefault();
if (evt == null)
continue;
currentEventDefinition = evt;
continue;
}
if (line == "General Schedule" || line == "General Session")
{
currentEventDefinition = EventDefinition.GeneralSchedule;
continue;
}
// "Voting Delegates" section header is no longer used - occurrences are categorized by name pattern
// Continue without setting currentEventDefinition for this section
continue;
}
var occurrenceName = match.Groups["Name"].Captures[0].Value;
var month = match.Groups["Month"].Captures[0].Value;
var dayOfMonth = match.Groups["DayOfMonth"].Captures[0].Value;
var timeAndLocation = match.Groups["TimeAndLocation"].Captures[0].Value;
occurrenceName = Regex.Replace(occurrenceName,
@"(?<Weekday>Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\s?$", "").Trim();
// Determine event definition based on occurrence name pattern or current section
EventDefinition? eventDefinition = DetermineEventDefinition(occurrenceName, currentEventDefinition);
// Skip if we can't determine the event definition
if (eventDefinition == null)
continue;
timeAndLocation = SanitizeInput(timeAndLocation);
var timeAndLocationMatch = _timeLocationRegex.Match(timeAndLocation);
var time = timeAndLocation;
var location = string.Empty;
if (timeAndLocationMatch.Success)
{
time= timeAndLocationMatch.Groups["Time"].Captures[0].Value;
if (timeAndLocationMatch.Groups["Location"].Success)
location = timeAndLocationMatch.Groups["Location"].Captures[0].Value;
}
var startDate = ParseDate(month, dayOfMonth, DateTime.Now.Year);
var startTime = ParseStartTime(time);
var t = new DateTime(startDate, startTime);
var eventOccurrence = new EventOccurrence
{
Name = occurrenceName, StartTime = t, Time = $"{time}", Date = $"{month} {dayOfMonth}",
Location = location
};
if (!occurrences.ContainsKey(eventDefinition))
occurrences.Add(eventDefinition, []);
occurrences[eventDefinition].Add(eventOccurrence);
}
return occurrences;
}
/// <summary>
/// Determines the EventDefinition for an occurrence based on its name pattern or current section context.
/// </summary>
private EventDefinition? DetermineEventDefinition(string occurrenceName, EventDefinition? currentEventDefinition)
{
// Check for special event name patterns first (regardless of current section)
if (occurrenceName.Contains("Meet the Candidates Session", StringComparison.OrdinalIgnoreCase))
return EventDefinition.MeetTheCandidates;
if (occurrenceName.Contains("Chapter Officer Meeting", StringComparison.OrdinalIgnoreCase))
return EventDefinition.ChapterOfficerMeeting;
if (occurrenceName.Contains("Voting Delegate Meeting", StringComparison.OrdinalIgnoreCase))
return EventDefinition.VotingDelegateMeeting;
// If we're in a General Schedule/Session section and no pattern matched, use GeneralSchedule
if (currentEventDefinition == EventDefinition.GeneralSchedule)
return EventDefinition.GeneralSchedule;
// If we have a current event definition from section header (e.g., regular events), use it
if (currentEventDefinition != null)
return currentEventDefinition;
// Cannot determine event definition
return null;
}
private string SanitizeInput(string input)
{
input = input.Replace("", "-");
input = input.Replace("—", "-");
return input;
}
private DateOnly ParseDate(string month, string dayOfMonth, int year)
{
int monthNum = 1;
switch (month)
{
case "February":
monthNum = 2;
break;
case "March":
monthNum = 3;
break;
case "April":
monthNum = 4;
break;
case "May":
monthNum = 5;
break;
case "June":
monthNum = 6;
break;
case "July":
monthNum = 7;
break;
}
var day = int.Parse(dayOfMonth);
return new DateOnly(year, monthNum, day); ;
}
private TimeOnly ParseStartTime(string time)
{
int hour = 0;
int minute = 0;
// get the part of the time before a timespan
if (time.Contains(" - "))
{
time = time[..time.IndexOf(" - ", StringComparison.Ordinal)];
}
if (time == "NOON")
hour = 12;
else
{
var timeMatch = _timeRe.Match(time.ToLower());
if (timeMatch.Success)
{
hour = int.Parse(timeMatch.Groups["Hour"].Captures[0].Value);
if (timeMatch.Groups["Minute"].Success)
{
minute = int.Parse(timeMatch.Groups["Minute"].Captures[0].Value);
}
if (timeMatch.Groups["APM"].Captures[0].Value is "p.m." or "pm" && hour < 12)
hour += 12;
}
}
return new TimeOnly(hour, minute, 0);
}
}