using System.Globalization; namespace LehrerApp.WebUntis; public static class WebUntisStudentReportParser { public static IReadOnlyList 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(); foreach (var row in rows.Skip(1)) { if (row.All(string.IsNullOrWhiteSpace)) continue; var values = new Dictionary(StringComparer.Ordinal); for (var index = 0; index < headers.Length; index++) values[headers[index]] = index < row.Count ? row[index].Trim() : null; var untisId = RequiredInt(Get(values, "id"), "id"); var externalKey = OptionalInt(Get(values, "externKey")); var name = Optional(Get(values, "name")); var lastName = Optional(Get(values, "longName")); var firstName = Optional(Get(values, "foreName")); var displayName = string.Join(' ', new[] { firstName, lastName }.Where(value => value is not null)); if (string.IsNullOrWhiteSpace(displayName)) displayName = name ?? $"Schüler {untisId}"; result.Add(new UntisStudent( untisId, externalKey, Get(values, "klasse.name")?.Trim() ?? "", name, lastName, firstName, displayName, Optional(Get(values, "gender")), GermanDate(Get(values, "birthDate")), Optional(Get(values, "birthDate")), GermanDate(Get(values, "entryDate")), Optional(Get(values, "entryDate")), GermanDate(Get(values, "exitDate")), Optional(Get(values, "exitDate")), Optional(Get(values, "text")), Optional(Get(values, "medicalReportDuty")), Optional(Get(values, "schulpflicht")), Optional(Get(values, "majority")), new UntisStudentAddress( Optional(Get(values, "adress.email")), Optional(Get(values, "adress.mobile")), Optional(Get(values, "adress.phone")), Optional(Get(values, "adress.city")), Optional(Get(values, "adress.postCode")), Optional(Get(values, "adress.street"))), Optional(Get(values, "attribute.iL")))); } return result; } private static string? Get(IReadOnlyDictionary 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? OptionalInt(string? value) => int.TryParse(value?.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed) ? parsed : null; private static int? GermanDate(string? value) { if (!DateOnly.TryParseExact(value?.Trim(), ["dd.MM.yyyy", "yyyyMMdd"], CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) return null; return date.Year * 10_000 + date.Month * 100 + date.Day; } }