Files
LehrerApp/LehrerApp.WebUntis/WebUntisStudentReportParser.cs
T
adminandClaude Sonnet 5 056e864edd fix: WebUntis-Schülerreport verkraftet fehlenden externKey
Klassenlehrer-Übersicht scheiterte komplett mit "Ungültige Zahl in Spalte
externKey", sobald ein Schüler der Klasse keinen gepflegten externen
Schlüssel hat (in echten Schuldaten vorkommend). externKey ist jetzt
optional statt Pflichtfeld (UntisStudent/UntisStudentDto.ExternKey -> int?);
betraf denselben Bericht wie der bestehende WebUntis-Klassenimport, der das
aber schon immer beim Weiterimport toleriert hatte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 22:47:04 +02:00

89 lines
3.7 KiB
C#

using System.Globalization;
namespace LehrerApp.WebUntis;
public static class WebUntisStudentReportParser
{
public static IReadOnlyList<UntisStudent> 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<UntisStudent>();
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 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<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? 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;
}
}