using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; namespace Elwig.Helpers { public static partial class Normalizer { [GeneratedRegex(@"(?=(?:(?:bundes)?straße|gasse|weg|platz|allee|ring|zeile?|steig|hof|markt|kai|ufer|brücke|tor|stiege|promenade|gang|siedlung)\b)|[\s-]+", RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)] private static partial Regex GeneratedStreetNameWordsRegex(); private static readonly Regex StreetNameWordsRegex = GeneratedStreetNameWordsRegex(); private static readonly HashSet StreetTypes = [ "Straße", "Gasse", "Weg", "Platz", "Allee", "Ring", "Zeile", "Zeil", "Steig", "Hof", "Markt", "Kai", "Ufer", "Brücke", "Tor", "Stiege", "Promenade", "Gang", "Siedlung"]; private static readonly HashSet StreetArticles = ["der", "die", "das", "des", "dem", "den"]; private static readonly HashSet StreetPrepositions = [ "an", "am", "auf", "aus", "bei", "beim", "hinter", "hinterm", "in", "im", "ins", "nach", "neben", "über", "unter", "von", "vom", "van", "vor", "zu", "zum", "zur", "zwischen", "durch", "mit"]; private static readonly HashSet StreetNumbers = [ "Zwei", "Drei", "Vier", "Fünf", "Sechs", "Sieben" ]; private static readonly HashSet StreetAdjectives = [ "alter", "alte", "alten", "altem", "altes", "äußere", "äußere", "äußeren", "äußerem", "äußeres", // "breiter", "breite", "breiten", "breitem", "breites", "dritter", "dritte", "dritten", "drittem", "drittes", "erster", "erste", "ersten", "erstem", "erstes", "großer", "große", "großen", "großem", "großes", "hoher", "hohe", "hohen", "hohem", "hohes", "innerer", "innere", "inneren", "innerem", "inneres", "kleiner", "kleine", "kleinen", "kleinem", "kleines", "kurzer", "kurze", "kurzen", "kurzem", "kurzes", "langer", "lange", "langen", "langem", "langes", "linker", "linke", "linken", "linkem", "linkes", "mittlerer", "mittlere", "mittleren", "mittlerem", "mittleres", "neuer", "neue", "neuen", "neuem", "neues", "oberer", "obere", "oberen", "oberem", "oberes", "rechter", "rechte", "rechten", "rechtem", "rechtes", "schmaler", "schmale", "schmalen", "schmalem", "schmales", "schöner", "schöne", "schönen", "schönem", "schönes", "unterer", "untere", "unteren", "unterem", "unteres", "hinterer", "hintere", "hinteren", "hinterem", "hinteres", "vorderer", "vordere", "vorderen", "vorderem", "vorderes", "runder", "runde", "runden", "rundem", "rundes", "kalter", "kalte", "kalten", "kaltem", "kaltes"]; private static readonly HashSet StreetLastNames = [ "Habsburger", "Lothringer", "Leitzenberger", "Luyderer", "Preiner", "Radinger", "Seefelder", "Staribacher", "Gschmeidler", "Hannover", "Münster", "Salzgitter", "Speyer", "Trier"]; private static readonly HashSet StreetPersonTitles = [ "St.", "Familie", "Dr.", "DDr.", "Hofrat", "Baurat", "Kommerzialrat", "Dir.", "Ing.", "Prof.", "Mag.", "DI", "OSR", "Präsident", "Landeshauptmann", "LH", "Bürgermeister", "Bgm.", "Minister", "Kaiser", "Kaiserin", "König", "Königin", "Erzherzog", "Erzherzogin", "Herzog", "Fürst", "Graf", "Gräfin", "Markgraf", "Kronprinz", "Prinz", "Ritter", "Freiherr", "Lord", "Abt", "Bischof", "Propst", "Dechant", "Kardinal", "Monsignore", "Pfarrer", "Pater", "Prälat", "Oberst"]; private static readonly HashSet StreetGivenNames = [ "Adalbert", "Adam", "Adele", "Adelheid", "Adolf", "Adolph", "Adrian", "Albert", "Albrecht", "Alexander", "Alfons", "Alfred", "Alois", "Aloisia", "Andreas", "Andrea", "Andre", "Anna", "Anton", "Arnold", "Arthur", "August", "Augustin", "Augustine", "Agnes", "Angelika", "Ada", "Adelheid", "Anni", "Anita", "Barbara", "Beate", "Beatrix", "Benedikt", "Bernhard", "Berta", "Bertha", "Berthold", "Brüder", "Bruno", "Brigitte", "Camillo", "Carl", "Caspar", "Catharina", "Christian", "Christine", "Christof", "Christoph", "Clara", "Clemens", "Conrad", "Claus", "Camillo", "Claudia", "Daniel", "Dietmar", "Dietrich", "Edgar", "Edi", "Edith", "Edmund", "Egon", "Elsa", "Eduard", "Emmerich", "Erhard", "Erich", "Ernst", "Erwin", "Eugen", "Ewald", "Eberhard", "Elisabeth", "Emanuel", "Emerich", "Emil", "Emma", "Engelbert", "Elias", "Franz", "Ferdinand", "Friedrich", "Felix", "Florian", "Fritz", "Franziska", "Fred", "Georg", "Gregor", "Gustav", "Gabriel", "Gebrüder", "Gabriele", "Gerhard", "Gerhart", "Gertrude", "Gottfried", "Gottlieb", "Grete", "Günther", "Günter", "Hans", "Hanns", "Heinrich", "Heinz", "Helmut", "Helmuth", "Hermann", "Herta", "Herbert", "Hubert", "Harald", "Horst", "Helene", "Helga", "Hieronymus", "Hilde", "Hugo", "Isabelle", "Ignaz", "Ilse", "Ingeborg", "Isidor", "Josef", "Josefine", "Joseph", "Johann", "Johanna", "Johan", "Johannes", "Hannes", "Harry", "Jakob", "Jacob", "Jan", "Julian", "Julius", "Julia", "Jürgen", "Karl", "Kasimir", "Konrad", "Klemens", "Klara", "Katharina", "Kurt", "Kaspar", "Klaus", "Koloman", "Konstantin", "Killian", "Leopold", "Leo", "Lorenz", "Lothar", "Louise", "Ludmilla", "Ludwig", "Lukas", "Ladislaus", "Lambert", "Laurenz", "Liese", "Lise", "Lilly", "Lina", "Lothar", "Lotte", "Luise", "Leonhard", "Lotte", "Martha", "Maria", "Marie", "Michael", "Martin", "Max", "Maximilian", "Manuel", "Manuela", "Moritz", "Marianne", "Manfred", "Marc", "Mathias", "Matthias", "Matthäus", "Margarete", "Margarethe", "Nico", "Niko", "Niklas", "Niclas", "Nikolaus", "Norbert", "Otto", "Ottokar", "Oskar", "Olga", "Oswald", "Othmar", "Paul", "Pauline", "Patrick", "Pepi", "Peter", "Philipp", "Philip", "Petrus", "Robert", "Rudolf", "Richard", "Ruprecht", "Raffael", "Rolf", "Roman", "Rosa", "Rosalia", "Rupert", "Raimund", "Sigmund", "Siegfried", "Simon", "Stanislaus", "Stefan", "Stephan", "Sepp", "Samuel", "Sebastian", "Susi", "Theodor", "Thomas", "Theresia", "Thaddäus", "Theo", "Theophil", "Tina", "Titus", "Toni", "Trude", "Ullrich", "Ulrich", "Udo", "Viktor", "Vinzenz", "Victor", "Wolfgang", "Willibald", "Wilhelm", "Willhelm", "Wilhelmine", "Walter", "Walther", "Wenzel", "Werner", "Willi", "Zacharias"]; private static readonly HashSet LocalityNames = [ "Groß-Enzersdorf", "Groß Burgstall", "Klein Eberharts", "Klein-Engersdorf", "Groß-Enzersdorf", "Klein-Erlauf", "Groß Gerungs", "Klein Gundholz", "Klein-Harras", "Klein-Heinrichschlag", "Groß-Höbarten", "Klein-Jetzelsdorf", "Klein-Mariazell", "Groß Meinharts", "Klein-Meiseldorf", "Groß-Neusiedl", "Klein-Neusiedl", "Klein-Pöchlarn", "Groß-Radischen", "Klein-Radischen", "Groß Raßberg", "Klein Reinprechts", "Klein-Reinprechtsdorf", "Groß-Reipersdorf", "Klein-Ruprechts", "Groß-Schollach", "Klein-Schollach", "Groß-Schweinbarth", "Groß-Siegharts", "Klein-Siegharts", "Groß Sierning", "Klein-Ulrichschlag", "Klein Weinberg", "Klein Wetzles", "Klein-Wien", ]; private static bool IsGivenName(this string word) { return StreetGivenNames.Contains(word); } private static bool IsFamilyName(this string word) { if (StreetLastNames.Contains(word)) return true; return false; } private static bool IsPersonalName(this string word) { return word.IsGivenName() || word.IsFamilyName(); } private static bool IsGeographicalDerivativeName(this string word) { if (!word.EndsWith("er") || word.IsPersonalName() || word == "Weißer") return false; return word.StartsWith("St. ") || word.StartsWith("Ober ") || word.StartsWith("Unter ") || word.StartsWith("Maria") || word.EndsWith("sdorfer") || word.EndsWith("ndorfer") || word.EndsWith("ldorfer") || word.EndsWith("felder") || word.EndsWith("sthaler") || word.EndsWith("nthaler") || word.EndsWith("lthaler") || word.EndsWith("dauer") || word.EndsWith("lauer") || word.EndsWith("mauer") || word.EndsWith("nauer") || word.EndsWith("ßauer") || word.EndsWith("lbacher") || word.EndsWith("nbacher") || word.EndsWith("ßbacher") || word.EndsWith("ringer") || word.EndsWith("singer") || word.EndsWith("sbrunner") || word.EndsWith("nbrunner") || word.EndsWith("lbrunner") || word.EndsWith("abrunner") || word.EndsWith("ingbrunner") || word.EndsWith("sburger") || word.EndsWith("nburger") || word.EndsWith("mburger") || word.EndsWith("lburger") || word.EndsWith("neuburger") || word.EndsWith("berger") || word.EndsWith("winkler") || word.EndsWith("lsteiner") || word.EndsWith("nsteiner") || word.EndsWith("städter") || word.EndsWith("stättner") || word.EndsWith("stettner") || word.EndsWith("brücker") || word.EndsWith("further") || word.EndsWith("furter") || word.EndsWith("aaer") || word.EndsWith("seer") || word.EndsWith("leer") || word.EndsWith("tzner") || word.EndsWith("zeller") || word.EndsWith("rther") || word.EndsWith("sdner") || word.EndsWith("gerner") || word.EndsWith("berner") || word.EndsWith("frieder") || word.EndsWith("litzer") || word.EndsWith("mitzer") || word.EndsWith("eutzer") || word.EndsWith("loner") || word.EndsWith("skirchner") || word.EndsWith("nkirchner") || word.EndsWith("ingkirchner") || word.EndsWith("ziger") || word.EndsWith("ließer") || word.EndsWith("gerer") || word.EndsWith("kreuzer") || word == "Wiener" || word == "Brünner" || word == "Znaimer" || word == "Triester" || word == "Prager" || word == "Berliner" || word == "Basler" || word == "Retzer" || word == "Tullner" || word == "Badner" || word == "Gruber" || word == "Sulzer" || word == "Harraser" || word == "Dörfleser" || word == "Neusiedler" || word == "Dornauer" || word == "Siebenhauser" || word == "Alser" || word == "Bockenheimer"; } public static string NormalizeAddress(string address, string? locality = null) { var idx = address.IndexOf(','); if (idx >= 0) address = address[..idx]; var (name, nr) = Utils.SplitAddress(address); return (NormalizeStreetName(name, locality) + " " + nr).Trim(); } public static string NormalizeStreetName(string name, string? locality = null) { name = name.Trim().ToLowerInvariant(); if (name == "") { return ""; } else if (name == "nr.") { if (locality == null) return "Nr."; name = locality.Trim().ToLowerInvariant(); } name = name.Replace("hautp", "haupt") .Replace("leop.", "leopold ").Replace("ferd.", "ferdinand ").Replace("gottfr.", "gottfried ") .Replace("heinr.", "heinrich").Replace("joh.", "johann ").Replace("jos.", "josef ") .Replace("msgr.", "monsignore ").Replace("pf.", "pfarrer ") .Replace("präs.", "präsident "); name = name.Replace("str.", "straße").Replace("strasse", "straße") .Replace("bruecke", "brücke").Replace("pl.", "platz") .Replace("gr.", "groß").Replace("kl.", "klein") .Replace("sankt-", "st.").Replace("sankt ", "st.") .Replace("wr.", "wiener ") .Replace(".", ". "); List words = [.. StreetNameWordsRegex.Split(name) .Where(w => !string.IsNullOrWhiteSpace(w)) .Select(w => StreetArticles.Contains(w) || StreetPrepositions.Contains(w) ? w : string.Concat(w[0..1].ToUpper(), w.AsSpan(1))) .Select(w => w == "Ddr." ? "DDr." : w == "Lh" || w == "Osr" ? w.ToUpper() : w) ]; if (words[0] == "Di") words[0] = "DI"; var temp = "|" + string.Join('|', words) + "|"; temp = temp.Replace("|Dipl.|Ing.|", "|DI|").Replace("|Dipl|Ing.|", "|DI|") .Replace("|Kaiser|Ebersdorf|", "|Kaiser-Ebersdorf|") .Replace("|Kalk|Deutz|", "|Kalk-Deutz|").Replace("|Kalk|Deutzer|", "|Kalk-Deutzer|") .Replace("|Hohe|Wand|", "|Hohe Wand|").Replace("|Hohen|Wand|", "|Hohen Wand|").Replace("|Hohewand|", "|Hohe Wand|") .Replace("|Alten|Markt|", "|Altenmarkt|") .Replace("|Kirch|Hof|", "|Kirchhof|").Replace("|Bisc|Hof|", "|Bischof|") .Replace("|Wiener|Neustadt|", "|Wiener Neustadt|").Replace("|Wiener|Neustädter|", "|Wiener Neustädter|") .Replace("|St.|Pölten|", "|St. Pölten|").Replace("|St.|Pöltner|", "|St. Pöltner|") .Replace("|Gross|", "|Groß|") .Replace("|Nr|", "|Nr.|").Replace("|Und|", "|und|"); words = [.. temp.Trim('|').Split('|')]; if (words.Count > 1 && (words[0] == "Groß" || words[0] == "Klein") && !StreetTypes.Contains(words[1])) { var w = words[0] + words[1].ToLowerInvariant(); words.RemoveRange(0, 2); words.Insert(0, w); } if (words[0].StartsWith("Groß") || words[0].StartsWith("Klein")) { var repl = LocalityNames.FirstOrDefault(n => n.Replace(" ", "").Replace("-", "").Equals(words[0], StringComparison.InvariantCultureIgnoreCase)); if (repl != null) words[0] = repl; } if (words.Count > 2 && words[^2] == "in") { // remove suffix "in ..." words.RemoveRange(words.Count - 2, 2); } char sep = name.Contains('-') ? '-' : ' '; if (words.Count <= 1) { // single word } else if (StreetTypes.Contains(words[0]) && !StreetTypes.Contains(words[^1])) { // "Straße der ..." / "Straße des ..." sep = ' '; } else if (StreetTypes.Contains(words[^1])) { // "... Straße" / "...straße" var type = words[^1]; var isCompound = true; var isAdjective = StreetAdjectives.Contains(words[^2].ToLowerInvariant()); var isGeographical = false; var isPerson = false; var geo = words.FindLastIndex(IsGeographicalDerivativeName); if (geo >= 0 && geo < words.Count - 2 && words[^2] != "Haupt" && words[^2] != "Bundes" && words[^2] != "Keller") { isPerson = true; isGeographical = true; } else if (words.Any(w => w.Contains(' ') && !w.IsGeographicalDerivativeName())) { isPerson = true; isGeographical = true; } else if (StreetNumbers.Contains(words[0])) { isPerson = true; isGeographical = true; } else if (!StreetPrepositions.Contains(words[0]) && !StreetArticles.Contains(words[0]) && words.FindIndex(StreetPrepositions.Contains) > 0) { isPerson = true; isGeographical = true; } if (words.Count(w => w.Contains('.')) >= 2 || words.Contains("van") || words.Contains("von") || words.Contains("St.") || StreetPersonTitles.Contains(words[0]) || words.Any(w => w.IsGivenName())) { isPerson = true; } if (!isPerson && isAdjective || StreetArticles.Contains(words[^2]) || StreetPrepositions.Contains(words[^2])) { isCompound = false; } if (!isPerson && words[^2].EndsWith("er") && !StreetLastNames.Contains(words[^2])) { isGeographical = IsGeographicalDerivativeName(words[^2]); } else if (words.Count >= 3 && words.Contains("Bad")) { isPerson = true; isGeographical = true; } sep = isPerson ? '-' : ' '; if (isPerson && words.Count == 2 && !isGeographical) { isPerson = false; isCompound = true; } if (isGeographical || isPerson) { isCompound = false; } if (isCompound) { var w = words[^2] + type.ToLowerInvariant(); words.RemoveRange(words.Count - 2, 2); words.Add(w); } if (isPerson) { words = [.. words.Select(w => w.Replace(' ', '-'))]; } } else { // unknown } if (sep == '-' && StreetPrepositions.Contains(words[0]) && words[0] != "von" && words[0] != "van") { sep = ' '; int i; for (i = 0; StreetPrepositions.Contains(words[i]) && i < words.Count; i++); var word = string.Join('-', words.Skip(i)); words = [.. words.Take(i).Append(word)]; } var norm = string.Join(sep, words); return string.Concat(norm[0..1].ToUpperInvariant(), norm.AsSpan(1)); } } }