95 lines
4.0 KiB
C#
95 lines
4.0 KiB
C#
using System.Globalization;
|
|
|
|
namespace LehrerApp.WebUntis;
|
|
|
|
public static class WebUntisLessonAbsenceParser
|
|
{
|
|
public static IReadOnlyList<UntisLessonAbsence> Parse(string content)
|
|
{
|
|
var rows = TabSeparatedTextReader.ParseRows(content, '\t');
|
|
if (rows.Count == 0) return [];
|
|
|
|
var headers = rows[0]
|
|
.Select((header, index) => (index == 0 ? header.TrimStart('\uFEFF') : header).Trim())
|
|
.ToArray();
|
|
var result = new List<UntisLessonAbsence>();
|
|
|
|
foreach (var row in rows.Skip(1))
|
|
{
|
|
if (row.All(string.IsNullOrWhiteSpace)) continue;
|
|
|
|
var values = new Dictionary<string, string?>(StringComparer.Ordinal);
|
|
for (var index = 0; index < headers.Length; index++)
|
|
values[headers[index]] = index < row.Count ? row[index].Trim() : null;
|
|
|
|
var (start, end) = ParseTimeRange(Get(values, "Zeit"));
|
|
var (externKey, inParentheses) = ParseExternKey(Get(values, "ENr"));
|
|
result.Add(new UntisLessonAbsence(
|
|
Get(values, "Schüler*innen")?.Trim() ?? "",
|
|
GermanShortDate(Get(values, "Datum")) ?? throw new InvalidDataException(
|
|
"Ungültiges Datum in Spalte \"Datum\"."),
|
|
RequiredInt(Get(values, "Fehlstd."), "Fehlstd."),
|
|
RequiredInt(Get(values, "Unentsch. Fehlstd."), "Unentsch. Fehlstd."),
|
|
RequiredInt(Get(values, "Fehlmin."), "Fehlmin."),
|
|
RequiredInt(Get(values, "Unentsch. Fehlmin."), "Unentsch. Fehlmin."),
|
|
start,
|
|
end,
|
|
Optional(Get(values, "Abwesenheitsgrund")),
|
|
externKey,
|
|
inParentheses,
|
|
Optional(Get(values, "Erledigt")),
|
|
ParseBoolean(Get(values, "Abwesenheit zählt"))));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static (int? Start, int? End) ParseTimeRange(string? value)
|
|
{
|
|
var parts = value?.Split('-', 2);
|
|
return parts is { Length: 2 } ? (ParseClock(parts[0]), ParseClock(parts[1])) : (null, null);
|
|
}
|
|
|
|
private static int? ParseClock(string value) =>
|
|
TimeOnly.TryParseExact(value.Trim(), "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None,
|
|
out var time)
|
|
? time.Hour * 100 + time.Minute
|
|
: null;
|
|
|
|
// WebUntis klammert die externe Schülerkennung in dieser Spalte teils ein, teils nicht - die
|
|
// Klammer selbst ist das Signal (siehe Statuszuordnung im Desktop-Client), nicht nur Formatierung,
|
|
// und wird deshalb hier separat zurückgegeben statt beim Parsen verworfen zu werden.
|
|
private static (int? Key, bool InParentheses) ParseExternKey(string? value)
|
|
{
|
|
var trimmed = value?.Trim();
|
|
if (string.IsNullOrEmpty(trimmed)) return (null, false);
|
|
var inParentheses = trimmed.StartsWith('(') && trimmed.EndsWith(')');
|
|
var digits = trimmed.Trim('(', ')');
|
|
return int.TryParse(digits, NumberStyles.Integer, CultureInfo.InvariantCulture, out var key)
|
|
? (key, inParentheses)
|
|
: (null, false);
|
|
}
|
|
|
|
private static bool ParseBoolean(string? value) =>
|
|
string.Equals(value?.Trim(), "true", StringComparison.OrdinalIgnoreCase);
|
|
|
|
private static string? Get(IReadOnlyDictionary<string, string?> values, string key) =>
|
|
values.TryGetValue(key, out var value) ? value : null;
|
|
|
|
private static string? Optional(string? value) =>
|
|
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
|
|
|
private static int RequiredInt(string? value, string field) =>
|
|
int.TryParse(value?.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed)
|
|
? parsed
|
|
: throw new InvalidDataException($"Ungültige Zahl in Spalte \"{field}\".");
|
|
|
|
private static int? GermanShortDate(string? value)
|
|
{
|
|
if (!DateOnly.TryParseExact(value?.Trim(), "dd.MM.yy", CultureInfo.InvariantCulture,
|
|
DateTimeStyles.None, out var date))
|
|
return null;
|
|
return date.Year * 10_000 + date.Month * 100 + date.Day;
|
|
}
|
|
}
|