c462ed4561
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.
209 lines
6.2 KiB
C#
209 lines
6.2 KiB
C#
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);
|
||
}
|
||
} |