using System.Text; namespace LehrerApp.Core.Importing; /// Erkennt den Notenlistenexport und erklärt dessen fehlende Teilnehmerdaten. public sealed class MarksPerLessonCsvImportHandler : IImportHandler { private static readonly string[] SignatureHeaders = ["Datum", "Name", "Klasse", "Fach", "Prüfungsart", "Note", "Bemerkung", "Benutzer", "Schlüssel (extern)", "Gesamtnote"]; private static readonly UTF8Encoding StrictUtf8 = new(false, true); public ImportFormatId FormatId => StudentImportFormats.MarksPerLessonCsv; public string DisplayName => "Unterrichts-Notenliste"; public IReadOnlyCollection SupportedExtensions { get; } = [".csv"]; public ValueTask DetectAsync(ImportFile file, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); string text; try { text = StrictUtf8.GetString(file.Content.Span); } catch (DecoderFallbackException) { return ValueTask.FromResult(ImportDetection.NoMatch()); } var end = text.IndexOfAny(['\r', '\n']); var firstLine = (end >= 0 ? text[..end] : text).TrimStart('\uFEFF'); var headers = firstLine.Split('\t').Select(x => x.Trim()).ToHashSet(StringComparer.OrdinalIgnoreCase); return ValueTask.FromResult(SignatureHeaders.All(headers.Contains) ? ImportDetection.Match(file.Extension.Equals(".csv", StringComparison.OrdinalIgnoreCase) ? 100 : 95) : ImportDetection.NoMatch()); } public ValueTask> ParseAsync(ImportFile file, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); IReadOnlyList messages = [ new(ImportMessageSeverity.Error, "marks-per-lesson.no-participants", "Die Notenliste enthält keine verlässlichen Teilnehmerdaten. Bitte exportiere die echte Kurs-/Teilnehmerliste.") ]; return ValueTask.FromResult(new ImportParseResult([], messages)); } }