using System; using System.Collections.Generic; using System.Linq; using System.Windows.Controls; using Elwig.Models; namespace Elwig.Helpers { public static class Validator { private static readonly Dictionary PHONE_NRS = new() { { "43", new string[][] { Array.Empty(), new string[] { "57", "59" }, new string[] { "50", "517", "718", "804", "720", "780", "800", "802", "810", "820", "821", "828", "900", "901", "930", "931", "939", "650", "651", "652", "653", "655", "657", "659", "660", "661", "663", "664", "665", "666", "667", "668", "669", "67", "68", "69" } } }, { "49", Array.Empty() }, { "48", Array.Empty() }, { "420", Array.Empty() }, { "421", Array.Empty() }, { "36", Array.Empty() }, { "386", Array.Empty() }, { "39", Array.Empty() }, { "33", Array.Empty() }, { "41", Array.Empty() }, { "423", Array.Empty() }, }; public static ValidationResult CheckInteger(TextBox input, bool required) { return CheckInteger(input, required, -1); } public static ValidationResult CheckInteger(TextBox input, bool required, int maxLen) { string text = ""; int pos = input.CaretIndex; for (int i = 0; i < input.Text.Length; i++) { char ch = input.Text[i]; if (char.IsAsciiDigit(ch)) text += ch; if (i == input.CaretIndex - 1) pos = text.Length; } input.Text = text; input.CaretIndex = pos; if (text.Length == 0) { return required ? new(false, "Wert ist nicht optional") : new(true, null); } if (maxLen >= 0 && input.Text.Length > maxLen) { input.Text = input.Text[..maxLen]; input.CaretIndex = Math.Min(pos, maxLen); } return new(true, null); } public static ValidationResult CheckPlz(TextBox input, bool required, AppDbContext ctx) { CheckInteger(input, false, 4); if (!required && input.Text.Length == 0) { return new(true, null); } else if (input.Text.Length != 4) { return new(false, "PLZ zu kurz"); } int plz = int.Parse(input.Text); if (ctx.Postleitzahlen.Find(plz) == null) { return new(false, "Ungültige PLZ"); } return new(true, null); } public static ValidationResult CheckPhoneNumber(TextBox input, bool required) { string text = ""; int pos = input.CaretIndex; for (int i = 0, v = 0; i < input.Text.Length && v < 15; i++) { char ch = input.Text[i]; if (v == 0 && input.Text.Length - i >= 2 && ch == '0' && input.Text[i + 1] == '0') { v++; i++; text += '+'; } else if (ch == '(' && input.Text.Length - i >= 3 && input.Text[i + 1] == '0' && input.Text[i + 2] == ')') { i += 2; } else if (v == 0 && ch == '0') { v += 3; text += "+43"; } else if (v == 0 && ch == '+') { v++; text += ch; } else if (v > 0 && char.IsAsciiDigit(ch)) { if (PHONE_NRS.Any(kv => text == "+" + kv.Key)) text += ' '; if (text.StartsWith("+43 ")) { var nr = text[4..]; var vws = PHONE_NRS["43"]; if (v >= 4 && v - 4 < vws.Length && vws[v - 4].Any(vw => nr.StartsWith(vw))) text += ' '; else if (nr == "1") text += ' '; else if (v == 7 && nr.Length == 4) text += ' '; } v++; text += ch; } if (i == input.CaretIndex - 1) pos = text.Length; } input.Text = text; input.CaretIndex = pos; if (!required && text.Length == 0) { return new(true, null); } else if (text.Length < 10) { return new(false, "Telefonnummer zu kurz"); } return new(true, null); } public static ValidationResult CheckEmailAddress(TextBox input, bool required) { string text = ""; int pos = input.CaretIndex; bool domain = false; for (int i = 0; i < input.Text.Length && text.Length < 256; i++) { char ch = input.Text[i]; if (domain) { if ((char.IsAsciiLetterOrDigit(ch)) || ".-_öäüßÖÄÜẞ".Any(c => c == ch)) { if (!(text.Last() == '.' && ch == '.')) text += char.ToLower(ch); } } else { if (ch == '@') domain = true; if (!char.IsControl(ch) && !char.IsWhiteSpace(ch)) text += ch; } if (i == input.CaretIndex - 1) pos = text.Length; } input.Text = text; input.CaretIndex = pos; if (text.Length == 0) { return required ? new(false, "E-Mail-Adresse ist nicht optional") : new(true, null); } else if (text[0] == '@' || !domain) { return new(false, "E-Mail-Adresse ungültig"); } var last = text.Split(".").Last(); if (last.Length < 2 || !last.All(ch => char.IsAsciiLetterLower(ch))) return new(false, "E-Mail-Adresse ungültig"); return new(true, null); } public static ValidationResult CheckIban(TextBox input, bool required) { string text = ""; int pos = input.CaretIndex; int v = 0; for (int i = 0; i < input.Text.Length && v < 34; i++) { char ch = input.Text[i]; if (char.IsAsciiLetterOrDigit(ch)) { if (((v < 2 && char.IsAsciiLetter(ch)) || (v >= 2 && v < 4 && char.IsAsciiDigit(ch)) || v >= 4) && ((!text.StartsWith("AT") && !text.StartsWith("DE")) || char.IsAsciiDigit(ch))) { if (v != 0 && v % 4 == 0) text += ' '; v++; text += char.ToUpper(ch); } } if (i == input.CaretIndex - 1) pos = text.Length; if (text.StartsWith("AT") && v >= 20) break; else if (text.StartsWith("DE") && v >= 22) break; } input.Text = text; input.CaretIndex = pos; if (!required && text.Length == 0) { return new(true, null); } else if (v < 5 || (text.StartsWith("AT") && v != 20) || (text.StartsWith("DE") && v != 22)) { return new(false, "IBAN hat falsche Länge"); } var validation = (text[4..] + text[..4]).Replace(" ", "") .Select(ch => char.IsAsciiDigit(ch) ? ch.ToString() : (ch - 'A' + 10).ToString()) .Aggregate((a, b) => a + b); if (Utils.Modulo(validation, 97) != 1) return new(false, "Prüfsumme der IBAN ist falsch"); return new(true, null); } public static ValidationResult CheckBic(TextBox input, bool required) { string text = ""; int pos = input.CaretIndex; for (int i = 0, v = 0; i < input.Text.Length; i++) { char ch = input.Text[i]; if ((v < 4 || v >= 6) && char.IsAsciiLetterOrDigit(ch)) { v++; text += char.ToUpper(ch); } else if (v >= 4 && v < 6 && char.IsAsciiLetter(ch)) { v++; text += char.ToUpper(ch); } if (i == input.CaretIndex - 1) pos = text.Length; } if (text.Length == 0) { return required ? new(false, "BIC ist nicht optional") : new(true, null); } if (text.Length > 11) { text = text[..11]; pos = Math.Min(pos, 11); } if (text.Length == 11 && text.EndsWith("XXX")) text = text[..8]; input.Text = text; input.CaretIndex = pos; if (text.Length != 11 && text.Length != 8) return new(false, "BIC ist ungültig"); return new(true, null); } public static ValidationResult CheckLfbisNr(TextBox input, bool required) { var res = CheckInteger(input, false, 7); if (!res.IsValid) { return res; } else if (!required && input.Text.Length == 0) { return new(true, null); } else if (input.Text.Length != 7) { return new(false, "Betriebsnummer zu kurz"); } // https://statistik.at/fileadmin/shared/QM/Standarddokumentationen/RW/std_r_land-forstw_register.pdf#page=41 int s = 0, v; for (int i = 0; i < 6; i++) s += (input.Text[i] - '0') * (7 - i); v = (11 - (s % 11)) % 10; if (v != (input.Text[6] - '0')) return new(false, "Prüfsumme der Betriebsnummer ist falsch"); return new(true, null); } public static ValidationResult CheckUstId(TextBox input, bool required) { string text = ""; int pos = input.CaretIndex; for (int i = 0, v = 0; i < input.Text.Length; i++) { char ch = input.Text[i]; if (v < 2 && char.IsAsciiLetter(ch)) { v++; text += char.ToUpper(ch); } else if (v >= 2 && char.IsAsciiLetterOrDigit(ch)) { if (text.StartsWith("AT")) { if (v == 2 && (ch == 'u' || ch == 'U')) { v++; text += 'U'; } else if (v > 2 && char.IsAsciiDigit(ch)) { v++; text += ch; } } else { v++; text += char.ToUpper(ch); } } if (i == input.CaretIndex - 1) { pos = text.Length; } else if (v >= 14) { break; } else if (text.StartsWith("AT") && v >= 11) { break; } } input.Text = text; input.CaretIndex = pos; if (text.Length == 0) return required ? new(false, "UID ist nicht optional") : new(true, null); if (text.StartsWith("AT")) { if (text.Length != 11 || text[2] != 'U') return new(false, "UID ist ungültig"); // http://www.pruefziffernberechnung.de/U/USt-IdNr.shtml int s = 0, v = 0; for (int i = 0; i < 7; i++) s += ((text[i + 3] - '0') * (i % 2 + 1)).ToString().Select(ch => ch - '0').Sum(); v = (96 - s) % 10; if (v != (text[10] - '0')) return new(false, "Prüfsumme der UID ist falsch"); } else { return new(false, "Not implemented yet"); } return new(true, null); } public static ValidationResult CheckMgNr(TextBox input, bool required, AppDbContext ctx, Member? m) { var res = CheckInteger(input, required); if (!res.IsValid) { return res; } else if (!required && input.Text.Length == 0) { return new(true, null); } int nr = int.Parse(input.Text); if (nr != m?.MgNr && ctx.MgNrExists(nr).GetAwaiter().GetResult()) { return new(false, "Mitgliedsnummer wird bereits verwendet"); } return new(true, null); } public static ValidationResult CheckPredecessorMgNr(TextBox input, bool required, AppDbContext ctx) { var res = CheckInteger(input, required); if (!res.IsValid) { return res; } else if (!required && input.Text.Length == 0) { return new(true, null); } else if (!ctx.MgNrExists(int.Parse(input.Text)).GetAwaiter().GetResult()) { return new(false, "Ein Mitglied mit dieser Mitgliedsnummer existiert nicht"); } return new(true, null); } public static ValidationResult CheckDate(TextBox input, bool required) { return CheckDate(input, required, false); } public static ValidationResult CheckPartialDate(TextBox input, bool required) { return CheckDate(input, required, true); } private static ValidationResult CheckDate(TextBox input, bool required, bool partial) { string text = ""; int pos = input.CaretIndex; int p = 0; var parts = new string?[3]; parts[0] = ""; for (int i = 0; i < input.Text.Length; i++) { char ch = input.Text[i]; if (ch == '.') { if (p < 2 && (parts[p]?.Length > 0 || pos == text.Length)) { if (parts[p]?.Length == 1 && ((pos != text.Length && pos != text.Length - 1) || !input.IsFocused)) { parts[p] = "0" + parts[p]; text = text[..(text.Length - 1)] + "0" + text[^1]; } parts[++p] = ""; text += '.'; } } else if (char.IsAsciiDigit(ch)) { if ((partial && parts[p]?.Length < 4) || (!partial && ((p < 2 && parts[p]?.Length < 2) || (p == 2 && parts[2]?.Length < 4)))) { parts[p] += ch; text += ch; } if (p == 0 && pos == 1 && input.Text.Length >= 4 && input.Text.Length <= 9) { parts[++p] = ""; text += '.'; continue; // skip caret update } } if (i == input.CaretIndex - 1) { pos = text.Length; } } input.Text = text; input.CaretIndex = pos; if (text.Length == 0) return required ? new(false, "Datum ist nicht optional") : new(true, null); if (partial) { if (p == 0) { // only year provided return (parts[0]?.Length == 4) ? new(true, null) : new(false, "Datum ist ungültig"); } else if (p == 1) { // only month and year provided int m = parts[0] != null && parts[0] != "" ? int.Parse(parts[0] ?? "0") : 0; if (parts[1]?.Length != 4 || parts[0]?.Length != 2 || m < 1 || m > 12) return new(false, "Datum ist ungültig"); return new(true, null); } } if (!DateOnly.TryParseExact(text, "dd.MM.yyyy", out _)) return new(false, "Datum ist ungültig"); return new(true, null); } public static ValidationResult CheckFbNr(TextBox input, bool required, AppDbContext ctx, AreaCom? c) { var res = CheckInteger(input, required); if (!res.IsValid) { return res; } else if (!required && input.Text.Length == 0) { return new(true, null); } int nr = int.Parse(input.Text); if (nr != c?.FbNr && ctx.FbNrExists(nr).GetAwaiter().GetResult()) { return new(false, "Flächenbindungsnummer wird bereits verwendet"); } return new(true, null); } public static ValidationResult CheckGstNr(TextBox input, bool required) { // TODO return new(true, "Not implemented yet"); } } }