Dateien nach "csv_processor" hochladen

This commit is contained in:
sebastian.zell 2025-10-19 14:47:35 +00:00
commit a4c5ed3308
5 changed files with 3770 additions and 0 deletions

891
csv_processor/anleitung.txt Normal file
View File

@ -0,0 +1,891 @@
# CSV-Processor v2.0 - Benutzerhandbuch
## Inhaltsverzeichnis
1. [Überblick](#überblick)
2. [Installation](#installation)
3. [Programmversionen](#programmversionen)
4. [CLI-Version: Schritt-für-Schritt](#cli-version)
5. [GUI-Version: Schritt-für-Schritt](#gui-version)
6. [Voreinstellungen nutzen](#voreinstellungen-nutzen)
7. [Mapping-Dateien erstellen](#mapping-dateien-erstellen)
8. [Häufige Anwendungsfälle](#häufige-anwendungsfälle)
9. [Tipps & Tricks](#tipps--tricks)
10. [Fehlerbehebung](#fehlerbehebung)
---
## Überblick
Der CSV-Processor ist ein professionelles Werkzeug zur Verarbeitung und Konvertierung von CSV- und Excel-Dateien. Er ermöglicht:
✅ **Import** von CSV, Excel (XLSX/XLS)
✅ **Export** als CSV, Excel (XLSX), OpenDocument (ODS)
✅ **Spaltennamen-Mapping** (z.B. "btr" → "Betrag")
✅ **Datenbereinigung** (leere Zeilen/Spalten entfernen)
✅ **Spaltenauswahl** (nur gewünschte Spalten exportieren)
✅ **Sortierung** nach beliebigen Spalten
✅ **Voreinstellungen** für wiederkehrende Aufgaben
---
## Installation
Das Programm wurde bereits auf Ihrem System installiert. Falls nicht, wenden Sie sich an die IT.
### Programme starten:
**CLI-Version (Terminal):**
```bash
csv-processor-cli
```
**GUI-Version (Grafisch):**
- Im Anwendungsmenü unter "Office" → "CSV-Processor"
- Oder im Terminal: `csv-processor-gui`
---
## Programmversionen
### CLI-Version (Terminal)
- **Für wen:** Fortgeschrittene Benutzer, Automatisierung
- **Vorteile:** Schnell, scriptfähig, volle Kontrolle
- **Start:** `csv-processor-cli`
### GUI-Version (Grafisch)
- **Für wen:** Alle Benutzer, besonders Einsteiger
- **Vorteile:** Intuitiv, visuelle Vorschau, einfache Bedienung
- **Start:** `csv-processor-gui` oder über Anwendungsmenü
**Empfehlung:** Für tägliche Arbeit die GUI-Version, für wiederkehrende Aufgaben mit Voreinstellungen beide geeignet.
---
## CLI-Version: Schritt-für-Schritt
### Programmstart
```bash
csv-processor-cli
```
Sie sehen das Hauptmenü:
```
╔═══════════════════════════════════════════════════════╗
║ CSV-PROCESSOR v2.0 - Dateiverarbeitung ║
╚═══════════════════════════════════════════════════════╝
Verfügbare Features:
Import: CSV, Excel (XLSX/XLS)
Export: CSV, Excel (XLSX), OpenDocument (ODS)
══════════════════════════════════════════════════════════
HAUPTMENÜ
══════════════════════════════════════════════════════════
1. Neue Datei verarbeiten
2. Voreinstellungen verwalten
0. Beenden
Ihre Wahl:
```
### Erste Verwendung (ohne Voreinstellung)
#### Schritt 1: Datei verarbeiten wählen
```
Ihre Wahl: 1
```
#### Schritt 2: Keine Voreinstellung
```
Möchten Sie eine Voreinstellung laden? [j/n]: n
```
#### Schritt 3: Quelldatei angeben
```
Pfad zur Quelldatei (CSV/XLSX/XLS/ODS): /home/benutzer/Downloads/daten.csv
```
**Bei CSV:** Sie sehen eine interaktive Konfiguration:
```
══════════════════════════════════════════════════════════
CSV-IMPORT KONFIGURATION
══════════════════════════════════════════════════════════
Datei: daten.csv
Erkanntes Encoding: utf-8
Erkannter Delimiter: Semikolon (;)
Erkanntes Quoting: MINIMAL
──────────────────────────────────────────────────────────
VORSCHAU DER ERSTEN ZEILEN:
──────────────────────────────────────────────────────────
Zeile 1:
Rohtext: Name;Betrag;Datum;Kategorie...
Geparst: 4 Felder
Feld 1: 'Name'
Feld 2: 'Betrag'
...
──────────────────────────────────────────────────────────
AKTUELLE EINSTELLUNGEN:
──────────────────────────────────────────────────────────
1. Encoding: utf-8
2. Delimiter: Semikolon (;)
3. Quoting: MINIMAL
0. ✓ Diese Einstellungen verwenden
──────────────────────────────────────────────────────────
Was möchten Sie ändern? [0-3]: 0
```
**Tipp:** Prüfen Sie die Vorschau! Wenn die Felder korrekt getrennt sind, drücken Sie `0`.
#### Schritt 4: Kopfzeile bestätigen
```
Hat die CSV-Datei eine Kopfzeile mit Spaltennamen? [j/n]: j
```
#### Schritt 5: Mapping anwenden (optional)
```
Möchten Sie Spaltennamen umbenennen? [j/n]: j
Pfad zur Mapping-JSON-Datei: /usr/share/csv-processor/mietermerkmale.json
✓ 5 Spaltennamen umbenannt
```
#### Schritt 6: Leere Zeilen
```
Sollen komplett leere Zeilen entfernt werden? [j/n]: j
✓ 3 leere Zeilen entfernt
```
#### Schritt 7: Unvollständige Zeilen (optional)
```
Möchten Sie Zeilen mit zu wenig Informationen analysieren/entfernen? [j/n]: n
```
#### Schritt 8: Leere Spalten
```
Sollen leere Spalten entfernt werden? [j/n]: j
✓ 2 leere Spalten entfernt
```
**Wichtig:** Leere Spalten werden VOR der Spaltenauswahl entfernt!
#### Schritt 9: Spaltenauswahl
Sie sehen eine Liste aller verfügbaren (nicht-leeren) Spalten:
```
══════════════════════════════════════════════════════════
SPALTENAUSWAHL
══════════════════════════════════════════════════════════
● = Angewählt | ○ = Abgewählt
──────────────────────────────────────────────────────────
● 1. btr → Betrag
● 2. dat → Datum
● 3. kto → Kontonummer
● 4. empf → Empfänger
● 5. verw → Verwendungszweck
──────────────────────────────────────────────────────────
Aktuell ausgewählt: 5 von 5 Spalten
══════════════════════════════════════════════════════════
Optionen:
[Nummern] - Spalten an/abwählen (z.B. '1,2,3' oder '1-5,10-15')
+ [Nummern] - Spalten ANwählen (z.B. '+1,2,3')
- [Nummern] - Spalten ABwählen (z.B. '-1,2,3')
alle - Alle Spalten anwählen
keine - Alle Spalten abwählen
q - Auswahl beenden und fortfahren
Ihre Wahl:
```
**Beispiele:**
- `3` - Spalte 3 umschalten (an→ab oder ab→an)
- `-3,5` - Spalten 3 und 5 ABwählen (rot)
- `+1-10` - Spalten 1 bis 10 ANwählen (grün)
- `1-5,10-15` - Spalten 1-5 und 10-15 umschalten
- `q` - Fertig
```
Ihre Wahl: -3
✓ 1 Spalte(n) abgewählt
Ihre Wahl: q
```
#### Schritt 10: Kopf-/Fußzeile (optional)
```
Möchten Sie eine Kopf- oder Fußzeile hinzufügen? [j/n]: j
Kopfzeile (Enter für keine): Export vom 19.10.2025
Fußzeile (Enter für keine): Erstellt mit CSV-Processor
```
#### Schritt 11: Sortierung (optional)
```
Möchten Sie die Daten sortieren? [j/n]: j
Verfügbare Spalten zum Sortieren:
1. Betrag
2. Datum
3. Empfänger
4. Verwendungszweck
Nummer der Spalte: 2
Datentyp für Sortierung:
1. Text (string)
2. Datum
3. Zeit
4. Dezimalzahl
Ihre Wahl [1-4]: 2
✓ Daten nach 'Datum' sortiert
```
#### Schritt 12: Ausgabeformat wählen
```
Verfügbare Ausgabeformate:
1. CSV
2. Excel (XLSX)
3. OpenDocument (ODS)
Ihre Wahl: 1
```
**Bei CSV-Export:**
```
CSV-EXPORT KONFIGURATION
Delimiter wählen:
1. Semikolon (;) - Standard für deutsche Excel-Versionen
2. Komma (,) - Internationaler Standard
3. Tab - Gut für Import in andere Programme
Ihre Wahl [1-3]: 1
Quoting (Anführungszeichen) wählen:
1. MINIMAL - Nur Felder mit Sonderzeichen (empfohlen)
2. ALLE - Alle Felder in Anführungszeichen
3. NICHT_NUMERISCH - Nur Text-Felder
4. KEINE - Keine Anführungszeichen
Ihre Wahl [1-4]: 1
```
#### Schritt 13: Zieldatei speichern
```
Name/Pfad der Zieldatei (ohne Endung): ausgabe
✓ CSV-Datei erfolgreich gespeichert: ausgabe.csv
```
#### Schritt 14: Als Voreinstellung speichern
```
Möchten Sie diese Einstellungen als Voreinstellung speichern? [j/n]: j
Name für die Voreinstellung: meine_standard_export
✓ Voreinstellung 'meine_standard_export' gespeichert.
```
**Fertig!** Sie sind zurück im Hauptmenü.
### Mit Voreinstellung arbeiten
Beim nächsten Mal:
```
Ihre Wahl: 1
Verfügbare Voreinstellungen:
1. meine_standard_export
2. mietermerkmale
Möchten Sie eine Voreinstellung laden? [j/n]: j
Nummer oder Name der Voreinstellung: 1
✓ Voreinstellung 'meine_standard_export' geladen.
```
Dann nur noch:
1. Quelldatei angeben
2. Zieldatei angeben
3. **Fertig!**
Alle anderen Schritte werden automatisch durchgeführt! 🚀
---
## GUI-Version: Schritt-für-Schritt
### Programmstart
```bash
csv-processor-gui
```
Oder über das Anwendungsmenü: **Office** → **CSV-Processor**
### Hauptfenster
![GUI Overview](diagram)
```
┌─────────────────────────────────────────────────────────┐
│ Datei Voreinstellungen Hilfe │
├─────────────────────────────────────────────────────────┤
│ ┌─ Quelldatei ────────────────────────────────────────┐ │
│ │ Datei: [________________] [Durchsuchen] [Laden] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Verarbeitungseinstellungen ─────────────────────────┐│
│ │ ☑ Datei hat Kopfzeile ││
│ │ ☑ Leere Zeilen entfernen ││
│ │ ☑ Leere Spalten entfernen (vor Spaltenauswahl) ││
│ │ Mapping-Datei: [________________] [...] ││
│ │ Ausgabeformat: ⦿ CSV ○ Excel ○ ODS ││
│ └─────────────────────────────────────────────────────┘│
│ │
│ ┌─ Spaltenauswahl ─────────────────────────────────────┐│
│ │ [Alle auswählen] [Alle abwählen] [Auswahl umkehren] ││
│ │ ┌──────────────────────────────────────────────────┐││
│ │ │ ☑ btr → Betrag │││
│ │ │ ☑ dat → Datum │││
│ │ │ ☑ empf → Empfänger │││
│ │ │ ☐ alte_spalte (nicht gemappt) │││
│ │ └──────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────┘│
│ │
│ ┌─ Status ──────────────────────────────────────────────┐│
│ │ 12:34:56 - Datei geladen: 5 Spalten, 100 Zeilen ││
│ │ 12:34:57 - Leere Spalten entfernt: 2 ││
│ └─────────────────────────────────────────────────────┘│
│ │
│ [Vorschau] [Verarbeiten & Speichern] │
└─────────────────────────────────────────────────────────┘
```
### Schritt-für-Schritt Anleitung
#### 1. Datei laden
**Option A: Durchsuchen**
1. Klick auf **[Durchsuchen]**
2. Datei auswählen (CSV oder Excel)
3. **[Öffnen]** klicken
**Option B: Pfad eingeben**
1. Pfad ins Feld eingeben: `/home/benutzer/Downloads/daten.csv`
2. **[Laden]** klicken
**Ergebnis:**
```
Status:
12:34:56 - Lade Datei: daten.csv
12:34:56 - Erfolgreich geladen: 8 Spalten, 234 Zeilen
```
#### 2. Einstellungen prüfen
Alle wichtigen Einstellungen sind als Checkboxen verfügbar:
- **☑ Datei hat Kopfzeile** - Fast immer aktiviert
- **☑ Leere Zeilen entfernen** - Empfohlen
- **☑ Leere Spalten entfernen** - Empfohlen (passiert vor Spaltenauswahl!)
#### 3. Mapping-Datei wählen (optional)
1. Klick auf **[...]** neben "Mapping-Datei"
2. Datei auswählen (z.B. `mietermerkmale.json`)
3. **[Öffnen]**
4. Datei neu laden mit **[Laden]**
**Ergebnis:** Spaltennamen werden umbenannt, z.B.:
- `btr → Betrag`
- `dat → Datum`
#### 4. Spalten auswählen
Im Bereich "Spaltenauswahl" sehen Sie alle verfügbaren Spalten.
**Alle Spalten sind standardmäßig ausgewählt (☑).**
**Spalten abwählen:**
- Klick auf Spalte → wird abgewählt (☐)
- Klick nochmal → wird wieder ausgewählt (☑)
**Schnellaktionen:**
- **[Alle auswählen]** - Alle Spalten auf ☑
- **[Alle abwählen]** - Alle Spalten auf ☐
- **[Auswahl umkehren]** - ☑↔☐
**Mehrfachauswahl:**
- **Strg + Klick** - Mehrere Spalten einzeln auswählen
- **Shift + Klick** - Bereich auswählen
**Wichtig:** Nur grüne Spalten (☑) werden exportiert. Zusätzlich werden leere Spalten automatisch übersprungen!
#### 5. Vorschau (optional)
Klick auf **[Vorschau]** um die verarbeiteten Daten zu sehen:
```
┌─ Datenvorschau ──────────────────────────────────────┐
│ Betrag │ Datum │ Empfänger │ Verwendung │
├───────────┼────────────┼──────────────┼─────────────┤
│ 150.50 │ 01.10.2025 │ Müller GmbH │ Miete │
│ 75.00 │ 03.10.2025 │ Stadtwerke │ Strom │
│ ... │ ... │ ... │ ... │
└──────────────────────────────────────────────────────┘
Zeige erste 100 von 234 Zeilen
[Schließen]
```
#### 6. Verarbeiten & Speichern
Klick auf **[Verarbeiten & Speichern]**
1. Dateiname eingeben: `ausgabe`
2. Speicherort wählen
3. **[Speichern]**
**Status-Meldungen:**
```
12:45:10 - Leere Zeilen entfernt: 3
12:45:10 - Leere Spalten entfernt: 2
12:45:10 - - alte_spalte1 (leer)
12:45:10 - - temp_field (leer)
12:45:11 - Übersprungene Spalten (leer): 1
12:45:11 - Abgewählte Spalten (rot): 2
12:45:11 - Finale Verarbeitung: 5 Spalten, 231 Zeilen
12:45:12 - Datei gespeichert: ausgabe.csv
```
**Erfolgsmeldung:**
```
┌─ Erfolg ─────────────────────────────────┐
│ │
│ Datei erfolgreich gespeichert: │
│ /home/benutzer/Documents/ausgabe.csv │
│ │
│ [OK] │
└───────────────────────────────────────────┘
```
#### 7. Voreinstellung speichern
**Menü** → **Voreinstellungen** → **Speichern**
```
┌─ Voreinstellung speichern ──────────────┐
│ Name der Voreinstellung: │
│ [meine_export_einstellung_________] │
│ │
│ [OK] [Abbrechen] │
└──────────────────────────────────────────┘
```
Name eingeben → **[OK]**
```
Status:
12:50:00 - Voreinstellung gespeichert: meine_export_einstellung
```
### Voreinstellung laden
**Menü** → **Voreinstellungen** → **Laden**
```
┌─ Voreinstellung laden ──────────────────┐
│ Voreinstellung auswählen: │
│ ┌─────────────────────────────────────┐ │
│ │ meine_export_einstellung │ │
│ │ mietermerkmale │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
│ [OK] [Abbrechen] │
└──────────────────────────────────────────┘
```
Voreinstellung auswählen → **[OK]**
**Alle Einstellungen werden automatisch geladen!**
---
## Voreinstellungen nutzen
### Was wird gespeichert?
Eine Voreinstellung enthält:
- ☑ Kopfzeile ja/nein
- ☑ Leere Zeilen entfernen
- ☑ Leere Spalten entfernen
- 📄 Pfad zur Mapping-Datei
- 📊 Spaltenauswahl
- 💾 Ausgabeformat (CSV/Excel/ODS)
- 🔧 CSV-Export-Einstellungen (Delimiter, Quoting)
- 📏 Sortierung (Spalte & Datentyp)
### Wann sind Voreinstellungen sinnvoll?
✅ **Monatliche Berichte** - Immer gleiche Verarbeitung
✅ **Standard-Exporte** - Wiederkehrende Aufgaben
✅ **Team-Workflows** - Einheitliche Formate
✅ **Verschiedene Datenquellen** - Unterschiedliche Mappings
### Voreinstellung bearbeiten (nur CLI)
**Hauptmenü** → **2** (Voreinstellungen verwalten)
```
╔═══════════════════════════════════════════════════════╗
║ VOREINSTELLUNGEN VERWALTEN ║
╚═══════════════════════════════════════════════════════╝
Verfügbare Voreinstellungen:
1. meine_export_einstellung
2. mietermerkmale
Optionen:
1. Voreinstellung bearbeiten
2. Voreinstellung löschen
0. Zurück zum Hauptmenü
Ihre Wahl: 1
```
**Voreinstellung bearbeiten:**
1. Voreinstellung auswählen
2. Alle Schritte durchgehen (mit aktuellen Werten vorausgefüllt)
3. Bei Schritt 6 (Spaltenauswahl): Beispieldatei laden
4. Spalten wie gewohnt aus-/abwählen
5. Am Ende: Überschreiben oder neu speichern
---
## Mapping-Dateien erstellen
### Was ist ein Mapping?
Ein Mapping übersetzt technische Spaltennamen in lesbare Namen:
```
"btr" → "Betrag"
"dat" → "Datum"
"empf" → "Empfänger"
```
### Format
Mapping-Dateien sind JSON-Dateien:
```json
{
"btr": "Betrag",
"dat": "Datum",
"kto": "Kontonummer",
"blz": "Bankleitzahl",
"empf": "Empfänger",
"verw": "Verwendungszweck"
}
```
### Eigenes Mapping erstellen
1. Texteditor öffnen (z.B. `nano`, `gedit`)
2. JSON-Format verwenden (siehe oben)
3. Als `.json` speichern (z.B. `mein_mapping.json`)
**Beispiel:**
```bash
nano ~/Dokumente/bank_mapping.json
```
Inhalt:
```json
{
"alt1": "Neuer Name 1",
"alt2": "Neuer Name 2",
"xyz": "Beschreibung XYZ"
}
```
Speichern: **Strg+O**, **Enter**, **Strg+X**
### Mitgeliefertes Mapping
Das System enthält bereits: `/usr/share/csv-processor/mietermerkmale.json`
Dieses können Sie als Vorlage verwenden oder direkt nutzen.
---
## Häufige Anwendungsfälle
### 1. Monatlicher Export aus Buchhaltung
**Situation:** Jeden Monat CSV-Export aus Buchhaltungssoftware → Excel-Datei für Chef
**Lösung:**
1. **Einmalig:** Voreinstellung "Buchhaltung_Export" erstellen
- Mapping: Spaltennamen übersetzen
- Spalten: Nur relevante auswählen
- Format: Excel
- Sortierung: Nach Datum
2. **Jeden Monat:**
- Programm starten
- Voreinstellung laden
- Neue CSV-Datei angeben
- Ausgabename: `Bericht_Oktober_2025.xlsx`
- **Fertig in 30 Sekunden!**
### 2. Daten für verschiedene Abteilungen
**Situation:** Gleiche Quelldaten, aber unterschiedliche Spalten für verschiedene Teams
**Lösung:**
- Voreinstellung "Export_Vertrieb" - Nur Vertriebsspalten
- Voreinstellung "Export_Buchhaltung" - Nur Finanzspalten
- Voreinstellung "Export_Geschäftsführung" - Übersicht
### 3. Excel zu CSV konvertieren
**Situation:** Excel-Datei von Kunde → Eigenes System braucht CSV
**Lösung (GUI):**
1. Excel-Datei laden
2. Alle Einstellungen auf Standard lassen
3. Ausgabeformat: CSV
4. Delimiter: Semikolon
5. Speichern
### 4. CSV bereinigen
**Situation:** CSV-Datei mit vielen leeren Zeilen/Spalten
**Lösung:**
1. Datei laden
2. ☑ Leere Zeilen entfernen
3. ☑ Leere Spalten entfernen
4. Alle Spalten ausgewählt lassen
5. Speichern
### 5. Spaltennamen vereinheitlichen
**Situation:** Verschiedene Systeme, unterschiedliche Spaltennamen, sollen gleich sein
**Lösung:**
1. Mapping-Datei erstellen mit allen Varianten:
```json
{
"btr": "Betrag",
"betrag": "Betrag",
"amount": "Betrag",
"sum": "Betrag"
}
```
2. Voreinstellung mit diesem Mapping
3. Auf alle Dateien anwenden
---
## Tipps & Tricks
### Performance
**Große Dateien (>10.000 Zeilen):**
- Verarbeitung kann 5-30 Sekunden dauern
- Bei Excel: Erstes Tabellenblatt ist schneller
- GUI: Status-Log beobachten
**Sehr große Dateien (>100.000 Zeilen):**
- CLI-Version ist etwas schneller
- Leere Spalten vorher entfernen spart Zeit
- Ggf. Datei vorher splitten
### Spaltenauswahl
**Trick: Schnell viele Spalten abwählen (CLI):**
```
Ihre Wahl: -10-50
```
Wählt Spalten 10 bis 50 ab (rot)
**Trick: Nur wenige Spalten behalten (GUI):**
1. **[Alle abwählen]**
2. **Strg+Klick** auf gewünschte Spalten
### Mappings
**Tipp:** Verwenden Sie sprechende Ziel-Namen:
- ✅ "Betrag in Euro"
- ✅ "Datum (Format: TT.MM.JJJJ)"
- ❌ "btr_eur"
**Tipp:** Ein Mapping pro Datenquelle:
- `system_a_mapping.json`
- `system_b_mapping.json`
- `kunde_xy_mapping.json`
### CSV-Export
**Für Excel (deutsche Version):**
- Delimiter: Semikolon
- Quoting: Minimal
**Für andere Software:**
- Delimiter: Komma oder Tab
- Quoting: Alle (sicherer)
**Für Datenbanken:**
- Delimiter: Tab
- Quoting: Minimal
- Keine Kopf-/Fußzeile
### Voreinstellungen
**Namenskonvention:**
```
[Zweck]_[Format]_[Besonderheit]
```
Beispiele:
- `Mietermerkmale_Excel_monatlich`
- `Buchhaltung_CSV_Vertrieb`
- `Datenbank_Import_Tab`
### Datensicherheit
✅ **Original-Dateien bleiben unverändert**
✅ **Keine Daten werden ins Internet gesendet**
✅ **Voreinstellungen enthalten keine Daten, nur Konfiguration**
---
## Fehlerbehebung
### "Datei nicht gefunden"
**Problem:** Pfad falsch oder Datei existiert nicht
**Lösung:**
- Pfad nochmal prüfen
- In GUI: **[Durchsuchen]** verwenden
- Tippfehler korrigieren
- Bei Netzlaufwerken: Verbindung prüfen
### "Fehler beim Lesen der CSV"
**Problem:** Encoding oder Format nicht erkannt
**Lösung (CLI):**
- Im Import-Dialog verschiedene Encodings probieren
- Delimiter manuell wählen
**Lösung (GUI):**
- Datei in Excel öffnen
- Als CSV mit UTF-8 speichern
- Erneut laden
### "Keine Spalten ausgewählt"
**Problem:** Alle Spalten abgewählt oder alle leer
**Lösung:**
- Prüfen ob Spalten grün (☑) sind
- Ggf. "Leere Spalten entfernen" deaktivieren
- Quelldatei prüfen: Enthält sie Daten?
### "Excel-Support nicht verfügbar"
**Problem:** Bibliothek `openpyxl` fehlt
**Lösung:**
```bash
pip3 install openpyxl
```
Oder IT kontaktieren.
### "Programm reagiert nicht" (GUI)
**Problem:** Große Datei wird verarbeitet
**Lösung:**
- Warten (Status-Log beobachten)
- Bei >100.000 Zeilen: 1-2 Minuten normal
- Ggf. CLI-Version verwenden
### Voreinstellung lädt nicht alle Spalten
**Grund:** Gespeicherte Spalten sind in aktueller Datei leer oder nicht vorhanden
**Das ist normal!** Das Programm überspringt automatisch:
1. Leere Spalten (wenn "Leere Spalten entfernen" aktiv)
2. Nicht vorhandene Spalten
**Status-Meldung beachten:**
```
Übersprungene Spalten (leer oder nicht vorhanden): 3
- alte_spalte1
- leere_spalte2
- nicht_vorhanden
```
---
## Support
Bei Problemen oder Fragen:
1. **Diese Anleitung durchlesen** 📖
2. **Status-/Log-Meldungen beachten** 📋
3. **IT-Abteilung kontaktieren** 💻
---
## Schnellreferenz
### CLI-Befehle
| Aktion | Befehl |
|--------|--------|
| Programm starten | `csv-processor-cli` |
| Datei verarbeiten | Hauptmenü → `1` |
| Voreinstellungen | Hauptmenü → `2` |
| Beenden | Hauptmenü → `0` oder `Strg+C` |
### GUI-Shortcuts
| Aktion | Shortcut |
|--------|----------|
| Programm starten | `csv-processor-gui` |
| Datei öffnen | **Strg+O** |
| Speichern | **Strg+S** |
| Voreinstellung laden | **Strg+L** |
### Spaltenauswahl (CLI)
| Eingabe | Bedeutung |
|---------|-----------|
| `q` | Fertig |
| `alle` | Alle auswählen |
| `keine` | Alle abwählen |
| `3` | Spalte 3 umschalten |
| `1,2,3` | Spalten 1,2,3 umschalten |
| `+5` | Spalte 5 ANwählen |
| `-5` | Spalte 5 ABwählen |
| `1-10` | Spalten 1-10 umschalten |
| `+1-10` | Spalten 1-10 ANwählen |
| `-1-10` | Spalten 1-10 ABwählen |
---
**Version:** 2.0
**Stand:** Oktober 2025
**Erstellt für:** Interne Nutzung

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,829 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CSV-Processor GUI v2.0: Grafische Oberfläche für CSV/Excel-Verarbeitung
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext, simpledialog
import csv
import json
import os
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
# Optional: Excel Support
try:
import openpyxl
from openpyxl.styles import Font, PatternFill
EXCEL_SUPPORT = True
except ImportError:
EXCEL_SUPPORT = False
try:
from odf.opendocument import OpenDocumentSpreadsheet
from odf.table import Table, TableRow, TableCell
from odf.text import P
ODT_SUPPORT = True
except ImportError:
ODT_SUPPORT = False
class CSVProcessorGUI:
def __init__(self, root):
self.root = root
self.root.title("CSV-Processor v2.0")
self.root.geometry("900x700")
self.config_dir = Path("csv_processor_config")
self.config_dir.mkdir(exist_ok=True)
self.current_config = {} # Für temporäre Voreinstellungs-Daten
self.headers = []
self.data = []
self.original_names = {}
self.selected_columns = set()
self.setup_ui()
def setup_ui(self):
"""Hauptoberfläche erstellen"""
# Menüleiste
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
# Datei-Menü
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Datei", menu=file_menu)
file_menu.add_command(label="Datei öffnen", command=self.load_file)
file_menu.add_command(label="Verarbeiten & Speichern", command=self.process_and_save)
file_menu.add_separator()
file_menu.add_command(label="Beenden", command=self.root.quit)
# Voreinstellungen-Menü
preset_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Voreinstellungen", menu=preset_menu)
preset_menu.add_command(label="Laden", command=self.load_preset)
preset_menu.add_command(label="Speichern", command=self.save_preset)
preset_menu.add_command(label="Verwalten", command=self.manage_presets)
# Hilfe-Menü
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Hilfe", menu=help_menu)
help_menu.add_command(label="Über", command=self.show_about)
# Hauptcontainer
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(4, weight=1)
# Datei-Auswahl
file_frame = ttk.LabelFrame(main_frame, text="Quelldatei", padding="10")
file_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5)
file_frame.columnconfigure(1, weight=1)
ttk.Label(file_frame, text="Datei:").grid(row=0, column=0, sticky=tk.W)
self.file_entry = ttk.Entry(file_frame, width=50)
self.file_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5)
ttk.Button(file_frame, text="Durchsuchen", command=self.browse_file).grid(row=0, column=2)
ttk.Button(file_frame, text="Laden", command=self.load_file).grid(row=0, column=3, padx=5)
# Einstellungen
settings_frame = ttk.LabelFrame(main_frame, text="Verarbeitungseinstellungen", padding="10")
settings_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5)
settings_frame.columnconfigure(1, weight=1)
row = 0
self.has_header_var = tk.BooleanVar(value=True)
ttk.Checkbutton(settings_frame, text="Datei hat Kopfzeile",
variable=self.has_header_var).grid(row=row, column=0, sticky=tk.W, columnspan=2)
row += 1
self.remove_empty_rows_var = tk.BooleanVar(value=True)
ttk.Checkbutton(settings_frame, text="Leere Zeilen entfernen",
variable=self.remove_empty_rows_var).grid(row=row, column=0, sticky=tk.W, columnspan=2)
row += 1
self.remove_empty_cols_var = tk.BooleanVar(value=True)
ttk.Checkbutton(settings_frame, text="Leere Spalten entfernen (automatisch vor Spaltenauswahl)",
variable=self.remove_empty_cols_var).grid(row=row, column=0, sticky=tk.W, columnspan=3)
# Info-Label
info_label = ttk.Label(settings_frame,
text=" Leere Spalten werden vor der Spaltenauswahl entfernt",
foreground="blue", font=('TkDefaultFont', 8))
info_label.grid(row=row+1, column=0, columnspan=3, sticky=tk.W, pady=(0,5))
row += 2
ttk.Label(settings_frame, text="Mapping-Datei:").grid(row=row, column=0, sticky=tk.W)
self.mapping_entry = ttk.Entry(settings_frame)
self.mapping_entry.grid(row=row, column=1, sticky=(tk.W, tk.E), padx=5)
ttk.Button(settings_frame, text="...", command=self.browse_mapping, width=3).grid(row=row, column=2)
row += 1
ttk.Label(settings_frame, text="Ausgabeformat:").grid(row=row, column=0, sticky=tk.W)
self.format_var = tk.StringVar(value="csv")
format_frame = ttk.Frame(settings_frame)
format_frame.grid(row=row, column=1, sticky=tk.W, padx=5)
ttk.Radiobutton(format_frame, text="CSV", variable=self.format_var, value="csv").pack(side=tk.LEFT)
if EXCEL_SUPPORT:
ttk.Radiobutton(format_frame, text="Excel", variable=self.format_var, value="xlsx").pack(side=tk.LEFT, padx=10)
if ODT_SUPPORT:
ttk.Radiobutton(format_frame, text="ODS", variable=self.format_var, value="ods").pack(side=tk.LEFT)
# Spaltenauswahl
columns_frame = ttk.LabelFrame(main_frame, text="Spaltenauswahl", padding="10")
columns_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=5)
button_frame = ttk.Frame(columns_frame)
button_frame.pack(fill=tk.X)
ttk.Button(button_frame, text="Alle auswählen", command=self.select_all_columns).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Alle abwählen", command=self.deselect_all_columns).pack(side=tk.LEFT)
ttk.Button(button_frame, text="Auswahl umkehren", command=self.invert_selection).pack(side=tk.LEFT, padx=5)
# Spalten-Liste mit Checkboxen
list_frame = ttk.Frame(columns_frame)
list_frame.pack(fill=tk.BOTH, expand=True, pady=5)
scrollbar = ttk.Scrollbar(list_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.columns_listbox = tk.Listbox(list_frame, selectmode=tk.MULTIPLE,
yscrollcommand=scrollbar.set, height=10)
self.columns_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.columns_listbox.yview)
# Vorschau
preview_frame = ttk.LabelFrame(main_frame, text="Datenvorschau", padding="10")
preview_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=5)
self.info_label = ttk.Label(preview_frame, text="Keine Datei geladen")
self.info_label.pack()
# Status und Log
log_frame = ttk.LabelFrame(main_frame, text="Status", padding="10")
log_frame.grid(row=4, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
self.log_text = scrolledtext.ScrolledText(log_frame, height=10, state='disabled')
self.log_text.pack(fill=tk.BOTH, expand=True)
# Aktionsbuttons
action_frame = ttk.Frame(main_frame)
action_frame.grid(row=5, column=0, sticky=(tk.W, tk.E), pady=10)
ttk.Button(action_frame, text="Verarbeiten & Speichern",
command=self.process_and_save).pack(side=tk.RIGHT, padx=5)
ttk.Button(action_frame, text="Vorschau", command=self.show_preview).pack(side=tk.RIGHT)
self.log("CSV-Processor GUI v2.0 gestartet")
self.log(f"Excel-Support: {'Ja' if EXCEL_SUPPORT else 'Nein'}")
self.log(f"ODS-Support: {'Ja' if ODT_SUPPORT else 'Nein'}")
def log(self, message):
"""Nachricht im Log anzeigen"""
self.log_text.config(state='normal')
self.log_text.insert(tk.END, f"{datetime.now().strftime('%H:%M:%S')} - {message}\n")
self.log_text.see(tk.END)
self.log_text.config(state='disabled')
def browse_file(self):
"""Datei zum Öffnen auswählen"""
filetypes = [
("Alle unterstützten Dateien", "*.csv *.xlsx *.xls"),
("CSV Dateien", "*.csv"),
]
if EXCEL_SUPPORT:
filetypes.append(("Excel Dateien", "*.xlsx *.xls"))
filetypes.append(("Alle Dateien", "*.*"))
filename = filedialog.askopenfilename(
title="Quelldatei auswählen",
filetypes=filetypes
)
if filename:
self.file_entry.delete(0, tk.END)
self.file_entry.insert(0, filename)
def browse_mapping(self):
"""Mapping-Datei auswählen"""
filename = filedialog.askopenfilename(
title="Mapping-Datei auswählen",
filetypes=[("JSON Dateien", "*.json"), ("Alle Dateien", "*.*")]
)
if filename:
self.mapping_entry.delete(0, tk.END)
self.mapping_entry.insert(0, filename)
def load_file(self):
"""Datei laden"""
filepath = self.file_entry.get()
if not filepath:
messagebox.showwarning("Warnung", "Bitte wählen Sie zuerst eine Datei aus.")
return
path = Path(filepath)
if not path.exists():
messagebox.showerror("Fehler", f"Datei nicht gefunden: {filepath}")
return
try:
self.log(f"Lade Datei: {path.name}")
# Datei laden
file_ext = path.suffix.lower()
if file_ext in ['.xlsx', '.xls']:
self.headers, self.data = self.read_excel(path)
else:
self.headers, self.data = self.read_csv(path)
# Mapping anwenden
mapping_file = self.mapping_entry.get()
if mapping_file and Path(mapping_file).exists():
mappings = self.load_column_mappings(Path(mapping_file))
self.headers, self.data, self.original_names = self.apply_column_mappings(
self.headers, self.data, mappings
)
self.log(f"Mapping angewendet: {len(mappings)} Spalten umbenannt")
else:
self.original_names = {h: h for h in self.headers}
# Spalten in Liste anzeigen
self.update_columns_list()
# Wenn Voreinstellung mit Spaltenauswahl geladen wurde, anwenden
if hasattr(self, 'current_config') and 'selected_columns' in self.current_config:
self.apply_column_selection_from_preset(self.current_config['selected_columns'])
# Config zurücksetzen nach Anwendung
self.current_config = {}
# Info aktualisieren
self.info_label.config(text=f"Geladen: {len(self.headers)} Spalten, {len(self.data)} Zeilen")
self.log(f"Erfolgreich geladen: {len(self.headers)} Spalten, {len(self.data)} Zeilen")
# Warnung anzeigen wenn "Leere Spalten entfernen" aktiv ist
if self.remove_empty_cols_var.get():
self.log(" Leere Spalten werden beim Verarbeiten automatisch entfernt")
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Laden: {str(e)}")
self.log(f"FEHLER: {str(e)}")
def read_csv(self, filepath: Path) -> Tuple[List[str], List[Dict]]:
"""CSV-Datei lesen"""
encodings = ['utf-8', 'latin-1', 'cp1252']
for encoding in encodings:
try:
with open(filepath, 'r', encoding=encoding) as f:
first_line = f.readline()
f.seek(0)
sniffer = csv.Sniffer()
delimiter = sniffer.sniff(first_line).delimiter
if self.has_header_var.get():
reader = csv.DictReader(f, delimiter=delimiter)
headers = list(reader.fieldnames)
data = list(reader)
else:
reader = csv.reader(f, delimiter=delimiter)
rows = list(reader)
if rows:
headers = [f"Spalte_{i+1}" for i in range(len(rows[0]))]
data = []
for row in rows:
row_dict = {headers[i]: row[i] if i < len(row) else ''
for i in range(len(headers))}
data.append(row_dict)
else:
headers = []
data = []
return headers, data
except UnicodeDecodeError:
continue
except Exception:
continue
raise Exception(f"Konnte Datei {filepath} nicht lesen")
def read_excel(self, filepath: Path) -> Tuple[List[str], List[Dict]]:
"""Excel-Datei lesen"""
if not EXCEL_SUPPORT:
raise Exception("Excel-Support nicht verfügbar. Installieren Sie: pip install openpyxl")
wb = openpyxl.load_workbook(filepath, data_only=True)
ws = wb.active
data = []
headers = None
for i, row in enumerate(ws.iter_rows(values_only=True)):
if i == 0 and self.has_header_var.get():
headers = [str(cell) if cell is not None else f"Spalte_{j+1}"
for j, cell in enumerate(row)]
else:
if headers is None:
headers = [f"Spalte_{j+1}" for j in range(len(row))]
row_dict = {}
for j, cell in enumerate(row):
if j < len(headers):
row_dict[headers[j]] = str(cell) if cell is not None else ''
data.append(row_dict)
return headers, data
def load_column_mappings(self, mapping_file: Path) -> Dict[str, str]:
"""Spaltennamen-Zuordnungen aus JSON laden"""
try:
with open(mapping_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return {}
def apply_column_mappings(self, headers: List[str], data: List[Dict],
mappings: Dict[str, str]) -> Tuple[List[str], List[Dict], Dict[str, str]]:
"""Spaltennamen umbenennen"""
new_headers = [mappings.get(h, h) for h in headers]
original_names = {new_h: old_h for old_h, new_h in zip(headers, new_headers)}
new_data = []
for row in data:
new_row = {}
for old_h, new_h in zip(headers, new_headers):
new_row[new_h] = row.get(old_h, '')
new_data.append(new_row)
return new_headers, new_data, original_names
def update_columns_list(self):
"""Spaltenliste aktualisieren"""
self.columns_listbox.delete(0, tk.END)
self.selected_columns = set(self.headers) # Alle initial ausgewählt
for i, header in enumerate(self.headers):
orig = self.original_names.get(header, header)
if orig != header:
display = f"{orig}{header}"
else:
display = f"{header}"
self.columns_listbox.insert(tk.END, display)
self.columns_listbox.selection_set(i)
def select_all_columns(self):
"""Alle Spalten auswählen"""
self.columns_listbox.selection_set(0, tk.END)
self.selected_columns = set(self.headers)
def deselect_all_columns(self):
"""Alle Spalten abwählen"""
self.columns_listbox.selection_clear(0, tk.END)
self.selected_columns = set()
def invert_selection(self):
"""Auswahl umkehren"""
for i in range(len(self.headers)):
if self.columns_listbox.selection_includes(i):
self.columns_listbox.selection_clear(i)
else:
self.columns_listbox.selection_set(i)
def get_selected_columns(self) -> List[str]:
"""Aktuell ausgewählte Spalten ermitteln"""
selected_indices = self.columns_listbox.curselection()
return [self.headers[i] for i in selected_indices]
def process_data(self) -> Tuple[List[str], List[Dict]]:
"""Daten verarbeiten"""
if not self.headers or not self.data:
raise Exception("Keine Daten geladen")
headers = self.headers[:]
data = self.data[:]
# 1. Leere Zeilen entfernen
if self.remove_empty_rows_var.get():
original_count = len(data)
data = [row for row in data if any(str(v).strip() for v in row.values())]
removed = original_count - len(data)
if removed > 0:
self.log(f"Leere Zeilen entfernt: {removed}")
# 2. Leere Spalten entfernen (VOR der Spaltenauswahl!)
if self.remove_empty_cols_var.get():
non_empty_headers = []
for header in headers:
if any(row.get(header, '').strip() for row in data):
non_empty_headers.append(header)
removed = len(headers) - len(non_empty_headers)
if removed > 0:
self.log(f"Leere Spalten entfernt: {removed}")
# Zeige welche Spalten entfernt wurden
removed_cols = [h for h in headers if h not in non_empty_headers]
for col in removed_cols[:5]:
orig = self.original_names.get(col, col)
if orig != col:
self.log(f" - {orig}{col} (leer)")
else:
self.log(f" - {col} (leer)")
if len(removed_cols) > 5:
self.log(f" ... und {len(removed_cols)-5} weitere")
headers = non_empty_headers
data = [{h: row.get(h, '') for h in headers} for row in data]
# 3. Spaltenauswahl anwenden (arbeitet mit nicht-leeren Spalten)
selected = self.get_selected_columns()
# Nur Spalten verwenden, die noch existieren (nicht leer) UND ausgewählt sind
final_columns = [h for h in headers if h in selected]
if not final_columns:
raise Exception("Keine Spalten ausgewählt oder alle ausgewählten Spalten sind leer")
# Zeige Info über übersprungene Spalten
skipped_empty = [h for h in selected if h not in headers]
skipped_deselected = [h for h in headers if h not in selected]
if skipped_empty:
self.log(f"Übersprungene Spalten (leer): {len(skipped_empty)}")
if skipped_deselected:
self.log(f"Abgewählte Spalten (rot): {len(skipped_deselected)}")
headers = final_columns
data = [{h: row.get(h, '') for h in headers} for row in data]
self.log(f"Finale Verarbeitung: {len(headers)} Spalten, {len(data)} Zeilen")
return headers, data
def show_preview(self):
"""Vorschau der verarbeiteten Daten"""
try:
headers, data = self.process_data()
preview_window = tk.Toplevel(self.root)
preview_window.title("Datenvorschau")
preview_window.geometry("800x400")
frame = ttk.Frame(preview_window, padding="10")
frame.pack(fill=tk.BOTH, expand=True)
# Treeview für Tabelle
tree = ttk.Treeview(frame, columns=headers, show='headings')
for col in headers:
tree.heading(col, text=col)
tree.column(col, width=100)
for row in data[:100]: # Max 100 Zeilen
values = [row.get(col, '') for col in headers]
tree.insert('', tk.END, values=values)
scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=scrollbar.set)
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
info_label = ttk.Label(preview_window,
text=f"Zeige erste {min(len(data), 100)} von {len(data)} Zeilen")
info_label.pack(pady=5)
except Exception as e:
messagebox.showerror("Fehler", f"Fehler bei Vorschau: {str(e)}")
def process_and_save(self):
"""Daten verarbeiten und speichern"""
try:
headers, data = self.process_data()
# Zieldatei wählen
output_format = self.format_var.get()
filetypes = []
if output_format == 'csv':
filetypes = [("CSV Dateien", "*.csv")]
elif output_format == 'xlsx':
filetypes = [("Excel Dateien", "*.xlsx")]
elif output_format == 'ods':
filetypes = [("ODS Dateien", "*.ods")]
filetypes.append(("Alle Dateien", "*.*"))
filename = filedialog.asksaveasfilename(
title="Datei speichern",
defaultextension=f".{output_format}",
filetypes=filetypes
)
if not filename:
return
# Speichern
target_file = Path(filename)
if output_format == 'csv':
self.write_csv(target_file, headers, data)
elif output_format == 'xlsx':
self.write_excel(target_file, headers, data)
elif output_format == 'ods':
self.write_ods(target_file, headers, data)
self.log(f"Datei gespeichert: {target_file.name}")
messagebox.showinfo("Erfolg", f"Datei erfolgreich gespeichert:\n{target_file}")
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Speichern: {str(e)}")
self.log(f"FEHLER: {str(e)}")
def write_csv(self, filepath: Path, headers: List[str], data: List[Dict]):
"""CSV-Datei schreiben"""
with open(filepath, 'w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=headers, delimiter=';')
writer.writeheader()
writer.writerows(data)
def write_excel(self, filepath: Path, headers: List[str], data: List[Dict]):
"""Excel-Datei schreiben"""
if not EXCEL_SUPPORT:
raise Exception("Excel-Support nicht verfügbar")
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Daten"
# Kopfzeile
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_num, value=header)
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="CCCCCC", end_color="CCCCCC", fill_type="solid")
# Daten
for row_num, row_data in enumerate(data, 2):
for col_num, header in enumerate(headers, 1):
ws.cell(row=row_num, column=col_num, value=row_data.get(header, ''))
wb.save(filepath)
def write_ods(self, filepath: Path, headers: List[str], data: List[Dict]):
"""ODS-Datei schreiben"""
if not ODT_SUPPORT:
raise Exception("ODS-Support nicht verfügbar")
doc = OpenDocumentSpreadsheet()
table = Table(name="Daten")
# Kopfzeile
row = TableRow()
for header in headers:
cell = TableCell()
cell.addElement(P(text=header))
row.addElement(cell)
table.addElement(row)
# Daten
for row_data in data:
row = TableRow()
for header in headers:
cell = TableCell()
cell.addElement(P(text=str(row_data.get(header, ''))))
row.addElement(cell)
table.addElement(row)
doc.spreadsheet.addElement(table)
doc.save(filepath)
def load_preset(self):
"""Voreinstellung laden"""
presets = [f.stem for f in self.config_dir.glob("*.json")]
if not presets:
messagebox.showinfo("Info", "Keine Voreinstellungen vorhanden.")
return
dialog = PresetDialog(self.root, "Voreinstellung laden", presets)
if dialog.result:
preset_file = self.config_dir / f"{dialog.result}.json"
try:
with open(preset_file, 'r', encoding='utf-8') as f:
config = json.load(f)
# Einstellungen anwenden
if 'has_header' in config:
self.has_header_var.set(config['has_header'])
if 'remove_empty_rows' in config:
self.remove_empty_rows_var.set(config['remove_empty_rows'])
if 'remove_empty' in config:
self.remove_empty_cols_var.set(config['remove_empty'])
# Mapping-Datei vorbelegen
if 'mapping_file' in config and config['mapping_file']:
self.mapping_entry.delete(0, tk.END)
self.mapping_entry.insert(0, config['mapping_file'])
self.log(f"Mapping-Datei vorbelegt: {config['mapping_file']}")
if 'output_format' in config:
self.format_var.set(config['output_format'])
# Quelldatei vorbelegen (falls gespeichert)
if 'source_file' in config and config['source_file']:
source_path = Path(config['source_file'])
if source_path.exists():
self.file_entry.delete(0, tk.END)
self.file_entry.insert(0, config['source_file'])
self.log(f"Quelldatei vorbelegt: {source_path.name}")
# Datei automatisch laden
self.load_file()
# Spaltenauswahl anwenden (nach dem Laden!)
if 'selected_columns' in config and self.headers:
self.apply_column_selection_from_preset(config['selected_columns'])
else:
self.log(f"⚠ Gespeicherte Quelldatei nicht gefunden: {config['source_file']}")
elif 'selected_columns' in config:
# Keine Quelldatei, aber Spaltenauswahl gespeichert
# Wird angewendet sobald Datei geladen wird
self.current_config = config
self.log(f"Spaltenauswahl wird angewendet sobald Datei geladen wird")
self.log(f"Voreinstellung geladen: {dialog.result}")
messagebox.showinfo("Erfolg",
f"Voreinstellung '{dialog.result}' geladen.\n\n"
"Hinweis: Leere Spalten werden beim Verarbeiten\n"
"automatisch übersprungen (vor Spaltenauswahl).")
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Laden: {str(e)}")
def apply_column_selection_from_preset(self, selected_columns: List[str]):
"""Spaltenauswahl aus Voreinstellung anwenden"""
saved_selection = set(selected_columns)
# Alle abwählen
self.columns_listbox.selection_clear(0, tk.END)
# Nur gespeicherte Spalten auswählen (die existieren)
selected_count = 0
for i, header in enumerate(self.headers):
if header in saved_selection:
self.columns_listbox.selection_set(i)
selected_count += 1
skipped_count = len(saved_selection) - selected_count
self.log(f"Spaltenauswahl angewendet: {selected_count} Spalten ausgewählt")
if skipped_count > 0:
self.log(f"{skipped_count} Spalten nicht verfügbar (leer oder nicht vorhanden)")
# Info-Text anzeigen
self.info_label.config(
text=f"Geladen: {len(self.headers)} Spalten, {len(self.data)} Zeilen | "
f"Ausgewählt: {selected_count} Spalten"
)
def save_preset(self):
"""Voreinstellung speichern"""
name = simpledialog.askstring("Voreinstellung speichern", "Name der Voreinstellung:")
if not name:
return
# Fragen ob Quelldatei mitgespeichert werden soll
save_source = False
if self.file_entry.get():
save_source = messagebox.askyesno(
"Quelldatei speichern?",
"Möchten Sie den Pfad zur Quelldatei in der Voreinstellung speichern?\n\n"
"Ja = Datei wird beim Laden der Voreinstellung automatisch geladen\n"
"Nein = Nur Einstellungen werden gespeichert"
)
config = {
'has_header': self.has_header_var.get(),
'remove_empty_rows': self.remove_empty_rows_var.get(),
'remove_empty': self.remove_empty_cols_var.get(),
'mapping_file': self.mapping_entry.get(),
'output_format': self.format_var.get(),
'selected_columns': self.get_selected_columns()
}
# Quelldatei optional speichern
if save_source:
config['source_file'] = self.file_entry.get()
preset_file = self.config_dir / f"{name}.json"
try:
with open(preset_file, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
info_text = f"Voreinstellung '{name}' gespeichert.\n\n"
info_text += f"Enthält:\n"
info_text += f"- Einstellungen: Ja\n"
info_text += f"- Mapping: {'Ja' if config['mapping_file'] else 'Nein'}\n"
info_text += f"- Quelldatei: {'Ja' if save_source else 'Nein'}\n"
info_text += f"- Spaltenauswahl: {len(config['selected_columns'])} Spalten"
self.log(f"Voreinstellung gespeichert: {name}")
messagebox.showinfo("Erfolg", info_text)
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Speichern: {str(e)}")
def manage_presets(self):
"""Voreinstellungen verwalten"""
PresetManagerWindow(self.root, self.config_dir)
def show_about(self):
"""Über-Dialog"""
messagebox.showinfo(
"Über CSV-Processor",
"CSV-Processor v2.0\n\n"
"Professionelle CSV/Excel-Verarbeitung\n\n"
f"Excel-Support: {'Ja' if EXCEL_SUPPORT else 'Nein'}\n"
f"ODS-Support: {'Ja' if ODT_SUPPORT else 'Nein'}"
)
class PresetDialog(simpledialog.Dialog):
"""Dialog zur Auswahl einer Voreinstellung"""
def __init__(self, parent, title, presets):
self.presets = presets
self.result = None
super().__init__(parent, title)
def body(self, master):
tk.Label(master, text="Voreinstellung auswählen:").pack(pady=5)
self.listbox = tk.Listbox(master)
for preset in self.presets:
self.listbox.insert(tk.END, preset)
self.listbox.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
return self.listbox
def apply(self):
selection = self.listbox.curselection()
if selection:
self.result = self.presets[selection[0]]
class PresetManagerWindow:
"""Fenster zur Verwaltung von Voreinstellungen"""
def __init__(self, parent, config_dir):
self.config_dir = config_dir
self.window = tk.Toplevel(parent)
self.window.title("Voreinstellungen verwalten")
self.window.geometry("400x300")
frame = ttk.Frame(self.window, padding="10")
frame.pack(fill=tk.BOTH, expand=True)
tk.Label(frame, text="Voreinstellungen:").pack(anchor=tk.W)
self.listbox = tk.Listbox(frame)
self.listbox.pack(fill=tk.BOTH, expand=True, pady=5)
button_frame = ttk.Frame(frame)
button_frame.pack(fill=tk.X)
ttk.Button(button_frame, text="Löschen", command=self.delete_preset).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Schließen", command=self.window.destroy).pack(side=tk.RIGHT)
self.refresh_list()
def refresh_list(self):
"""Liste aktualisieren"""
self.listbox.delete(0, tk.END)
for preset in self.config_dir.glob("*.json"):
self.listbox.insert(tk.END, preset.stem)
def delete_preset(self):
"""Voreinstellung löschen"""
selection = self.listbox.curselection()
if not selection:
messagebox.showwarning("Warnung", "Bitte wählen Sie eine Voreinstellung aus.")
return
preset_name = self.listbox.get(selection[0])
if messagebox.askyesno("Bestätigung", f"Voreinstellung '{preset_name}' wirklich löschen?"):
preset_file = self.config_dir / f"{preset_name}.json"
preset_file.unlink()
self.refresh_list()
messagebox.showinfo("Erfolg", f"Voreinstellung '{preset_name}' gelöscht.")
def main():
root = tk.Tk()
app = CSVProcessorGUI(root)
root.mainloop()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,504 @@
{
"has_header": true,
"remove_empty_rows": true,
"remove_empty": true,
"mapping_file": "/home/sebastian.zell/py/csv_processor/mietermerkmale.json",
"output_format": "csv",
"selected_columns": [
"Objekt-Zusammenfassung",
"Ort",
"PLZ",
"Straße und Hausnummer",
"Objektname",
"Objektnummer",
"Auftraggeber",
"Aufnahmedatum",
"Erfasser",
"Freitext Erfasser",
"Wohnungsadresse",
"Geografische Breite",
"Geografische Länge",
"Höhe",
"Genauigkeit",
"Einheitsnummer",
"Mietername",
"Gebäudeart",
"Stockwerk",
"Grundriss",
"Keller vorhanden",
"Kellernummer",
"Foto Keller",
"Wintergarten vorhanden",
"Wintergarten beheizt",
"Balkone vorhanden",
"Französische Balkone",
"Aufzug erreichbar",
"Mieter anwesend",
"Aufgemessen",
"Foto Klingel",
"Fläche in qm",
"Einzug",
"Auszug",
"Wohnwert Text",
"Eingangstür",
"Gaszähler in Wohnung",
"Lage Gaszähler",
"Nummer Gaszähler",
"Stand Gaszähler",
"Foto Gaszähler",
"Kaltwasserzähler in Wohnung",
"Lage Kaltwasserzähler",
"Nummer Kaltwasserzähler",
"Stand Kaltwasserzähler",
"Foto Kaltwasserzähler",
"Warmwasserzähler vorhanden",
"Lage Warmwasserzähler",
"Nummer Warmwasserzähler",
"Stand Warmwasserzähler",
"Foto Warmwasserzähler",
"Wärmemengenzähler vorhanden",
"Wärmemengenzähler zentral",
"Lage Wärmemengenzähler",
"Nummer Wärmemengenzähler",
"Stand Wärmemengenzähler",
"Foto Wärmemengenzähler",
"Lage Wärmemengenzähler (R)",
"Nummer Wärmemengenzähler (R)",
"Stand Wärmemengenzähler (R)",
"Foto Wärmemengenzähler (R)",
"Stromzähler vorhanden",
"Lage Stromzähler",
"Nummer Stromzähler",
"Stand Stromzähler",
"Foto Stromzähler",
"Küche",
"Details Küche",
"Foto Küche",
"Bad",
"Details Bad",
"Heizung",
"Lage Therme",
"Warmwasser",
"Lage Boiler",
"Lage Durchlauferhitzer",
"Rauminformationen",
"Raum-Check",
"Zimmer 1 - Bezeichnung",
"Zimmer 1 - Fenster vorhanden",
"Zimmer 1 - Fenstertyp",
"Zimmer 1 - Boden",
"Zimmer 1 - Boden (Text)",
"Zimmer 1 - Wand",
"Zimmer 1 - Wand (Text)",
"Zimmer 1 - Decke",
"Zimmer 1 - Decke (Text)",
"Zimmer 1 - Anschlüsse",
"Zimmer 1 - Sonnenschutz",
"Zimmer 1 - Foto",
"Zimmer 1 - Bemerkungen",
"Zimmer 2 - Bemerkungen",
"Zimmer 2 - Bezeichnung",
"Zimmer 2 - Fenster vorhanden",
"Zimmer 2 - Fenstertyp",
"Zimmer 2 - Boden",
"Zimmer 2 - Boden (Text)",
"Zimmer 2 - Wand",
"Zimmer 2 - Wand (Text)",
"Zimmer 2 - Decke",
"Zimmer 2 - Decke (Text)",
"Zimmer 2 - Anschlüsse",
"Zimmer 2 - Sonnenschutz",
"Zimmer 2 - Bemerkungen",
"Zimmer 2 - Foto",
"Zimmer 3 - Bezeichnung",
"Zimmer 3 - Fenster vorhanden",
"Zimmer 3 - Fenstertyp",
"Zimmer 3 - Boden",
"Zimmer 3 - Boden (Text)",
"Zimmer 3 - Wand",
"Zimmer 3 - Wand (Text)",
"Zimmer 3 - Decke",
"Zimmer 3 - Decke (Text)",
"Zimmer 3 - Anschlüsse",
"Zimmer 3 - Sonnenschutz",
"Zimmer 3 - Bemerkungen",
"Zimmer 3 - Foto",
"Zimmer 4 - Bezeichnung",
"Zimmer 4 - Fenster vorhanden",
"Zimmer 4 - Fenstertyp",
"Zimmer 4 - Boden",
"Zimmer 4 - Boden (Text)",
"Zimmer 4 - Wand",
"Zimmer 4 - Wand (Text)",
"Zimmer 4 - Decke",
"Zimmer 4 - Decke (Text)",
"Zimmer 4 - Anschlüsse",
"Zimmer 4 - Sonnenschutz",
"Zimmer 4 - Bemerkungen",
"Zimmer 4 - Foto",
"Zimmer 5 - Bezeichnung",
"Zimmer 5 - Fenster vorhanden",
"Zimmer 5 - Fenstertyp",
"Zimmer 5 - Boden",
"Zimmer 5 - Boden (Text)",
"Zimmer 5 - Wand",
"Zimmer 5 - Wand (Text)",
"Zimmer 5 - Decke",
"Zimmer 5 - Decke (Text)",
"Zimmer 5 - Anschlüsse",
"Zimmer 5 - Sonnenschutz",
"Zimmer 5 - Bemerkungen",
"Zimmer 5 - Foto",
"Zimmer 6 - Bezeichnung",
"Zimmer 6 - Fenster vorhanden",
"Zimmer 6 - Fenstertyp",
"Zimmer 6 - Boden",
"Zimmer 6 - Boden (Text)",
"Zimmer 6 - Wand",
"Zimmer 6 - Wand (Text)",
"Zimmer 6 - Decke",
"Zimmer 6 - Decke (Text)",
"Zimmer 6 - Anschlüsse",
"Zimmer 6 - Sonnenschutz",
"Zimmer 6 - Bemerkungen",
"Zimmer 6 - Foto",
"Zimmer 7 - Bezeichnung",
"Zimmer 7 - Fenster vorhanden",
"Zimmer 7 - Fenstertyp",
"Zimmer 7 - Boden",
"Zimmer 7 - Boden (Text)",
"Zimmer 7 - Wand",
"Zimmer 7 - Wand (Text)",
"Zimmer 7 - Decke",
"Zimmer 7 - Decke (Text)",
"Zimmer 7 - Anschlüsse",
"Zimmer 7 - Sonnenschutz",
"Zimmer 7 - Bemerkungen",
"Zimmer 7 - Foto",
"Zimmer 8 - Bezeichnung",
"Zimmer 8 - Fenster vorhanden",
"Zimmer 8 - Fenstertyp",
"Zimmer 8 - Boden",
"Zimmer 8 - Boden (Text)",
"Zimmer 8 - Wand",
"Zimmer 8 - Wand (Text)",
"Zimmer 8 - Decke",
"Zimmer 8 - Decke (Text)",
"Zimmer 8 - Anschlüsse",
"Zimmer 8 - Sonnenschutz",
"Zimmer 8 - Bemerkungen",
"Zimmer 8 - Foto",
"Zimmer 9 - Bezeichnung",
"Zimmer 9 - Fenster vorhanden",
"Zimmer 9 - Fenstertyp",
"Zimmer 9 - Boden",
"Zimmer 9 - Boden (Text)",
"Zimmer 9 - Wand",
"Zimmer 9 - Wand (Text)",
"Zimmer 9 - Decke",
"Zimmer 9 - Decke (Text)",
"Zimmer 9 - Anschlüsse",
"Zimmer 9 - Sonnenschutz",
"Zimmer 9 - Bemerkungen",
"Zimmer 9 - Foto",
"Küche - Bezeichnung",
"Küche - Fenster vorhanden",
"Küche - Fenstertyp",
"Küche - Boden",
"Küche - Boden (Text)",
"Küche - Wand",
"Küche - Wand (Text)",
"Küche - Decke",
"Küche - Decke (Text)",
"Küche - Anschlüsse",
"Küche - Sonnenschutz",
"Küche - Bemerkungen",
"Küche - Foto",
"Bad - Bezeichnung",
"Bad - Fenster vorhanden",
"Bad - Fenstertyp",
"Bad - Boden",
"Bad - Boden (Text)",
"Bad - Wand",
"Bad - Wand (Text)",
"Bad - Decke",
"Bad - Decke (Text)",
"Bad - Anschlüsse",
"Bad - Sonnenschutz",
"Bad - Bemerkungen",
"Bad - Foto",
"Flur - Bezeichnung",
"Flur - Fenster vorhanden",
"Flur - Fenstertyp",
"Flur - Boden",
"Flur - Boden (Text)",
"Flur - Wand",
"Flur - Wand (Text)",
"Flur - Decke",
"Flur - Decke (Text)",
"Flur - Anschlüsse",
"Flur - Sonnenschutz",
"Flur - Bemerkungen",
"Flur - Foto",
"Abstellraum - Bezeichnung",
"Abstellraum - Fenster vorhanden",
"Abstellraum - Fenstertyp",
"Abstellraum - Boden",
"Abstellraum - Boden (Text)",
"Abstellraum - Wand",
"Abstellraum - Wand (Text)",
"Abstellraum - Decke",
"Abstellraum - Decke (Text)",
"Abstellraum - Anschlüsse",
"Abstellraum - Sonnenschutz",
"Abstellraum - Bemerkungen",
"Abstellraum - Foto",
"WC separat - Bezeichnung",
"WC separat - Fenster vorhanden",
"WC separat - Fenstertyp",
"WC separat - Boden",
"WC separat - Boden (Text)",
"WC separat - Wand",
"WC separat - Wand (Text)",
"WC separat - Decke",
"grp_kammer-grp_wc_separat-wc_separat_decke_text",
"WC separat - Anschlüsse",
"WC separat - Sonnenschutz",
"WC separat - Bemerkungen",
"WC separat - Foto",
"Kammer - Bezeichnung",
"Kammer - Fenster vorhanden",
"Kammer - Fenstertyp",
"Kammer - Boden",
"Kammer - Boden (Text)",
"Kammer - Wand",
"Kammer - Wand (Text)",
"Kammer - Decke",
"Kammer - Decke (Text)",
"Kammer - Anschlüsse",
"Kammer - Sonnenschutz",
"Kammer - Bemerkungen",
"Kammer - Foto",
"Allgemeine Fotos",
"Besonderheiten",
"E-Mail senden",
"E-Mail-Empfänger",
"Unterschrift nötig",
"Unterschrift Kunde",
"Unterschrift Mitarbeiter",
"Fertig",
"Kellergruppe",
"Instanz-ID",
"Instanz-Name",
"Entitäts-Label",
"Wohnzimmer - Bezeichnung",
"Wohnzimmer - Fenster vorhanden",
"Wohnzimmer - Fenstertyp",
"Wohnzimmer - Boden",
"Wohnzimmer - Boden (Text)",
"Wohnzimmer - Wand",
"Wohnzimmer - Wand (Text)",
"Wohnzimmer - Decke",
"Wohnzimmer - Decke (Text)",
"Wohnzimmer - Anschlüsse",
"Wohnzimmer - Sonnenschutz",
"Wohnzimmer - Bemerkungen",
"Wohnzimmer - Foto",
"Schlafzimmer - Bezeichnung",
"Schlafzimmer - Fenster vorhanden",
"Schlafzimmer - Fenstertyp",
"Schlafzimmer - Boden",
"Schlafzimmer - Boden (Text)",
"Schlafzimmer - Wand",
"Schlafzimmer - Wand (Text)",
"Schlafzimmer - Decke",
"Schlafzimmer - Decke (Text)",
"Schlafzimmer - Anschlüsse",
"Schlafzimmer - Sonnenschutz",
"Schlafzimmer - Bemerkungen",
"Schlafzimmer - Foto",
"Kinderzimmer - Bezeichnung",
"Kinderzimmer - Fenster vorhanden",
"Kinderzimmer - Fenstertyp",
"Kinderzimmer - Boden",
"Kinderzimmer - Boden (Text)",
"Kinderzimmer - Wand",
"Kinderzimmer - Wand (Text)",
"Kinderzimmer - Decke",
"Kinderzimmer - Decke (Text)",
"Kinderzimmer - Anschlüsse",
"Kinderzimmer - Sonnenschutz",
"Kinderzimmer - Bemerkungen",
"Kinderzimmer - Foto",
"Küche - Bezeichnung",
"Küche - Fenster vorhanden",
"Küche - Fenstertyp",
"Küche - Boden",
"Küche - Boden (Text)",
"Küche - Wand",
"Küche - Wand (Text)",
"Küche - Decke",
"Küche - Decke (Text)",
"Küche - Anschlüsse",
"Küche - Sonnenschutz",
"Küche - Bemerkungen",
"Küche - Foto",
"Bad - Bezeichnung",
"Bad - Fenster vorhanden",
"Bad - Fenstertyp",
"Bad - Boden",
"Bad - Boden (Text)",
"Bad - Wand",
"Bad - Wand (Text)",
"Bad - Decke",
"Bad - Decke (Text)",
"Bad - Anschlüsse",
"Bad - Sonnenschutz",
"Bad - Bemerkungen",
"Bad - Foto",
"Flur - Bezeichnung",
"Flur - Fenster vorhanden",
"Flur - Fenstertyp",
"Flur - Boden",
"Flur - Boden (Text)",
"Flur - Wand",
"Flur - Wand (Text)",
"Flur - Decke",
"Flur - Decke (Text)",
"Flur - Anschlüsse",
"Flur - Sonnenschutz",
"Flur - Bemerkungen",
"Flur - Foto",
"Abstellraum - Bezeichnung",
"Abstellraum - Fenster vorhanden",
"Abstellraum - Fenstertyp",
"Abstellraum - Boden",
"Abstellraum - Boden (Text)",
"Abstellraum - Wand",
"Abstellraum - Wand (Text)",
"Abstellraum - Decke",
"Abstellraum - Decke (Text)",
"Abstellraum - Anschlüsse",
"Abstellraum - Sonnenschutz",
"Abstellraum - Bemerkungen",
"Abstellraum - Foto",
"WC separat - Bezeichnung",
"WC separat - Fenster vorhanden",
"WC separat - Fenstertyp",
"WC separat - Boden",
"WC separat - Boden (Text)",
"WC separat - Wand",
"WC separat - Wand (Text)",
"WC separat - Decke",
"WC separat - Decke (Text)",
"WC separat - Anschlüsse",
"WC separat - Sonnenschutz",
"WC separat - Bemerkungen",
"WC separat - Foto",
"Waschküche - Bezeichnung",
"Waschküche - Fenster vorhanden",
"Waschküche - Fenstertyp",
"Waschküche - Boden",
"Waschküche - Boden (Text)",
"Waschküche - Wand",
"Waschküche - Wand (Text)",
"Waschküche - Decke",
"Waschküche - Decke (Text)",
"Waschküche - Anschlüsse",
"Waschküche - Sonnenschutz",
"Waschküche - Bemerkungen",
"Waschküche - Foto",
"Hauswirtschaftsraum - Bezeichnung",
"Hauswirtschaftsraum - Fenster vorhanden",
"Hauswirtschaftsraum - Fenstertyp",
"Hauswirtschaftsraum - Boden",
"Hauswirtschaftsraum - Boden (Text)",
"Hauswirtschaftsraum - Wand",
"Hauswirtschaftsraum - Wand (Text)",
"Hauswirtschaftsraum - Decke",
"Hauswirtschaftsraum - Decke (Text)",
"Hauswirtschaftsraum - Anschlüsse",
"Hauswirtschaftsraum - Sonnenschutz",
"Hauswirtschaftsraum - Bemerkungen",
"Hauswirtschaftsraum - Foto",
"Technikraum - Bezeichnung",
"Technikraum - Fenster vorhanden",
"Technikraum - Fenstertyp",
"Technikraum - Boden",
"Technikraum - Boden (Text)",
"Technikraum - Wand",
"Technikraum - Wand (Text)",
"Technikraum - Decke",
"Technikraum - Decke (Text)",
"Technikraum - Anschlüsse",
"Technikraum - Sonnenschutz",
"Technikraum - Bemerkungen",
"Technikraum - Foto",
"Speisekammer - Bezeichnung",
"Speisekammer - Fenster vorhanden",
"Speisekammer - Fenstertyp",
"Speisekammer - Boden",
"Speisekammer - Boden (Text)",
"Speisekammer - Wand",
"Speisekammer - Wand (Text)",
"Speisekammer - Decke",
"Speisekammer - Decke (Text)",
"Speisekammer - Anschlüsse",
"Speisekammer - Sonnenschutz",
"Speisekammer - Bemerkungen",
"Speisekammer - Foto",
"Heizungsraum - Bezeichnung",
"Heizungsraum - Fenster vorhanden",
"Heizungsraum - Fenstertyp",
"Heizungsraum - Boden",
"Heizungsraum - Boden (Text)",
"Heizungsraum - Wand",
"Heizungsraum - Wand (Text)",
"Heizungsraum - Decke",
"Heizungsraum - Decke (Text)",
"Heizungsraum - Anschlüsse",
"Heizungsraum - Sonnenschutz",
"Heizungsraum - Bemerkungen",
"Heizungsraum - Foto",
"Durchgangszimmer - Bezeichnung",
"Durchgangszimmer - Fenster vorhanden",
"Durchgangszimmer - Fenstertyp",
"Durchgangszimmer - Boden",
"Durchgangszimmer - Boden (Text)",
"Durchgangszimmer - Wand",
"Durchgangszimmer - Wand (Text)",
"Durchgangszimmer - Decke",
"Durchgangszimmer - Decke (Text)",
"Durchgangszimmer - Anschlüsse",
"Durchgangszimmer - Sonnenschutz",
"Durchgangszimmer - Bemerkungen",
"Durchgangszimmer - Foto",
"Esszimmer - Bezeichnung",
"Esszimmer - Fenster vorhanden",
"Esszimmer - Fenstertyp",
"Esszimmer - Boden",
"Esszimmer - Boden (Text)",
"Esszimmer - Wand",
"Esszimmer - Wand (Text)",
"Esszimmer - Decke",
"Esszimmer - Decke (Text)",
"Esszimmer - Anschlüsse",
"Esszimmer - Sonnenschutz",
"Esszimmer - Bemerkungen",
"Esszimmer - Foto",
"Arbeitszimmer - Bezeichnung",
"Arbeitszimmer - Fenster vorhanden",
"Arbeitszimmer - Fenstertyp",
"Arbeitszimmer - Boden",
"Arbeitszimmer - Boden (Text)",
"Arbeitszimmer - Wand",
"Arbeitszimmer - Wand (Text)",
"Arbeitszimmer - Decke",
"Arbeitszimmer - Decke (Text)",
"Arbeitszimmer - Anschlüsse",
"Arbeitszimmer - Sonnenschutz",
"Arbeitszimmer - Bemerkungen",
"Arbeitszimmer - Foto"
],
"source_file": "/home/sebastian.zell/Downloads/stammdaten_wohnungen(3).csv"
}