Dateien nach "csv_processor" hochladen
This commit is contained in:
commit
a4c5ed3308
|
|
@ -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
|
||||
|
||||

|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 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
|
|
@ -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()
|
||||
|
|
@ -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"
|
||||
}
|
||||
Loading…
Reference in New Issue