Compare commits

...
Author SHA1 Message Date
DanielandClaude Sonnet 5 272bad3866 Weitere fehlende Umlaute in Docstrings/Kommentaren behoben (#42)
Gefunden ueber das neue Skript scripts/pruefe_umlaute.py, das genau dieses
Muster (ASCII-Ersatzschreibweise statt Umlaut in String-Inhalten und
Kommentaren mit Leerzeichen) erkennt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MneDjzrjmbpQt8udFYxN9e
2026-09-14 12:21:01 +02:00
DanielandClaude Sonnet 5 72844cd257 Fehlende Umlaute in Drucklayout-Hinweisen und -Beschriftungen behoben (#42)
Weitere Fundstellen aus der gezielten Umlaut-Pruefung: MessageBox-/Label-Texte
in print_logic.py, print_layout.py, print_layout_baufreigabe.py und
print_tab.py, die wortwoertlich als ASCII generiert wurden ('gueltig',
'verfuegbar', 'waehlen', 'Massstab' als Anzeigetext, 'Geprueft: '-Label).
Interne Bezeichner (aktion-Codes, Projektvariablen-Keys, Item-IDs) bewusst
unveraendert gelassen, um bestehende Log-/Vergleichslogik nicht zu brechen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MneDjzrjmbpQt8udFYxN9e
2026-09-14 12:09:30 +02:00
DanielandClaude Sonnet 5 a754fcfe34 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
2026-09-14 11:09:41 +02:00
22ottomi 7755e3e8cb Merge pull request 'Fix: DataGrabber lädt ohne sn_plan41 (ModuleNotFoundError beim Laden von sn_verfahrensgebiet)' (#57) from bugfix/35-sn-plan41-dependency into unstable
Release Plugin / release (push) Successful in 4s
Reviewed-on: #57
2026-08-31 09:53:48 +02:00
Michael Otto bc9bb20597 Fix: DataGrabber lädt ohne sn_plan41 (ModuleNotFoundError beim Laden von sn_verfahrensgebiet)
DataGrabber.py hatte einen harten Top-Level-Import von
sn_plan41.modules.listenauswerter.Listenauswerter, obwohl DataGrabber sich
selbst als generischer, plugin-übergreifender Orchestrator dokumentiert.
sn_verfahrensgebiet importiert DataGrabber und instanziiert ihn beim
Plugin-Start – dadurch schlug classFactory() fehl, sobald sn_plan41 nicht
installiert war (ModuleNotFoundError: No module named 'sn_plan41').

Import in die einzige Stelle verschoben, die ihn tatsächlich braucht
(_process_excel_source, nur für Excel-Quellen relevant), mit klarer
Fehlermeldung falls doch mal ohne sn_plan41 eine Excel-Quelle verarbeitet
werden soll.

Fixes sn_verfahrensgebiet#35
2026-08-31 09:52:48 +02:00
10 changed files with 150 additions and 66 deletions
+1
View File
@@ -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
- 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)
+1
View File
@@ -22,6 +22,7 @@ from .ly_metadata_wrapper import (
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 .laenderkennung import get_laenderkennung, LAENDERKENNUNGEN
from .text_wrapper import sanitize_bezeichner
from .message_wrapper import (
_get_message_bar,
+28
View File
@@ -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)
+13 -2
View File
@@ -30,7 +30,6 @@ from sn_basis.modules.linkpruefer import Linkpruefer
from sn_basis.modules.layerpruefer import Layerpruefer
from sn_basis.modules.stilpruefer import Stilpruefer
from sn_basis.modules.excel_importer import ExcelImporter
from sn_plan41.modules.listenauswerter import Listenauswerter
SourceType = Literal["service", "database", "excel", "unknown"]
@@ -286,7 +285,19 @@ class DataGrabber:
}
source_dict["rows"].append(result_row)
# Validierung über Listenauswerter
# Validierung über Listenauswerter Import erst hier (statt am Modul-
# anfang), da DataGrabber generisch für alle sn_*-Plugins nutzbar sein
# soll und nicht bei jedem Aufruf zwingend sn_plan41 installiert ist;
# nur die Excel-Quellenverarbeitung braucht Listenauswerter.
try:
from sn_plan41.modules.listenauswerter import Listenauswerter
except ImportError as exc:
raise ImportError(
"Die Verarbeitung von Excel-Quellen benötigt das Plugin "
"sn_plan41 (sn_plan41.modules.listenauswerter.Listenauswerter), "
"das hier nicht installiert ist."
) from exc
listenauswerter = Listenauswerter(self.pruefmanager, self.stil_pruefer or Stilpruefer())
validated, validation_results = listenauswerter.validate_rows(source_dict)
results.extend(validation_results)
+2 -2
View File
@@ -30,13 +30,13 @@ from __future__ import annotations
from typing import Any, Dict, List, Optional
import os
import json
import re
import datetime
import sqlite3
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.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
@@ -209,7 +209,7 @@ class Datenschreiber:
continue
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:
layer_name = f"layer_{ident}"
+2 -2
View File
@@ -45,7 +45,7 @@ MM = QgsUnitTypes.LayoutMillimeters
class PrintLayout:
"""Erzeugt ein QGIS-Einzelblatt-Layout fuer den Druck."""
"""Erzeugt ein QGIS-Einzelblatt-Layout für den Druck."""
_FRAME_STROKE_WIDTH_MM = 0.5
_LEGEND_WIDTH_MM = 180.0
@@ -340,7 +340,7 @@ class PrintLayout:
("[% @sn_landkreise_proj %]", 16.0, -48.0, 171.0, 4.0, "Format_1", "Landkreis"),
("[% @sn_kartenname %]", 16.0, -32.0, 137.0, 12.0, "Format_3", "Kartenname"),
("Gefertigt: ", 16.0, -23.0, 81.0, 4.0, "Format_1", "Gefertigt"),
("Geprueft: ", 101.0, -23.0, 81.0, 4.0, "Format_1", "Geprüft"),
("Geprüft: ", 101.0, -23.0, 81.0, 4.0, "Format_1", "Geprüft"),
(
"Maßstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]",
16.0,
+16 -16
View File
@@ -1,9 +1,9 @@
"""
sn_basis/modules/print_layout_baufreigabe.py - Layout-Erstellung fuer Baufreigabe-Vorlagen.
sn_basis/modules/print_layout_baufreigabe.py - Layout-Erstellung für Baufreigabe-Vorlagen.
Baufreigabe-Layouts verwenden immer feste DIN A3/A4 Seitengroessen im Querformat,
ein 120 mm breites Kartenschild (statt 180 mm) und erbenerben die Standard-Logik
fuer Hauptkarte, Quellenangabe und Legende vom PrintLayout.
Baufreigabe-Layouts verwenden immer feste DIN A3/A4 Seitengrößen im Querformat,
ein 120 mm breites Kartenschild (statt 180 mm) und erben die Standard-Logik
für Hauptkarte, Quellenangabe und Legende vom PrintLayout.
Atlas-Integration erfolgt nach Layouterstellung durch externe Konfiguration
in der PrintLogic.
@@ -32,7 +32,7 @@ MM = QgsUnitTypes.LayoutMillimeters
class PrintLayoutBaufreigabe(PrintLayout):
"""Erzeugt QGIS-Einzelblatt-Layouts mit fester DIN A3/A4 Querformat-Groesse und 80mm Kartenschild."""
"""Erzeugt QGIS-Einzelblatt-Layouts mit fester DIN A3/A4 Querformat-Größe und 80mm Kartenschild."""
_FRAME_STROKE_WIDTH_MM = 0.5
_KARTENSCHILD_WIDTH_MM = 80.0
@@ -41,7 +41,7 @@ class PrintLayoutBaufreigabe(PrintLayout):
_GRID_INTERVAL_MAP_UNITS = 500.0
# DIN-Formate: nur A3 und A4 im Querformat
_DIN_A3_LANDSCAPE = (420, 297) # Breite x Hoehe (Querformat)
_DIN_A3_LANDSCAPE = (420, 297) # Breite x Höhe (Querformat)
_DIN_A4_LANDSCAPE = (297, 210) # Querformat
_TEXT_FORMAT_TEMPLATES = {
@@ -99,25 +99,25 @@ class PrintLayoutBaufreigabe(PrintLayout):
fit_extent_to_map: bool = True,
atlas_driven: bool = True,
) -> Any:
"""Erstellt ein Baufreigabe-Layout mit fester DIN A3/A4 Querformat-Groesse.
"""Erstellt ein Baufreigabe-Layout mit fester DIN A3/A4 Querformat-Größe.
Args:
name: Name des Layouts
din_format: "DIN A3" oder "DIN A4"
map_width_mm: Breite der Hauptkarte
map_height_mm: Hoehe der Hauptkarte
map_height_mm: Höhe der Hauptkarte
extent: QgsRectangle der Kartenausdehnung
plotmassstab: Plotmassstab (z.B. 5000 fuer 1:5000)
plotmassstab: Plotmaßstab (z.B. 5000 für 1:5000)
thema: Name des Visibility-Presets (optional)
fit_extent_to_map: Extent bei Bedarf auf das Karten-Seitenverhaeltnis erweitern
fit_extent_to_map: Extent bei Bedarf auf das Karten-Seitenverhältnis erweitern
Returns:
QgsPrintLayout objektInstance (wird im Layout-Dialog geoeffnet)
QgsPrintLayout objektInstance (wird im Layout-Dialog geöffnet)
Raises:
ValueError: Falls Name bereits existiert oder Format ungueltig ist
ValueError: Falls Name bereits existiert oder Format ungültig ist
"""
# Seitenformat auswaehlen (nur A3/A4 im Querformat)
# Seitenformat auswählen (nur A3/A4 im Querformat)
if din_format == "DIN A3":
page_width_mm, page_height_mm = self._DIN_A3_LANDSCAPE
elif din_format == "DIN A4":
@@ -266,9 +266,9 @@ class PrintLayoutBaufreigabe(PrintLayout):
map_top_mm: float,
map_height_mm: float,
) -> None:
"""Erstellt Legende, Quellenangabe und Kartenschild fuer Baufreigabe-Layout.
"""Erstellt Legende, Quellenangabe und Kartenschild für Baufreigabe-Layout.
Angepasst fuer 80mm Kartenschild statt 180mm.
Angepasst für 80mm Kartenschild statt 180mm.
"""
if self._current_layout is None:
raise RuntimeError("Kein Layout aktiv.")
@@ -279,7 +279,7 @@ class PrintLayoutBaufreigabe(PrintLayout):
kartenfenster_untere_rechte_ecke_mm[1] + rel_y_mm,
)
# Legende: 80mm Breite, hoehe adaptive wie Standard
# Legende: 80mm Breite, he adaptiv wie Standard
legende_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0
legende_height_mm = max(map_height_mm - 122.0, 20.0)
legende_y = map_top_mm
+33 -33
View File
@@ -1,5 +1,5 @@
"""
sn_basis/modules/print_logic.py - Fachlogik fuer den Druck-Tab (nur Einzelblatt).
sn_basis/modules/print_logic.py - Fachlogik für den Druck-Tab (nur Einzelblatt).
"""
from __future__ import annotations
@@ -16,7 +16,7 @@ from sn_basis.modules.print_layout_baufreigabe import PrintLayoutBaufreigabe
class AtlasConfig(TypedDict, total=False):
"""Strukturierte Konfiguration fuer Atlas-Optionen.
"""Strukturierte Konfiguration für Atlas-Optionen.
Alle Felder sind optional (total=False) und haben vordefinierte Standartwerte.
"""
@@ -113,7 +113,7 @@ STANDARD_A4_LANDSCAPE_H_MM = 210.0
class PrintLogic:
"""Kapselt die Fachlogik fuer den Druck-Tab (ohne Atlas)."""
"""Kapselt die Fachlogik für den Druck-Tab (ohne Atlas)."""
def __init__(self, pruefmanager: Pruefmanager) -> None:
self.pruefmanager = pruefmanager
@@ -125,8 +125,8 @@ class PrintLogic:
def _show_target_size_too_small(self) -> dict:
self.pruefmanager.zeige_hinweis(
"Zielgroesse zu klein",
"Die gewaehlte Zielgroesse reicht fuer den Einzelblatt-Druck nicht aus.",
"Zielgröße zu klein",
"Die gewählte Zielgröße reicht für den Einzelblatt-Druck nicht aus.",
)
return self._failed_result()
@@ -348,13 +348,13 @@ class PrintLogic:
if int(cast(Any, selected_count())) == 0:
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
"Auf dem Layer sind keine Features ausgewaehlt.",
"Auf dem Layer sind keine Features ausgewählt.",
)
return None, ""
except Exception:
pass
# Bestehenden Atlaslayer loeschen (optional Wiederverwedung)
# Bestehenden Atlaslayer löschen (optional Wiederverwendung)
existing_atlas = self._get_existing_layer_by_name("Atlaslayer")
if existing_atlas is not None:
try:
@@ -367,7 +367,7 @@ class PrintLogic:
if qgs_vector_layer is None:
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
"QgsVectorLayer ist nicht verfuegbar.",
"QgsVectorLayer ist nicht verfügbar.",
)
return None, ""
@@ -392,7 +392,7 @@ class PrintLogic:
)
return None, ""
# Ausgewaehlte Features kopieren
# Ausgewählte Features kopieren
try:
get_selected = getattr(source_layer, "selectedFeatures", None)
if not callable(get_selected):
@@ -400,15 +400,15 @@ class PrintLogic:
selected_features = list(cast(Any, get_selected()))
if not selected_features:
raise Exception("Keine Features ausgewaehlt")
raise Exception("Keine Features ausgewählt")
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
if qgs_feature is None:
raise Exception("QgsFeature Symbol nicht verfuegbar")
raise Exception("QgsFeature Symbol nicht verfügbar")
data_provider = getattr(atlas_layer, "dataProvider", lambda: None)()
if data_provider is None:
raise Exception("dataProvider nicht verfuegbar")
raise Exception("dataProvider nicht verfügbar")
new_features = []
for source_feature in selected_features:
@@ -470,7 +470,7 @@ class PrintLogic:
layout_name: str = "",
atlas_config: AtlasConfig | None = None,
) -> bool:
"""Konfiguriert den Atlas fuer ein Baufreigabe-Layout mit erweiterten Optionen.
"""Konfiguriert den Atlas für ein Baufreigabe-Layout mit erweiterten Optionen.
Wird nach erfolgreicher Baufreigabe-Layout-Erstellung aufgerufen.
Setzt den Coverage-Layer (aus sn_abdeckungslayer Variable) und konfiguriert den Atlas
@@ -483,7 +483,7 @@ class PrintLogic:
project = QgsProject.instance()
layout_manager = getattr(project, "layoutManager", lambda: None)()
if layout_manager is None:
self.pruefmanager.zeige_hinweis("Atlas konfigurieren", "Layout Manager nicht verfuegbar.")
self.pruefmanager.zeige_hinweis("Atlas konfigurieren", "Layout Manager nicht verfügbar.")
return False
if not layout_name:
@@ -672,7 +672,7 @@ class PrintLogic:
if map_canvas is None:
self.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Das Kartenfenster ist nicht verfuegbar.",
"Das Kartenfenster ist nicht verfügbar.",
)
return False
@@ -684,7 +684,7 @@ class PrintLogic:
if None in (qgs_geometry, qgs_rectangle, qgs_wkb_types, qgs_map_tool_emit_point, qgs_rubber_band):
self.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Die Rechteck-Erfassung ist in dieser QGIS-Umgebung nicht verfuegbar.",
"Die Rechteck-Erfassung ist in dieser QGIS-Umgebung nicht verfügbar.",
)
return False
@@ -736,13 +736,13 @@ class PrintLogic:
if width <= 0 or height <= 0:
logic.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Das gezeichnete Rechteck ist ungueltig.",
"Das gezeichnete Rechteck ist ungültig.",
)
else:
if logic._store_druckrechteck(layer, rectangle):
logic.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Das Druckrechteck wurde uebernommen.",
"Das Druckrechteck wurde übernommen.",
)
try:
self.rubber_band.reset(polygon_geometry)
@@ -920,7 +920,7 @@ class PrintLogic:
if extent is None:
self.pruefmanager.zeige_hinweis(
"Druckbereich",
"Das aktuelle Kartenfenster liefert keine gueltige Ausdehnung.",
"Das aktuelle Kartenfenster liefert keine gültige Ausdehnung.",
)
return None, None
return extent, None
@@ -931,7 +931,7 @@ class PrintLogic:
if extent is None:
self.pruefmanager.zeige_hinweis(
"Druckbereich",
"Auf dem aktuellen Layer sind keine gueltigen Objekte ausgewaehlt.",
"Auf dem aktuellen Layer sind keine gültigen Objekte ausgewählt.",
)
return None, None
return self._buffer_extent(extent, plotmassstab), None
@@ -948,7 +948,7 @@ class PrintLogic:
if not ergebnis.ok:
self.pruefmanager.zeige_hinweis(
"Verfahrensgebiets-Layer angeben",
"Kein gueltiger Verfahrensgebiets-Layer gefunden. Bitte Layerauswahl im jeweiligen Plugin setzen.",
"Kein gültiger Verfahrensgebiets-Layer gefunden. Bitte Layerauswahl im jeweiligen Plugin setzen.",
)
return None, None
@@ -1001,12 +1001,12 @@ class PrintLogic:
required_map_height_mm: float,
max_format: str = "DIN A3",
) -> tuple[str, float, float, float, float, bool]:
"""Waehlt fuer Baufreigabe ein Blatt im Querformat.
"""Wählt für Baufreigabe ein Blatt im Querformat.
Args:
required_map_width_mm: benoetigte Breite der Hauptkarte in mm
required_map_height_mm: benoetigte Hoehe der Hauptkarte in mm
max_format: maximale Blattgroesse, standardmaessig DIN A3
required_map_width_mm: benötigte Breite der Hauptkarte in mm
required_map_height_mm: benötigte Höhe der Hauptkarte in mm
max_format: maximale Blattgröße, standardmäßig DIN A3
Returns:
(din_format, page_width_mm, page_height_mm, map_w_mm, map_h_mm, fallback_applied)
@@ -1029,8 +1029,8 @@ class PrintLogic:
if map_w >= required_map_width_mm and map_h >= required_map_height_mm:
return din_format, page_w, page_h, map_w, map_h, False
# Wenn der gewuenschte Ausschnitt nicht passt, verwende das maximal erlaubte Blatt
# und erweitere die Hauptkarte auf die verfuegbare Kartenflaeche (zentriert per fit_extent_to_map).
# Wenn der gewünschte Ausschnitt nicht passt, verwende das maximal erlaubte Blatt
# und erweitere die Hauptkarte auf die verfügbare Kartenfläche (zentriert per fit_extent_to_map).
din_format, page_w, page_h = limited_candidates[-1]
map_w = max(page_w - BAUFREIGABE_LAYOUT_FIXBREITE_MM, 50.0)
map_h = max(page_h - BAUFREIGABE_LAYOUT_FIXHOEHE_MM, 50.0)
@@ -1059,7 +1059,7 @@ class PrintLogic:
return map_width_mm + fixed_width_mm, map_height_mm + fixed_height_mm
def _get_required_map_dimensions_mm(self, extent: Any, plotmassstab: float) -> tuple[float, float]:
"""Berechnet die benoetigte Kartenfenstergroesse in mm fuer Extent und Massstab."""
"""Berechnet die benötigte Kartenfenstergröße in mm für Extent und Maßstab."""
if extent is None or not isinstance(plotmassstab, (int, float)) or plotmassstab <= 0:
return 0.0, 0.0
@@ -1077,7 +1077,7 @@ class PrintLogic:
required_map_height_mm: float,
max_format: str,
) -> tuple[float, float, float, float] | None:
"""Waehlt das kleinste passende DIN-Blatt fuer das Standardlayout."""
"""Wählt das kleinste passende DIN-Blatt für das Standardlayout."""
try:
max_index = DIN_AUTO_REIHENFOLGE.index(max_format)
except ValueError:
@@ -1170,7 +1170,7 @@ class PrintLogic:
resolved_layer = layer or self._resolve_layer_from_project()
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41, KARTENNAME_BAUFREIGABE):
self.pruefmanager.zeige_hinweis("Kartennamen waehlen", "Kartennamen waehlen")
self.pruefmanager.zeige_hinweis("Kartennamen wählen", "Kartennamen wählen")
return self._failed_result()
if massstab_auswahl == MASSSTAB_WIE_KARTENFENSTER:
@@ -1191,7 +1191,7 @@ class PrintLogic:
massstab_zahl = float(PLOTMASSSTAB_BY_AUSWAHL.get(massstab_auswahl, 0))
if massstab_zahl <= 0:
self.pruefmanager.zeige_hinweis("Massstab fehlt", "Kein gueltiger Massstab angegeben.")
self.pruefmanager.zeige_hinweis("Maßstab fehlt", "Kein gültiger Maßstab angegeben.")
return self._failed_result()
extent, layer_for_extent = self._resolve_extent(resolved_layer, druckbereich_auswahl, massstab_zahl)
@@ -1262,7 +1262,7 @@ class PrintLogic:
vorlage_name = (vorlage_name or "").strip() or kartenname
# Atlas-Status vorab bestimmen, damit er sowohl an create_ als auch configure_ uebergeben wird.
# Atlas-Status vorab bestimmen, damit er sowohl an create_ als auch configure_ übergeben wird.
atlas_raw_bf = get_variable(ATLAS_ERZEUGEN_UI_VAR, scope="project")
atlas_erzeugen_bf = self._is_enabled_flag(atlas_raw_bf)
@@ -1283,7 +1283,7 @@ class PrintLogic:
atlas_driven=atlas_erzeugen_bf,
)
else:
# Standard-Layout: dynamische Groessenanpassung
# Standard-Layout: dynamische Größenanpassung
PrintLayout().create_single_page_layout(
name=vorlage_name,
page_width_mm=auto_page_w,
+43
View File
@@ -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()
+11 -11
View File
@@ -1,5 +1,5 @@
"""
sn_basis/ui/tabs/print_tab.py - Zentraler Druck-Tab fuer sn-Plugins.
sn_basis/ui/tabs/print_tab.py - Zentraler Druck-Tab für sn-Plugins.
"""
from __future__ import annotations
@@ -41,7 +41,7 @@ from sn_basis.modules.print_logic import (
KARTENNAME_VAR = "tab_b_kartenname"
KARTENNAME_PLACEHOLDER = "Kartenname waehlen"
KARTENNAME_PLACEHOLDER = "Kartenname wählen"
KARTENNAME_38 = "§38"
KARTENNAME_41 = "§41"
KARTENNAME_BAUFREIGABE = "Anlage zur Baufreigabe"
@@ -59,7 +59,7 @@ DRUCKBEREICH_OPTIONEN = [
class PrintTab(QWidget):
"""UI-Klasse fuer den zentralen Druck-Tab."""
"""UI-Klasse für den zentralen Druck-Tab."""
tab_title = "Druck"
@@ -130,7 +130,7 @@ class PrintTab(QWidget):
self._kartenname_combo = kartenname_combo
main_layout.addWidget(kartenname_combo)
massstab_label = QLabel("Massstab")
massstab_label = QLabel("Maßstab")
massstab_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(massstab_label)
@@ -152,7 +152,7 @@ class PrintTab(QWidget):
self._thema_combo = thema_combo
main_layout.addWidget(thema_combo)
zielgroesse_label = QLabel("max. Blattgroesse")
zielgroesse_label = QLabel("max. Blattgröße")
zielgroesse_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(zielgroesse_label)
@@ -610,7 +610,7 @@ class PrintTab(QWidget):
return None
def _on_atlas_layer_changed(self, layer_or_name: Any) -> None:
"""Speichert den ausgewaehlten Atlas-Layer als Layer-ID in Projektvariablen."""
"""Speichert den ausgewählten Atlas-Layer als Layer-ID in Projektvariablen."""
if layer_or_name is None or layer_or_name == "":
set_variable("sn_abdeckungslayer", "", scope="project")
return
@@ -639,10 +639,10 @@ class PrintTab(QWidget):
pass
def _on_auswahl_as_atlas_clicked(self) -> None:
"""Handler fuer Button 'Auswahl als Atlas anlegen'.
"""Handler für Button 'Auswahl als Atlas anlegen'.
Erstellt einen neuen Layer 'Atlaslayer' aus der aktuellen Feature-Auswahl
und traegt ihn ins Atlas-Dropdown ein.
und trägt ihn ins Atlas-Dropdown ein.
"""
if not self.logic or not self.pruefmanager:
return
@@ -673,11 +673,11 @@ class PrintTab(QWidget):
except Exception:
pass
# Fallback nur fuer Mock/klassische ComboBox
# Fallback nur für Mock/klassische ComboBox
self._populate_atlas_layer_combo()
def _populate_atlas_layer_combo(self) -> None:
"""Fallback-Befuellung, falls kein echtes QgsMapLayerComboBox Verhalten verfuegbar ist."""
"""Fallback-Befüllung, falls kein echtes QgsMapLayerComboBox Verhalten verfügbar ist."""
if not self._atlas_layer_combo:
return