Umlaute/Sonderzeichen in Bezeichnern zentral behandeln (#42)
Regex zur Bezeichner-Bereinigung aus Datenschreiber.py in functions/text_wrapper.py (sanitize_bezeichner) ausgelagert und um Bindestrich ergänzt, der bisher fehlte. Neue Tests decken Umlaute, ß, Unterstrich, Bindestrich, eigenes Ersatzzeichen und Leerfälle ab. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MneDjzrjmbpQt8udFYxN9e
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
- Bezeichner-Bereinigung (Layernamen) um Bindestrich ergänzt und in eine zentrale Funktion `sanitize_bezeichner` (`functions/text_wrapper.py`) ausgelagert, damit Umlaute/Sonderzeichen nicht mehr pro Aufrufstelle vergessen werden (#42)
|
||||||
- Landkreis-Dropdown (statt Freitext) in den Projekteigenschaften ergänzt, inkl. Landkreiskennzahl als eigene Projektvariable
|
- Landkreis-Dropdown (statt Freitext) in den Projekteigenschaften ergänzt, inkl. Landkreiskennzahl als eigene Projektvariable
|
||||||
- Landkreisliste liegt jetzt als JSON (sn_basis/data/landkreise.json) statt hartcodiert vor, damit auch andere Module und Nicht-Entwickler sie pflegen können
|
- Landkreisliste liegt jetzt als JSON (sn_basis/data/landkreise.json) statt hartcodiert vor, damit auch andere Module und Nicht-Entwickler sie pflegen können
|
||||||
- Auswahldialog für mehrere BauRaumOderBodenordnungsrecht-Treffer auf Mehrfachauswahl umgestellt (mehrere Objekte gemeinsam laden/verschmelzen)
|
- Auswahldialog für mehrere BauRaumOderBodenordnungsrecht-Treffer auf Mehrfachauswahl umgestellt (mehrere Objekte gemeinsam laden/verschmelzen)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from .ly_metadata_wrapper import (
|
|||||||
from .ly_style_wrapper import apply_style
|
from .ly_style_wrapper import apply_style
|
||||||
from .dialog_wrapper import ask_yes_no, ask_overwrite_append_cancel_custom, ask_ergaenzen_entfernen_ersetzen_abbrechen, ask_detailpruefung_oder_vg_laden
|
from .dialog_wrapper import ask_yes_no, ask_overwrite_append_cancel_custom, ask_ergaenzen_entfernen_ersetzen_abbrechen, ask_detailpruefung_oder_vg_laden
|
||||||
from .laenderkennung import get_laenderkennung, LAENDERKENNUNGEN
|
from .laenderkennung import get_laenderkennung, LAENDERKENNUNGEN
|
||||||
|
from .text_wrapper import sanitize_bezeichner
|
||||||
|
|
||||||
from .message_wrapper import (
|
from .message_wrapper import (
|
||||||
_get_message_bar,
|
_get_message_bar,
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"""
|
||||||
|
sn_basis/functions/text_wrapper.py – Bezeichner-Hilfsfunktionen.
|
||||||
|
|
||||||
|
Zentrale Stelle, um Freitext (z. B. ein Thema oder ein Schriftfeldinhalt) in einen
|
||||||
|
technischen Bezeichner (Layername, Dateiname) umzuwandeln. Nicht pro Aufrufstelle
|
||||||
|
ein eigenes Regex schreiben – Umlaute, Unterstrich und Bindestrich werden hier an
|
||||||
|
einer Stelle gepflegt (siehe docs/wissen/fallstricke.md, Issue Plugin_SN_Basis #42).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Buchstaben, Ziffern, deutsche Umlaute/ß, Unterstrich und Bindestrich gelten immer
|
||||||
|
# als zulässig. Wurde in der Vergangenheit wiederholt vergessen, wenn ein neues
|
||||||
|
# Regex direkt im Aufrufer entstand statt diese Funktion zu nutzen.
|
||||||
|
_BEZEICHNER_ERLAUBT = r"A-Za-z0-9_\-ÄäÖöÜüß"
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_bezeichner(text: str, ersatz: str = "_") -> str:
|
||||||
|
"""Wandelt `text` in einen technischen Bezeichner um.
|
||||||
|
|
||||||
|
Alle Zeichen außerhalb von Buchstaben/Ziffern/Umlauten/ß/Unterstrich/Bindestrich
|
||||||
|
werden durch `ersatz` ersetzt, anschließend werden führende und folgende
|
||||||
|
Vorkommen von `ersatz` entfernt. Gedacht für Layer- und Dateinamen – nicht für
|
||||||
|
Freitext in Schriftfeldern oder der Legende, wo auch andere Zeichen sichtbar
|
||||||
|
bleiben sollen.
|
||||||
|
"""
|
||||||
|
bereinigt = re.sub(rf"[^{_BEZEICHNER_ERLAUBT}]+", ersatz, text)
|
||||||
|
return bereinigt.strip(ersatz)
|
||||||
@@ -30,13 +30,13 @@ from __future__ import annotations
|
|||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import re
|
|
||||||
import datetime
|
import datetime
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
from sn_basis.functions import qgiscore_wrapper as qgiscore
|
from sn_basis.functions import qgiscore_wrapper as qgiscore
|
||||||
from sn_basis.functions.os_wrapper import normalize_path, is_absolute_path
|
from sn_basis.functions.os_wrapper import normalize_path, is_absolute_path
|
||||||
from sn_basis.functions.sys_wrapper import get_plugin_root, join_path, file_exists
|
from sn_basis.functions.sys_wrapper import get_plugin_root, join_path, file_exists
|
||||||
|
from sn_basis.functions.text_wrapper import sanitize_bezeichner
|
||||||
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||||
|
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ class Datenschreiber:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
layer_name_raw = thema or str(ident)
|
layer_name_raw = thema or str(ident)
|
||||||
layer_name = re.sub(r"[^A-Za-z0-9_ÄäÖöÜüß]+", "_", layer_name_raw).strip("_")
|
layer_name = sanitize_bezeichner(layer_name_raw)
|
||||||
if not layer_name:
|
if not layer_name:
|
||||||
layer_name = f"layer_{ident}"
|
layer_name = f"layer_{ident}"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""sn_basis/tests/test_text_wrapper.py
|
||||||
|
|
||||||
|
Unit-Tests für :mod:`sn_basis.functions.text_wrapper`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from sn_basis.functions.text_wrapper import sanitize_bezeichner
|
||||||
|
|
||||||
|
|
||||||
|
class TestSanitizeBezeichner(unittest.TestCase):
|
||||||
|
def test_umlaute_bleiben_erhalten(self):
|
||||||
|
"""Prüft, dass Umlaute und ß nicht durch den Ersatz entfernt werden."""
|
||||||
|
self.assertEqual(sanitize_bezeichner("Grünfläche"), "Grünfläche")
|
||||||
|
self.assertEqual(sanitize_bezeichner("Straße"), "Straße")
|
||||||
|
|
||||||
|
def test_bindestrich_bleibt_erhalten(self):
|
||||||
|
"""Prüft, dass ein Bindestrich im Bezeichner erhalten bleibt (Issue #42)."""
|
||||||
|
self.assertEqual(sanitize_bezeichner("Wege-Plan"), "Wege-Plan")
|
||||||
|
|
||||||
|
def test_unterstrich_bleibt_erhalten(self):
|
||||||
|
"""Prüft, dass ein Unterstrich im Bezeichner erhalten bleibt."""
|
||||||
|
self.assertEqual(sanitize_bezeichner("thema_flur_1"), "thema_flur_1")
|
||||||
|
|
||||||
|
def test_leerzeichen_wird_durch_ersatz_zusammengefasst(self):
|
||||||
|
"""Prüft, dass Leerzeichen (kein zulässiges Zeichen) zu einem Ersatzzeichen werden."""
|
||||||
|
self.assertEqual(sanitize_bezeichner("Flurstücke Gemarkung"), "Flurstücke_Gemarkung")
|
||||||
|
|
||||||
|
def test_fuehrender_und_folgender_ersatz_wird_entfernt(self):
|
||||||
|
"""Prüft, dass ein Ersatzzeichen am Anfang oder Ende des Ergebnisses entfernt wird."""
|
||||||
|
self.assertEqual(sanitize_bezeichner("!Thema!"), "Thema")
|
||||||
|
|
||||||
|
def test_eigenes_ersatzzeichen_wird_verwendet(self):
|
||||||
|
"""Prüft, dass ein abweichendes Ersatzzeichen anstelle von Unterstrich genutzt wird."""
|
||||||
|
self.assertEqual(sanitize_bezeichner("a b", ersatz="-"), "a-b")
|
||||||
|
|
||||||
|
def test_nur_unzulaessige_zeichen_ergibt_leeren_string(self):
|
||||||
|
"""Prüft, dass ein Bezeichner ganz aus unzulässigen Zeichen zu einem leeren String wird."""
|
||||||
|
self.assertEqual(sanitize_bezeichner("!!!"), "")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user