Migrate to GitLab
This commit is contained in:
1
excel_filter/.env
Normal file
1
excel_filter/.env
Normal file
@@ -0,0 +1 @@
|
||||
PYTHONPATH=src
|
||||
10
excel_filter/.vscode/settings.json
vendored
Normal file
10
excel_filter/.vscode/settings.json
vendored
Normal 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"
|
||||
}
|
||||
39
excel_filter/ExcelFilter.spec
Normal file
39
excel_filter/ExcelFilter.spec
Normal 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
52
excel_filter/LICENSE.txt
Normal 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,
|
||||
ist’s 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
1
excel_filter/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
""
|
||||
BIN
excel_filter/app_icon.ico
Normal file
BIN
excel_filter/app_icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
124
excel_filter/build_executable.py
Normal file
124
excel_filter/build_executable.py
Normal 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!")
|
||||
111
excel_filter/build_installer.bat
Normal file
111
excel_filter/build_installer.bat
Normal 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
|
||||
61
excel_filter/check_dependencies.bat
Normal file
61
excel_filter/check_dependencies.bat
Normal 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
|
||||
54
excel_filter/check_dependencies_simple.bat
Normal file
54
excel_filter/check_dependencies_simple.bat
Normal 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
|
||||
111
excel_filter/check_environment.bat
Normal file
111
excel_filter/check_environment.bat
Normal 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
|
||||
130
excel_filter/check_python.py
Normal file
130
excel_filter/check_python.py
Normal 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)
|
||||
27
excel_filter/create_icon.py
Normal file
27
excel_filter/create_icon.py
Normal 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()
|
||||
55
excel_filter/create_installer.iss
Normal file
55
excel_filter/create_installer.iss
Normal 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
|
||||
1
excel_filter/dependencies_installed.marker
Normal file
1
excel_filter/dependencies_installed.marker
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
504
excel_filter/filter.py
Normal file
504
excel_filter/filter.py
Normal 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"))
|
||||
1317
excel_filter/gui_components/config_tab.py
Normal file
1317
excel_filter/gui_components/config_tab.py
Normal file
File diff suppressed because it is too large
Load Diff
403
excel_filter/gui_components/execution_tab.py
Normal file
403
excel_filter/gui_components/execution_tab.py
Normal 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
|
||||
178
excel_filter/gui_components/help_tab.py
Normal file
178
excel_filter/gui_components/help_tab.py
Normal 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
|
||||
365
excel_filter/gui_components/main_window.py
Normal file
365
excel_filter/gui_components/main_window.py
Normal 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()
|
||||
1070
excel_filter/gui_components/regex_builder_tab.py
Normal file
1070
excel_filter/gui_components/regex_builder_tab.py
Normal file
File diff suppressed because it is too large
Load Diff
1032
excel_filter/gui_new.py
Normal file
1032
excel_filter/gui_new.py
Normal file
File diff suppressed because it is too large
Load Diff
22
excel_filter/launch_gui.bat
Normal file
22
excel_filter/launch_gui.bat
Normal 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
|
||||
173
excel_filter/locales/de.json
Normal file
173
excel_filter/locales/de.json
Normal 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"
|
||||
}
|
||||
173
excel_filter/locales/en.json
Normal file
173
excel_filter/locales/en.json
Normal 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
75
excel_filter/main.py
Normal 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
20
excel_filter/presets.json
Normal 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
13
excel_filter/pytest.ini
Normal 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
|
||||
5
excel_filter/requirements.txt
Normal file
5
excel_filter/requirements.txt
Normal 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
|
||||
10
excel_filter/run_pyinstaller.bat
Normal file
10
excel_filter/run_pyinstaller.bat
Normal 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
|
||||
10
excel_filter/run_pyinstaller_simple.bat
Normal file
10
excel_filter/run_pyinstaller_simple.bat
Normal 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
|
||||
9
excel_filter/test_iscc.bat
Normal file
9
excel_filter/test_iscc.bat
Normal file
@@ -0,0 +1,9 @@
|
||||
off
|
||||
echo
|
||||
This
|
||||
is
|
||||
a
|
||||
mock
|
||||
ISCC.exe
|
||||
exit
|
||||
0
|
||||
1
excel_filter/tests/__init__.py
Normal file
1
excel_filter/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Tests package
|
||||
26
excel_filter/tests/debug_import.py
Normal file
26
excel_filter/tests/debug_import.py
Normal 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()
|
||||
42
excel_filter/tests/generate_test_data.py
Normal file
42
excel_filter/tests/generate_test_data.py
Normal 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)
|
||||
71
excel_filter/tests/simple_test.py
Normal file
71
excel_filter/tests/simple_test.py
Normal 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.")
|
||||
29
excel_filter/tests/test_batch.bat
Normal file
29
excel_filter/tests/test_batch.bat
Normal 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
|
||||
81
excel_filter/tests/test_column_update.py
Normal file
81
excel_filter/tests/test_column_update.py
Normal 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)
|
||||
97
excel_filter/tests/test_complete_implementation.py
Normal file
97
excel_filter/tests/test_complete_implementation.py
Normal 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.")
|
||||
159
excel_filter/tests/test_filter.py
Normal file
159
excel_filter/tests/test_filter.py
Normal 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
|
||||
103
excel_filter/tests/test_final_implementation.py
Normal file
103
excel_filter/tests/test_final_implementation.py
Normal 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.")
|
||||
42
excel_filter/tests/test_gui.py
Normal file
42
excel_filter/tests/test_gui.py
Normal 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()
|
||||
727
excel_filter/tests/test_gui_new_comprehensive.py
Normal file
727
excel_filter/tests/test_gui_new_comprehensive.py
Normal 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"])
|
||||
180
excel_filter/tests/test_gui_new_simple.py
Normal file
180
excel_filter/tests/test_gui_new_simple.py
Normal 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)
|
||||
349
excel_filter/tests/test_large_files.py
Normal file
349
excel_filter/tests/test_large_files.py
Normal 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()
|
||||
104
excel_filter/tests/test_main.py
Normal file
104
excel_filter/tests/test_main.py
Normal 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()
|
||||
448
excel_filter/tests/test_numeric_filter.py
Normal file
448
excel_filter/tests/test_numeric_filter.py
Normal 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()
|
||||
42
excel_filter/tests/test_styling.py
Normal file
42
excel_filter/tests/test_styling.py
Normal 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()
|
||||
59
excel_filter/tests/test_tabs.py
Normal file
59
excel_filter/tests/test_tabs.py
Normal 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()
|
||||
157
excel_filter/tests/test_utils.py
Normal file
157
excel_filter/tests/test_utils.py
Normal 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()
|
||||
556
excel_filter/translations.py
Normal file
556
excel_filter/translations.py
Normal 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")
|
||||
}
|
||||
110
excel_filter/utils/file_utils.py
Normal file
110
excel_filter/utils/file_utils.py
Normal 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}")
|
||||
114
excel_filter/utils/logging_utils.py
Normal file
114
excel_filter/utils/logging_utils.py
Normal 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}")
|
||||
Reference in New Issue
Block a user