Migrate to GitLab

This commit is contained in:
jonasgaudian
2026-02-12 09:51:22 +01:00
commit d7f49197c0
57 changed files with 9838 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.pyc
excel_filter/build/
excel_filter/dist/

7
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

151
README.md Normal file
View File

@@ -0,0 +1,151 @@
# Excel Filter Tool
A Python application for filtering Excel files based on regex patterns with both CLI and GUI interfaces.
## Overview
The Excel Filter Tool allows you to:
- Analyze Excel files without opening them
- Filter rows based on configurable regex patterns
- Create new Excel files with filtered data
- Use a graphical interface or command line
- Configure settings via JSON files
## Quick Start
### Windows Users (Recommended)
**Pre-built installer available!**
Download and run `ExcelFilterSetup.exe` from the `dist/` directory for automatic installation including Python and all dependencies.
**OR** use the provided batch files:
- `launch_gui_tkinter.bat` - Launches the Tkinter GUI (recommended)
- `launch_gui.bat` - Launches the PySimpleGUI interface
- `launch_cli.bat` - Launches the command line version
### macOS/Linux Users
```bash
cd excel_filter
pip install -r requirements.txt
python -m src.excel_filter.gui
```
For CLI usage:
```bash
python -m src.excel_filter.main --input input.xlsx --output output.xlsx --pattern "error|warning"
```
### Command Line Usage
```bash
python -m src.excel_filter.main --input input.xlsx --output output.xlsx --pattern "error|warning"
```
### Graphical Interface
```bash
python -m src.excel_filter.gui
```
## Usage Guide
### Basic Filtering
```bash
python -m src.excel_filter.main --input data.xlsx --output filtered.xlsx --pattern "error|warning|critical"
```
### Filter Specific Columns
```bash
python -m src.excel_filter.main --input data.xlsx --output filtered.xlsx --pattern "active" --columns "Status Message"
```
### Using Configuration Files
Create a `config.json` file:
```json
{
"input_file": "input.xlsx",
"output_file": "output_filtered.xlsx",
"pattern": "error|warning|critical",
"sheet_name": "Sheet1",
"columns": ["Status", "Message", "Description"]
}
```
Then run:
```bash
python -m src.excel_filter.main --config config.json
```
## Regex Patterns
The tool uses Python's `re` module. Examples:
- `error|warning` - Match "error" or "warning"
- `^A.*` - Lines starting with "A"
- `\d{3}` - Three-digit numbers
- `[A-Z].*` - Lines starting with capital letter
## Packaging & Distribution
### Build Executable
```bash
pip install pyinstaller pandas openpyxl customtkinter
python build_executable.py
```
### Create Installer
```bash
double-click build_installer.bat
```
This creates `ExcelFilterSetup.exe` that:
- Installs the application as a native Windows executable
- Automatically installs Python and dependencies if needed
- Creates desktop and Start menu shortcuts
## Technical Details
```
### Key Components
- **ExcelFilter Class**: Core filtering functionality
- **CLI Interface**: Command line processing
- **GUI Interface**: Graphical interface
- **Configuration Management**: JSON-based settings
### Dependencies
- `openpyxl` - Excel file operations
- `pandas` - Data manipulation
- `customtkinter` - GUI interface
## Examples
### Filter Log Files
```bash
python -m src.excel_filter.main --input logs.xlsx --output errors.xlsx --pattern "error|warning"
```
### Filter Customer Data
```bash
python -m src.excel_filter.main --input customers.xlsx --output active_customers.xlsx --pattern "active" --columns Status
```
### Filter Numeric Data
```bash
python -m src.excel_filter.main --input sales.xlsx --output high_sales.xlsx --pattern "1000.*" --columns Amount
```

1
excel_filter/.env Normal file
View File

@@ -0,0 +1 @@
PYTHONPATH=src

10
excel_filter/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"python.testing.pytestArgs": [
"--rootdir=.",
"tests"
],
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false,
"python.envFile": "${workspaceFolder}/.env",
"python.defaultInterpreterPath": "python"
}

View File

@@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['gui_new.py'],
pathex=[],
binaries=[],
datas=[('app_icon.ico', '.'), ('check_python.py', '.'), ('check_environment.bat', '.'), ('LICENSE.txt', '.'), ('locales', 'locales')],
hiddenimports=['openpyxl', 'pandas.io.excel._openpyxl', 'pandas.io.excel._base', 'pandas.io.common', 'tkinter', 'tkinter.filedialog', 'tkinter.messagebox', 'tkinter.ttk'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='ExcelFilter',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['app_icon.ico'],
)

52
excel_filter/LICENSE.txt Normal file
View File

@@ -0,0 +1,52 @@
Excel Filter Tool - License Agreement
======================================
Allgemeine Bestimmungen (die niemand liest)
Herzlich willkommen zu "Excel Tool" dem Tool, das Ihre Tabellenkalkulationen revolutionieren wird (oder auch nicht). Durch die Installation dieses Tools erklären Sie sich damit einverstanden, dass wir Ihre Seele nicht stehlen, Ihre Katze nicht entführen und Ihre Kaffeetasse nicht heimlich umstellen. Sollten Sie diese Vereinbarung nicht akzeptieren, installieren Sie das Tool trotzdem, denn wir wissen beide: Sie klicken eh einfach auf "Weiter".
Nutzungsrechte (oder: Was Sie tun dürfen, ohne dass wir Sie verklagen)
Sie dürfen "Excel Tool" nutzen, um Zahlen zu filtern, zu löschen oder einfach nur anzustarren.
Sie dürfen das Tool auf maximal 37 Geräten installieren oder auf 42, wenn Sie an einem Dienstag geboren sind.
Sie dürfen das Tool nicht nutzen, um:
Die Weltherrschaft zu erlangen.
Ihren Chef davon zu überzeugen, dass Sie eigentlich mehr Gehalt verdienen.
Toast zu rösten. Dafür gibt es Toaster.
Haftungsausschluss (weil Anwälte das mögen)
Wir haften nicht für:
Plötzliche Lust auf Käse, die während der Nutzung auftritt.
Die Erkenntnis, dass Ihr Leben vielleicht doch nicht in Excel-Tabellen organisiert werden sollte.
Falls Ihr Computer nach der Installation anfängt, in Reimen zu sprechen (siehe Abschnitt 5).
Datenschutz (oder: Was wir mit Ihren Daten machen Spoiler: Nichts!)
Wir sammeln keine Daten. Außer vielleicht Ihre Lieblingsfarbe.
Das obligatorische Gedicht
"Excel, Excel, wunderbar,
voll mit Zahlen, wunderbar.
Doch wenn die Formel flucht,
ists mit der Freude flugs verflucht."
Gewährleistung (oder: Warum wir nichts versprechen)
"Excel Tool" funktioniert garantiert außer bei Vollmond, wenn Merkur rückläufig ist oder Ihr WLAN mal wieder spinnt. In diesen Fällen empfehlen wir: Einfach neu starten. Oder beten.
Kündigung (weil jeder Vertrag eine braucht)
Diese Lizenz endet automatisch, wenn:
Sie das Tool löschen (oder vergessen, wo Sie es gespeichert haben).
Die Menschheit von KI überrannt wird (dann haben wir andere Sorgen).
Sie beschließen, dass Tabellen doch überbewertet sind.
Sonstiges (der Teil, den wirklich niemand liest)
Sollten Sie diese Lizenz ausdrucken und als Tapete verwenden, schicken Sie uns bitte ein Foto.
Dieser Vertrag ist in allen Universen gültig außer in dem, in dem Hunde die Herrschaft übernommen haben.
Durch Weiterklicken bestätigen Sie, dass Sie diesen Text gelesen haben (wir wissen, dass Sie das nicht getan haben).

1
excel_filter/__init__.py Normal file
View File

@@ -0,0 +1 @@
""

BIN
excel_filter/app_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python
"""
Script to build the Excel Filter application into a standalone executable using PyInstaller.
"""
import os
import subprocess
import sys
# Define the main script to be converted
MAIN_SCRIPT = "gui_new.py"
# Define the output directory for the executable
OUTPUT_DIR = "dist"
# Define the name of the executable
EXECUTABLE_NAME = "ExcelFilter"
# Define additional files to include (e.g., icons, scripts, etc.)
# Note: config.json and presets.json are no longer included as they are now stored
# in the user's data directory (AppData on Windows) to avoid permission issues
ADDITIONAL_FILES = [
("app_icon.ico", "."),
("check_python.py", "."),
("check_environment.bat", "."),
("LICENSE.txt", "."),
("locales", "locales"),
]
# Define hidden imports (if any)
HIDDEN_IMPORTS = [
"openpyxl",
"pandas.io.excel._openpyxl",
"pandas.io.excel._base",
"pandas.io.common",
"tkinter",
"tkinter.filedialog",
"tkinter.messagebox",
"tkinter.ttk"
]
# Define excluded modules (if any)
EXCLUDED_MODULES = []
# Define the PyInstaller command
pyinstaller_command = [
"pyinstaller",
"--onefile", # Create a single executable file
"--windowed", # Prevent console from showing (for GUI applications)
f"--name={EXECUTABLE_NAME}", # Name of the executable
f"--distpath={OUTPUT_DIR}", # Output directory
"--icon=app_icon.ico", # Set the icon for the executable
MAIN_SCRIPT, # Main script to convert
]
# Add the additional files using the --add-data option
for src, dest in ADDITIONAL_FILES:
pyinstaller_command.append(f"--add-data={src};{dest}")
# Add hidden imports
for hidden_import in HIDDEN_IMPORTS:
pyinstaller_command.append(f"--hidden-import={hidden_import}")
# Filter out empty strings from the command
pyinstaller_command = [cmd for cmd in pyinstaller_command if cmd]
# Run the PyInstaller command
print("Building the executable...")
print("Command:", " ".join(pyinstaller_command))
try:
# Use python -m PyInstaller to ensure PyInstaller is found
pyinstaller_full_command = [sys.executable, "-m", "PyInstaller"] + pyinstaller_command[1:]
print("Full command:", " ".join(pyinstaller_full_command))
subprocess.run(pyinstaller_full_command, check=True)
print("Executable built successfully!")
except subprocess.CalledProcessError as e:
print(f"Error building the executable: {e}")
sys.exit(1)
except Exception as e:
print(f"An unexpected error occurred: {e}")
sys.exit(1)
# Clean up the build directory (optional)
def cleanup_build_directory():
"""Attempt to clean up the build directory with multiple strategies"""
if os.path.exists("build"):
import shutil
import time
import stat
# Try multiple cleanup strategies
for attempt in range(3):
try:
# First, try to make files writable
for root, dirs, files in os.walk("build"):
for d in dirs:
try:
os.chmod(os.path.join(root, d), stat.S_IWRITE)
except:
pass
for f in files:
try:
os.chmod(os.path.join(root, f), stat.S_IWRITE)
except:
pass
# Now try to remove the directory
shutil.rmtree("build")
print("Build directory cleaned up.")
return True
except Exception as e:
if attempt < 2: # Don't wait on last attempt
time.sleep(1) # Wait a bit and try again
else:
print(f"Warning: Could not clean up build directory: {e}")
print("You can manually delete the 'build' directory if needed.")
return False
return True
# Attempt cleanup
cleanup_build_directory()
print("Done!")

View File

@@ -0,0 +1,111 @@
@echo off
setlocal enabledelayedexpansion
:: Check if Python is installed
python --version >nul 2>&1
if %errorlevel% neq 0 (
echo Python is not installed. Please install Python first.
pause
exit /b 1
)
:: Install PyInstaller if not already installed
python -m pip show pyinstaller >nul 2>&1
if %errorlevel% neq 0 (
echo Installing PyInstaller...
python -m pip install pyinstaller
if %errorlevel% neq 0 (
echo Failed to install PyInstaller.
pause
exit /b 1
)
)
:: Build the executable
python build_executable.py
if %errorlevel% neq 0 (
echo Failed to build the executable.
pause
exit /b 1
)
:: Find Inno Setup installation path
set ISCC_PATH=
:: Check if ISCC_PATH is already set in environment variables
if defined ISCC_PATH (
if exist "%ISCC_PATH%" (
goto :found_iscc
)
)
:: Check common installation paths
set "common_paths=C:\Program Files (x86)\Inno Setup 6
C:\Program Files\Inno Setup 6
C:\Program Files (x86)\Inno Setup 5
C:\Program Files\Inno Setup 5
C:\Inno Setup 6
C:\Inno Setup 5"
for %%p in (%common_paths%) do (
if exist "%%p\ISCC.exe" (
set "ISCC_PATH=%%p\ISCC.exe"
goto :found_iscc
)
)
:: Check for any Inno Setup folder in Program Files directories
for /d %%d in ("C:\Program Files (x86)\Inno Setup *") do (
if exist "%%d\ISCC.exe" (
set "ISCC_PATH=%%d\ISCC.exe"
goto :found_iscc
)
)
for /d %%d in ("C:\Program Files\Inno Setup *") do (
if exist "%%d\ISCC.exe" (
set "ISCC_PATH=%%d\ISCC.exe"
goto :found_iscc
)
)
:: Try to find ISCC.exe in system PATH
where ISCC.exe >nul 2>&1
if %errorlevel% equ 0 (
set "ISCC_PATH=ISCC.exe"
goto :found_iscc
)
:found_iscc
:: Debug: Show what path was found
if defined ISCC_PATH (
echo Debug: Found ISCC.exe at "%ISCC_PATH%"
)
if "%ISCC_PATH%"=="" (
echo Inno Setup is not installed. Please install Inno Setup first.
echo.
echo The script looked for ISCC.exe in:
echo - Environment variables
echo - Common installation paths
echo - Program Files directories
echo - System PATH
echo.
echo If Inno Setup is installed in a custom location, please:
echo 1. Set the ISCC_PATH environment variable to point to ISCC.exe
echo 2. Or add the Inno Setup directory to your system PATH
pause
exit /b 1
)
:: Create the installer
"%ISCC_PATH%" create_installer.iss
if %errorlevel% neq 0 (
echo Failed to create the installer.
pause
exit /b 1
)
echo Installer created successfully!
pause

View File

@@ -0,0 +1,61 @@
@echo off
:: Excel Filter Tool - Abhaengigkeitspruefung
:: Diese Datei ueberprueft und installiert Abhaengigkeiten nur einmal
:: Wechsel in das Projektverzeichnis
cd /d "%~dp0"
:: Marker-Datei fuer erfolgreiche Installation
set MARKER_FILE=dependencies_installed.marker
:: Ueberpruefen, ob die Marker-Datei existiert
if exist "%MARKER_FILE%" (
:: Marker-Datei existiert - Abhaengigkeiten sollten installiert sein
exit /b 0
)
:: Ueberpruefen, ob Python installiert ist
where python >nul 2>nul
if not "%ERRORLEVEL%"=="0" (
echo Fehler: Python ist nicht installiert oder nicht im PATH.
echo Bitte installieren Sie Python 3.7 oder hoeher.
exit /b 1
)
:: Versuchen, die Hauptmodule zu importieren
python -c "import gui_new; import main" >nul 2>nul
if "%ERRORLEVEL%"=="0" (
:: Module koennen importiert werden - Abhaengigkeiten sind installiert
:: Marker-Datei erstellen
echo. > "%MARKER_FILE%"
exit /b 0
)
:: Abhaengigkeiten installieren
echo Installiere Abhaengigkeiten...
echo Dies geschieht nur einmal beim ersten Start.
echo.
pip install -r requirements.txt
if not "%ERRORLEVEL%"=="0" (
echo.
echo Fehler: Konnte Abhaengigkeiten nicht installieren.
echo.
echo Moegliche Loesungen:
echo 1. Versuchen Sie, pip zu aktualisieren:
echo pip install --upgrade pip
echo 2. Installieren Sie die Abhaengigkeiten manuell:
echo pip install openpyxl pandas PySimpleGUI python-docx
echo 3. Verwenden Sie eine virtuelle Umgebung:
echo python -m venv venv
echo venv\Scripts\activate
echo pip install -r requirements.txt
echo.
pause
exit /b 1
)
:: Marker-Datei erstellen, um zukuenftige Installationen zu vermeiden
echo. > "%MARKER_FILE%"
echo Abhaengigkeiten erfolgreich installiert.
exit /b 0

View File

@@ -0,0 +1,54 @@
@echo off
:: Einfache Abhaengigkeitspruefung
:: Diese Version vermeidet komplexe if-Bedingungen
:: Wechsel in das Projektverzeichnis
cd /d "%~dp0"
:: Marker-Datei fuer erfolgreiche Installation
set MARKER_FILE=dependencies_installed.marker
:: Ueberpruefen, ob die Marker-Datei existiert
if exist "%MARKER_FILE%" goto dependencies_ok
:: Ueberpruefen, ob Python installiert ist
where python >nul 2>nul
if errorlevel 1 (
echo Fehler: Python ist nicht installiert oder nicht im PATH.
echo Bitte installieren Sie Python 3.7 oder hoeher.
exit /b 1
)
:: Versuchen, die Hauptmodule zu importieren
python -c "import gui_new; import main" >nul 2>nul
if errorlevel 1 goto install_dependencies
:: Marker-Datei erstellen
echo. > "%MARKER_FILE%"
goto dependencies_ok
:install_dependencies
echo Installiere Abhaengigkeiten...
echo Dies geschieht nur einmal beim ersten Start.
echo.
pip install -r requirements.txt
if errorlevel 1 (
echo.
echo Fehler: Konnte Abhaengigkeiten nicht installieren.
echo.
echo Moegliche Loesungen:
echo 1. pip install --upgrade pip
echo 2. pip install openpyxl pandas PySimpleGUI python-docx
echo 3. Verwenden Sie eine virtuelle Umgebung
echo.
pause
exit /b 1
)
:: Marker-Datei erstellen
echo. > "%MARKER_FILE%"
echo Abhaengigkeiten erfolgreich installiert.
:dependencies_ok
exit /b 0

View File

@@ -0,0 +1,111 @@
@echo off
setlocal enabledelayedexpansion
:: Excel Filter Tool - Environment Check
:: This script checks if Python is available and provides appropriate guidance
echo Excel Filter Tool - Environment Check
call :log_message "Checking system environment..."
:: Check if Python is available
call :find_python
if "!PYTHON_FOUND!" == "1" (
call :log_message "Python found: !PYTHON_PATH!"
call :run_python_check
) else (
call :log_message "Python not found on this system"
call :show_python_instructions
)
endlocal
goto :eof
:find_python
set PYTHON_FOUND=0
set PYTHON_PATH=
:: Method 1: Check if python is in PATH
call :log_message "Checking PATH for Python..."
where python >nul 2>&1
if !errorlevel! equ 0 (
set PYTHON_FOUND=1
set PYTHON_PATH=python
goto :eof
)
:: Method 2: Check common Python installation locations
call :log_message "Checking common Python installation locations..."
for %%P in (
"%ProgramFiles%\Python*\python.exe",
"%ProgramFiles(x86)%\Python*\python.exe",
"%LocalAppData%\Programs\Python*\python.exe",
"%UserProfile%\AppData\Local\Microsoft\WindowsApps\python.exe",
"%UserProfile%\AppData\Local\Programs\Python*\python.exe"
) do (
if exist %%P (
set PYTHON_FOUND=1
set PYTHON_PATH=%%P
call :log_message "Found Python at: %%P"
goto :eof
)
)
goto :eof
:run_python_check
call :log_message "Running Python environment check..."
:: Run the Python check script
"!PYTHON_PATH!" "%~dp0check_python.py"
if !errorlevel! equ 0 (
call :log_message "Python environment check completed successfully"
) else (
call :log_message "Python environment check failed"
call :show_python_instructions
)
goto :eof
:show_python_instructions
call :log_message "========================================"
call :log_message "Python is required to run Excel Filter Tool"
call :log_message "========================================"
call :log_message ""
call :log_message "Please install Python 3.9 or later:"
call :log_message "1. Download Python from: https://www.python.org/downloads/"
call :log_message "2. Run the installer"
call :log_message "3. Make sure to check 'Add Python to PATH' during installation"
call :log_message "4. Restart your computer after installation"
call :log_message "5. Run Excel Filter Tool again"
call :log_message ""
call :log_message "For technical users:"
call :log_message "- Recommended version: Python 3.9+"
call :log_message "- Architecture: 64-bit (recommended)"
call :log_message "- Required packages: pandas, openpyxl, customtkinter"
call :log_message ""
:: Create a help file with instructions
set HELP_FILE=%TEMP%\ExcelFilter_Python_Help.txt
echo Excel Filter Tool - Python Installation Instructions > "!HELP_FILE!"
echo. >> "!HELP_FILE!"
echo Python is required to run the Excel Filter Tool. >> "!HELP_FILE!"
echo. >> "!HELP_FILE!"
echo Installation Steps: >> "!HELP_FILE!"
echo 1. Download Python from: https://www.python.org/downloads/ >> "!HELP_FILE!"
echo 2. Run the Python installer >> "!HELP_FILE!"
echo 3. Check "Add Python to PATH" during installation >> "!HELP_FILE!"
echo 4. Complete the installation >> "!HELP_FILE!"
echo 5. Restart your computer >> "!HELP_FILE!"
echo 6. Run Excel Filter Tool again >> "!HELP_FILE!"
echo. >> "!HELP_FILE!"
echo Note: Excel Filter Tool requires Python 3.9 or later. >> "!HELP_FILE!"
:: Show the help file to the user
notepad "!HELP_FILE!"
goto :eof
:log_message
:: Helper function to log messages with timestamp
echo [%TIME%] %*
goto :eof

View File

@@ -0,0 +1,130 @@
#!/usr/bin/env python
"""
Script to check if Python is installed and verify dependencies.
This script assumes Python is already available and focuses on dependency checking.
"""
import os
import subprocess
import sys
import pkg_resources
def check_python_version():
"""Check Python version and ensure it meets minimum requirements."""
try:
result = subprocess.run([sys.executable, "--version"], capture_output=True, text=True, check=True)
version_str = result.stdout.strip().replace("Python", "").strip()
# Parse version
major, minor, _ = map(int, version_str.split('.'))
if major < 3 or (major == 3 and minor < 9):
print(f"[ERROR] Python {version_str} is too old. Excel Filter Tool requires Python 3.9 or later.")
return False
else:
print(f"[SUCCESS] Python {version_str} is compatible.")
return True
except Exception as e:
print(f"[ERROR] checking Python version: {e}")
return False
def check_dependencies():
"""Check if all required Python packages are installed."""
required_packages = {
'pandas': 'Data analysis library',
'openpyxl': 'Excel file handling',
'customtkinter': 'Modern UI components',
}
missing_packages = []
print("Checking required Python packages...")
for package, description in required_packages.items():
try:
pkg_resources.get_distribution(package)
print(f"[SUCCESS] {package} ({description}) - installed")
except pkg_resources.DistributionNotFound:
print(f"[ERROR] {package} ({description}) - NOT installed")
missing_packages.append(package)
return missing_packages
def install_missing_dependencies(missing_packages):
"""Install missing Python packages."""
if not missing_packages:
return True
print(f"\n🔧 Installing {len(missing_packages)} missing package(s)...")
try:
for package in missing_packages:
print(f"Installing {package}...")
subprocess.run([sys.executable, "-m", "pip", "install", package], check=True)
print(f"[SUCCESS] {package} installed successfully")
return True
except subprocess.CalledProcessError as e:
print(f"[ERROR] Failed to install packages: {e}")
return False
except Exception as e:
print(f"[ERROR] Unexpected error during installation: {e}")
return False
def check_tkinter():
"""Check if Tkinter is available (it's part of standard library but sometimes missing)."""
try:
import tkinter
from tkinter import ttk
print("[SUCCESS] Tkinter (GUI library) - available")
return True
except ImportError:
print("[ERROR] Tkinter (GUI library) - NOT available")
print(" Note: Tkinter is required for the graphical interface")
return False
except Exception as e:
print(f"[ERROR] Error checking Tkinter: {e}")
return False
def main():
"""Main function to check Python environment."""
print("Excel Filter Tool - Python Environment Check")
print("=" * 50)
# Check Python version
python_ok = check_python_version()
if not python_ok:
print("\nPlease install Python 3.9 or later from:")
print(" https://www.python.org/downloads/")
print(" Make sure to check 'Add Python to PATH' during installation")
return False
# Check Tkinter
tkinter_ok = check_tkinter()
# Check other dependencies
missing_packages = check_dependencies()
# Install missing dependencies if any
if missing_packages:
install_success = install_missing_dependencies(missing_packages)
if not install_success:
print(f"\n[WARNING] Some dependencies could not be installed automatically")
print(" You may need to install them manually using:")
print(" pip install " + " ".join(missing_packages))
return False
# Final summary
print("\n" + "=" * 50)
if python_ok and tkinter_ok and not missing_packages:
print("[SUCCESS] All checks passed! Excel Filter Tool is ready to use.")
return True
else:
print("[WARNING] Some issues were found. Please review the messages above.")
return False
if __name__ == "__main__":
success = main()
print(f"\nEnvironment check completed with status: {'SUCCESS' if success else 'ISSUES_FOUND'}")
sys.exit(0 if success else 1)

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os
from PIL import Image, ImageDraw
# Create a simple Windows 11 style icon
def create_icon():
# Create a 256x256 image with transparent background
icon_size = 256
icon = Image.new('RGBA', (icon_size, icon_size), (0, 0, 0, 0))
draw = ImageDraw.Draw(icon)
# Draw Windows 11 style icon (blue square with white filter symbol)
# Background square
draw.rounded_rectangle([(50, 50), (206, 206)], radius=20, fill='#0078d4')
# White filter symbol
draw.rounded_rectangle([(80, 80), (226, 120)], radius=5, fill='white')
draw.rounded_rectangle([(80, 130), (226, 170)], radius=5, fill='white')
draw.rounded_rectangle([(80, 180), (226, 220)], radius=5, fill='white')
# Save as ICO
icon.save('app_icon.ico', format='ICO', sizes=[(256, 256), (128, 128), (64, 64), (32, 32), (16, 16)])
print("Icon created successfully!")
if __name__ == "__main__":
create_icon()

View File

@@ -0,0 +1,55 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Excel Filter"
#define MyAppVersion "1.1"
#define MyAppPublisher "Jonas Gaudian"
#define MyAppURL "https://www.gaudian.eu/"
#define MyAppExeName "ExcelFilter.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{BAF687B9-1B85-4DA4-810A-ABC6172B3765}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
LicenseFile=LICENSE.txt
OutputDir=.
OutputBaseFilename=ExcelFilterSetup
SetupIconFile=app_icon.ico
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "german"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "presets.json"; DestDir: "{app}"; Flags: ignoreversion
Source: "app_icon.ico"; DestDir: "{app}"; Flags: ignoreversion
Source: "check_python.py"; DestDir: "{app}"; Flags: ignoreversion
Source: "check_environment.bat"; DestDir: "{app}"; Flags: ignoreversion
Source: "locales\*"; DestDir: "{app}\locales"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\check_environment.bat"; Description: "Checking system environment"; Flags: runhidden nowait postinstall skipifsilent
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent

View File

@@ -0,0 +1 @@

504
excel_filter/filter.py Normal file
View File

@@ -0,0 +1,504 @@
"""
Excel Filter Module
Main functionality for filtering Excel files based on regex patterns
"""
import re
import pandas as pd
from typing import List, Dict, Any, Optional
import logging
import time
import os
import json
from pathlib import Path
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Simple translation system for backend messages
class BackendTranslations:
"""
Simple translation system for backend modules
"""
def __init__(self, language="de"):
self.current_language = language
self.translations = {}
# Load translations from JSON files
self.load_translations()
def load_translations(self):
"""
Load translation files from the locales directory
"""
# Get the directory where this script is located
script_dir = Path(__file__).parent
locales_dir = script_dir / "locales"
# Load the language file
lang_file = locales_dir / f"{self.current_language}.json"
if lang_file.exists():
try:
with open(lang_file, 'r', encoding='utf-8') as f:
self.translations = json.load(f)
except Exception as e:
print(f"Error loading {lang_file}: {e}")
self.translations = {}
else:
self.translations = {}
def get(self, key, default=None):
"""
Get a translation for a key
"""
return self.translations.get(key, default if default is not None else key)
def __getitem__(self, key):
"""
Get a translation using dictionary-style access
"""
return self.get(key)
# Global backend translations instance
_backend_translations = BackendTranslations()
def get_backend_translation(key, **kwargs):
"""
Get a backend translation and format it with provided arguments
"""
message = _backend_translations.get(key, key)
if kwargs:
try:
message = message.format(**kwargs)
except (KeyError, ValueError):
pass # Keep original message if formatting fails
return message
class ExcelFilter:
"""
Class for filtering Excel files based on regex patterns and numeric filters
"""
def __init__(self, input_file: str, output_file: str, pattern: str = None,
sheet_name: str = None, columns: List[str] = None,
numeric_filter: Dict[str, Any] = None, language: str = "de"):
"""
Initializes the ExcelFilter
Args:
input_file: Path to the input file
output_file: Path to the output file
pattern: Regex pattern for filtering (optional)
sheet_name: Name of the worksheet (optional)
columns: List of column names to search (optional)
numeric_filter: Dictionary with numeric filter settings (optional)
Format: {'column': str or None, 'operator': str, 'value': float}
If 'column' is None, the filter applies to all columns
"""
self.input_file = input_file
self.output_file = output_file
self.pattern = pattern
self.sheet_name = sheet_name
self.columns = columns
self.numeric_filter = numeric_filter
# Statistics collection
self.stats = {
'start_time': None,
'end_time': None,
'input_file_size': 0,
'output_file_size': 0,
'input_rows': 0,
'input_columns': 0,
'output_rows': 0,
'output_columns': 0,
'memory_usage_mb': 0,
'filters_applied': [],
'processing_time_seconds': 0,
'compression_ratio': 0.0,
'rows_filtered': 0,
'rows_removed': 0
}
# Log initialization with all parameters
logger.info(f"ExcelFilter initialized: input_file='{input_file}', output_file='{output_file}', "
f"pattern='{pattern}', sheet_name='{sheet_name}', columns={columns}, "
f"numeric_filter={numeric_filter}")
def read_excel(self) -> pd.DataFrame:
"""
Reads the Excel file and returns a DataFrame
"""
try:
# Get input file size
if os.path.exists(self.input_file):
self.stats['input_file_size'] = os.path.getsize(self.input_file)
if self.sheet_name:
df = pd.read_excel(self.input_file, sheet_name=self.sheet_name)
else:
df = pd.read_excel(self.input_file)
# Collect input statistics
self.stats['input_rows'] = len(df)
self.stats['input_columns'] = len(df.columns)
self.stats['memory_usage_mb'] = df.memory_usage(deep=True).sum() / (1024 * 1024)
logger.info(get_backend_translation("input_file_loaded", rows=len(df), columns=len(df.columns)))
logger.info(get_backend_translation("file_size_info", size=self.stats['input_file_size'] / (1024*1024)))
logger.info(get_backend_translation("memory_usage_info", size=self.stats['memory_usage_mb']))
return df
except FileNotFoundError:
logger.error(get_backend_translation("file_not_found_error", input_file=self.input_file))
raise FileNotFoundError(f"The file {self.input_file} was not found")
except Exception as e:
logger.error(get_backend_translation("error_reading_excel_file", error=str(e)))
raise Exception(f"Error reading the Excel file: {e}")
def filter_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Filters the DataFrame based on regex patterns and/or numeric filters
"""
try:
filtered_df = df
applied_filters = []
# Apply regex filtering if pattern is provided
if self.pattern and self.pattern.strip():
filtered_df = self._apply_regex_filter(filtered_df)
applied_filters.append("Regex")
# Apply numeric filtering if enabled
if self.numeric_filter:
filtered_df = self._apply_numeric_filter(filtered_df)
applied_filters.append("Numeric")
# Update statistics
self.stats['filters_applied'] = applied_filters
self.stats['rows_filtered'] = len(filtered_df)
self.stats['rows_removed'] = len(df) - len(filtered_df)
if not applied_filters:
logger.warning(get_backend_translation("no_filter_criteria_specified"))
logger.info(get_backend_translation("no_filters_applied_rows_remain", rows=len(df)))
return df
# Calculate filtering efficiency
retention_rate = (len(filtered_df) / len(df)) * 100 if len(df) > 0 else 0
removal_rate = (self.stats['rows_removed'] / len(df)) * 100 if len(df) > 0 else 0
logger.info(get_backend_translation("filters_applied_list", filters=', '.join(applied_filters)))
logger.info(get_backend_translation("filter_results_summary", retained=len(filtered_df), removed=self.stats['rows_removed']))
logger.info(get_backend_translation("retention_removal_rates", retention=retention_rate, removal=removal_rate))
return filtered_df
except Exception as e:
logger.error(f"Error filtering: {e}")
raise Exception(f"Error filtering: {e}")
def _apply_regex_filter(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Applies regex filtering to the DataFrame
"""
try:
# Compile the regex pattern
# Intelligent pattern recognition:
# - If the pattern contains spaces, search as exact phrase
# - If the pattern seems to be a complete word, use word boundaries
# - Otherwise allow substring matching
if ' ' in self.pattern:
# Exact phrase with word boundaries
regex_pattern = rf"\b{re.escape(self.pattern)}\b"
elif len(self.pattern) <= 4:
# Short patterns (4 or fewer characters) - allow substring matching
regex_pattern = self.pattern
elif len(self.pattern) > 2 and self.pattern.isalpha():
# Probably a complete word
regex_pattern = rf"\b{re.escape(self.pattern)}\b"
else:
# Substring matching for other cases
regex_pattern = self.pattern
regex = re.compile(regex_pattern, re.IGNORECASE)
logger.info(get_backend_translation("regex_pattern_compiled", original=self.pattern, compiled=regex_pattern))
# Determine the columns to search
if self.columns:
columns_to_search = self.columns
logger.info(get_backend_translation("regex_filter_searching_columns", columns=columns_to_search))
else:
columns_to_search = df.columns
logger.info(get_backend_translation("regex_filter_searching_all_columns", columns=list(columns_to_search)))
# Filter function with detailed logging
def regex_filter_row(row):
row_matches = False
for col in columns_to_search:
if col in row and pd.notna(row[col]):
cell_value = str(row[col])
if regex.search(cell_value):
logger.debug(get_backend_translation("regex_match_found", row=row.name, column=col, value=cell_value))
row_matches = True
break
return row_matches
# Apply filter
filtered_df = df[df.apply(regex_filter_row, axis=1)]
logger.info(get_backend_translation("regex_filter_results", rows=len(filtered_df)))
return filtered_df
except re.error as e:
logger.error(get_backend_translation("invalid_regex_pattern", error=str(e)))
raise Exception(f"Invalid regex pattern: {e}")
def _apply_numeric_filter(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Applies numeric filtering to the DataFrame
"""
column = self.numeric_filter['column']
operator = self.numeric_filter['operator']
value = self.numeric_filter['value']
logger.info(get_backend_translation("numeric_filter_applied", column=column, operator=operator, value=value))
if column is None:
# Apply filter across all columns - a row matches if ANY column meets the criteria
return self._apply_numeric_filter_all_columns(df, operator, value)
else:
# Apply filter to specific column
return self._apply_numeric_filter_single_column(df, column, operator, value)
def _apply_numeric_filter_single_column(self, df: pd.DataFrame, column: str,
operator: str, value: float) -> pd.DataFrame:
"""
Apply numeric filter to a single column
"""
# Check if the column exists
if column not in df.columns:
logger.error(get_backend_translation("column_does_not_exist", column=column))
raise ValueError(f"Column '{column}' does not exist in the DataFrame")
# Convert the column to numeric values (ignore errors for non-numeric values)
numeric_series = pd.to_numeric(df[column], errors='coerce')
# Apply the comparison operator
if operator == '>':
mask = numeric_series > value
elif operator == '<':
mask = numeric_series < value
elif operator == '>=':
mask = numeric_series >= value
elif operator == '<=':
mask = numeric_series <= value
elif operator == '=':
mask = numeric_series == value
else:
logger.error(get_backend_translation("unknown_operator", operator=operator))
raise ValueError(f"Unknown operator: {operator}")
# Apply filter
filtered_df = df[mask]
logger.info(get_backend_translation("numeric_filter_single_column_results", matches=mask.sum(), total=len(df), column=column, operator=operator, value=value))
# Log some examples of the filtered values
if len(filtered_df) > 0:
sample_values = filtered_df[column].head(3).tolist()
logger.debug(get_backend_translation("sample_filtered_values", values=sample_values))
return filtered_df
def _apply_numeric_filter_all_columns(self, df: pd.DataFrame, operator: str, value: float) -> pd.DataFrame:
"""
Apply numeric filter across all columns - a row matches if ANY column meets the criteria
"""
logger.info(get_backend_translation("numeric_filter_all_columns", operator=operator, value=value))
# Create a mask that will be True for rows where ANY column meets the criteria
combined_mask = pd.Series([False] * len(df), index=df.index)
# Check each column
for col in df.columns:
# Convert the column to numeric values
numeric_series = pd.to_numeric(df[col], errors='coerce')
# Apply the comparison operator
if operator == '>':
col_mask = numeric_series > value
elif operator == '<':
col_mask = numeric_series < value
elif operator == '>=':
col_mask = numeric_series >= value
elif operator == '<=':
col_mask = numeric_series <= value
elif operator == '=':
col_mask = numeric_series == value
else:
logger.error(get_backend_translation("unknown_operator", operator=operator))
raise ValueError(f"Unknown operator: {operator}")
# Combine with OR logic (any column matching makes the row match)
combined_mask = combined_mask | col_mask
# Log matches for this column
matches = col_mask.sum()
if matches > 0:
logger.debug(get_backend_translation("column_matches_found", column=col, matches=matches))
# Apply filter
filtered_df = df[combined_mask]
logger.info(get_backend_translation("numeric_filter_all_columns_results", matches=combined_mask.sum(), total=len(df), operator=operator, value=value))
return filtered_df
def write_excel(self, df: pd.DataFrame):
"""
Writes the filtered DataFrame to a new Excel file
"""
try:
# If specific columns were selected, only write those
if self.columns:
# Only keep the selected columns (if they exist in the DataFrame)
columns_to_keep = [col for col in self.columns if col in df.columns]
df_filtered = df[columns_to_keep]
logger.info(get_backend_translation("writing_selected_columns", columns=columns_to_keep))
else:
# Write all columns
df_filtered = df
logger.info(get_backend_translation("writing_all_columns", columns=list(df.columns)))
# Collect output statistics
self.stats['output_rows'] = len(df_filtered)
self.stats['output_columns'] = len(df_filtered.columns)
df_filtered.to_excel(self.output_file, index=False)
# Get output file size and calculate compression ratio
if os.path.exists(self.output_file):
self.stats['output_file_size'] = os.path.getsize(self.output_file)
if self.stats['input_file_size'] > 0:
self.stats['compression_ratio'] = self.stats['output_file_size'] / self.stats['input_file_size']
logger.info(get_backend_translation("output_file_written", file=self.output_file))
logger.info(get_backend_translation("output_dimensions", rows=self.stats['output_rows'], columns=self.stats['output_columns']))
logger.info(get_backend_translation("output_file_size", size=self.stats['output_file_size'] / (1024*1024)))
if self.stats['input_file_size'] > 0:
compression_pct = (self.stats['compression_ratio'] - 1) * 100
if compression_pct > 0:
logger.info(get_backend_translation("compression_larger", percent=compression_pct))
else:
logger.info(get_backend_translation("compression_smaller", percent=compression_pct))
except PermissionError:
logger.error(get_backend_translation("no_write_permission", file=self.output_file))
raise PermissionError(f"No write permission for the file {self.output_file}")
except Exception as e:
logger.error(get_backend_translation("error_writing_excel_file", error=str(e)))
raise Exception(f"Error writing the Excel file: {e}")
def process(self):
"""
Main method for processing the Excel file
"""
# Start timing
self.stats['start_time'] = time.time()
try:
logger.info(get_backend_translation("starting_excel_filter_processing"))
df = self.read_excel()
filtered_df = self.filter_dataframe(df)
self.write_excel(filtered_df)
# End timing and calculate final statistics
self.stats['end_time'] = time.time()
self.stats['processing_time_seconds'] = self.stats['end_time'] - self.stats['start_time']
self._log_final_statistics()
logger.info(get_backend_translation("excel_filter_processing_completed"))
return True, None
except FileNotFoundError as e:
error_msg = get_backend_translation("error_file_not_found", error=str(e))
logger.error(error_msg)
return False, error_msg
except PermissionError as e:
error_msg = get_backend_translation("error_permission", error=str(e))
logger.error(error_msg)
return False, error_msg
except pd.errors.EmptyDataError as e:
error_msg = get_backend_translation("error_empty_excel", error=str(e))
logger.error(error_msg)
return False, error_msg
except pd.errors.ParserError as e:
error_msg = get_backend_translation("error_parser", error=str(e))
logger.error(error_msg)
return False, error_msg
except re.error as e:
error_msg = get_backend_translation("error_invalid_regex", error=str(e))
logger.error(error_msg)
return False, error_msg
except ValueError as e:
error_msg = get_backend_translation("error_invalid_input", error=str(e))
logger.error(error_msg)
return False, error_msg
except Exception as e:
error_msg = get_backend_translation("error_unexpected", type=type(e).__name__, error=str(e))
logger.error(error_msg)
return False, error_msg
def get_statistics(self) -> Dict[str, Any]:
"""
Returns the collected statistics
Returns:
Dictionary with all collected statistics
"""
return self.stats.copy()
def _log_final_statistics(self):
"""
Logs the final comprehensive statistics of the processing
"""
logger.info(get_backend_translation("processing_statistics"))
logger.info(get_backend_translation("processing_time", time=self.stats['processing_time_seconds']))
# File statistics
logger.info(get_backend_translation("file_statistics"))
logger.info(get_backend_translation("input_file_size", size=self.stats['input_file_size'] / (1024*1024)))
logger.info(get_backend_translation("output_file_size", size=self.stats['output_file_size'] / (1024*1024)))
if self.stats['compression_ratio'] > 0:
compression_pct = (self.stats['compression_ratio'] - 1) * 100
logger.info(get_backend_translation("compression_rate", rate=compression_pct))
# Data dimensions
logger.info(get_backend_translation("data_dimensions"))
logger.info(get_backend_translation("input_dimensions", rows=self.stats['input_rows'], columns=self.stats['input_columns']))
logger.info(get_backend_translation("output_dimensions", rows=self.stats['output_rows'], columns=self.stats['output_columns']))
# Filtering results
if self.stats['filters_applied']:
logger.info(get_backend_translation("filter_results"))
logger.info(get_backend_translation("applied_filters", filters=', '.join(self.stats['filters_applied'])))
if self.stats['input_rows'] > 0:
retention_rate = (self.stats['rows_filtered'] / self.stats['input_rows']) * 100
removal_rate = (self.stats['rows_removed'] / self.stats['input_rows']) * 100
logger.info(get_backend_translation("rows_retained", rows=self.stats['rows_filtered'], rate=retention_rate))
logger.info(get_backend_translation("rows_removed", rows=self.stats['rows_removed'], rate=removal_rate))
# Performance metrics
logger.info(get_backend_translation("performance_metrics"))
logger.info(get_backend_translation("memory_usage", size=self.stats['memory_usage_mb']))
if self.stats['processing_time_seconds'] > 0 and self.stats['input_rows'] > 0:
rows_per_second = self.stats['input_rows'] / self.stats['processing_time_seconds']
logger.info(get_backend_translation("processing_speed", speed=rows_per_second))
logger.info(get_backend_translation("end_statistics"))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,403 @@
"""
Execution tab component for the Excel Filter GUI
"""
import tkinter as tk
from tkinter import ttk
import tkinter.font as tkfont
class ExecutionTab:
"""
Execution tab component that shows the command and logs
"""
def __init__(self, parent_frame, win11_colors, scale_factor=1.0, translations=None):
"""
Initialize the execution tab
Args:
parent_frame: Parent frame to attach to
win11_colors: Windows 11 color palette dictionary
scale_factor: DPI scaling factor for high-resolution displays
translations: Translations object for internationalization
"""
self.frame = ttk.Frame(parent_frame)
self.win11_colors = win11_colors
self.scale_factor = scale_factor
self.translations = translations
# Variables
self.command_var = tk.StringVar(value=self.translations.get("ready_to_execute", "Bereit zur Ausführung..."))
self.status_var = tk.StringVar(value="🔄 " + (self.translations.get("status_ready", "Bereit")))
self.progress_var = tk.DoubleVar(value=0.0)
# Execution state
self.is_executing = False
# References to other components (will be set later)
self.config_tab = None
self.main_gui = None
self.create_widgets()
def create_widgets(self):
"""
Create all widgets for the execution tab with modern design
"""
# Scale factors for DPI-aware sizing - ensure minimum text size of 12
scaled_padding = int(8 * self.scale_factor)
scaled_small_padding = int(5 * self.scale_factor)
scaled_large_padding = int(15 * self.scale_factor)
scaled_font_size = max(int(10 * self.scale_factor), 12)
scaled_bold_font_size = max(int(12 * self.scale_factor), 12)
scaled_title_font_size = max(int(14 * self.scale_factor), 12)
# Main container with minimal padding
main_frame = ttk.Frame(self.frame)
main_frame.pack(fill=tk.BOTH, expand=True, padx=scaled_small_padding, pady=scaled_small_padding)
# Status indicator at the top
status_frame = ttk.Frame(main_frame)
status_frame.pack(fill=tk.X, pady=(0, scaled_padding))
status_icon = ttk.Label(status_frame, textvariable=self.status_var,
font=('Segoe UI', scaled_bold_font_size, 'bold') if 'Segoe UI' in tkfont.families() else ('Helvetica', scaled_bold_font_size, 'bold'))
status_icon.pack(side=tk.LEFT)
# Progress bar (initially hidden)
self.progress_bar = ttk.Progressbar(status_frame, variable=self.progress_var, maximum=100,
mode='determinate', length=int(300 * self.scale_factor))
self.progress_bar.pack(side=tk.RIGHT, padx=(scaled_padding, 0))
self.progress_bar.pack_forget() # Hide initially
# Command display with smart line breaking
command_frame = ttk.LabelFrame(main_frame, text="🎯 " + (self.translations["command_to_execute"] if self.translations else "Auszuführender Befehl"),
padding=str(scaled_padding))
command_frame.pack(fill=tk.X, pady=(0, scaled_padding))
# Create a text widget for better command display with line breaks
command_text_frame = ttk.Frame(command_frame)
command_text_frame.pack(fill=tk.X)
self.command_text = tk.Text(command_text_frame, height=6, state=tk.DISABLED, wrap=tk.WORD,
bg=self.win11_colors.get('surface', '#ffffff'),
font=('Courier', scaled_font_size),
borderwidth=1, relief="solid",
padx=scaled_padding, pady=scaled_padding,
selectbackground=self.win11_colors.get('primary', '#0078d4'))
self.command_text.pack(fill=tk.X)
# Execution controls frame
controls_frame = ttk.Frame(main_frame)
controls_frame.pack(fill=tk.X, pady=(0, scaled_large_padding))
# Execute button with modern styling
self.execute_button = ttk.Button(
controls_frame,
text="🚀 " + (self.translations["execute_button"] if self.translations else "AUSFÜHREN"),
command=self.execute_command,
state=tk.NORMAL
)
self.execute_button.pack(side=tk.TOP, pady=scaled_padding, ipadx=int(40 * self.scale_factor), ipady=int(10 * self.scale_factor))
# Style the execute button
style = ttk.Style()
style.configure('Execute.TButton',
font=('Segoe UI', scaled_bold_font_size, 'bold') if 'Segoe UI' in tkfont.families() else ('Helvetica', scaled_bold_font_size, 'bold'),
padding=int(20 * self.scale_factor), borderwidth=0,
background=self.win11_colors['primary'],
foreground='white')
style.map('Execute.TButton',
foreground=[('active', 'white'), ('pressed', 'white'), ('disabled', '#cccccc')],
background=[('active', self.win11_colors.get('primary_dark', '#005a9e')),
('pressed', self.win11_colors.get('primary_dark', '#005a9e')),
('disabled', '#f0f0f0')])
self.execute_button.configure(style='Execute.TButton')
# Log display with modern design
log_frame = ttk.LabelFrame(main_frame, text="📋 " + (self.translations["activity_log"] if self.translations else "Aktivitätsprotokoll"),
padding=str(scaled_padding))
log_frame.pack(fill=tk.BOTH, expand=True)
# Control buttons for log
log_controls = ttk.Frame(log_frame)
log_controls.pack(fill=tk.X, pady=(0, scaled_padding))
ttk.Button(log_controls, text="🧹 " + (self.translations["clear_log"] if self.translations else "Löschen"), command=self.clear_log).pack(side=tk.LEFT, padx=(0, scaled_small_padding))
ttk.Button(log_controls, text="💾 " + (self.translations["save_log"] if self.translations else "Speichern"), command=self.save_log).pack(side=tk.LEFT)
# Log text with enhanced styling
log_container = ttk.Frame(log_frame)
log_container.pack(fill=tk.BOTH, expand=True)
# Scrollbar
log_scrollbar = ttk.Scrollbar(log_container)
log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Log text widget
self.log_text = tk.Text(log_container, height=int(12 * self.scale_factor), state=tk.DISABLED,
wrap=tk.WORD, bg=self.win11_colors.get('surface', '#ffffff'),
font=('Segoe UI', scaled_font_size) if 'Segoe UI' in tkfont.families() else ('Helvetica', scaled_font_size),
borderwidth=1, relief="solid",
padx=scaled_padding, pady=scaled_padding,
selectbackground=self.win11_colors.get('primary', '#0078d4'),
selectforeground='white',
yscrollcommand=log_scrollbar.set)
self.log_text.pack(fill=tk.BOTH, expand=True)
log_scrollbar.config(command=self.log_text.yview)
# Initial log message
self.log_message(self.translations["ready_for_execution"] if self.translations else "Excel Filter bereit zur Ausführung")
self.log_message(""+(self.translations["configure_and_execute"] if self.translations else "Konfigurieren Sie die Einstellungen und klicken Sie auf 'AUSFÜHREN'"))
def execute_command(self):
"""Execute the command"""
# This will be connected to the main GUI's process_file method
if hasattr(self, 'main_gui') and self.main_gui:
self.main_gui.process_file()
else:
self.log_message(self.translations["error_main_gui_not_connected"] if self.translations else "Fehler: Haupt-GUI nicht verbunden")
def update_command_display(self):
"""
Update the command display based on current configuration with smart line breaking
"""
if not self.config_tab:
self._set_command_text(self.translations["ready_to_execute"] if self.translations else "Bereit zur Ausführung...")
return
try:
input_file = self.config_tab.input_file_var.get()
output_file = self.config_tab.output_file_var.get()
pattern = self.config_tab.pattern_var.get()
sheet_name = self.config_tab.sheet_var.get()
# Build formatted command display
lines = []
# Input file
if input_file:
lines.append(f"{self.translations['input_file_label'] if self.translations else 'Eingabedatei:'} {input_file}")
else:
lines.append(f"{self.translations['input_file_label'] if self.translations else 'Eingabedatei:'} {self.translations['not_selected'] if self.translations else '(nicht ausgewählt)'}")
# Output file
if output_file:
lines.append(f"{self.translations['output_file_label'] if self.translations else 'Ausgabedatei:'} {output_file}")
else:
lines.append(f"{self.translations['output_file_label'] if self.translations else 'Ausgabedatei:'} {self.translations['not_selected'] if self.translations else '(nicht ausgewählt)'}")
# Pattern
if pattern:
# Break long patterns into multiple lines if needed
if len(pattern) > 50:
lines.append(f"{self.translations['search_pattern_label'] if self.translations else 'Suchmuster:'} {pattern[:47]}...")
else:
lines.append(f"{self.translations['search_pattern_label'] if self.translations else 'Suchmuster:'} {pattern}")
else:
lines.append(f"{self.translations['search_pattern_label'] if self.translations else 'Suchmuster:'} {self.translations['not_specified'] if self.translations else '(nicht angegeben)'}")
# Sheet name
if sheet_name:
lines.append(f"{self.translations['worksheet_label'] if self.translations else 'Arbeitsblatt:'} {sheet_name}")
# Selected columns
if hasattr(self.config_tab, 'get_selected_columns'):
selected_columns = self.config_tab.get_selected_columns()
if selected_columns:
# Format columns nicely
if len(selected_columns) <= 3:
columns_text = ", ".join(selected_columns)
else:
columns_text = f"{', '.join(selected_columns[:3])} {self.translations['more_columns'].format(count=len(selected_columns) - 3) if self.translations else f'(+{len(selected_columns) - 3} weitere)'}"
lines.append(f"{self.translations['columns_label'] if self.translations else 'Spalten:'} {columns_text}")
# Numeric filter settings
if hasattr(self.config_tab, 'get_numeric_filter_settings'):
numeric_settings = self.config_tab.get_numeric_filter_settings()
if numeric_settings:
if self.translations:
lines.append(f"🔢 {self.translations['numeric_filter_label'].format(column=numeric_settings['column'], operator=numeric_settings['operator'], value=numeric_settings['value'])}")
else:
lines.append(f"🔢 Numerischer Filter: {numeric_settings['column']} {numeric_settings['operator']} {numeric_settings['value']}")
# Set the formatted command text
self._set_command_text("\n".join(lines))
except Exception as e:
error_text = self.translations["error_updating_command_display"].format(error=str(e)) if self.translations else f"Fehler beim Aktualisieren der Befehlsanzeige: {e}"
self._set_command_text(error_text)
self.log_message(error_text)
def _set_command_text(self, text):
"""
Set the command text in the text widget
Args:
text: Text to display
"""
self.command_text.config(state=tk.NORMAL)
self.command_text.delete(1.0, tk.END)
self.command_text.insert(1.0, text)
self.command_text.config(state=tk.DISABLED)
def log_message(self, message):
"""
Log a message to the log text widget
Args:
message: Message to log
"""
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
def set_config_tab(self, config_tab):
"""
Set the reference to the config tab
Args:
config_tab: Reference to the config tab component
"""
self.config_tab = config_tab
# Set up variable tracing to update command display when config changes
if hasattr(config_tab, 'input_file_var'):
config_tab.input_file_var.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'output_file_var'):
config_tab.output_file_var.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'pattern_var'):
config_tab.pattern_var.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'sheet_var'):
config_tab.sheet_var.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'no_regex_var'):
config_tab.no_regex_var.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'numeric_filter_enabled'):
config_tab.numeric_filter_enabled.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'numeric_column_var'):
config_tab.numeric_column_var.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'numeric_operator_var'):
config_tab.numeric_operator_var.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'numeric_value_var'):
config_tab.numeric_value_var.trace_add("write", lambda *args: self.update_command_display())
if hasattr(config_tab, 'sheet_var'):
config_tab.sheet_var.trace_add("write", lambda *args: self.update_command_display())
# Set up callback for column changes
if hasattr(config_tab, 'set_on_columns_changed'):
config_tab.set_on_columns_changed(lambda: self.update_command_display())
# Initial update
self.update_command_display()
def set_main_gui(self, main_gui):
"""
Set the reference to the main GUI
Args:
main_gui: Reference to the main GUI instance
"""
self.main_gui = main_gui
def clear_log(self):
"""
Clear the log text
"""
self.log_text.config(state=tk.NORMAL)
self.log_text.delete(1.0, tk.END)
self.log_text.config(state=tk.DISABLED)
self.log_message(self.translations["log_cleared"] if self.translations else "Protokoll gelöscht")
self.log_message(self.translations["ready_for_execution"] if self.translations else "Excel Filter bereit zur Ausführung")
def save_log(self):
"""
Save the log content to a file
"""
try:
from tkinter import filedialog
import datetime
# Generate default filename with timestamp
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
default_filename = f"excel_filter_log_{timestamp}.txt"
# Ask user for save location
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("Textdateien", "*.txt"), ("Alle Dateien", "*.*")],
initialfile=default_filename,
title="Protokoll speichern"
)
if file_path:
# Get log content
log_content = self.log_text.get(1.0, tk.END).strip()
# Add header with timestamp
header = f"Excel Filter Protokoll - {datetime.datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n"
header += "=" * 50 + "\n\n"
# Write to file
with open(file_path, 'w', encoding='utf-8') as f:
f.write(header + log_content)
self.log_message(self.translations["log_saved"].format(file=file_path) if self.translations else f"💾 Protokoll gespeichert: {file_path}")
except Exception as e:
self.log_message(f"❌ Fehler beim Speichern des Protokolls: {e}")
def start_execution(self):
"""
Start execution - show progress bar and update status
"""
self.is_executing = True
self.status_var.set("" + (self.translations["execution_running"] if self.translations else "Läuft..."))
self.progress_var.set(0.0)
self.progress_bar.pack(side=tk.RIGHT, padx=(int(8 * self.scale_factor), 0))
self.execute_button.config(state=tk.DISABLED, text="" + (self.translations["waiting"] if self.translations else "WARTEN..."))
self.log_message(self.translations["execution_started"] if self.translations else "▶️ Ausführung gestartet")
def update_progress(self, value, message=None):
"""
Update progress bar and optionally log a message
Args:
value: Progress value (0-100)
message: Optional message to log
"""
self.progress_var.set(value)
if message:
self.log_message(message)
def finish_execution(self, success=True):
"""
Finish execution - hide progress bar and update status
"""
self.is_executing = False
self.progress_var.set(100.0)
if success:
self.status_var.set("✅ Fertig")
self.log_message(self.translations["execution_completed"] if self.translations else "✅ Ausführung erfolgreich abgeschlossen")
else:
self.status_var.set("❌ Fehler")
self.log_message(self.translations["execution_failed"] if self.translations else "❌ Ausführung mit Fehlern beendet")
# Hide progress bar after a short delay
self.frame.after(2000, lambda: self.progress_bar.pack_forget())
# Re-enable execute button
self.execute_button.config(state=tk.NORMAL, text="🚀 " + (self.translations["execute_button"] if self.translations else "AUSFÜHREN"))
def get_frame(self):
"""
Get the frame for this tab
Returns:
The frame containing all widgets
"""
return self.frame

View File

@@ -0,0 +1,178 @@
"""
Help tab component for the Excel Filter GUI
"""
import tkinter as tk
from tkinter import ttk
import tkinter.font as tkfont
class HelpTab:
"""
Help tab component that shows documentation and usage information
"""
def __init__(self, parent_frame, win11_colors, scale_factor=1.0, translations=None, switch_language_callback=None):
"""
Initialize the help tab
Args:
parent_frame: Parent frame to attach to
win11_colors: Windows 11 color palette dictionary
scale_factor: DPI scaling factor for high-resolution displays
translations: Translations object for internationalization
switch_language_callback: Callback function to switch language
"""
self.frame = ttk.Frame(parent_frame)
self.win11_colors = win11_colors
self.scale_factor = scale_factor
self.translations = translations
self.switch_language_callback = switch_language_callback
self.create_widgets()
def create_widgets(self):
"""
Create all widgets for the help tab
"""
# Scale factors for DPI-aware sizing - ensure minimum text size of 12
scaled_padding = int(10 * self.scale_factor)
scaled_text_padding = int(12 * self.scale_factor)
scaled_pack_padding = int(2 * self.scale_factor)
scaled_font_size = max(int(10 * self.scale_factor), 12)
# Main container
help_main_frame = ttk.Frame(self.frame)
help_main_frame.pack(fill=tk.BOTH, expand=True, padx=scaled_padding, pady=scaled_padding)
# Language selector at the top of help tab
if self.translations:
language_frame = ttk.Frame(help_main_frame)
language_frame.pack(fill=tk.X, pady=(0, scaled_padding))
# Language selector label
language_label = ttk.Label(language_frame, text=self.translations["language"])
language_label.pack(side=tk.LEFT, padx=(0, int(5 * self.scale_factor)))
# Frame for flag buttons
flags_frame = ttk.Frame(language_frame)
flags_frame.pack(side=tk.LEFT, padx=5)
# German flag button
german_button = ttk.Button(
flags_frame,
text="🇩🇪 German",
command=lambda: self.switch_language('de'),
style='TButton'
)
german_button.pack(side=tk.LEFT, padx=(0, 5))
# English flag button
english_button = ttk.Button(
flags_frame,
text="🇺🇸 English",
command=lambda: self.switch_language('en'),
style='TButton'
)
english_button.pack(side=tk.LEFT)
# Highlight current language
if self.translations.current_language == 'de':
german_button.config(style='Primary.TButton')
else:
english_button.config(style='Primary.TButton')
# Scrollbar for help content
help_scrollbar = ttk.Scrollbar(help_main_frame)
help_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Help text with scrollbar - Windows 11 Fluent Design
help_text_widget = tk.Text(help_main_frame, wrap=tk.WORD,
bg=self.win11_colors['surface'],
fg=self.win11_colors['text'],
font=('Segoe UI', scaled_font_size) if 'Segoe UI' in tkfont.families() else ('Helvetica', scaled_font_size),
borderwidth=1, relief="solid", padx=scaled_text_padding, pady=scaled_text_padding,
selectbackground=self.win11_colors['primary'],
selectforeground='white',
yscrollcommand=help_scrollbar.set)
help_text_widget.pack(fill=tk.BOTH, expand=True, padx=scaled_pack_padding, pady=scaled_pack_padding)
help_scrollbar.config(command=help_text_widget.yview)
# Configure scrollbar with Windows 11 styling
style = ttk.Style()
style.configure('TScrollbar',
background=self.win11_colors['border'],
troughcolor=self.win11_colors['surface'],
bordercolor=self.win11_colors['border'],
arrowcolor=self.win11_colors['text'])
# Help content from translations
help_content = self.translations.get("help_content", "Help content not available")
help_text_widget.insert(tk.END, help_content)
help_text_widget.config(state=tk.DISABLED)
def switch_language(self, new_language):
"""
Switch the application language and update help content
Args:
new_language: New language code ('en' or 'de')
"""
if self.switch_language_callback:
self.switch_language_callback(new_language)
# Update help content after language change
self.update_help_content()
# Update button styles to reflect current language
self.update_button_styles()
def update_help_content(self):
"""
Update the help content text when language changes
"""
# Find the help text widget and update its content
for child in self.frame.winfo_children():
if isinstance(child, ttk.Frame):
for subchild in child.winfo_children():
if isinstance(subchild, tk.Text):
# Clear existing content and insert new translated content
subchild.config(state=tk.NORMAL)
subchild.delete(1.0, tk.END)
subchild.insert(tk.END, self.translations.get("help_content", "Help content not available"))
subchild.config(state=tk.DISABLED)
break
def update_button_styles(self):
"""
Update the language button styles to reflect the current language
"""
# Find the language buttons and update their styles
for child in self.frame.winfo_children():
if isinstance(child, ttk.Frame):
for subchild in child.winfo_children():
if isinstance(subchild, ttk.Frame):
for widget in subchild.winfo_children():
if isinstance(widget, ttk.Frame):
for button in widget.winfo_children():
if isinstance(button, ttk.Button):
if "German" in button.cget("text"):
if self.translations.current_language == 'de':
button.config(style='Primary.TButton')
else:
button.config(style='TButton')
elif "English" in button.cget("text"):
if self.translations.current_language == 'en':
button.config(style='Primary.TButton')
else:
button.config(style='TButton')
def get_frame(self):
"""
Get the frame for this tab
Returns:
The frame containing all widgets
"""
return self.frame

View File

@@ -0,0 +1,365 @@
"""
Main window component for the Excel Filter GUI
"""
import tkinter as tk
from tkinter import ttk
import tkinter.font as tkfont
class MainWindow:
"""
Main window component that handles the overall GUI structure
"""
def __init__(self, root):
"""
Initialize the main window
Args:
root: Tkinter root window
"""
self.root = root
self.root.title("Excel Filter Tool")
self.root.geometry("1200x700")
self.root.resizable(True, True)
# Windows 11 specific: Enable modern window styling and DPI awareness first
self.scale_factor = 1.0
try:
import ctypes
import sys
if sys.platform == 'win32':
try:
# Set DPI awareness for sharp rendering on high-DPI displays
try:
ctypes.windll.shcore.SetProcessDpiAwareness(2) # PROCESS_PER_MONITOR_DPI_AWARE
except:
try:
ctypes.windll.shcore.SetProcessDpiAwareness(1) # PROCESS_SYSTEM_DPI_AWARE
except:
ctypes.windll.user32.SetProcessDPIAware()
# Set Tkinter scaling to match system DPI for proper element sizes
try:
hdc = ctypes.windll.user32.GetDC(0)
dpi_x = ctypes.windll.gdi32.GetDeviceCaps(hdc, 88) # LOGPIXELSX
ctypes.windll.user32.ReleaseDC(0, hdc)
self.scale_factor = dpi_x / 96.0 # 96 DPI = 100% scaling
self.root.tk.call('tk', 'scaling', self.scale_factor)
except:
pass # If scaling setup fails, continue without it
# Enable dark mode for title bar
ctypes.windll.dwmapi.DwmSetWindowAttribute(
self.root.winfo_id(),
20,
ctypes.c_int(2),
ctypes.sizeof(ctypes.c_int)
)
except:
pass # If DPI awareness or DwmSetWindowAttribute fails, continue without it
except:
pass # If ctypes import fails, continue without it
# Windows 11 Design
self.root.configure(bg='#f0f0f0')
# Font configuration - ensure minimum size of 12
try:
base_font_size = max(int(10 * self.scale_factor), 12)
self.default_font = ('Segoe UI', base_font_size) if 'Segoe UI' in tkfont.families() else ('Helvetica', base_font_size)
except:
self.default_font = ('Helvetica', 12)
# Windows 11 Color palette
self.win11_primary = '#0078d4' # Windows 11 Blue
self.win11_light = '#f0f0f0' # Light gray
self.win11_dark = '#e0e0e0' # Dark gray
self.win11_accent = '#005a9e' # Accent color
# Create main frame with minimal padding - tabs right at the top
self.main_frame = ttk.Frame(self.root)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=0)
# Create notebook for tabs with Windows 11 styling - right at the top
self.notebook = ttk.Notebook(self.main_frame)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=0, pady=0)
# Configure notebook padding and spacing for Windows 11 look
style = ttk.Style()
style.layout('TNotebook.Tab', [
('Notebook.tab', {
'sticky': 'nswe',
'children': [
('Notebook.padding', {
'side': 'top',
'children': [
('Notebook.label', {'side': 'top', 'sticky': ''})
]
})
]
})
])
# Set window icon and other Windows 11 properties
try:
self.root.iconbitmap('app_icon.ico') # Windows 11 style icon
except:
pass # Icon not found, continue without it
# Windows 11 window properties
self.root.attributes('-alpha', 1.0) # Ensure full opacity
# Add Windows 11 style window controls (minimize, maximize, close)
# This is handled automatically by Windows for Tkinter windows
# Set window minimum size for better usability (larger minimum to accommodate the bigger interface)
min_width = int(1200 * self.scale_factor)
min_height = int(700 * self.scale_factor)
self.root.minsize(min_width, min_height)
self.root.update()
# Configure styles
self.configure_styles()
def configure_styles(self):
"""
Configure Tkinter styles for Windows 11 Fluent Design look
"""
style = ttk.Style()
# Use clam theme for better appearance
style.theme_use('clam')
# Scale factors for DPI-aware sizing - ensure minimum size of 12
font_size_normal = max(int(10 * self.scale_factor), 12)
font_size_bold = max(int(10 * self.scale_factor), 12)
font_size_heading = max(int(12 * self.scale_factor), 12)
tab_padding = [int(16 * self.scale_factor), int(8 * self.scale_factor)]
button_padding = (int(12 * self.scale_factor), int(6 * self.scale_factor))
primary_button_padding = (int(16 * self.scale_factor), int(8 * self.scale_factor))
entry_padding = int(10 * self.scale_factor)
combobox_padding = int(8 * self.scale_factor)
notebook_padding = [int(10 * self.scale_factor), int(5 * self.scale_factor)]
# Windows 11 Fluent Design color palette
win11_primary = '#0078d4' # Windows 11 Blue
win11_primary_dark = '#005a9e' # Darker blue for hover effects
win11_primary_light = '#cce4f7' # Light blue for active states
win11_background = '#f0f0f0' # Light gray background
win11_surface = '#ffffff' # White surface
win11_text = '#212121' # Dark text
win11_text_secondary = '#616161' # Secondary text
win11_border = '#e0e0e0' # Border color
win11_hover = '#e8f0fe' # Hover effect
win11_pressed = '#d0e0f5' # Pressed effect
# Tab styling - Windows 11 Fluent Design style
style.configure('TNotebook', background=win11_background, borderwidth=1, relief='solid')
style.configure('TNotebook.Tab',
font=('Segoe UI', font_size_normal, 'normal') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_normal, 'normal'),
padding=tab_padding,
background=win11_background,
foreground=win11_text,
borderwidth=0,
relief='flat')
style.map('TNotebook.Tab',
background=[("selected", win11_surface), ("active", win11_hover), ("!selected", win11_background)],
foreground=[("selected", win11_primary), ("!selected", win11_text_secondary)],
relief=[("selected", 'flat'), ("active", 'flat'), ("!selected", 'flat')])
# Configure notebook padding and spacing
style.configure('TNotebook', tabposition='n', padding=notebook_padding)
# Button styling - Windows 11 Fluent Design
style.configure('TButton',
font=('Segoe UI', font_size_normal, 'normal') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_normal, 'normal'),
padding=button_padding,
borderwidth=1,
focuscolor=win11_primary,
background=win11_surface,
foreground=win11_text,
bordercolor=win11_border,
relief='flat')
style.map('TButton',
foreground=[('active', win11_text), ('disabled', win11_text_secondary)],
background=[('active', win11_hover), ('pressed', win11_pressed), ('disabled', win11_background)],
bordercolor=[('active', win11_primary), ('pressed', win11_primary_dark)],
lightcolor=[('active', win11_hover)],
darkcolor=[('active', win11_pressed)])
# Primary button style (for important actions)
style.configure('Primary.TButton',
font=('Segoe UI', font_size_bold, 'bold') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_bold, 'bold'),
padding=primary_button_padding,
background=win11_primary,
foreground=win11_surface,
borderwidth=0,
focuscolor=win11_primary_dark)
style.map('Primary.TButton',
background=[('active', win11_primary_dark), ('pressed', win11_primary_dark), ('disabled', win11_background)],
foreground=[('active', win11_surface), ('pressed', win11_surface), ('disabled', win11_text_secondary)])
# Entry field styling - Windows 11 style
style.configure('TEntry',
font=('Segoe UI', font_size_normal, 'normal') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_normal, 'normal'),
padding=entry_padding,
fieldbackground=win11_surface,
borderwidth=1,
relief='solid',
bordercolor=win11_border,
foreground=win11_text)
style.map('TEntry',
fieldbackground=[('focus', win11_surface), ('readonly', win11_background)],
bordercolor=[('focus', win11_primary), ('readonly', win11_border)])
# Label styling - Windows 11 style
style.configure('TLabel',
font=('Segoe UI', font_size_normal, 'normal') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_normal, 'normal'),
background=win11_background,
foreground=win11_text)
# Heading label style
style.configure('Heading.TLabel',
font=('Segoe UI', font_size_heading, 'bold') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_heading, 'bold'),
background=win11_background,
foreground=win11_primary)
# Frame styling
style.configure('TFrame', background=win11_background)
style.configure('TLabelframe',
font=('Segoe UI', font_size_bold, 'bold') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_bold, 'bold'),
background=win11_background,
borderwidth=1,
relief='groove',
bordercolor=win11_border)
style.configure('TLabelframe.Label',
font=('Segoe UI', font_size_bold, 'bold') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_bold, 'bold'),
foreground=win11_primary,
background=win11_background)
# Checkbutton styling
style.configure('TCheckbutton',
font=('Segoe UI', font_size_normal, 'normal') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_normal, 'normal'),
background=win11_background,
foreground=win11_text)
style.map('TCheckbutton',
background=[('active', win11_hover), ('selected', win11_background)],
foreground=[('active', win11_text), ('selected', win11_text)])
# Combobox styling
style.configure('TCombobox',
font=('Segoe UI', font_size_normal, 'normal') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_normal, 'normal'),
padding=combobox_padding,
fieldbackground=win11_surface,
borderwidth=1,
relief='solid',
bordercolor=win11_border,
foreground=win11_text)
style.map('TCombobox',
fieldbackground=[('focus', win11_surface), ('readonly', win11_background)],
bordercolor=[('focus', win11_primary), ('readonly', win11_border)])
# Scrollbar styling
style.configure('TScrollbar',
background=win11_background,
troughcolor=win11_background,
bordercolor=win11_border,
arrowcolor=win11_text)
style.map('TScrollbar',
background=[('active', win11_border), ('disabled', win11_background)],
troughcolor=[('active', win11_hover), ('disabled', win11_background)])
# Treeview styling
style.configure('Treeview',
font=('Segoe UI', font_size_normal, 'normal') if 'Segoe UI' in tkfont.families() else ('Helvetica', font_size_normal, 'normal'),
background=win11_surface,
foreground=win11_text,
fieldbackground=win11_surface,
bordercolor=win11_border)
style.map('Treeview',
background=[('selected', win11_primary_light)],
foreground=[('selected', win11_text)])
# Progressbar styling
style.configure('TProgressbar',
background=win11_primary,
troughcolor=win11_background,
bordercolor=win11_border)
def add_tab(self, tab_frame, text):
"""
Add a tab to the notebook with Windows 11 styling
Args:
tab_frame: Frame to add as tab
text: Tab title
Returns:
The added tab frame
"""
# Add the tab with Windows 11 styling
self.notebook.add(tab_frame, text=text)
# Apply Windows 11 specific styling to the tab
style = ttk.Style()
# Get the tab index
tab_index = len(self.notebook.tabs()) - 1
tab_id = self.notebook.tabs()[tab_index]
# Configure the tab to have Windows 11 appearance with scaled padding
scaled_tab_padding = [int(16 * self.scale_factor), int(8 * self.scale_factor)]
self.notebook.tab(tab_id, padding=scaled_tab_padding)
return tab_frame
def enhance_tab_appearance(self):
"""
Enhance tab appearance to look more like Windows 11
This should be called after all tabs are added
"""
style = ttk.Style()
# Windows 11 specific: Make tabs look more modern
style.configure('TNotebook.Tab',
borderwidth=0,
focuscolor='#0078d4',
lightcolor='#f0f0f0',
darkcolor='#f0f0f0')
# Add a subtle border to the notebook itself
try:
self.notebook.configure(borderwidth=1, relief='solid')
except:
pass # Some Tkinter versions don't support borderwidth for notebooks
# Configure the notebook background to match Windows 11
style.configure('TNotebook',
background='#f0f0f0',
borderwidth=1,
relief='solid',
bordercolor='#e0e0e0')
# Windows 11 specific: Add hover and selection effects
style.map('TNotebook.Tab',
background=[('selected', '#ffffff'),
('active', '#e8f0fe'),
('!selected', '#f0f0f0')],
foreground=[('selected', '#0078d4'),
('!selected', '#616161')])
# Windows 11 specific: Make selected tab more prominent with scaled values
scaled_tab_padding = [int(16 * self.scale_factor), int(8 * self.scale_factor)]
scaled_font_size = int(10 * self.scale_factor)
style.configure('TNotebook.Tab',
padding=scaled_tab_padding,
font=('Segoe UI', scaled_font_size, 'normal') if 'Segoe UI' in tkfont.families() else ('Helvetica', scaled_font_size, 'normal'))
def run(self):
"""
Run the main application loop
"""
self.root.mainloop()

File diff suppressed because it is too large Load Diff

1032
excel_filter/gui_new.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
@echo off
:: Excel Filter Tool - GUI Launcher
cd /d "%~dp0"
call check_dependencies_simple.bat
if errorlevel 1 (
echo Error checking dependencies
pause
exit /b 1
)
echo Start Excel Filter Tool GUI...
python gui_new.py
if errorlevel 1 (
echo Error when starting the GUI
pause
exit /b 1
)
echo GUI was closed
pause

View File

@@ -0,0 +1,173 @@
{
"app_title": "Excel Filter Tool",
"tab_config": "Konfiguration",
"tab_execution": "Ausführung",
"tab_regex_builder": "Regex-Builder",
"tab_help": "Hilfe",
"config_section": "Konfiguration",
"load_button": "Laden",
"save_button": "Speichern",
"input_file": "Eingabedatei:",
"output_file": "Ausgabedatei:",
"browse_button": "Durchsuchen...",
"worksheet": "Arbeitsblatt:",
"process_button": "🚀 VERARBEITEN",
"status_ready": "Bereit",
"success": "Erfolg",
"error": "Fehler",
"language": "Sprache:",
"english": "Englisch",
"german": "Deutsch",
"help_content": "\nExcel Filter Tool\n\nFUNKTION\n--------------------------\nDas Excel Filter Tool ist ein Werkzeug zur Datenanalyse und -filterung von Excel-Dateien.\nEs ermöglicht das automatische Extrahieren, Filtern und Transformieren von Daten basierend auf einstellbaren Suchkriterien.\n\nHauptfunktionen:\n• Intelligente Textsuche anhand von Regex-Mustern\n• Numerische Filterung (größer/kleiner als, zwischen Werten)\n• Spaltenbasierte Filterung\n\nARBEITSABLAUF\n----------------------------------------\nDer Arbeitsablauf ist in vier Hauptphasen unterteilt, die jeweils eigene Tabs haben:\n\n1. KONFIGURATION\n- Wähle die Eingabe-Excel-Datei aus\n- Bestimme die Ausgabedatei\n- Wähle das zu filternde Arbeitsblatt\n- Konfiguriere die Filterkriterien (Regex und/oder numerisch)\n- Wählen die Spalten aus, welche übernommen werden sollen\n- Speicher/Lade die Konfigurationen für wiederkehrende Aufgaben\n\n2. MUSTER-ERSTELLUNG (Regex-Builder-Tab)\n- Nutze den visuellen Regex-Builder für zur einfachen Mustererstellung\n- Wähle aus vorgefertigten Bausteinen (Text, Zahlen, Spezialzeichen)\n- Definiere Mengen (einmal, mehrmals, optional)\n- Füge optionale Anker und Gruppen hinzu\n- Teste die Muster mit Beispieltexten\n- Verwalte gespeicherte Muster\n\n3. AUSFÜHRUNG\n- Überprüfe den auszuführenden Befehl vor der Verarbeitung\n- Starten die Analyse\n- Verfolgen den Fortschritt in Echtzeit\n- Automatische Öffnung der Ergebnisdatei\n\nERWEITERTE KONFIGURATIONSOPTIONEN\n----------------------------------\n\nRegex-Filterung (Standardmodus)\n- Suche nach Textmustern mit voller Regex-Unterstützung\n- Unterstützung für komplexe Suchmuster\n- Wortgrenzen, Groß-/Kleinschreibung, Spezialzeichen\n\nNumerische Filterung\n- Filtern nach Zahlenwerten (größer/kleiner als)\n- Bereichsfilterung (zwischen Werten)\n- Spaltenübergreifende numerische Suche\n\nSpaltenbasierte Filterung\n- Auswahl spezifischer zu durchsuchender Spalten\n- Automatische Spaltenerkennung aus Excel-Dateien\n- Individuelle Spaltenauswahl für zielgerichtete Suche\n\nKONFIGURATIONSMANAGEMENT\n--------------------------\n• Speichern/Laden von Konfigurationen für wiederkehrende Aufgaben\n• Persönliche Musterbibliothek mit benutzerdefinierten Regex-Mustern\n• Automatische Speicherung von zuletzt verwendeten Dateipfaden\n• Wiederherstellung vorheriger Arbeitssitzungen\n",
"file_not_found_error": "Fehler: Die Datei {input_file} wurde nicht gefunden",
"error_reading_excel_file": "Fehler beim Lesen der Excel-Datei: {error}",
"no_filter_criteria_specified": "Keine Filterkriterien angegeben - alle Zeilen werden beibehalten",
"no_filters_applied_rows_remain": "Keine Filter angewendet: {rows} Zeilen bleiben",
"filters_applied_list": "Filter angewendet: {filters}",
"filter_results_summary": "Filterergebnisse: {retained:,} Zeilen beibehalten, {removed:,} Zeilen entfernt",
"retention_removal_rates": "Beibehaltungsrate: {retention:.1f}%, Entfernungsrate: {removal:.1f}%",
"regex_pattern_compiled": "Regex-Muster: '{original}' -> Kompiliert als: '{compiled}'",
"regex_filter_searching_columns": "Regex-Filter: Suche in bestimmten Spalten: {columns}",
"regex_filter_searching_all_columns": "Regex-Filter: Suche in allen Spalten: {columns}",
"regex_match_found": "Zeile {row}: Regex-Übereinstimmung in Spalte '{column}' mit Wert '{value}'",
"regex_filter_results": "Regex-Filter: {rows} Zeilen gefunden",
"invalid_regex_pattern": "Ungültiges Regex-Muster: {error}",
"numeric_filter_applied": "Numerischer Filter: {column} {operator} {value}",
"column_does_not_exist": "Spalte '{column}' existiert nicht im DataFrame",
"unknown_operator": "Unbekannter Operator: {operator}",
"numeric_filter_single_column_results": "Numerischer Filter: {matches} von {total} Zeilen erfüllen {column} {operator} {value}",
"sample_filtered_values": "Beispielwerte: {values}",
"numeric_filter_all_columns": "Numerischer Filter auf alle Spalten: {operator} {value}",
"column_matches_found": "Spalte '{column}': {matches} Übereinstimmungen",
"numeric_filter_all_columns_results": "Numerischer Filter (alle Spalten): {matches} von {total} Zeilen erfüllen {operator} {value}",
"writing_selected_columns": "Nur ausgewählte Spalten werden geschrieben: {columns}",
"writing_all_columns": "Alle Spalten werden geschrieben: {columns}",
"output_file_written": "Ausgabedatei geschrieben: {file}",
"output_dimensions": "Ausgabedimensionen: {rows:,} Zeilen × {columns} Spalten",
"output_file_size": "Ausgabedateigröße: {size:.2f} MB",
"compression_larger": "Kompression: +{percent:.1f}% (größer als Original)",
"compression_smaller": "Kompression: {percent:.1f}% (kleiner als Original)",
"no_write_permission": "Fehler: Keine Schreibberechtigung für die Datei {file}",
"error_writing_excel_file": "Fehler beim Schreiben der Excel-Datei: {error}",
"starting_excel_filter_processing": "Excel-Filter-Verarbeitung wird gestartet...",
"excel_filter_processing_completed": "Excel-Filter-Verarbeitung erfolgreich abgeschlossen!",
"processing_statistics": "=== VERARBEITUNGSSTATISTIKEN ===",
"processing_time": "Verarbeitungszeit: {time:.2f} Sekunden",
"file_statistics": "Dateistatistiken:",
"input_file_size": " Eingabedatei: {size:.2f} MB",
"output_file_size": " Ausgabedatei: {size:.2f} MB",
"compression_rate": " Kompressionsrate: {rate:+.1f}%",
"data_dimensions": "Datendimensionen:",
"input_dimensions": " Eingabe: {rows:,} Zeilen × {columns} Spalten",
"output_dimensions": " Ausgabe: {rows:,} Zeilen × {columns} Spalten",
"filter_results": "Filterergebnisse:",
"applied_filters": " Angewendete Filter: {filters}",
"rows_retained": " Zeilen beibehalten: {rows:,} ({rate:.1f}%)",
"rows_removed": " Zeilen entfernt: {rows:,} ({rate:.1f}%)",
"performance_metrics": "Leistungsmetriken:",
"memory_usage": " Speicherverbrauch: {size:.2f} MB",
"processing_speed": " Verarbeitungsgeschwindigkeit: {speed:.0f} Zeilen/Sekunde",
"end_statistics": "=== ENDE STATISTIKEN ===",
"error_file_not_found": "Datei nicht gefunden: {error}",
"error_permission": "Berechtigungsfehler: {error}",
"error_empty_excel": "Leere Excel-Datei oder ungültiges Format: {error}",
"error_parser": "Excel-Datei kann nicht geparst werden: {error}",
"error_invalid_regex": "Ungültiges Regex-Muster: {error}",
"error_invalid_input": "Ungültige Eingabe oder Konfiguration: {error}",
"error_unexpected": "Unerwarteter Fehler: {type}: {error}",
"input_file_loaded": "Eingabedatei geladen: {rows} Zeilen × {columns} Spalten",
"file_size_info": "Dateigröße: {size:.2f} MB",
"memory_usage_info": "Speicherverbrauch: {size:.2f} MB",
"ready_to_execute": "Bereit zur Ausführung...",
"status_ready": "Bereit",
"command_to_execute": "Auszuführender Befehl",
"execute_button": "AUSFÜHREN",
"activity_log": "Aktivitätsprotokoll",
"log_cleared": "Protokoll gelöscht",
"log_saved": "Protokoll gespeichert: {file}",
"error_saving_log": "Fehler beim Speichern des Protokolls: {error}",
"execution_started": "Ausführung gestartet",
"execution_running": "Läuft...",
"waiting": "WARTEN...",
"ready_for_execution": "Excel Filter bereit zur Ausführung",
"configure_and_execute": "Konfigurieren Sie die Einstellungen und klicken Sie auf 'AUSFÜHREN'",
"error_main_gui_not_connected": "Fehler: Haupt-GUI nicht verbunden",
"input_file_label": "Eingabedatei:",
"output_file_label": "Ausgabedatei:",
"search_pattern_label": "Suchmuster:",
"worksheet_label": "Arbeitsblatt:",
"columns_label": "Spalten:",
"not_selected": "(nicht ausgewählt)",
"not_specified": "(nicht angegeben)",
"more_columns": "(+{count} weitere)",
"numeric_filter_label": "Numerischer Filter: {column} {operator} {value}",
"error_updating_command_display": "Fehler beim Aktualisieren der Befehlsanzeige: {error}",
"clear_log": "Protokoll löschen",
"save_log": "Protokoll speichern",
"save_log_title": "Protokoll speichern",
"log_header": "Excel Filter Protokoll - {timestamp}\n{'=' * 50}\n\n",
"execution_completed": "Ausführung erfolgreich abgeschlossen",
"execution_failed": "Ausführung mit Fehlern beendet",
"execution_finished": "Ausführung beendet",
"input_file_selected": "Eingabedatei ausgewählt: {file}",
"output_file_selected": "Ausgabedatei ausgewählt: {file}",
"sheet_selection_updated": "Arbeitsblattauswahl aktualisiert: {sheets}",
"column_selection_updated": "Spaltenauswahl aktualisiert: {columns}",
"all_columns_selected": "Alle Spalten ausgewählt",
"all_columns_deselected": "Alle Spalten abgewählt",
"config_loaded_success": "Konfiguration erfolgreich geladen",
"config_saved_success": "Konfiguration erfolgreich gespeichert",
"error_loading_config": "Fehler beim Laden der Konfiguration: {error}",
"error_saving_config": "Fehler beim Speichern der Konfiguration: {error}",
"no_config_found": "Keine Konfiguration gefunden",
"select_input_file_title": "Eingabedatei auswählen",
"save_output_file_title": "Ausgabedatei speichern",
"excel_files_filter": "Excel-Dateien",
"all_files_filter": "Alle Dateien",
"input_file_required_error": "Bitte wählen Sie eine Eingabedatei aus",
"output_file_required_error": "Bitte wählen Sie eine Ausgabedatei aus",
"pattern_required_error": "Bitte geben Sie ein Filtermuster ein",
"no_regex_mode_message": "Kein Regex-Muster verwendet - es werden nur die ausgewählten Spalten kopiert",
"warning_no_pattern_no_columns": "Warnung: Kein Regex-Muster und keine Spalten ausgewählt",
"copy_all_data_question": "Kein Regex-Muster und keine Spalten ausgewählt. Möchten Sie alle Daten kopieren?",
"initializing_analysis_engine": "Erweiterte Analyse-Engine wird initialisiert...",
"loading_excel_structure": "Excel-Dateistruktur wird geladen und analysiert...",
"performing_pattern_recognition": "Intelligente Mustererkennung wird durchgeführt...",
"applying_advanced_filters": "Erweiterte Filteralgorithmen werden angewendet...",
"calculating_performance_metrics": "Leistungsmetriken und Statistiken werden berechnet...",
"optimizing_output_file": "Ausgabedatei wird optimiert und komprimiert...",
"creating_detailed_reports": "Detaillierte Verarbeitungsberichte werden erstellt...",
"analysis_completed_successfully": "Excel-Filter-Analyse erfolgreich abgeschlossen!",
"analysis_failed": "Analyse fehlgeschlagen",
"advanced_analysis_running": "Erweiterte Excel-Analyse läuft...",
"advanced_analysis_completed": "Erweiterte Analyse erfolgreich abgeschlossen",
"advanced_analysis_failed": "Erweiterte Analyse fehlgeschlagen",
"analysis_engine_error": "Analyse-Engine-Fehler",
"critical_error_analysis_engine": "Kritischer Fehler in der Analyse-Engine",
"file_statistics_header": "DATEI-STATISTIKEN:",
"data_processing_header": "DATENVERARBEITUNG:",
"applied_filters_header": "ANGEWENDETE FILTER:",
"performance_metrics_header": "LEISTUNGSMETRIKEN:",
"auto_opening_output_file": "Ausgabedatei wird automatisch geöffnet...",
"opening_file_error": "Fehler beim Öffnen der Datei: {error}",
"file_saved_at": "Datei gespeichert unter: {path}",
"file_size_mb": "{size:.2f} MB",
"compression_rate_pct": "{rate:+.1f}%",
"rows_reduced": "{count:,} Zeilen reduziert ({rate:.1f}%)",
"filter_efficiency": "Filter-Effizienz: {rate:.1f}% Zeilen beibehalten",
"processing_time_sec": "{time:.2f} Sekunden",
"memory_usage_mb": "{size:.2f} MB",
"processing_speed_rows_per_sec": "{speed:.0f} Zeilen/Sekunde",
"file_size_mb_template": "{size:.2f} MB",
"compression_rate_template": "{rate:+.1f}%",
"rows_count_template": "{count:,} Zeilen",
"percentage_template": "{rate:.1f}%",
"time_seconds_template": "{time:.2f} Sekunden",
"speed_template": "{speed:.0f} Zeilen/Sekunde",
"memory_mb_template": "{size:.2f} MB"
}

View File

@@ -0,0 +1,173 @@
{
"app_title": "Excel Filter Tool",
"tab_config": "Configuration",
"tab_execution": "Execution",
"tab_regex_builder": "Regex Builder",
"tab_help": "Help",
"config_section": "Configuration",
"load_button": "Load",
"save_button": "Save",
"input_file": "Input file:",
"output_file": "Output file:",
"browse_button": "Browse...",
"worksheet": "Worksheet:",
"process_button": "🚀 PROCESS",
"status_ready": "Ready",
"success": "Success",
"error": "Error",
"language": "Language:",
"english": "English",
"german": "German",
"help_content": "\nExcel Filter Tool\n\nFUNCTION\n--------------------------\nThe Excel Filter Tool is a data analysis and filtering tool for Excel files.\nIt enables automatic extraction, filtering, and transformation of data based on configurable search criteria.\n\nMain features:\n• Intelligent text search using regex patterns\n• Numeric filtering (greater/less than, between values)\n• Column-based filtering\n\nWORKFLOW\n----------------------------------------\nThe workflow is divided into four main phases, each with its own tabs:\n\n1. CONFIGURATION\n- Select the input Excel file\n- Specify the output file\n- Select the worksheet to filter\n- Configure filter criteria (regex and/or numeric)\n- Select columns to be included\n- Save/load configurations for recurring tasks\n\n2. PATTERN CREATION (Regex-Builder Tab)\n- Use the visual regex builder for easy pattern creation\n- Select from predefined components (text, numbers, special characters)\n- Define quantities (once, multiple times, optional)\n- Add optional anchors and groups\n- Test patterns with sample texts\n- Manage stored patterns\n\n3. EXECUTION\n- Review the command to execute before processing\n- Start the analysis\n- Track progress in real time\n- Automatic opening of the result file\n\nADVANCED CONFIGURATION OPTIONS\n----------------------------------\n\nRegex Filtering (Standard Mode)\n- Search for text patterns with full regex support\n- Support for complex search patterns\n- Word boundaries, case sensitivity, special characters\n\nNumeric Filtering\n- Filter by numeric values (greater/less than)\n- Range filtering (between values)\n- Cross-column numeric search\n\nColumn-Based Filtering\n- Selection of specific columns to search\n- Automatic column detection from Excel files\n- Individual column selection for targeted search\n\nCONFIGURATION MANAGEMENT\n--------------------------\n• Save/load configurations for recurring tasks\n• Personal pattern library with custom regex patterns\n• Automatic saving of recently used file paths\n• Restoration of previous work sessions\n",
"file_not_found_error": "Error: The file {input_file} was not found",
"error_reading_excel_file": "Error reading the Excel file: {error}",
"no_filter_criteria_specified": "No filter criteria specified - all rows will be retained",
"no_filters_applied_rows_remain": "No filters applied: {rows} rows remain",
"filters_applied_list": "Filters applied: {filters}",
"filter_results_summary": "Filter results: {retained:,} rows retained, {removed:,} rows removed",
"retention_removal_rates": "Retention Rate: {retention:.1f}%, Removal Rate: {removal:.1f}%",
"regex_pattern_compiled": "Regex pattern: '{original}' -> Compiled as: '{compiled}'",
"regex_filter_searching_columns": "Regex filter: Searching specific columns: {columns}",
"regex_filter_searching_all_columns": "Regex filter: Searching all columns: {columns}",
"regex_match_found": "Row {row}: Regex match in column '{column}' with value '{value}'",
"regex_filter_results": "Regex filter: {rows} rows found",
"invalid_regex_pattern": "Invalid regex pattern: {error}",
"numeric_filter_applied": "Numeric filter: {column} {operator} {value}",
"column_does_not_exist": "Column '{column}' does not exist in the DataFrame",
"unknown_operator": "Unknown operator: {operator}",
"numeric_filter_single_column_results": "Numeric filter: {matches} of {total} rows meet {column} {operator} {value}",
"sample_filtered_values": "Sample values: {values}",
"numeric_filter_all_columns": "Numeric filter on all columns: {operator} {value}",
"column_matches_found": "Column '{column}': {matches} matches",
"numeric_filter_all_columns_results": "Numeric filter (all columns): {matches} of {total} rows meet {operator} {value}",
"writing_selected_columns": "Writing only selected columns: {columns}",
"writing_all_columns": "Writing all columns: {columns}",
"output_file_written": "Output file written: {file}",
"output_dimensions": "Output dimensions: {rows:,} rows × {columns} columns",
"output_file_size": "Output file size: {size:.2f} MB",
"compression_larger": "Compression: +{percent:.1f}% (larger than original)",
"compression_smaller": "Compression: {percent:.1f}% (smaller than original)",
"no_write_permission": "Error: No write permission for the file {file}",
"error_writing_excel_file": "Error writing the Excel file: {error}",
"starting_excel_filter_processing": "Starting Excel filter processing...",
"excel_filter_processing_completed": "Excel filter processing completed successfully!",
"processing_statistics": "=== PROCESSING STATISTICS ===",
"processing_time": "Processing time: {time:.2f} seconds",
"file_statistics": "File statistics:",
"input_file_size": " Input file: {size:.2f} MB",
"output_file_size": " Output file: {size:.2f} MB",
"compression_rate": " Compression rate: {rate:+.1f}%",
"data_dimensions": "Data dimensions:",
"input_dimensions": " Input: {rows:,} rows × {columns} columns",
"output_dimensions": " Output: {rows:,} rows × {columns} columns",
"filter_results": "Filter results:",
"applied_filters": " Applied filters: {filters}",
"rows_retained": " Rows retained: {rows:,} ({rate:.1f}%)",
"rows_removed": " Rows removed: {rows:,} ({rate:.1f}%)",
"performance_metrics": "Performance metrics:",
"memory_usage": " Memory usage: {size:.2f} MB",
"processing_speed": " Processing speed: {speed:.0f} rows/second",
"end_statistics": "=== END STATISTICS ===",
"error_file_not_found": "File not found: {error}",
"error_permission": "Permission error: {error}",
"error_empty_excel": "Empty Excel file or invalid format: {error}",
"error_parser": "Excel file cannot be parsed: {error}",
"error_invalid_regex": "Invalid regex pattern: {error}",
"error_invalid_input": "Invalid input or configuration: {error}",
"error_unexpected": "Unexpected error: {type}: {error}",
"input_file_loaded": "Input file loaded: {rows} rows × {columns} columns",
"file_size_info": "File size: {size:.2f} MB",
"memory_usage_info": "Memory usage: {size:.2f} MB",
"ready_to_execute": "Ready to execute...",
"status_ready": "Ready",
"command_to_execute": "Command to Execute",
"execute_button": "EXECUTE",
"activity_log": "Activity Log",
"log_cleared": "Log cleared",
"log_saved": "Log saved: {file}",
"error_saving_log": "Error saving log: {error}",
"execution_started": "Execution started",
"execution_running": "Running...",
"waiting": "WAITING...",
"ready_for_execution": "Excel Filter ready for execution",
"configure_and_execute": "Configure settings and click 'EXECUTE'",
"error_main_gui_not_connected": "Error: Main GUI not connected",
"input_file_label": "Input file:",
"output_file_label": "Output file:",
"search_pattern_label": "Search pattern:",
"worksheet_label": "Worksheet:",
"columns_label": "Columns:",
"not_selected": "(not selected)",
"not_specified": "(not specified)",
"more_columns": "(+{count} more)",
"numeric_filter_label": "Numeric Filter: {column} {operator} {value}",
"error_updating_command_display": "Error updating command display: {error}",
"clear_log": "Clear Log",
"save_log": "Save Log",
"save_log_title": "Save Log",
"log_header": "Excel Filter Log - {timestamp}\n{'=' * 50}\n\n",
"execution_completed": "Execution completed successfully",
"execution_failed": "Execution completed with errors",
"execution_finished": "Execution finished",
"input_file_selected": "Input file selected: {file}",
"output_file_selected": "Output file selected: {file}",
"sheet_selection_updated": "Sheet selection updated: {sheets}",
"column_selection_updated": "Column selection updated: {columns}",
"all_columns_selected": "All columns selected",
"all_columns_deselected": "All columns deselected",
"config_loaded_success": "Configuration successfully loaded",
"config_saved_success": "Configuration successfully saved",
"error_loading_config": "Error loading configuration: {error}",
"error_saving_config": "Error saving configuration: {error}",
"no_config_found": "No configuration found",
"select_input_file_title": "Select input file",
"save_output_file_title": "Save output file",
"excel_files_filter": "Excel Files",
"all_files_filter": "All Files",
"input_file_required_error": "Please select an input file",
"output_file_required_error": "Please select an output file",
"pattern_required_error": "Please enter a filter pattern",
"no_regex_mode_message": "No regex pattern used - only selected columns will be copied",
"warning_no_pattern_no_columns": "Warning: No regex pattern and no columns selected",
"copy_all_data_question": "No regex pattern and no columns selected. Copy all data?",
"initializing_analysis_engine": "Initializing advanced analysis engine...",
"loading_excel_structure": "Loading and analyzing Excel file structure...",
"performing_pattern_recognition": "Performing intelligent pattern recognition...",
"applying_advanced_filters": "Applying advanced filter algorithms...",
"calculating_performance_metrics": "Calculating performance metrics and statistics...",
"optimizing_output_file": "Optimizing and compressing output file...",
"creating_detailed_reports": "Creating detailed processing reports...",
"analysis_completed_successfully": "Excel-filter analysis completed successfully!",
"analysis_failed": "Analysis failed",
"advanced_analysis_running": "Advanced Excel analysis running...",
"advanced_analysis_completed": "Advanced analysis successfully completed",
"advanced_analysis_failed": "Advanced analysis failed",
"analysis_engine_error": "Analysis engine error",
"critical_error_analysis_engine": "Critical error in analysis engine",
"file_statistics_header": "FILE STATISTICS:",
"data_processing_header": "DATA PROCESSING:",
"applied_filters_header": "APPLIED FILTERS:",
"performance_metrics_header": "PERFORMANCE METRICS:",
"auto_opening_output_file": "Output file will be automatically opened...",
"opening_file_error": "Error opening file: {error}",
"file_saved_at": "File saved at: {path}",
"file_size_mb": "{size:.2f} MB",
"compression_rate_pct": "{rate:+.1f}%",
"rows_reduced": "{count:,} rows reduced ({rate:.1f}%)",
"filter_efficiency": "Filter efficiency: {rate:.1f}% rows retained",
"processing_time_sec": "{time:.2f} seconds",
"memory_usage_mb": "{size:.2f} MB",
"processing_speed_rows_per_sec": "{speed:.0f} rows/second",
"file_size_mb_template": "{size:.2f} MB",
"compression_rate_template": "{rate:+.1f}%",
"rows_count_template": "{count:,} rows",
"percentage_template": "{rate:.1f}%",
"time_seconds_template": "{time:.2f} seconds",
"speed_template": "{speed:.0f} rows/second",
"memory_mb_template": "{size:.2f} MB"
}

75
excel_filter/main.py Normal file
View File

@@ -0,0 +1,75 @@
"""
Main module for command line functionality
"""
import argparse
import json
import logging
from filter import ExcelFilter
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def load_config(config_file: str) -> dict:
"""
Loads a configuration file
"""
try:
with open(config_file, 'r') as f:
config = json.load(f)
logger.info(f"Configuration loaded: {config_file}")
return config
except Exception as e:
logger.error(f"Error loading configuration: {e}")
raise
def main():
"""
Main function for command line application
"""
parser = argparse.ArgumentParser(description='Excel Filter Tool')
parser.add_argument('--input', required=True, help='Input file (Excel)')
parser.add_argument('--output', required=True, help='Output file (Excel)')
parser.add_argument('--pattern', help='Regex pattern for filtering')
parser.add_argument('--config', help='Configuration file (JSON)')
parser.add_argument('--sheet', help='Name of the worksheet')
parser.add_argument('--columns', nargs='+', help='Columns to search')
args = parser.parse_args()
# Load configuration or use command line arguments
if args.config:
config = load_config(args.config)
pattern = config.get('pattern', args.pattern)
sheet_name = config.get('sheet_name', args.sheet)
columns = config.get('columns', args.columns)
else:
pattern = args.pattern
sheet_name = args.sheet
columns = args.columns
if not pattern:
logger.error("No regex pattern specified")
return
# Create ExcelFilter instance and execute
excel_filter = ExcelFilter(
input_file=args.input,
output_file=args.output,
pattern=pattern,
sheet_name=sheet_name,
columns=columns
)
success = excel_filter.process()
if success:
logger.info("Processing completed successfully")
else:
logger.error("Processing failed")
if __name__ == '__main__':
main()

20
excel_filter/presets.json Normal file
View File

@@ -0,0 +1,20 @@
{
"presets": {
"Fehler und Warnungen": "error|warning|critical",
"Nur Fehler": "error",
"Nur Warnungen": "warning",
"Zahlen 100-199": "1\\d{2}",
"E-Mail-Adressen": "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
"Telefonnummern": "\\+?[0-9\\s-]{10,}",
"Datum (YYYY-MM-DD)": "\\d{4}-\\d{2}-\\d{2}"
},
"descriptions": {
"Fehler und Warnungen": "Zeigt entweder \"error\", \"warning\" oder \"critical\"",
"Nur Fehler": "Findet Zeilen mit 'error'",
"Nur Warnungen": "Findet Zeilen mit 'warning'",
"Zahlen 100-199": "Findet Zahlen zwischen 100 und 199",
"E-Mail-Adressen": "Findet E-Mail-Adressen",
"Telefonnummern": "Findet Telefonnummern",
"Datum (YYYY-MM-DD)": "Findet Daten im Format YYYY-MM-DD"
}
}

13
excel_filter/pytest.ini Normal file
View File

@@ -0,0 +1,13 @@
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--tb=short
--strict-markers
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: marks tests as integration tests
unit: marks tests as unit tests

View File

@@ -0,0 +1,5 @@
openpyxl>=3.1.2
pandas>=2.0.3
python-docx>=1.1.0
pytest>=8.0.0
psutil>=5.8.0

View File

@@ -0,0 +1,10 @@
@echo off
setlocal enabledelayedexpansion
cd /d "%~dp0"
echo Building the executable...
python -m PyInstaller --onefile --windowed --name=ExcelFilter --distpath=dist --icon=app_icon.ico gui_new.py --add-data=config.json;. --add-data=presets.json;. --add-data=app_icon.ico;. --add-data=locales;locales
echo Executable built successfully!
pause

View File

@@ -0,0 +1,10 @@
@echo off
setlocal enabledelayedexpansion
cd /d "%~dp0"
echo Building the executable...
python -m PyInstaller --windowed --name=ExcelFilter --distpath=dist --icon=app_icon.ico gui_new.py
echo Executable built successfully!
pause

View File

@@ -0,0 +1,9 @@
off
echo
This
is
a
mock
ISCC.exe
exit
0

View File

@@ -0,0 +1 @@
# Tests package

View File

@@ -0,0 +1,26 @@
import sys
import os
# Add the src directory to the path
sys.path.append(os.path.join(os.path.dirname(__file__), 'excel_filter', 'src'))
print("Python path:", sys.path)
print("Current directory:", os.getcwd())
try:
from excel_filter.gui_components.config_tab import ConfigTab
print("SUCCESS: ConfigTab imported")
# Try to instantiate
import tkinter as tk
root = tk.Tk()
config_tab = ConfigTab(root, {}, {}, {})
print("SUCCESS: ConfigTab instantiated")
root.destroy()
except Exception as e:
print(f"ERROR: {e}")
import traceback
traceback.print_exc()

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python
"""
Skript zum Generieren von Testdaten für die Excel-Filter-Tests
"""
import pandas as pd
# Testdaten erstellen
test_data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank', 'Grace', 'Hank'],
'Status': ['Active', 'Inactive', 'Active', 'Inactive', 'Active', 'Inactive', 'Active', 'Inactive'],
'Message': [
'Hello World',
'Error occurred',
'Warning: Low disk space',
'Critical failure',
'System running normally',
'Error: Database connection failed',
'Warning: High CPU usage',
'Info: System update available'
],
'Value': [100, 200, 150, 50, 300, 120, 180, 90],
'Description': [
'Normal operation',
'Error in module X',
'Disk space warning',
'Critical system error',
'All systems operational',
'Database error',
'CPU warning',
'Update information'
]
}
# DataFrame erstellen
df = pd.DataFrame(test_data)
# In Excel-Datei speichern
df.to_excel('tests/test_data.xlsx', index=False)
print("Testdaten erfolgreich generiert!")
print(df)

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""
Simple test to verify the config_tab.py fixes
"""
import sys
sys.path.append('src')
from excel_filter.gui_components.config_tab import ConfigTab
def test_methods():
"""Test that the methods are properly implemented"""
print("Testing ConfigTab methods...")
# Test that the class can be instantiated
try:
# Create a minimal mock frame
import tkinter as tk
root = tk.Tk()
win11_colors = {'primary': '#0078d4', 'light': '#f0f0f0', 'dark': '#e0e0e0', 'accent': '#005a9e'}
pattern_presets = {"test": "test"}
pattern_descriptions = {"test": "test"}
config_tab = ConfigTab(root, win11_colors, pattern_presets, pattern_descriptions)
# Test that methods exist and are callable
assert hasattr(config_tab, 'select_all_columns'), "select_all_columns method missing"
assert hasattr(config_tab, 'deselect_all_columns'), "deselect_all_columns method missing"
assert hasattr(config_tab, 'process_file'), "process_file method missing"
assert hasattr(config_tab, 'save_config'), "save_config method missing"
assert hasattr(config_tab, 'load_config'), "load_config method missing"
assert hasattr(config_tab, 'browse_input_file'), "browse_input_file method missing"
assert hasattr(config_tab, 'browse_output_file'), "browse_output_file method missing"
assert hasattr(config_tab, 'update_columns_selection'), "update_columns_selection method missing"
# Test that methods are callable
config_tab.select_all_columns()
config_tab.deselect_all_columns()
config_tab.process_file()
config_tab.save_config()
config_tab.load_config()
config_tab.browse_input_file()
config_tab.browse_output_file()
config_tab.update_columns_selection()
print("[SUCCESS] All methods are properly implemented and callable!")
# Test with some data
config_tab.input_file_var.set("test_input.xlsx")
config_tab.output_file_var.set("test_output.xlsx")
config_tab.pattern_var.set("error|warning")
# This should work now (previously would have done nothing)
config_tab.process_file()
print("[SUCCESS] Process file method works with valid data!")
root.destroy()
return True
except Exception as e:
print(f"[ERROR] Error: {e}")
return False
if __name__ == "__main__":
success = test_methods()
if success:
print("\n[INFO] All tests passed! The config_tab.py fixes are working correctly.")
else:
print("\n[ERROR] Some tests failed. Please check the implementation.")

View File

@@ -0,0 +1,29 @@
@echo off
:: Test Batchdatei zur Fehlerdiagnose
echo Test 1: Einfache if-Bedingung
if "%ERRORLEVEL%"=="0" (
echo Test 1 erfolgreich
) else (
echo Test 1 fehlgeschlagen
)
echo.
echo Test 2: if not-Bedingung
set TESTVAR=0
if not "%TESTVAR%"=="0" (
echo Test 2 erfolgreich
) else (
echo Test 2 fehlgeschlagen
)
echo.
echo Test 3: if exist-Bedingung
if exist "test_batch.bat" (
echo Test 3 erfolgreich - Datei existiert
) else (
echo Test 3 fehlgeschlagen - Datei existiert nicht
)
echo.
pause

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""
Test script to verify column selection updates
"""
import sys
import os
sys.path.append('src')
from excel_filter.gui_new import ExcelFilterGUI
import tkinter as tk
def test_column_update():
"""Test that column selection updates are properly reflected in command display"""
# Create main window
root = tk.Tk()
root.withdraw() # Hide the main window for testing
try:
# Create the GUI
app = ExcelFilterGUI(root)
print("Testing column selection updates...")
# Set up test data
app.config_tab.input_file_var.set("test_input.xlsx")
app.config_tab.output_file_var.set("test_output.xlsx")
app.config_tab.pattern_var.set("test_pattern")
app.config_tab.sheet_var.set("Sheet1")
# Mock some column variables
app.config_tab.columns_vars = {
'Column1': tk.IntVar(value=1),
'Column2': tk.IntVar(value=0),
'Column3': tk.IntVar(value=1)
}
# Set up the connections (this should have been done in create_tabs)
app.config_tab.set_execution_tab(app.execution_tab)
app.config_tab.set_on_columns_changed(lambda: app.execution_tab.update_command_display())
# Add tracing for individual column variables
for var in app.config_tab.columns_vars.values():
var.trace_add("write", lambda *args: app.execution_tab.update_command_display())
print("Initial command display:")
initial_command = app.execution_tab.command_var.get()
print(f"Command: {initial_command}")
# Change a column selection
print("\nChanging Column2 selection...")
app.config_tab.columns_vars['Column2'].set(1)
root.update() # Process the trace updates
updated_command = app.execution_tab.command_var.get()
print(f"Updated command: {updated_command}")
# Check if columns appear in command
columns_in_command = "Spalten:" in updated_command
print(f"Columns appear in command: {columns_in_command}")
if columns_in_command:
print("[SUCCESS] Column selection update works!")
return True
else:
print("[ERROR] Column selection update failed!")
return False
except Exception as e:
print(f"Test failed with error: {e}")
import traceback
traceback.print_exc()
return False
finally:
root.destroy()
if __name__ == "__main__":
success = test_column_update()
sys.exit(0 if success else 1)

View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""
Complete test to verify all the implemented functionality in config_tab.py
"""
import sys
sys.path.append('excel_filter/src')
from excel_filter.gui_components.config_tab import ConfigTab
def test_complete_implementation():
"""Test all the implemented methods"""
print("Testing complete implementation of ConfigTab...")
try:
# Create a minimal mock frame
import tkinter as tk
root = tk.Tk()
win11_colors = {'primary': '#0078d4', 'light': '#f0f0f0', 'dark': '#e0e0e0', 'accent': '#005a9e'}
pattern_presets = {"test": "test"}
pattern_descriptions = {"test": "test"}
config_tab = ConfigTab(root, win11_colors, pattern_presets, pattern_descriptions)
print("[SUCCESS] ConfigTab instantiated successfully")
# Test file browsing methods
print("Testing browse methods...")
# These will open dialogs, but we can at least verify they don't crash
try:
config_tab.browse_input_file()
print("[SUCCESS] browse_input_file() works")
except Exception as e:
print(f"[ERROR] browse_input_file() failed: {e}")
try:
config_tab.browse_output_file()
print("[SUCCESS] browse_output_file() works")
except Exception as e:
print(f"[ERROR] browse_output_file() failed: {e}")
# Test column selection methods
print("Testing column selection methods...")
config_tab.select_all_columns()
print("[SUCCESS] select_all_columns() works")
config_tab.deselect_all_columns()
print("[SUCCESS] deselect_all_columns() works")
# Test config methods
print("Testing config methods...")
config_tab.save_config()
print("[SUCCESS] save_config() works")
config_tab.load_config()
print("[SUCCESS] load_config() works")
# Test process file with valid data
print("Testing process_file with valid data...")
config_tab.input_file_var.set("test_input.xlsx")
config_tab.output_file_var.set("test_output.xlsx")
config_tab.pattern_var.set("error|warning")
config_tab.process_file()
print("[SUCCESS] process_file() works with valid data")
# Test update methods (these might fail without actual files, but shouldn't crash)
print("Testing update methods...")
try:
config_tab.update_sheet_selection()
print("[SUCCESS] update_sheet_selection() works")
except Exception as e:
print(f"[WARNING] update_sheet_selection() failed (expected without file): {e}")
try:
config_tab.update_columns_selection()
print("[SUCCESS] update_columns_selection() works")
except Exception as e:
print(f"[WARNING] update_columns_selection() failed (expected without file): {e}")
print("\n[INFO] All methods are properly implemented and functional!")
root.destroy()
return True
except Exception as e:
print(f"[ERROR]: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = test_complete_implementation()
if success:
print("\n[INFO] All tests passed! The config_tab.py is fully functional.")
else:
print("\n[ERROR] Some tests failed. Please check the implementation.")

View File

@@ -0,0 +1,159 @@
"""
Test module for ExcelFilter
"""
import os
import sys
import pandas as pd
# Add src directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from excel_filter.filter import ExcelFilter
class TestExcelFilter:
"""
Test class for ExcelFilter
"""
def setup_method(self):
"""
Setup for the tests
"""
import tempfile
import os
# Create temporary files
self.temp_dir = tempfile.mkdtemp()
self.test_input = os.path.join(self.temp_dir, "test_data.xlsx")
self.test_output = os.path.join(self.temp_dir, "test_output.xlsx")
# Create test data
test_data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Status': ['Active', 'Inactive', 'Active', 'Inactive', 'Active'],
'Message': ['Hello World', 'Error occurred', 'Warning: Low disk space', 'Critical failure', 'System running normally'],
'Value': [100, 200, 150, 50, 300]
}
df = pd.DataFrame(test_data)
df.to_excel(self.test_input, index=False)
def teardown_method(self):
"""
Cleanup after the tests
"""
import shutil
if os.path.exists(self.test_output):
os.remove(self.test_output)
if os.path.exists(self.test_input):
os.remove(self.test_input)
if hasattr(self, 'temp_dir') and os.path.exists(self.temp_dir):
shutil.rmtree(self.temp_dir)
def test_filter_error_pattern(self):
"""
Test filtering with an error pattern
"""
pattern = "error|warning|critical"
excel_filter = ExcelFilter(
input_file=self.test_input,
output_file=self.test_output,
pattern=pattern
)
success = excel_filter.process()
assert success
# Verify output file
result_df = pd.read_excel(self.test_output)
assert len(result_df) == 3 # Bob, Charlie, David
def test_filter_specific_column(self):
"""
Test filtering in a specific column
"""
pattern = "active"
excel_filter = ExcelFilter(
input_file=self.test_input,
output_file=self.test_output,
pattern=pattern,
columns=['Status']
)
success = excel_filter.process()
assert success
# Verify output file
result_df = pd.read_excel(self.test_output)
assert len(result_df) == 3 # Alice, Charlie, Eve
def test_filter_value_pattern(self):
"""
Test filtering with a numeric pattern
"""
pattern = "1\d{2}" # Numbers between 100-199
excel_filter = ExcelFilter(
input_file=self.test_input,
output_file=self.test_output,
pattern=pattern,
columns=['Value']
)
success = excel_filter.process()
assert success
# Verify output file
result_df = pd.read_excel(self.test_output)
assert len(result_df) == 2 # Alice (100), Charlie (150)
def test_filter_no_matches(self):
"""
Test filtering with a pattern that finds no matches
"""
pattern = "nonexistent"
excel_filter = ExcelFilter(
input_file=self.test_input,
output_file=self.test_output,
pattern=pattern
)
success = excel_filter.process()
assert success
# Verify output file
result_df = pd.read_excel(self.test_output)
assert len(result_df) == 0 # No matches
def test_filter_invalid_file(self):
"""
Test filtering with an invalid file
"""
pattern = "error"
excel_filter = ExcelFilter(
input_file="nonexistent.xlsx",
output_file=self.test_output,
pattern=pattern
)
success = excel_filter.process()
assert not success
def test_filter_empty_pattern(self):
"""
Test filtering with an empty pattern
"""
pattern = ""
excel_filter = ExcelFilter(
input_file=self.test_input,
output_file=self.test_output,
pattern=pattern
)
success = excel_filter.process()
assert success
# Verify output file
result_df = pd.read_excel(self.test_output)
assert len(result_df) == len(pd.read_excel(self.test_input)) # All rows

View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""
Final test to verify the complete implementation including ExcelFilter integration
"""
import sys
sys.path.append('excel_filter/src')
def test_final_implementation():
"""Test the complete implementation"""
print("Testing final implementation...")
try:
# Test import
from excel_filter.gui_components.config_tab import ConfigTab
print("[SUCCESS] ConfigTab imported successfully")
# Test that all required methods exist
required_methods = [
'browse_input_file', 'browse_output_file',
'select_all_columns', 'deselect_all_columns',
'get_selected_columns', 'update_columns_selection',
'update_sheet_selection', 'save_config', 'load_config',
'process_file', 'open_file'
]
print("Checking required methods:")
for method in required_methods:
if hasattr(ConfigTab, method):
print(f"[SUCCESS] {method}() - EXISTS")
else:
print(f"[ERROR] {method}() - MISSING")
return False
# Test that ExcelFilter can be imported (this tests the integration)
try:
from excel_filter.filter import ExcelFilter
print("[SUCCESS] ExcelFilter integration available")
except ImportError as e:
print(f"[WARNING] ExcelFilter import warning: {e}")
print(" (This is expected if running in isolation)")
# Test instantiation
import tkinter as tk
root = tk.Tk()
win11_colors = {'primary': '#0078d4', 'light': '#f0f0f0', 'dark': '#e0e0e0', 'accent': '#005a9e'}
pattern_presets = {"test": "test"}
pattern_descriptions = {"test": "test"}
config_tab = ConfigTab(root, win11_colors, pattern_presets, pattern_descriptions)
print("[SUCCESS] ConfigTab instantiated successfully")
# Test method functionality
print("Testing method functionality:")
# Test column selection
config_tab.select_all_columns()
print(" [SUCCESS] select_all_columns() works")
config_tab.deselect_all_columns()
print(" [SUCCESS] deselect_all_columns() works")
# Test get_selected_columns
selected = config_tab.get_selected_columns()
print(f" [SUCCESS] get_selected_columns() returns: {selected}")
# Test config methods
config_tab.save_config()
print(" [SUCCESS] save_config() works")
config_tab.load_config()
print(" [SUCCESS] load_config() works")
# Test process_file with mock data
config_tab.input_file_var.set("test_input.xlsx")
config_tab.output_file_var.set("test_output.xlsx")
config_tab.pattern_var.set("error|warning")
# This should now work with proper validation
try:
config_tab.process_file()
print(" [SUCCESS] process_file() works (will fail without real files, but validates correctly)")
except Exception as e:
print(f" [WARNING] process_file() failed as expected: {e}")
root.destroy()
print("\n[INFO] All tests passed! The config_tab.py is now fully implemented with ExcelFilter integration.")
return True
except Exception as e:
print(f"[ERROR]: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = test_final_implementation()
if success:
print("\n[INFO] Complete implementation verified successfully!")
else:
print("\n[ERROR] Implementation verification failed.")

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python3
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src'))
import tkinter as tk
from excel_filter.gui_new import ExcelFilterGUI
def test_gui_initialization():
"""Test GUI initialization without running the main loop"""
print("Testing GUI initialization...")
# Create a test window
root = tk.Tk()
root.title("GUI Test")
root.geometry("800x600")
try:
# Initialize the GUI
app = ExcelFilterGUI(root)
print("ExcelFilterGUI initialized successfully")
# Test that main components exist
assert hasattr(app, 'root'), "Root window not set"
assert hasattr(app, 'main_window'), "Main window not set"
assert hasattr(app, 'config_tab'), "Config tab not set"
assert hasattr(app, 'execution_tab'), "Execution tab not set"
assert hasattr(app, 'regex_builder_tab'), "Regex builder tab not set"
print("All main GUI components initialized successfully")
print("GUI initialization test passed")
except Exception as e:
print(f"GUI initialization failed: {e}")
raise
finally:
# Clean up: destroy the root window without running mainloop
root.destroy()
if __name__ == "__main__":
test_gui_initialization()

View File

@@ -0,0 +1,727 @@
#!/usr/bin/env python3
import sys
import os
import tempfile
import json
import pytest
from unittest.mock import Mock, patch, MagicMock
from pathlib import Path
# Add the parent directory to the path for imports
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
import tkinter as tk
from gui_new import ExcelFilterGUI
@pytest.fixture
def mock_root():
"""Create a mock tkinter root window"""
root = Mock(spec=tk.Tk)
root.title = Mock()
root.configure = Mock()
root.iconbitmap = Mock()
root.destroy = Mock()
root.update = Mock()
return root
@pytest.fixture
def temp_files():
"""Create temporary files for testing"""
with tempfile.TemporaryDirectory() as temp_dir:
# Create a temporary config file
config_file = os.path.join(temp_dir, 'config.json')
with open(config_file, 'w') as f:
json.dump({
'input_file': 'test_input.xlsx',
'output_file': 'test_output.xlsx',
'pattern': 'error|warning',
'sheet_name': 'Sheet1',
'columns': ['Column1', 'Column2']
}, f)
# Create a temporary presets file
presets_file = os.path.join(temp_dir, 'presets.json')
with open(presets_file, 'w') as f:
json.dump({
'presets': {'test_pattern': 'test.*'},
'descriptions': {'test_pattern': 'Test pattern'}
}, f)
yield temp_dir, config_file, presets_file
class TestExcelFilterGUI:
def test_update_columns_selection_signature(self):
"""Test that update_columns_selection method accepts selected_columns parameter"""
# Test the method signature directly without full GUI initialization
import inspect
method = getattr(ExcelFilterGUI, 'update_columns_selection')
sig = inspect.signature(method)
# Check if 'selected_columns' parameter exists
assert 'selected_columns' in sig.parameters
param = sig.parameters['selected_columns']
assert param.default is None
def test_color_palette(self):
"""Test that Windows 11 color palette is properly defined"""
# Test colors without full initialization
expected_colors = ['primary', 'primary_dark', 'primary_light', 'background',
'surface', 'text', 'text_secondary', 'border', 'hover', 'pressed']
# Check that all expected colors are defined
for color in expected_colors:
assert hasattr(ExcelFilterGUI, 'win11_colors') or color in ExcelFilterGUI().win11_colors
def test_file_paths(self):
"""Test that file paths are correctly set"""
gui = ExcelFilterGUI.__new__(ExcelFilterGUI) # Create instance without __init__
gui.__init__ = Mock() # Mock __init__ to avoid full initialization
# Manually set attributes for testing
gui.config_file = "config.json"
gui.presets_file = "presets.json"
assert gui.config_file == "config.json"
assert gui.presets_file == "presets.json"
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_create_tabs(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Test tab creation"""
# Setup mocks similar to initialization test
mock_main_window_instance = Mock()
mock_main_window_instance.notebook = Mock()
mock_main_window_instance.scale_factor = 1.0
mock_main_window_instance.language_frame = Mock()
mock_main_window_instance.enhance_tab_appearance = Mock()
mock_main_window.return_value = mock_main_window_instance
mock_config_tab_instance = Mock()
mock_config_tab_instance.get_frame.return_value = Mock()
mock_config_tab_instance.set_execution_tab = Mock()
mock_config_tab_instance.set_main_gui = Mock()
mock_config_tab_instance.set_on_columns_changed = Mock()
mock_config_tab.return_value = mock_config_tab_instance
mock_execution_tab_instance = Mock()
mock_execution_tab_instance.get_frame.return_value = Mock()
mock_execution_tab_instance.set_config_tab = Mock()
mock_execution_tab_instance.set_main_gui = Mock()
mock_execution_tab_instance.update_command_display = Mock()
mock_execution_tab.return_value = mock_execution_tab_instance
mock_regex_tab_instance = Mock()
mock_regex_tab_instance.get_frame.return_value = Mock()
mock_regex_tab.return_value = mock_regex_tab_instance
mock_help_tab_instance = Mock()
mock_help_tab_instance.get_frame.return_value = Mock()
mock_help_tab.return_value = mock_help_tab_instance
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
gui = ExcelFilterGUI(mock_root)
# Verify tabs were added to notebook
assert mock_main_window_instance.notebook.add.call_count == 4
# Verify config tab connections
assert mock_config_tab_instance.browse_input_file is not None
assert mock_config_tab_instance.browse_output_file is not None
assert mock_config_tab_instance.update_columns_selection is not None
assert mock_config_tab_instance.select_all_columns is not None
assert mock_config_tab_instance.deselect_all_columns is not None
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_update_columns_selection_no_file(self, mock_help_tab, mock_regex_tab,
mock_execution_tab, mock_config_tab,
mock_main_window, mock_root):
"""Test update_columns_selection with no input file"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('tkinter.messagebox.showerror') as mock_error:
gui = ExcelFilterGUI(mock_root)
# Set empty input file
gui.config_tab.input_file_var.get.return_value = ""
# Call update_columns_selection
gui.update_columns_selection()
# Verify error message was shown
mock_error.assert_called_once()
args = mock_error.call_args[0]
assert "Bitte geben Sie eine Eingabedatei an" in args[1]
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
@patch('pandas.read_excel')
def test_update_columns_selection_with_file(self, mock_read_excel, mock_help_tab,
mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Test update_columns_selection with valid input file"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
# Mock pandas DataFrame
mock_df = Mock()
mock_df.columns.tolist.return_value = ['Column1', 'Column2', 'Column3']
mock_read_excel.return_value = mock_df
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('tkinter.ttk.Checkbutton') as mock_checkbutton:
gui = ExcelFilterGUI(mock_root)
# Set valid input file
gui.config_tab.input_file_var.get.return_value = "test.xlsx"
gui.config_tab.sheet_var.get.return_value = "Sheet1"
# Mock container operations
mock_container = Mock()
gui.config_tab.columns_container = mock_container
mock_container.winfo_children.return_value = []
mock_container.grid = Mock()
mock_container.update_idletasks = Mock()
# Call update_columns_selection
gui.update_columns_selection()
# Verify pandas was called
mock_read_excel.assert_called_once_with("test.xlsx", sheet_name="Sheet1")
# Verify checkboxes were created (3 columns)
assert mock_checkbutton.call_count == 3
# Verify regex column combobox was updated
gui.config_tab.regex_column_combobox.__setitem__.assert_called_with(
'values', ['Alle Spalten', 'Column1', 'Column2', 'Column3']
)
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_update_columns_selection_with_selected_columns(self, mock_help_tab, mock_regex_tab,
mock_execution_tab, mock_config_tab,
mock_main_window, mock_root):
"""Test update_columns_selection with pre-selected columns"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('pandas.read_excel') as mock_read_excel:
mock_df = Mock()
mock_df.columns.tolist.return_value = ['Column1', 'Column2', 'Column3']
mock_read_excel.return_value = mock_df
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('tkinter.ttk.Checkbutton') as mock_checkbutton:
gui = ExcelFilterGUI(mock_root)
# Set valid input file
gui.config_tab.input_file_var.get.return_value = "test.xlsx"
gui.config_tab.sheet_var.get.return_value = "Sheet1"
# Mock container
mock_container = Mock()
gui.config_tab.columns_container = mock_container
mock_container.winfo_children.return_value = []
mock_container.grid = Mock()
mock_container.update_idletasks = Mock()
# Create mock variables for columns
mock_vars = {}
for i, col in enumerate(['Column1', 'Column2', 'Column3']):
mock_var = Mock()
mock_vars[col] = mock_var
gui.config_tab.columns_vars = mock_vars
# Call with selected columns
selected_columns = ['Column1', 'Column3']
gui.update_columns_selection(selected_columns=selected_columns)
# Verify that selected columns were set
mock_vars['Column1'].set.assert_called_with(1)
mock_vars['Column3'].set.assert_called_with(1)
# Column2 should not be set (not in selected_columns)
mock_vars['Column2'].set.assert_not_called()
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_select_all_columns(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Test select_all_columns method"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
gui = ExcelFilterGUI(mock_root)
# Setup mock column variables
mock_var1 = Mock()
mock_var2 = Mock()
gui.config_tab.columns_vars = {
'Column1': mock_var1,
'Column2': mock_var2
}
# Call select_all_columns
gui.select_all_columns()
# Verify all variables were set to 1
mock_var1.set.assert_called_with(1)
mock_var2.set.assert_called_with(1)
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_deselect_all_columns(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Test deselect_all_columns method"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
gui = ExcelFilterGUI(mock_root)
# Setup mock column variables
mock_var1 = Mock()
mock_var2 = Mock()
gui.config_tab.columns_vars = {
'Column1': mock_var1,
'Column2': mock_var2
}
# Call deselect_all_columns
gui.deselect_all_columns()
# Verify all variables were set to 0
mock_var1.set.assert_called_with(0)
mock_var2.set.assert_called_with(0)
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
@patch('gui_new.browse_file')
def test_browse_input_file(self, mock_browse_file, mock_help_tab, mock_regex_tab,
mock_execution_tab, mock_config_tab, mock_main_window, mock_root):
"""Test browse_input_file method"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
mock_browse_file.return_value = "/path/to/test.xlsx"
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
gui = ExcelFilterGUI(mock_root)
# Mock the methods that will be called
gui.update_sheet_selection = Mock()
gui.update_columns_selection = Mock()
# Call browse_input_file
gui.browse_input_file()
# Verify file was set
gui.config_tab.input_file_var.set.assert_called_with("/path/to/test.xlsx")
# Verify update methods were called
gui.update_sheet_selection.assert_called_once()
gui.update_columns_selection.assert_called_once()
# Verify output file was auto-filled
expected_output = "/path/to/test_filtered.xlsx"
gui.config_tab.output_file_var.set.assert_called_with(expected_output)
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
@patch('gui_new.browse_save_file')
def test_browse_output_file(self, mock_browse_save_file, mock_help_tab, mock_regex_tab,
mock_execution_tab, mock_config_tab, mock_main_window, mock_root):
"""Test browse_output_file method"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
mock_browse_save_file.return_value = "/path/to/output.xlsx"
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
gui = ExcelFilterGUI(mock_root)
# Call browse_output_file
gui.browse_output_file()
# Verify file was set
gui.config_tab.output_file_var.set.assert_called_with("/path/to/output.xlsx")
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
@patch('gui_new.load_config')
def test_load_config(self, mock_load_config, mock_help_tab, mock_regex_tab,
mock_execution_tab, mock_config_tab, mock_main_window, mock_root):
"""Test load_config method"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
mock_load_config.return_value = {
'input_file': 'test.xlsx',
'output_file': 'output.xlsx',
'pattern': 'error.*',
'sheet_name': 'Sheet1',
'columns': ['Col1', 'Col2']
}
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('tkinter.messagebox.showinfo') as mock_info:
with patch('os.path.exists', return_value=True):
gui = ExcelFilterGUI(mock_root)
# Mock update methods
gui.update_sheet_selection = Mock()
gui.update_columns_selection = Mock()
# Call load_config
gui.load_config()
# Verify config values were set
gui.config_tab.input_file_var.set.assert_called_with('test.xlsx')
gui.config_tab.output_file_var.set.assert_called_with('output.xlsx')
gui.config_tab.pattern_var.set.assert_called_with('error.*')
gui.config_tab.sheet_var.set.assert_called_with('Sheet1')
# Verify update methods were called
gui.update_sheet_selection.assert_called_once()
gui.update_columns_selection.assert_called_once_with(selected_columns=['Col1', 'Col2'])
# Verify success message
mock_info.assert_called_once()
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
@patch('gui_new.save_config')
def test_save_config(self, mock_save_config, mock_help_tab, mock_regex_tab,
mock_execution_tab, mock_config_tab, mock_main_window, mock_root):
"""Test save_config method"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('tkinter.messagebox.showinfo') as mock_info:
gui = ExcelFilterGUI(mock_root)
# Setup mock return values
gui.config_tab.input_file_var.get.return_value = 'input.xlsx'
gui.config_tab.output_file_var.get.return_value = 'output.xlsx'
gui.config_tab.pattern_var.get.return_value = 'pattern.*'
gui.config_tab.sheet_var.get.return_value = 'Sheet1'
gui.config_tab.columns_var.get.return_value = 'columns'
# Call save_config
gui.save_config()
# Verify save_config was called with correct data
expected_config = {
'input_file': 'input.xlsx',
'output_file': 'output.xlsx',
'pattern': 'pattern.*',
'sheet_name': 'Sheet1',
'columns': 'columns'
}
mock_save_config.assert_called_once_with("config.json", expected_config)
# Verify success message
mock_info.assert_called_once()
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_load_presets(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Test load_presets method"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Default Pattern')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('os.path.exists', return_value=True):
with patch('gui_new.save_presets') as mock_save_presets:
gui = ExcelFilterGUI(mock_root)
# Call load_presets
presets, descriptions = gui.load_presets()
# Verify presets were loaded
assert isinstance(presets, dict)
assert isinstance(descriptions, dict)
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_save_presets(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Test save_presets method"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('gui_new.open', Mock()) as mock_open:
with patch('json.dump') as mock_dump:
gui = ExcelFilterGUI(mock_root)
# Set up test presets
gui.pattern_presets = {'test': 'pattern'}
gui.pattern_descriptions = {'test': 'description'}
# Call save_presets
gui.save_presets()
# Verify json.dump was called
mock_dump.assert_called_once()
args = mock_dump.call_args[0]
assert args[0]['presets'] == {'test': 'pattern'}
assert args[0]['descriptions'] == {'test': 'description'}
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_open_file_windows(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Test open_file method on Windows"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('sys.platform', 'win32'):
with patch('os.startfile') as mock_startfile:
gui = ExcelFilterGUI(mock_root)
# Call open_file
gui.open_file('test.xlsx')
# Verify os.startfile was called
mock_startfile.assert_called_once_with('test.xlsx')
@patch('gui_new.MainWindow')
@patch('gui_new.ConfigTab')
@patch('gui_new.ExecutionTab')
@patch('gui_new.RegexBuilderTab')
@patch('gui_new.HelpTab')
def test_open_file_unix(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Test open_file method on Unix-like systems"""
# Setup basic mocks
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root)
with patch('gui_new.Translations') as mock_translations:
mock_translations_instance = Mock()
mock_translations_instance.__getitem__ = Mock(return_value='Test')
mock_translations_instance.app_title = 'Test App'
mock_translations.return_value = mock_translations_instance
with patch('builtins.open', Mock()):
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
with patch('sys.platform', 'linux'):
with patch('subprocess.run') as mock_run:
gui = ExcelFilterGUI(mock_root)
# Call open_file
gui.open_file('test.xlsx')
# Verify subprocess.run was called with xdg-open
mock_run.assert_called_once_with(['xdg-open', 'test.xlsx'])
def _setup_basic_mocks(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
mock_config_tab, mock_main_window, mock_root):
"""Helper method to setup basic mocks for tests"""
# Setup MainWindow mock
mock_main_window_instance = Mock()
mock_main_window_instance.notebook = Mock()
mock_main_window_instance.scale_factor = 1.0
mock_main_window_instance.language_frame = Mock()
mock_main_window_instance.enhance_tab_appearance = Mock()
mock_main_window.return_value = mock_main_window_instance
# Setup ConfigTab mock
mock_config_tab_instance = Mock()
mock_config_frame = Mock()
mock_config_frame.winfo_children.return_value = [] # Make it iterable
mock_config_tab_instance.frame = mock_config_frame
mock_config_tab_instance.get_frame.return_value = mock_config_frame
mock_config_tab_instance.set_execution_tab = Mock()
mock_config_tab_instance.set_main_gui = Mock()
mock_config_tab_instance.set_on_columns_changed = Mock()
mock_config_tab_instance.input_file_var = Mock()
mock_config_tab_instance.output_file_var = Mock()
mock_config_tab_instance.pattern_var = Mock()
mock_config_tab_instance.sheet_var = Mock()
mock_config_tab_instance.columns_var = Mock()
mock_config_tab_instance.regex_column_combobox = Mock()
mock_config_tab_instance.columns_container = Mock()
mock_config_tab_instance.columns_vars = {}
mock_config_tab_instance.log_message = Mock()
mock_config_tab_instance.log_error = Mock()
mock_config_tab_instance.status_var = Mock()
mock_config_tab.return_value = mock_config_tab_instance
# Setup ExecutionTab mock
mock_execution_tab_instance = Mock()
mock_execution_tab_instance.get_frame.return_value = Mock()
mock_execution_tab_instance.set_config_tab = Mock()
mock_execution_tab_instance.set_main_gui = Mock()
mock_execution_tab_instance.update_command_display = Mock()
mock_execution_tab.return_value = mock_execution_tab_instance
# Setup RegexBuilderTab mock
mock_regex_tab_instance = Mock()
mock_regex_tab_instance.get_frame.return_value = Mock()
mock_regex_tab.return_value = mock_regex_tab_instance
# Setup HelpTab mock
mock_help_tab_instance = Mock()
mock_help_tab_instance.get_frame.return_value = Mock()
mock_help_tab.return_value = mock_help_tab_instance
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""
Simple focused tests for gui_new.py - ExcelFilterGUI class
Tests the key functionality that was fixed
"""
import sys
import os
import pytest
from unittest.mock import Mock, patch
# Add the parent directory to the path for imports
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from gui_new import ExcelFilterGUI
class TestExcelFilterGUISimple:
"""Simple focused tests for ExcelFilterGUI"""
def test_update_columns_selection_method_signature(self):
"""Test that update_columns_selection method accepts selected_columns parameter"""
import inspect
# Get the method signature
method = getattr(ExcelFilterGUI, 'update_columns_selection')
sig = inspect.signature(method)
# Verify 'selected_columns' parameter exists with correct default
assert 'selected_columns' in sig.parameters
param = sig.parameters['selected_columns']
assert param.default is None
print("SUCCESS: update_columns_selection method signature is correct")
def test_class_attributes_exist(self):
"""Test that key class attributes can be accessed"""
# Test that we can access class attributes without instantiation
assert hasattr(ExcelFilterGUI, 'update_columns_selection')
assert hasattr(ExcelFilterGUI, 'select_all_columns')
assert hasattr(ExcelFilterGUI, 'deselect_all_columns')
assert hasattr(ExcelFilterGUI, 'load_config')
assert hasattr(ExcelFilterGUI, 'save_config')
print("SUCCESS: All key methods exist on the class")
def test_win11_colors_attribute(self):
"""Test that win11_colors is properly defined"""
# Create a minimal instance to test colors (avoiding full initialization)
gui = ExcelFilterGUI.__new__(ExcelFilterGUI)
gui.win11_colors = {
'primary': '#0078d4',
'primary_dark': '#005a9e',
'primary_light': '#cce4f7',
'background': '#f0f0f0',
'surface': '#ffffff',
'text': '#212121',
'text_secondary': '#616161',
'border': '#e0e0e0',
'hover': '#e8f0fe',
'pressed': '#d0e0f5',
'success': '#107c10',
'warning': '#c55c00',
'error': '#c42b1c'
}
# Check that it's a dictionary with expected keys
colors = gui.win11_colors
assert isinstance(colors, dict)
expected_keys = ['primary', 'background', 'surface', 'text', 'border']
for key in expected_keys:
assert key in colors
print("SUCCESS: Windows 11 color palette is properly defined")
def test_method_can_be_called_with_selected_columns(self):
"""Test that the method can be called with selected_columns parameter"""
# Create a minimal mock instance to test method calling
gui = ExcelFilterGUI.__new__(ExcelFilterGUI)
# Mock the required attributes
gui.config_tab = Mock()
gui.config_tab.input_file_var = Mock()
gui.config_tab.input_file_var.get.return_value = "" # Empty file to avoid full execution
# This should not raise an exception about unexpected keyword arguments
try:
gui.update_columns_selection(selected_columns=['Col1', 'Col2'])
print("SUCCESS: Method accepts selected_columns parameter without error")
except TypeError as e:
if "unexpected keyword argument" in str(e):
pytest.fail(f"Method still doesn't accept selected_columns: {e}")
else:
# Other TypeErrors are expected (due to mocking), just not the kwarg error
print("SUCCESS: Method accepts selected_columns parameter (other errors expected due to mocking)")
def test_selected_columns_parameter_restoration_logic(self):
"""Test that the selected_columns parameter restoration logic works"""
# Create a minimal mock instance to test the restoration logic
gui = ExcelFilterGUI.__new__(ExcelFilterGUI)
# Mock the required attributes for the method
gui.config_tab = Mock()
gui.config_tab.input_file_var = Mock()
gui.config_tab.input_file_var.get.return_value = "test.xlsx"
gui.config_tab.sheet_var = Mock()
gui.config_tab.sheet_var.get.return_value = "Sheet1"
gui.config_tab.columns_container = Mock()
gui.config_tab.columns_container.winfo_children.return_value = []
gui.config_tab.columns_container.grid = Mock()
gui.config_tab.columns_container.update_idletasks = Mock()
gui.config_tab.columns_vars = {}
gui.config_tab.log_message = Mock()
gui.config_tab.log_error = Mock()
# Mock regex column combobox
gui.config_tab.regex_column_combobox = Mock()
gui.config_tab.regex_column_combobox.__setitem__ = Mock()
# Mock pandas operations
with patch('pandas.read_excel') as mock_read_excel, \
patch('tkinter.ttk.Checkbutton') as mock_checkbutton:
mock_df = Mock()
mock_df.columns.tolist.return_value = ['Column1', 'Column2', 'Column3']
mock_read_excel.return_value = mock_df
# Create mock variable for testing selection restoration
mock_var1 = Mock()
mock_var2 = Mock()
mock_var3 = Mock()
# Simulate the method creating column variables
def side_effect(*args, **kwargs):
column = args[1] if args else kwargs.get('text', '')
if column == 'Column1':
gui.config_tab.columns_vars['Column1'] = mock_var1
elif column == 'Column2':
gui.config_tab.columns_vars['Column2'] = mock_var2
elif column == 'Column3':
gui.config_tab.columns_vars['Column3'] = mock_var3
mock_checkbutton.side_effect = side_effect
# Call update_columns_selection with selected_columns
selected_columns = ['Column1', 'Column3']
gui.update_columns_selection(selected_columns=selected_columns)
# Verify that selected columns were set to 1
mock_var1.set.assert_called_with(1)
mock_var3.set.assert_called_with(1)
# Column2 should not be set since it's not in selected_columns
mock_var2.set.assert_not_called()
print("SUCCESS: selected_columns parameter correctly restores column selections")
if __name__ == "__main__":
# Run the tests manually
test_instance = TestExcelFilterGUISimple()
print("Running simple GUI tests...")
print()
try:
test_instance.test_update_columns_selection_method_signature()
test_instance.test_class_attributes_exist()
test_instance.test_win11_colors_attribute()
test_instance.test_method_can_be_called_with_selected_columns()
test_instance.test_selected_columns_parameter_restoration_logic()
print()
print("SUCCESS: All simple tests passed!")
print("The fix for the selected_columns parameter is working correctly.")
except Exception as e:
print(f"FAIL: Test failed: {e}")
import traceback
traceback.print_exc()
sys.exit(1)

View File

@@ -0,0 +1,349 @@
import os
import sys
import pandas as pd
import numpy as np
import tempfile
import time
import gc
import pytest
# Add src directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from excel_filter.filter import ExcelFilter
class TestLargeFileHandling:
"""
Test cases for handling very large Excel files
"""
def generate_large_test_data(self, num_rows=50000):
"""
Generate a large dataset for testing
"""
np.random.seed(42) # For reproducible results
# Create diverse data
names = [f"User_{i}" for i in range(num_rows)]
ages = np.random.randint(18, 80, num_rows)
salaries = np.random.uniform(30000, 200000, num_rows).round(2)
departments = np.random.choice(['HR', 'IT', 'Finance', 'Marketing', 'Sales', 'Operations'], num_rows)
statuses = np.random.choice(['Active', 'Inactive', 'Pending', 'Terminated'], num_rows)
cities = np.random.choice(['New York', 'London', 'Tokyo', 'Berlin', 'Paris', 'Sydney', 'Toronto', 'Singapore'], num_rows)
# Create various message types
messages = []
for i in range(num_rows):
if i % 100 == 0:
messages.append("ERROR: Critical system failure detected")
elif i % 50 == 0:
messages.append("WARNING: Low disk space on server")
elif i % 25 == 0:
messages.append("INFO: User login successful")
elif i % 10 == 0:
messages.append("DEBUG: Processing completed successfully")
else:
messages.append(f"Normal operation log entry {i}")
# Create some rows with specific patterns for testing
special_indices = np.random.choice(num_rows, size=int(num_rows * 0.1), replace=False)
for idx in special_indices[:len(special_indices)//3]:
messages[idx] = "CRITICAL: Database connection lost"
for idx in special_indices[len(special_indices)//3:2*len(special_indices)//3]:
messages[idx] = "ALERT: Security breach detected"
for idx in special_indices[2*len(special_indices)//3:]:
messages[idx] = "NOTICE: System maintenance scheduled"
# Create DataFrame
# For very large datasets, limit the date range to avoid pandas datetime limits
if num_rows > 50000:
# For large datasets, use a repeating date range to avoid datetime overflow
join_dates = []
base_dates = pd.date_range('2010-01-01', periods=min(50000, num_rows), freq='D')
for i in range(num_rows):
join_dates.append(base_dates[i % len(base_dates)])
else:
join_dates = pd.date_range('2010-01-01', periods=num_rows, freq='D')
df = pd.DataFrame({
'Name': names,
'Age': ages,
'Salary': salaries,
'Department': departments,
'Status': statuses,
'City': cities,
'Message': messages,
'Employee_ID': range(100000, 100000 + num_rows),
'Join_Date': join_dates,
'Performance_Score': np.random.uniform(1.0, 5.0, num_rows).round(1)
})
return df
def test_large_file_generation_and_basic_filtering(self):
"""
Test basic filtering on a large file (10k rows)
"""
# Generate large test data
df = self.generate_large_test_data(10000)
with tempfile.TemporaryDirectory() as temp_dir:
input_file = os.path.join(temp_dir, "large_test_data.xlsx")
output_file = os.path.join(temp_dir, "filtered_output.xlsx")
# Save to Excel
start_time = time.time()
df.to_excel(input_file, index=False)
save_time = time.time() - start_time
print(f"Generated and saved {len(df)} rows in {save_time:.2f} seconds")
# Test filtering for ERROR messages
pattern = r"ERROR|CRITICAL"
excel_filter = ExcelFilter(
input_file=input_file,
output_file=output_file,
pattern=pattern,
columns=['Message']
)
# Process and measure time
start_time = time.time()
success = excel_filter.process()
process_time = time.time() - start_time
assert success, "Filtering should succeed"
# Verify results
result_df = pd.read_excel(output_file)
expected_count = len(df[df['Message'].str.contains(pattern, case=False, na=False)])
print(f"Filtered {len(df)} rows to {len(result_df)} rows in {process_time:.2f} seconds")
print(f"Expected {expected_count} matches")
assert len(result_df) == expected_count, f"Expected {expected_count} rows, got {len(result_df)}"
# Verify all results contain the pattern
import re
for msg in result_df['Message']:
assert re.search(pattern, str(msg), re.IGNORECASE), f"Message '{msg}' should match pattern '{pattern}'"
def test_large_file_numeric_filtering(self):
"""
Test numeric filtering on large files
"""
df = self.generate_large_test_data(30000)
with tempfile.TemporaryDirectory() as temp_dir:
input_file = os.path.join(temp_dir, "large_numeric_test.xlsx")
output_file = os.path.join(temp_dir, "numeric_filtered.xlsx")
df.to_excel(input_file, index=False)
# Filter for high salaries (> 150000)
numeric_filter = {'column': 'Salary', 'operator': '>', 'value': 150000}
excel_filter = ExcelFilter(
input_file=input_file,
output_file=output_file,
numeric_filter=numeric_filter
)
success = excel_filter.process()
assert success
result_df = pd.read_excel(output_file)
expected_count = len(df[df['Salary'] > 150000])
assert len(result_df) == expected_count
assert all(result_df['Salary'] > 150000)
def test_large_file_combined_filtering(self):
"""
Test combined regex and numeric filtering on large files
"""
df = self.generate_large_test_data(40000)
with tempfile.TemporaryDirectory() as temp_dir:
input_file = os.path.join(temp_dir, "combined_test.xlsx")
output_file = os.path.join(temp_dir, "combined_filtered.xlsx")
df.to_excel(input_file, index=False)
# Filter: IT department employees with salary > 100000
pattern = r"IT"
numeric_filter = {'column': 'Salary', 'operator': '>', 'value': 100000}
excel_filter = ExcelFilter(
input_file=input_file,
output_file=output_file,
pattern=pattern,
columns=['Department', 'Salary'],
numeric_filter=numeric_filter
)
success = excel_filter.process()
assert success
result_df = pd.read_excel(output_file)
expected_df = df[
(df['Department'].str.contains(pattern, case=False, na=False)) &
(df['Salary'] > 100000)
]
assert len(result_df) == len(expected_df)
# Verify all results meet both criteria
assert all(result_df['Department'].str.contains(pattern, case=False, na=False))
assert all(result_df['Salary'] > 100000)
def test_memory_efficiency_large_file(self):
"""
Test memory efficiency with very large files (100k+ rows)
"""
# Skip this test if we're in a memory-constrained environment
try:
df = self.generate_large_test_data(100000)
except MemoryError:
pytest.skip("Not enough memory for 100k row test")
with tempfile.TemporaryDirectory() as temp_dir:
input_file = os.path.join(temp_dir, "memory_test.xlsx")
output_file = os.path.join(temp_dir, "memory_filtered.xlsx")
df.to_excel(input_file, index=False)
# Simple filter that should match ~10% of rows
pattern = r"ERROR|WARNING|CRITICAL|ALERT"
excel_filter = ExcelFilter(
input_file=input_file,
output_file=output_file,
pattern=pattern,
columns=['Message']
)
# Monitor memory usage if possible
try:
import psutil
process = psutil.Process(os.getpid())
initial_memory = process.memory_info().rss / 1024 / 1024 # MB
success = excel_filter.process()
final_memory = process.memory_info().rss / 1024 / 1024 # MB
memory_used = final_memory - initial_memory
assert success
print(f"Memory used: {memory_used:.1f}MB")
# Should not use excessive memory (arbitrary threshold)
assert memory_used < 2000, f"Used {memory_used:.1f}MB, should be less than 2000MB"
except ImportError:
# psutil not available, just run the filter without memory monitoring
print("psutil not available, skipping memory monitoring")
success = excel_filter.process()
assert success
result_df = pd.read_excel(output_file)
expected_count = len(df[df['Message'].str.contains(pattern, case=False, na=False)])
assert len(result_df) == expected_count
def test_edge_cases_large_file(self):
"""
Test edge cases with large files
"""
df = self.generate_large_test_data(25000)
# Add some edge case data
df.loc[0, 'Message'] = "" # Empty string
df.loc[1, 'Message'] = None # NaN value
df.loc[2, 'Salary'] = np.nan # NaN numeric
df.loc[3, 'Message'] = "A" * 10000 # Very long string
with tempfile.TemporaryDirectory() as temp_dir:
input_file = os.path.join(temp_dir, "edge_case_test.xlsx")
output_file = os.path.join(temp_dir, "edge_case_filtered.xlsx")
df.to_excel(input_file, index=False)
# Filter that should handle edge cases gracefully
pattern = r"ERROR"
excel_filter = ExcelFilter(
input_file=input_file,
output_file=output_file,
pattern=pattern,
columns=['Message']
)
success = excel_filter.process()
assert success
result_df = pd.read_excel(output_file)
# Should find ERROR messages but handle edge cases
error_messages = df[df['Message'].str.contains(pattern, case=False, na=False)]
assert len(result_df) == len(error_messages)
@pytest.mark.slow
def test_very_large_file_stress_test(self):
"""
Stress test with a very large file (marked as slow test)
"""
# This test is marked as slow and may be skipped in regular runs
try:
df = self.generate_large_test_data(200000) # 200k rows
except MemoryError:
pytest.skip("Not enough memory for 200k row stress test")
with tempfile.TemporaryDirectory() as temp_dir:
input_file = os.path.join(temp_dir, "stress_test.xlsx")
output_file = os.path.join(temp_dir, "stress_filtered.xlsx")
# Time the save operation
start_time = time.time()
df.to_excel(input_file, index=False)
save_time = time.time() - start_time
file_size = os.path.getsize(input_file) / 1024 / 1024 # MB
print(f"Created {file_size:.1f}MB test file with {len(df)} rows in {save_time:.2f} seconds")
# Apply complex filtering
pattern = r"ERROR|WARNING|CRITICAL"
numeric_filter = {'column': 'Age', 'operator': '>', 'value': 50}
excel_filter = ExcelFilter(
input_file=input_file,
output_file=output_file,
pattern=pattern,
columns=['Message', 'Age'],
numeric_filter=numeric_filter
)
start_time = time.time()
success = excel_filter.process()
process_time = time.time() - start_time
assert success
result_df = pd.read_excel(output_file)
expected_df = df[
(df['Message'].str.contains(pattern, case=False, na=False)) &
(df['Age'] > 50)
]
print(f"Processed {len(df)} rows in {process_time:.2f} seconds")
print(f"Filtered to {len(result_df)} rows (expected {len(expected_df)})")
assert len(result_df) == len(expected_df)
# Verify results are correct
assert all(result_df['Message'].str.contains(pattern, case=False, na=False))
assert all(result_df['Age'] > 50)
# Cleanup
gc.collect()

View File

@@ -0,0 +1,104 @@
"""
Unit tests for main.py
"""
import unittest
import json
import tempfile
import os
from unittest.mock import patch
from excel_filter.main import load_config, main
class TestMain(unittest.TestCase):
"""
Test class for main module functions
"""
def test_load_config_success(self):
"""
Test successful config loading
"""
config_data = {
"pattern": "test.*",
"sheet_name": "Sheet1",
"columns": ["A", "B"]
}
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump(config_data, f)
config_file = f.name
try:
result = load_config(config_file)
self.assertEqual(result, config_data)
finally:
os.unlink(config_file)
def test_load_config_file_not_found(self):
"""
Test config loading with non-existent file
"""
with self.assertRaises(Exception):
load_config("nonexistent.json")
@patch('sys.argv', ['main.py', '--input', 'input.xlsx', '--output', 'output.xlsx', '--pattern', 'test'])
@patch('excel_filter.main.ExcelFilter')
def test_main_with_args(self, mock_filter_class):
"""
Test main function with command line arguments
"""
mock_filter = mock_filter_class.return_value
mock_filter.process.return_value = True
main()
mock_filter_class.assert_called_once_with(
input_file='input.xlsx',
output_file='output.xlsx',
pattern='test',
sheet_name=None,
columns=None
)
mock_filter.process.assert_called_once()
@patch('sys.argv', ['main.py', '--input', 'input.xlsx', '--output', 'output.xlsx', '--config', 'config.json'])
@patch('excel_filter.main.load_config')
@patch('excel_filter.main.ExcelFilter')
def test_main_with_config(self, mock_filter_class, mock_load_config):
"""
Test main function with config file
"""
mock_load_config.return_value = {
"pattern": "config_pattern",
"sheet_name": "ConfigSheet",
"columns": ["Col1", "Col2"]
}
mock_filter = mock_filter_class.return_value
mock_filter.process.return_value = True
main()
mock_load_config.assert_called_once_with('config.json')
mock_filter_class.assert_called_once_with(
input_file='input.xlsx',
output_file='output.xlsx',
pattern='config_pattern',
sheet_name='ConfigSheet',
columns=['Col1', 'Col2']
)
mock_filter.process.assert_called_once()
@patch('sys.argv', ['main.py', '--input', 'input.xlsx', '--output', 'output.xlsx'])
@patch('excel_filter.main.logger')
def test_main_no_pattern(self, mock_logger):
"""
Test main function with no pattern specified
"""
main()
mock_logger.error.assert_called_once_with("Kein Regex-Muster angegeben")
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,448 @@
#!/usr/bin/env python3
import pytest
import pandas as pd
import tempfile
import os
import sys
import unittest
from unittest.mock import Mock, patch
# Add src directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
# Import the classes to test
from excel_filter.filter import ExcelFilter
from excel_filter.gui_components.config_tab import ConfigTab
from excel_filter.translations import Translations
class TestNumericFilter:
"""Test cases for numeric filtering functionality"""
def setup_method(self):
"""Set up test fixtures"""
# Create test data
self.test_data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Age': [25, 30, 35, 40, 45],
'Salary': [50000.50, 60000.75, 70000.25, 80000.00, 90000.90],
'Score': [85.5, 92.0, 78.3, 96.7, 88.1],
'Department': ['HR', 'IT', 'Finance', 'IT', 'HR']
}
self.df = pd.DataFrame(self.test_data)
def test_numeric_filter_greater_than(self):
"""Test filtering values greater than a threshold"""
numeric_filter = {'column': 'Age', 'operator': '>', 'value': 30}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
expected_ages = [35, 40, 45] # Ages > 30
assert len(result) == 3
assert result['Age'].tolist() == expected_ages
def test_numeric_filter_less_than(self):
"""Test filtering values less than a threshold"""
numeric_filter = {'column': 'Salary', 'operator': '<', 'value': 70000.00}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
expected_salaries = [50000.50, 60000.75] # Salaries < 70000
assert len(result) == 2
assert result['Salary'].tolist() == expected_salaries
def test_numeric_filter_greater_equal(self):
"""Test filtering values greater than or equal to a threshold"""
numeric_filter = {'column': 'Score', 'operator': '>=', 'value': 88.1}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
expected_scores = [92.0, 96.7, 88.1] # Scores >= 88.1
assert len(result) == 3
assert result['Score'].tolist() == expected_scores
def test_numeric_filter_less_equal(self):
"""Test filtering values less than or equal to a threshold"""
numeric_filter = {'column': 'Age', 'operator': '<=', 'value': 30}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
expected_ages = [25, 30] # Ages <= 30
assert len(result) == 2
assert result['Age'].tolist() == expected_ages
def test_numeric_filter_equal(self):
"""Test filtering values equal to a specific value"""
numeric_filter = {'column': 'Age', 'operator': '=', 'value': 35}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
assert len(result) == 1
assert result['Age'].iloc[0] == 35
assert result['Name'].iloc[0] == 'Charlie'
def test_numeric_filter_combined_with_regex(self):
"""Test combining numeric filter with regex filter"""
numeric_filter = {'column': 'Age', 'operator': '>', 'value': 30}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
pattern=r'HR', # Regex pattern for HR department
numeric_filter=numeric_filter
)
result = excel_filter.filter_dataframe(self.df)
# Should find people in HR department who are older than 30
# Alice (25, HR) - too young
# Eve (45, HR) - matches both criteria
assert len(result) == 1
assert result['Name'].iloc[0] == 'Eve'
assert result['Age'].iloc[0] == 45
assert result['Department'].iloc[0] == 'HR'
def test_numeric_filter_no_matches(self):
"""Test numeric filter that matches no rows"""
numeric_filter = {'column': 'Age', 'operator': '>', 'value': 100}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
assert len(result) == 0
assert result.shape[0] == 0
def test_numeric_filter_invalid_column(self):
"""Test numeric filter with non-existent column"""
numeric_filter = {'column': 'NonExistentColumn', 'operator': '>', 'value': 30}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
with pytest.raises(ValueError, match="Spalte 'NonExistentColumn' existiert nicht"):
excel_filter._apply_numeric_filter(self.df)
def test_numeric_filter_invalid_operator(self):
"""Test numeric filter with invalid operator"""
numeric_filter = {'column': 'Age', 'operator': 'invalid', 'value': 30}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
with pytest.raises(ValueError, match="Unbekannter Operator"):
excel_filter._apply_numeric_filter(self.df)
def test_numeric_filter_non_numeric_values(self):
"""Test numeric filter on column with non-numeric values"""
# Add a column with mixed data types
df_mixed = self.df.copy()
df_mixed['Mixed'] = ['text', 25, 30.5, '45', None]
numeric_filter = {'column': 'Mixed', 'operator': '>', 'value': 25}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(df_mixed)
# pandas to_numeric with errors='coerce' converts '45' to 45.0
# So we get both 30.5 and 45.0 as matches for > 25
assert len(result) == 2
# Check that we have the expected numeric values (ignoring non-numeric)
numeric_values = []
for val in result['Mixed']:
try:
numeric_values.append(float(val))
except (ValueError, TypeError):
continue # Skip non-numeric values
assert sorted(numeric_values) == [30.5, 45.0]
def test_numeric_filter_with_decimals(self):
"""Test numeric filter with decimal values"""
numeric_filter = {'column': 'Salary', 'operator': '>', 'value': 65000.50}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
expected_salaries = [70000.25, 80000.00, 90000.90] # Salaries > 65000.50
assert len(result) == 3
assert result['Salary'].tolist() == expected_salaries
def test_numeric_filter_boundary_values(self):
"""Test numeric filter with boundary values"""
# Test exactly equal to boundary
numeric_filter = {'column': 'Score', 'operator': '=', 'value': 88.1}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
assert len(result) == 1
assert result['Score'].iloc[0] == 88.1
# Test greater than with boundary
numeric_filter = {'column': 'Score', 'operator': '>', 'value': 88.1}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
expected_scores = [92.0, 96.7] # Scores > 88.1
assert len(result) == 2
assert result['Score'].tolist() == expected_scores
def test_numeric_filter_all_columns(self):
"""Test numeric filter applied to all columns"""
# Test filtering across all columns for values > 30
numeric_filter = {'column': 'Alle Spalten', 'operator': '>', 'value': 30}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
# Should find rows where ANY column has a value > 30
# All rows have Salary and Score values > 30, so all rows match
assert len(result) == 5 # All rows match
# Verify all rows are included
names = sorted(result['Name'].tolist())
assert names == ['Alice', 'Bob', 'Charlie', 'David', 'Eve']
def test_numeric_filter_all_columns_less_than(self):
"""Test numeric filter applied to all columns with less than"""
# Test filtering across all columns for values < 30
numeric_filter = {'column': 'Alle Spalten', 'operator': '<', 'value': 30}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
# Should find rows where ANY column has a value < 30
# Row 0: Age=25 (Age < 30)
# Row 1: Age=30 (Age = 30, not < 30), but Score=92.0? Wait, let me check data
# Actually, looking at the data: Row 0 has Age=25 (< 30), others don't have values < 30
# Row 4 has Score=88.1 but that's not < 30
# So only Row 0 should match
assert len(result) == 1
assert result['Age'].iloc[0] == 25
assert result['Name'].iloc[0] == 'Alice'
def test_numeric_filter_all_columns_no_matches(self):
"""Test numeric filter on all columns with no matches"""
numeric_filter = {'column': 'Alle Spalten', 'operator': '>', 'value': 100000}
excel_filter = ExcelFilter(
input_file=None, output_file=None,
numeric_filter=numeric_filter
)
result = excel_filter._apply_numeric_filter(self.df)
assert len(result) == 0
class TestNumericFilterLogic:
"""Test cases for numeric filter logic without UI dependencies"""
def setup_method(self):
"""Set up test fixtures"""
self.translations = Translations()
@pytest.mark.parametrize("display_op,expected_op", [
("> (größer als)", ">"),
("< (kleiner als)", "<"),
(">= (größer/gleich)", ">="),
("<= (kleiner/gleich)", "<="),
("= (gleich)", "=")
])
def test_numeric_filter_settings_conversion(self, display_op, expected_op):
"""Test conversion of UI display values to filter settings"""
# Create operator mapping dict
operator_map = {
"> (größer als)": ">",
"< (kleiner als)": "<",
">= (größer/gleich)": ">=",
"<= (kleiner/gleich)": "<=",
"= (gleich)": "="
}
# Simulate the logic from get_numeric_filter_settings method
enabled = True
column = 'TestColumn'
operator = display_op
value = '123.45'
# Test the logic
result = (
None if not enabled
else {
'column': column,
'operator': operator_map.get(operator, operator),
'value': float(value)
} if column and operator and value
else None
)
assert result is not None
assert result['operator'] == expected_op
assert result['column'] == 'TestColumn'
assert result['value'] == 123.45
class TestNumericFilterIntegration:
"""Integration tests for numeric filtering with full workflow"""
def setup_method(self):
"""Set up test fixtures"""
self.test_data = {
'Product': ['Widget A', 'Widget B', 'Widget C', 'Widget D', 'Widget E'],
'Price': [10.99, 25.50, 15.75, 30.00, 8.25],
'Stock': [100, 250, 75, 300, 50],
'Rating': [4.2, 4.8, 3.9, 4.9, 3.5],
'Category': ['Electronics', 'Tools', 'Electronics', 'Tools', 'Accessories']
}
def test_full_workflow_regex_and_numeric(self):
"""Test complete workflow with both regex and numeric filtering"""
# Create test Excel file
df = pd.DataFrame(self.test_data)
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp_input:
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp_output:
input_file = temp_input.name
output_file = temp_output.name
try:
# Save test data to input file
df.to_excel(input_file, index=False)
# Apply filters: Category contains 'Tool' AND Price > 20
numeric_filter = {'column': 'Price', 'operator': '>', 'value': 20.0}
excel_filter = ExcelFilter(
input_file=input_file,
output_file=output_file,
pattern=r'Tool', # Regex for category
numeric_filter=numeric_filter
)
success = excel_filter.process()
assert success
# Load and verify results
result_df = pd.read_excel(output_file)
# Should find Widget B (Tools, $25.50) and Widget D (Tools, $30.00)
assert len(result_df) == 2
assert 'Widget B' in result_df['Product'].tolist()
assert 'Widget D' in result_df['Product'].tolist()
# Verify prices are > 20
for price in result_df['Price']:
assert price > 20.0
finally:
# Clean up
try:
os.unlink(input_file)
os.unlink(output_file)
except:
pass
def test_numeric_filter_only(self):
"""Test numeric filtering without regex"""
df = pd.DataFrame(self.test_data)
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp_input:
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp_output:
input_file = temp_input.name
output_file = temp_output.name
try:
# Save test data
df.to_excel(input_file, index=False)
# Filter: Stock >= 100
numeric_filter = {'column': 'Stock', 'operator': '>=', 'value': 100}
excel_filter = ExcelFilter(
input_file=input_file,
output_file=output_file,
numeric_filter=numeric_filter
)
success = excel_filter.process()
assert success
# Verify results
result_df = pd.read_excel(output_file)
# Should find Widget A (100), Widget B (250), Widget D (300)
assert len(result_df) == 3
for stock in result_df['Stock']:
assert stock >= 100
finally:
try:
os.unlink(input_file)
os.unlink(output_file)
except:
pass
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python3
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
import tkinter as tk
from tkinter import ttk
from excel_filter.gui_components.main_window import MainWindow
def test_styling():
root = tk.Tk()
# Test the main window creation
try:
main_window = MainWindow(root)
print("MainWindow created successfully")
# Test color palette
print("Windows 11 color palette loaded")
# Test styles
style = ttk.Style()
print("Tkinter styles configured")
# Test window properties
print(f"Window title: {root.title()}")
print(f"Window size: {root.geometry()}")
print("\nWindows 11 Styling Test Results:")
print("All styling components loaded successfully!")
print("GUI should now look like a Windows 11 application")
# Close the window without running mainloop
root.destroy()
except Exception as e:
print(f"Error: {e}")
root.destroy()
if __name__ == "__main__":
test_styling()

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
import tkinter as tk
from tkinter import ttk
from excel_filter.gui_components.main_window import MainWindow
def test_tab_styling():
root = tk.Tk()
try:
main_window = MainWindow(root)
# Add some test tabs
tab1 = ttk.Frame(main_window.notebook)
tab2 = ttk.Frame(main_window.notebook)
tab3 = ttk.Frame(main_window.notebook)
main_window.add_tab(tab1, "Configuration")
main_window.add_tab(tab2, "Execution")
main_window.add_tab(tab3, "Help")
# Enhance tab appearance
main_window.enhance_tab_appearance()
print("Tab styling test results:")
print("Tabs created successfully")
print("Windows 11 tab styling applied")
print("Tab enhancement completed")
# Test tab properties
style = ttk.Style()
tab_style = style.configure('TNotebook.Tab')
print(f"Tab font: {tab_style.get('font', 'default')}")
print(f"Tab padding: {tab_style.get('padding', 'default')}")
# Test notebook properties
notebook_style = style.configure('TNotebook')
print(f"Notebook background: {notebook_style.get('background', 'default')}")
print("\nWindows 11 Tab Styling:")
print("Selected tabs have white background with blue text")
print("Unselected tabs have light gray background with gray text")
print("Hover effects applied")
print("Proper padding and spacing")
print("Segoe UI font (if available)")
# Close the window
root.destroy()
except Exception as e:
print(f"Error: {e}")
root.destroy()
if __name__ == "__main__":
test_tab_styling()

View File

@@ -0,0 +1,157 @@
"""
Unit tests for utility modules
"""
import json
import tempfile
import os
import logging
import sys
import pytest
from unittest.mock import patch, MagicMock
# Add src directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from excel_filter.utils.file_utils import load_config, save_config
from excel_filter.utils.logging_utils import setup_logging, log_message, log_error
class TestFileUtils:
"""
Test class for file_utils functions
"""
def test_load_config_success(self):
"""
Test successful config loading
"""
config_data = {
"pattern": "test.*",
"sheet_name": "Sheet1",
"columns": ["A", "B"]
}
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump(config_data, f)
config_file = f.name
try:
result = load_config(config_file)
assert result == config_data
finally:
os.unlink(config_file)
def test_load_config_file_not_found(self):
"""
Test config loading with non-existent file returns empty dict
"""
result = load_config("nonexistent.json")
assert result == {}
def test_load_config_invalid_json(self):
"""
Test config loading with invalid JSON
"""
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
f.write("invalid json")
config_file = f.name
try:
with pytest.raises(Exception):
load_config(config_file)
finally:
os.unlink(config_file)
def test_save_config_success(self):
"""
Test successful config saving
"""
config_data = {
"pattern": "test.*",
"sheet_name": "Sheet1"
}
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
config_file = f.name
try:
save_config(config_file, config_data)
# Verify file was saved correctly
with open(config_file, 'r') as f:
saved_data = json.load(f)
assert saved_data == config_data
finally:
os.unlink(config_file)
def test_save_config_error(self):
"""
Test config saving with error
"""
# Try to save to invalid path
with pytest.raises(Exception):
save_config("/invalid/path/config.json", {"test": "data"})
class TestLoggingUtils:
"""
Test class for logging_utils functions
"""
def test_setup_logging_without_file(self):
"""
Test setup_logging without log file
"""
# Store original level
original_level = logging.getLogger().getEffectiveLevel()
setup_logging()
# Verify setup_logging can be called without error
# The exact level may be influenced by pytest or other configurations
logger = logging.getLogger()
assert logger is not None # Just verify logging is accessible
@patch('logging.FileHandler')
def test_setup_logging_with_file(self, mock_file_handler):
"""
Test setup_logging with log file
"""
setup_logging(log_file="test.log", level=logging.DEBUG)
# Verify FileHandler was created
mock_file_handler.assert_called_once_with("test.log")
def test_log_message_normal(self):
"""
Test logging normal message
"""
mock_widget = MagicMock()
log_message(mock_widget, "Test message")
# Verify widget methods were called
mock_widget.config.assert_called()
mock_widget.insert.assert_called()
mock_widget.see.assert_called_with('end')
def test_log_message_error(self):
"""
Test logging error message
"""
mock_widget = MagicMock()
log_message(mock_widget, "Error message", is_error=True)
# Verify error formatting
mock_widget.tag_add.assert_called()
mock_widget.tag_config.assert_called_with("error", foreground="red")
def test_log_error(self):
"""
Test log_error function
"""
mock_widget = MagicMock()
log_error(mock_widget, "Test error")
# Verify it calls log_message with error flag
mock_widget.config.assert_called()
mock_widget.insert.assert_called()
mock_widget.tag_add.assert_called()

View File

@@ -0,0 +1,556 @@
"""
Translations for the Excel Filter GUI
Supports English and German languages using JSON files for better maintainability
"""
import json
import os
from pathlib import Path
class Translations:
"""
Translation manager for the Excel Filter GUI
Loads translations from JSON files for better maintainability and collaboration
"""
def __init__(self, language="de"):
"""
Initialize translations with default language
Args:
language: Default language code ('en' or 'de')
"""
self.current_language = language
self.translations = {}
# Load translations from JSON files
self.load_translations()
def load_translations(self):
"""
Load translation files from the locales directory
"""
import sys
# Determine the base path for resources
if getattr(sys, 'frozen', False):
# If the application is run as a bundle, the PyInstaller bootloader
# extends the sys module by a flag frozen=True and sets the app
# path into variable _MEIPASS'.
base_path = Path(sys._MEIPASS)
else:
base_path = Path(__file__).parent
locales_dir = base_path / "locales"
# Create locales directory if it doesn't exist and we are not frozen
# (cannot create files in _MEIPASS usually, or it's temporary)
if not getattr(sys, 'frozen', False):
locales_dir.mkdir(exist_ok=True)
# Load each language file
for lang_code in ["en", "de"]:
lang_file = locales_dir / f"{lang_code}.json"
if lang_file.exists():
try:
with open(lang_file, 'r', encoding='utf-8') as f:
self.translations[lang_code] = json.load(f)
except Exception as e:
print(f"Error loading {lang_file}: {e}")
self.translations[lang_code] = {}
else:
# Create default language file if it doesn't exist
self.create_default_translations(lang_code, lang_file)
def create_default_translations(self, lang_code, lang_file):
"""
Create default translation files
"""
if lang_code == "en":
translations = self.get_english_translations()
else:
translations = self.get_german_translations()
self.translations[lang_code] = translations
# Save to file
try:
with open(lang_file, 'w', encoding='utf-8') as f:
json.dump(translations, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error creating {lang_file}: {e}")
def get_english_translations(self):
"""
Get default English translations
"""
return {
"app_title": "Excel Filter Tool",
"tab_config": "Configuration",
"tab_execution": "Execution",
"tab_regex_builder": "Regex Builder",
"tab_help": "Help",
"config_section": "Configuration",
"load_button": "Load",
"save_button": "Save",
"input_file": "Input file:",
"output_file": "Output file:",
"browse_button": "Browse...",
"worksheet": "Worksheet:",
"process_button": "🚀 PROCESS",
"status_ready": "Ready",
"success": "Success",
"error": "Error",
"language": "Language:",
"english": "English",
"german": "German",
"help_content": "\nExcel Filter Tool\n\nFUNCTION\n--------------------------\nThe Excel Filter Tool is a data analysis and filtering tool for Excel files.\nIt enables automatic extraction, filtering, and transformation of data based on configurable search criteria.\n\nMain features:\n• Intelligent text search using regex patterns\n• Numeric filtering (greater/less than, between values)\n• Column-based filtering\n\nWORKFLOW\n----------------------------------------\nThe workflow is divided into four main phases, each with its own tabs:\n\n1. CONFIGURATION\n- Select the input Excel file\n- Specify the output file\n- Select the worksheet to filter\n- Configure filter criteria (regex and/or numeric)\n- Select columns to be included\n- Save/load configurations for recurring tasks\n\n2. PATTERN CREATION (Regex-Builder Tab)\n- Use the visual regex builder for easy pattern creation\n- Select from predefined components (text, numbers, special characters)\n- Define quantities (once, multiple times, optional)\n- Add optional anchors and groups\n- Test patterns with sample texts\n- Manage stored patterns\n\n3. EXECUTION\n- Review the command to execute before processing\n- Start the analysis\n- Track progress in real time\n- Automatic opening of the result file\n\nADVANCED CONFIGURATION OPTIONS\n----------------------------------\n\nRegex Filtering (Standard Mode)\n- Search for text patterns with full regex support\n- Support for complex search patterns\n- Word boundaries, case sensitivity, special characters\n\nNumeric Filtering\n- Filter by numeric values (greater/less than)\n- Range filtering (between values)\n- Cross-column numeric search\n\nColumn-Based Filtering\n- Selection of specific columns to search\n- Automatic column detection from Excel files\n- Individual column selection for targeted search\n\nCONFIGURATION MANAGEMENT\n--------------------------\n• Save/load configurations for recurring tasks\n• Personal pattern library with custom regex patterns\n• Automatic saving of recently used file paths\n• Restoration of previous work sessions\n",
# Config Tab
"tooltip_config_section": "You can save and load configurations instead of entering everything every time.",
"enable_regex": "Enable Regex Filter",
"tooltip_enable_regex": "If enabled, regex patterns are used for filtering",
"regex_options": " Regex Options ",
"regex_pattern": "Regex Pattern:",
"preset_patterns": "Preset Patterns:",
"apply_regex_column": "Apply Regex to Column:",
"all_columns": "All Columns",
"manage_patterns": "Manage Patterns...",
"enable_numeric": "Enable Numeric Filter",
"numeric_filters": "Numeric Filters",
"column": "Column:",
"comparison": "Comparison:",
"value": "Value:",
"regex_builder_info": "Custom patterns for filtering can be created in the Regex Builder.",
"enable_column_selection": "Enable Column Selection",
"column_selection": "Column Selection",
"select_all": "Select All",
"deselect_all": "Deselect All",
"proceed_to_execution": "Proceed to Execution",
"msg_no_regex_copy": "No regex pattern used - only selected columns will be copied",
"msg_warn_no_selection": "Warning: No regex pattern and no columns selected - all data will be copied",
"msg_copy_all": "Copying all columns",
"msg_copy_selected": "Copying selected columns: {}",
"msg_written_success": "Successfully written: {}",
"msg_ask_open": "Do you want to open the output file?",
"msg_process_success": "Processing completed successfully",
"msg_process_failed": "Processing failed",
"numeric_invalid": "❌ Invalid numeric value",
"numeric_incomplete": "Configuration incomplete",
"filter_desc": "Filter: {} {} {}",
# Execution Tab
"ready_to_execute": "Ready to execute...",
"status_ready": "Ready",
"command_to_execute": "Command to Execute",
"execute_button": "EXECUTE",
"activity_log": "Activity Log",
"clear_log": "Clear",
"save_log": "Save",
"log_cleared": "Log cleared",
"ready_for_execution": "Excel Filter ready for execution",
"configure_and_execute": "Configure settings and click 'EXECUTE'",
"error_main_gui_not_connected": "Error: Main GUI not connected",
"input_file_label": "Input File:",
"output_file_label": "Output File:",
"search_pattern_label": "Search Pattern:",
"worksheet_label": "Worksheet:",
"columns_label": "Columns:",
"numeric_filter_label": "Numeric Filter: {column} {operator} {value}",
"not_selected": "(not selected)",
"not_specified": "(not specified)",
"more_columns": "(+{} more)",
"error_updating_command_display": "Error updating command display: {error}",
"execution_running": "Running...",
"waiting": "WAITING...",
"execution_started": "▶️ Execution started",
"execution_completed": "✅ Execution completed successfully",
"execution_failed": "❌ Execution failed with errors",
"log_saved": "💾 Log saved: {file}",
# Regex Builder Tab
"tab_builder": "🧱 Block Builder",
"tab_tester": "🧪 Tester",
"tab_examples": "📚 Examples",
"step_1": "📝 Step 1: Search Character",
"step_2": "🔢 Step 2: How often?",
"step_3": "⚙️ Step 3: Options",
"btn_letters": "🔤 Letters",
"desc_letters": "a-z, A-Z",
"btn_digits": "🔢 Digits",
"desc_digits": "0-9",
"btn_alphanum": "🔤🔢 Alphanum.",
"desc_alphanum": "Letters+Digits",
"btn_any": "❓ Any",
"desc_any": "Any Character",
"btn_custom": "📝 Custom Text",
"desc_custom": "Your Text",
"btn_special": "⚙️ Special",
"desc_special": "Punctuation",
"btn_once": "🎯 1-time",
"desc_once": "Exactly once",
"btn_one_plus": " 1+ times",
"desc_one_plus": "At least 1-time",
"btn_zero_one": "❓ 0-1 times",
"desc_zero_one": "Optional",
"btn_zero_plus": "♾️ 0+ times",
"desc_zero_plus": "Any number of times",
"btn_n_times": "📏 N-times",
"desc_n_times": "Exactly N-times",
"btn_m_n_times": "📊 M-N times",
"desc_m_n_times": "M to N-times",
"btn_start": "^ Start",
"desc_start": "Line start",
"btn_end": "$ End",
"desc_end": "Line end",
"btn_word": "\\b Word",
"desc_word": "Word boundary",
"btn_group": "( ) Group",
"desc_group": "Grouping",
"btn_or": "| OR",
"desc_or": "Alternative",
"btn_alt": "(?: ) Alt.",
"desc_alt": "Non-capturing group",
"preview": "👀 Preview",
"selection": "Selection:",
"btn_add": " Add",
"btn_reset": "🔄 Reset",
"regex_result": "🎯 Regex Pattern",
"btn_manage": "📝 Manage Patterns",
"btn_save_preset": "💾 Save as Pattern",
"btn_copy": "📋 Copy",
"btn_reset_pattern": "🔄 Reset Pattern",
"btn_undo": "↶ Undo",
"btn_redo": "↷ Redo",
"btn_apply": "💾 Apply to Main",
"btn_test": "🧪 Test",
"test_input": "Enter Test Text:",
"test_text_frame": "📝 Test Text",
"test_pattern_frame": "🔍 Regex Pattern",
"btn_load_example": "📋 Load Example",
"result_label": "Result:",
"matches_label": "Found Matches:",
"common_examples": "📚 Common Regex Examples",
"msg_pattern_applied": "Regex pattern applied to main window!",
"msg_main_not_avail": "Main window reference not available. Copy pattern manually.",
"msg_no_pattern_apply": "No regex pattern available to apply",
"msg_copied": "Regex pattern copied to clipboard!",
"msg_no_pattern_copy": "No regex pattern available to copy",
"msg_no_undo": "No more undo actions available",
"msg_no_redo": "No more redo actions available",
"msg_enter_pattern": "❌ Please enter a regex pattern",
"msg_enter_text": "❌ Please enter a test text",
"msg_pattern_found": "✅ Pattern found! {} matches ({} unique)",
"msg_no_matches": "❌ No matches found",
"msg_invalid_regex": "❌ Invalid regex pattern: {}",
"msg_example_loaded": "Example loaded - click 'Test'",
"builder_desc_letters": "Letters (a-z, A-Z)",
"builder_desc_digits": "Digits (0-9)",
"builder_desc_alphanum": "Letters and Digits",
"builder_desc_any": "Any character",
"builder_desc_custom": "The text: '{}'",
"builder_desc_custom_generic": "Your custom text",
"builder_desc_special": "Special characters (space, dot, comma, etc.)",
"builder_desc_once": "appears exactly once",
"builder_desc_one_plus": "appears once or more",
"builder_desc_zero_one": "is optional (0 or 1 time)",
"builder_desc_zero_plus": "appears any number of times (0 or more)",
"builder_desc_n_times": "appears exactly {} times",
"builder_desc_m_n_times": "appears {} to {} times",
"builder_desc_start": "at line start",
"builder_desc_end": "at line end",
"builder_desc_word": "as whole word",
"builder_desc_or": "with alternatives",
"default_test_text": "Sample text with error and warning 123 numbers and email: test@example.com",
"default_result": "Test result appears here...",
"no_matches": "No matches",
"auto_desc": "Description automatically generated...",
"select_char_first": "Select a character type first...",
"enter_name": "Enter name",
"save_pattern": "Save Pattern",
"name_for_pattern": "Name for new pattern:",
"msg_save_success": "Pattern '{}' saved successfully",
"overwrite_confirm": "Pattern '{}' already exists. Overwrite?",
# Additional keys needed for gui_new.py
"errors_and_warnings": "Errors and Warnings",
"errors_only": "Errors only",
"warnings_only": "Warnings only",
"critical_errors": "Critical Errors",
"numbers_100_199": "Numbers 100-199",
"email_addresses": "E-mail addresses",
"phone_numbers": "Phone numbers (DE)",
"date_yyyy_mm_dd": "Date (YYYY-MM-DD)",
"desc_errors_and_warnings": "Finds 'error', 'warning' or 'critical'",
"desc_errors_only": "Finds 'error'",
"desc_warnings_only": "Finds 'warning'",
"desc_critical_errors": "Finds 'critical'",
"desc_numbers_100_199": "Finds numbers from 100 to 199",
"desc_email_addresses": "Finds standard e-mail addresses",
"desc_phone_numbers": "Finds phone numbers (German format)",
"desc_date_yyyy_mm_dd": "Finds dates in ISO format",
}
def get_german_translations(self):
"""
Get default German translations
"""
return {
"app_title": "Excel Filter Tool",
"tab_config": "Konfiguration",
"tab_execution": "Ausführung",
"tab_regex_builder": "Regex-Builder",
"tab_help": "Hilfe",
"config_section": "Konfiguration",
"load_button": "Laden",
"save_button": "Speichern",
"input_file": "Eingabedatei:",
"output_file": "Ausgabedatei:",
"browse_button": "Durchsuchen...",
"worksheet": "Arbeitsblatt:",
"process_button": "🚀 VERARBEITEN",
"status_ready": "Bereit",
"success": "Erfolg",
"error": "Fehler",
"language": "Sprache:",
"english": "Englisch",
"german": "Deutsch",
"help_content": "\nExcel Filter Tool\n\nFUNKTION\n--------------------------\nDas Excel Filter Tool ist ein Werkzeug zur Datenanalyse und -filterung von Excel-Dateien.\nEs ermöglicht das automatische Extrahieren, Filtern und Transformieren von Daten basierend auf einstellbaren Suchkriterien.\n\nHauptfunktionen:\n• Intelligente Textsuche anhand von Regex-Mustern\n• Numerische Filterung (größer/kleiner als, zwischen Werten)\n• Spaltenbasierte Filterung\n\nARBEITSABLAUF\n----------------------------------------\nDer Arbeitsablauf ist in vier Hauptphasen unterteilt, die jeweils eigene Tabs haben:\n\n1. KONFIGURATION\n- Wähle die Eingabe-Excel-Datei aus\n- Bestimme die Ausgabedatei\n- Wähle das zu filternde Arbeitsblatt\n- Konfiguriere die Filterkriterien (Regex und/oder numerisch)\n- Wählen die Spalten aus, welche übernommen werden sollen\n- Speicher/Lade die Konfigurationen für wiederkehrende Aufgaben\n\n2. MUSTER-ERSTELLUNG (Regex-Builder-Tab)\n- Nutze den visuellen Regex-Builder für zur einfachen Mustererstellung\n- Wähle aus vorgefertigten Bausteinen (Text, Zahlen, Spezialzeichen)\n- Definiere Mengen (einmal, mehrmals, optional)\n- Füge optionale Anker und Gruppen hinzu\n- Teste die Muster mit Beispieltexten\n- Verwalte gespeicherte Muster\n\n3. AUSFÜHRUNG\n- Überprüfe den auszuführenden Befehl vor der Verarbeitung\n- Starten die Analyse\n- Verfolgen den Fortschritt in Echtzeit\n- Automatische Öffnung der Ergebnisdatei\n\nERWEITERTE KONFIGURATIONSOPTIONEN\n----------------------------------\n\nRegex-Filterung (Standardmodus)\n- Suche nach Textmustern mit voller Regex-Unterstützung\n- Unterstützung für komplexe Suchmuster\n- Wortgrenzen, Groß-/Kleinschreibung, Spezialzeichen\n\nNumerische Filterung\n- Filtern nach Zahlenwerten (größer/kleiner als)\n- Bereichsfilterung (zwischen Werten)\n- Spaltenübergreifende numerische Suche\n\nSpaltenbasierte Filterung\n- Auswahl spezifischer zu durchsuchender Spalten\n- Automatische Spaltenerkennung aus Excel-Dateien\n- Individuelle Spaltenauswahl für zielgerichtete Suche\n\nKONFIGURATIONSMANAGEMENT\n--------------------------\n• Speichern/Laden von Konfigurationen für wiederkehrende Aufgaben\n• Persönliche Musterbibliothek mit benutzerdefinierten Regex-Mustern\n• Automatische Speicherung von zuletzt verwendeten Dateipfaden\n• Wiederherstellung vorheriger Arbeitssitzungen\n",
# Config Tab
"tooltip_config_section": "Du kannst Konfigurationen speichern und laden, statt alles jedes Mal neu einzugeben.",
"enable_regex": "Regex-Filter aktivieren",
"tooltip_enable_regex": "Wenn aktiviert, werden Regex-Muster für die Filterung verwendet",
"regex_options": " Regex-Optionen ",
"regex_pattern": "Regex-Muster:",
"preset_patterns": "Voreingestellte Muster:",
"apply_regex_column": "Regex auf Spalte anwählen:",
"all_columns": "Alle Spalten",
"manage_patterns": "Muster verwalten…",
"enable_numeric": "Numerische Filter aktivieren",
"numeric_filters": "Numerische Filter",
"column": "Spalte:",
"comparison": "Vergleich:",
"value": "Wert:",
"regex_builder_info": "Eigene Muster zur Filterung können im Regex-Builder erstellt werden.",
"enable_column_selection": "Spaltenauswahl aktivieren",
"column_selection": "Spaltenauswahl",
"select_all": "Alle auswählen",
"deselect_all": "Alle abwählen",
"proceed_to_execution": "Weiter zur Ausführung",
"msg_no_regex_copy": "Kein Regex-Muster verwendet - es werden nur die ausgewählten Spalten kopiert",
"msg_warn_no_selection": "Warnung: Kein Regex-Muster und keine Spalten ausgewählt - alle Daten werden kopiert",
"msg_copy_all": "Kopiere alle Spalten",
"msg_copy_selected": "Kopiere ausgewählte Spalten: {}",
"msg_written_success": "Erfolgreich geschrieben: {}",
"msg_ask_open": "Möchten Sie die Ausgabedatei öffnen?",
"msg_process_success": "Verarbeitung erfolgreich abgeschlossen",
"msg_process_failed": "Verarbeitung fehlgeschlagen",
"numeric_invalid": "❌ Ungültiger numerischer Wert",
"numeric_incomplete": "Konfiguration unvollständig",
"filter_desc": "Filter: {} {} {}",
# Execution Tab
"ready_to_execute": "Bereit zur Ausführung...",
"status_ready": "Bereit",
"command_to_execute": "Auszuführender Befehl",
"execute_button": "AUSFÜHREN",
"activity_log": "Aktivitätsprotokoll",
"clear_log": "Löschen",
"save_log": "Speichern",
"log_cleared": "Protokoll gelöscht",
"ready_for_execution": "Excel Filter bereit zur Ausführung",
"configure_and_execute": "Konfigurieren Sie die Einstellungen und klicken Sie auf 'AUSFÜHREN'",
"error_main_gui_not_connected": "Fehler: Haupt-GUI nicht verbunden",
"input_file_label": "Eingabedatei:",
"output_file_label": "Ausgabedatei:",
"search_pattern_label": "Suchmuster:",
"worksheet_label": "Arbeitsblatt:",
"columns_label": "Spalten:",
"numeric_filter_label": "Numerischer Filter: {column} {operator} {value}",
"not_selected": "(nicht ausgewählt)",
"not_specified": "(nicht angegeben)",
"more_columns": "(+{} weitere)",
"error_updating_command_display": "Fehler beim Aktualisieren der Befehlsanzeige: {error}",
"execution_running": "Läuft...",
"waiting": "WARTEN...",
"execution_started": "▶️ Ausführung gestartet",
"execution_completed": "✅ Ausführung erfolgreich abgeschlossen",
"execution_failed": "❌ Ausführung mit Fehlern beendet",
"log_saved": "💾 Protokoll gespeichert: {file}",
# Regex Builder Tab
"tab_builder": "🧱 Baustein-Builder",
"tab_tester": "🧪 Tester",
"tab_examples": "📚 Beispiele",
"step_1": "📝 Schritt 1: Zeichen suchen",
"step_2": "🔢 Schritt 2: Wie oft?",
"step_3": "⚙️ Schritt 3: Optionen",
"btn_letters": "🔤 Buchstaben",
"desc_letters": "a-z, A-Z",
"btn_digits": "🔢 Zahlen",
"desc_digits": "0-9",
"btn_alphanum": "🔤🔢 Alphanum.",
"desc_alphanum": "Buchst.+Zahlen",
"btn_any": "❓ Beliebig",
"desc_any": "Ein Zeichen",
"btn_custom": "📝 Eigener Text",
"desc_custom": "Ihr Text",
"btn_special": "⚙️ Spezial",
"desc_special": "Satzzeichen",
"btn_once": "🎯 1-mal",
"desc_once": "Genau einmal",
"btn_one_plus": " 1+ mal",
"desc_one_plus": "Mindestens 1-mal",
"btn_zero_one": "❓ 0-1 mal",
"desc_zero_one": "Optional",
"btn_zero_plus": "♾️ 0+ mal",
"desc_zero_plus": "Beliebig oft",
"btn_n_times": "📏 N-mal",
"desc_n_times": "Genau N-mal",
"btn_m_n_times": "📊 M-N mal",
"desc_m_n_times": "M bis N-mal",
"btn_start": "^ Anfang",
"desc_start": "Zeilenanfang",
"btn_end": "$ Ende",
"desc_end": "Zeilenende",
"btn_word": "\\b Wort",
"desc_word": "Wortgrenze",
"btn_group": "( ) Gruppe",
"desc_group": "Gruppierung",
"btn_or": "| ODER",
"desc_or": "Alternative",
"btn_alt": "(?: ) Alt.",
"desc_alt": "Gruppe o. Speicher",
"preview": "👀 Vorschau",
"selection": "Auswahl:",
"btn_add": " Hinzufügen",
"btn_reset": "🔄 Zurücksetzen",
"regex_result": "🎯 Regex-Muster",
"btn_manage": "📝 Muster verwalten",
"btn_save_preset": "💾 Als Muster speichern",
"btn_copy": "📋 Kopieren",
"btn_reset_pattern": "🔄 Muster zurücksetzen",
"btn_undo": "↶ Rückgängig",
"btn_redo": "↷ Wiederholen",
"btn_apply": "💾 In Hauptfenster übernehmen",
"btn_test": "🧪 Testen",
"test_input": "Testtext eingeben:",
"test_text_frame": "📝 Testtext",
"test_pattern_frame": "🔍 Regex-Muster",
"btn_load_example": "📋 Beispiel laden",
"result_label": "Ergebnis:",
"matches_label": "Gefundene Treffer:",
"common_examples": "📚 Häufig verwendete Regex-Beispiele",
"msg_pattern_applied": "Regex-Muster wurde in das Hauptfenster übernommen!",
"msg_main_not_avail": "Hauptfenster-Referenz nicht verfügbar. Kopieren Sie das Muster manuell.",
"msg_no_pattern_apply": "Kein Regex-Muster zum Übernehmen verfügbar",
"msg_copied": "Regex-Muster wurde in die Zwischenablage kopiert!",
"msg_no_pattern_copy": "Kein Regex-Muster zum Kopieren verfügbar",
"msg_no_undo": "Keine weiteren Rückgängig-Aktionen verfügbar",
"msg_no_redo": "Keine Wiederholen-Aktionen verfügbar",
"msg_enter_pattern": "❌ Bitte geben Sie ein Regex-Muster ein",
"msg_enter_text": "❌ Bitte geben Sie einen Testtext ein",
"msg_pattern_found": "✅ Muster gefunden! {} Treffer (davon {} einzigartig)",
"msg_no_matches": "❌ Keine Treffer gefunden",
"msg_invalid_regex": "❌ Ungültiges Regex-Muster: {}",
"msg_example_loaded": "Beispiel geladen - klicken Sie auf 'Testen'",
"builder_desc_letters": "Buchstaben (a-z, A-Z)",
"builder_desc_digits": "Zahlen (0-9)",
"builder_desc_alphanum": "Buchstaben und Zahlen",
"builder_desc_any": "Ein beliebiges Zeichen",
"builder_desc_custom": "Der Text: '{}'",
"builder_desc_custom_generic": "Ihr eigener Suchtext",
"builder_desc_special": "Sonderzeichen (Leerzeichen, Punkt, Komma, etc.)",
"builder_desc_once": "erscheint genau einmal",
"builder_desc_one_plus": "erscheint ein- oder mehrmals",
"builder_desc_zero_one": "ist optional (0- oder 1-mal)",
"builder_desc_zero_plus": "erscheint beliebig oft (0-mal oder mehr)",
"builder_desc_n_times": "erscheint genau {}-mal",
"builder_desc_m_n_times": "erscheint {} bis {}-mal",
"builder_desc_start": "am Zeilenanfang",
"builder_desc_end": "am Zeilenende",
"builder_desc_word": "als ganzes Wort",
"builder_desc_or": "mit Alternativen",
"default_test_text": "Beispieltext mit error und warning 123 Zahlen und E-Mail: test@example.com",
"default_result": "Hier erscheint das Testergebnis...",
"no_matches": "Keine Treffer",
"auto_desc": "Beschreibung wird automatisch generiert...",
"select_char_first": "Wählen Sie zuerst einen Zeichentyp aus...",
"enter_name": "Geben Sie einen Namen ein",
"save_pattern": "Muster speichern",
"name_for_pattern": "Name für das neue Muster:",
"msg_save_success": "Muster '{}' wurde gespeichert",
"overwrite_confirm": "Ein Muster mit dem Namen '{}' existiert bereits. Möchten Sie es überschreiben?",
# Additional keys needed for gui_new.py
"errors_and_warnings": "Fehler und Warnungen",
"errors_only": "Nur Fehler",
"warnings_only": "Nur Warnungen",
"critical_errors": "Kritische Fehler",
"numbers_100_199": "Zahlen 100-199",
"email_addresses": "E-Mail-Adressen",
"phone_numbers": "Telefonnummern (DE)",
"date_yyyy_mm_dd": "Datum (YYYY-MM-DD)",
"desc_errors_and_warnings": "Findet 'error', 'warning' oder 'critical'",
"desc_errors_only": "Findet nur 'error'",
"desc_warnings_only": "Findet nur 'warning'",
"desc_critical_errors": "Findet nur 'critical'",
"desc_numbers_100_199": "Findet Zahlen von 100 bis 199",
"desc_email_addresses": "Findet übliche E-Mail-Adressen",
"desc_phone_numbers": "Findet Telefonnummern (deutsches Format)",
"desc_date_yyyy_mm_dd": "Findet Datum im ISO-Format",
}
def set_language(self, language):
"""
Set the current language
Args:
language: Language code ('en' or 'de')
"""
if language in self.translations:
self.current_language = language
else:
raise ValueError(f"Unsupported language: {language}")
def get(self, key, default=None):
"""
Get a translation for a key
Args:
key: Translation key
default: Default value if key not found
Returns:
Translated string or default value
"""
return self.translations[self.current_language].get(key, default if default is not None else key)
def __getitem__(self, key):
"""
Get a translation using dictionary-style access
"""
return self.get(key)
def get_available_languages(self):
"""
Get list of available language codes
Returns:
List of language codes
"""
return list(self.translations.keys())
def get_language_names(self):
"""
Get dictionary mapping language codes to display names
Returns:
Dictionary with language codes as keys and display names as values
"""
return {
"en": self.get("english"),
"de": self.get("german")
}

View File

@@ -0,0 +1,110 @@
"""
File utility functions for the Excel Filter Tool
"""
import tkinter as tk
from tkinter import filedialog
import json
import logging
# Logging konfigurieren
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def browse_file(initialdir=None, filetypes=None, title="Datei auswählen"):
"""
Opens a file dialog to browse and select a file
Args:
initialdir: Initial directory to open
filetypes: File types to filter by
title: Dialog title
Returns:
Selected file path or None if canceled
"""
root = tk.Tk()
root.withdraw() # Hide the main window
file_path = filedialog.askopenfilename(
initialdir=initialdir,
title=title,
filetypes=filetypes
)
root.destroy()
return file_path
def browse_save_file(initialdir=None, filetypes=None, title="Datei speichern", defaultextension=".xlsx"):
"""
Opens a file dialog to browse and save a file
Args:
initialdir: Initial directory to open
filetypes: File types to filter by
title: Dialog title
defaultextension: Default file extension
Returns:
Selected file path or None if canceled
"""
root = tk.Tk()
root.withdraw() # Hide the main window
file_path = filedialog.asksaveasfilename(
initialdir=initialdir,
title=title,
filetypes=filetypes,
defaultextension=defaultextension
)
root.destroy()
return file_path
def load_config(config_file: str) -> dict:
"""
Loads configuration from a JSON file
Args:
config_file: Path to the configuration file
Returns:
Dictionary containing the configuration
Raises:
Exception if the file cannot be loaded
"""
try:
with open(config_file, 'r') as f:
config = json.load(f)
logger.info(f"Configuration loaded: {config_file}")
return config
except FileNotFoundError:
logger.warning(f"Configuration file not found: {config_file}")
return {}
except Exception as e:
logger.error(f"Error loading configuration: {e}")
raise Exception(f"Error loading configuration: {e}")
def save_config(config_file: str, config: dict):
"""
Saves configuration to a JSON file
Args:
config_file: Path to the configuration file
config: Dictionary containing the configuration to save
Raises:
Exception if the file cannot be saved
"""
try:
with open(config_file, 'w') as f:
json.dump(config, f, indent=4)
logger.info(f"Configuration saved: {config_file}")
except Exception as e:
logger.error(f"Error saving configuration: {e}")
raise Exception(f"Error saving configuration: {e}")

View File

@@ -0,0 +1,114 @@
"""
Logging utility functions for the Excel Filter Tool
"""
import logging
from datetime import datetime
import tkinter as tk
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def setup_logging(log_file=None, level=logging.INFO):
"""
Sets up logging configuration
Args:
log_file: Optional path to log file
level: Logging level
"""
handlers = [logging.StreamHandler()]
if log_file:
handlers.append(logging.FileHandler(log_file))
logging.basicConfig(
level=level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=handlers
)
def log_message(text_widget, message, is_error=False):
"""
Logs a message to a Tkinter Text widget
Args:
text_widget: Tkinter Text widget to log to
message: Message to log
is_error: Whether this is an error message
"""
timestamp = datetime.now().strftime("%H:%M:%S")
log_entry = f"[{timestamp}] {message}\n"
text_widget.config(state=tk.NORMAL)
text_widget.insert(tk.END, log_entry)
if is_error:
text_widget.tag_add("error", f"{text_widget.index(tk.END)} - 1 lines", tk.END)
text_widget.tag_config("error", foreground="red")
text_widget.see(tk.END)
text_widget.config(state=tk.DISABLED)
# Also log to console
if is_error:
logger.error(message)
else:
logger.info(message)
def log_error(text_widget, message):
"""
Logs an error message to a Tkinter Text widget
Args:
text_widget: Tkinter Text widget to log to
message: Error message to log
"""
log_message(text_widget, f"[ERROR]: {message}", is_error=True)
def log_detailed_error(text_widget, error_type, error_message, context=None):
"""
Logs a detailed error message with context information
Args:
text_widget: Tkinter Text widget to log to
error_type: Type of error (e.g., "Datei nicht gefunden", "Regex-Fehler")
error_message: Detailed error message
context: Optional context information
"""
timestamp = datetime.now().strftime("%H:%M:%S")
text_widget.config(state=tk.NORMAL)
# Add separator
text_widget.insert(tk.END, "=" * 60 + "\n")
# Add error header
error_header = f"[{timestamp}] [ERROR] {error_type.upper()}"
text_widget.insert(tk.END, error_header + "\n")
# Add error message
text_widget.insert(tk.END, f"[ERROR] {error_message}\n")
# Add context if provided
if context:
text_widget.insert(tk.END, f"Kontext: {context}\n")
text_widget.insert(tk.END, "=" * 60 + "\n")
# Apply error styling
start_line = float(text_widget.index(tk.END)) - 5.0 # Approximate start line
text_widget.tag_add("error", f"{start_line} lines", tk.END)
text_widget.tag_config("error", foreground="red")
text_widget.see(tk.END)
text_widget.config(state=tk.DISABLED)
# Also log to console
logger.error(f"{error_type}: {error_message}")
if context:
logger.error(f"Context: {context}")

24
presets.json Normal file
View File

@@ -0,0 +1,24 @@
{
"presets": {
"Fehler und Warnungen": "error|warning|critical",
"Nur Fehler": "error",
"Nur Warnungen": "warning",
"Kritische Fehler": "critical",
"Zahlen 100-199": "1\\d{2}",
"E-Mail-Adressen": "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
"Telefonnummern": "\\+?[0-9\\s-]{10,}",
"Datum (YYYY-MM-DD)": "\\d{4}-\\d{2}-\\d{2}",
"Zahlen mit Komma oder Punkt":"\\d?[,|.]\\d?"
},
"descriptions": {
"Fehler und Warnungen": "Findet Zeilen mit 'error', 'warning' oder 'critical'",
"Nur Fehler": "Findet Zeilen mit 'error'",
"Nur Warnungen": "Findet Zeilen mit 'warning'",
"Kritische Fehler": "Findet Zeilen mit 'critical'",
"Zahlen 100-199": "Findet Zahlen zwischen 100 und 199",
"E-Mail-Adressen": "Findet E-Mail-Adressen",
"Telefonnummern": "Findet Telefonnummern",
"Datum (YYYY-MM-DD)": "Findet Daten im Format YYYY-MM-DD",
"Zahlen mit Komma oder Punkt":"Zahlen die durch einen Punkt oder Komma getrennt sind"
}
}