diff options
Diffstat (limited to 'extras/html/themes/fix_css_validation.py')
| -rwxr-xr-x | extras/html/themes/fix_css_validation.py | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/extras/html/themes/fix_css_validation.py b/extras/html/themes/fix_css_validation.py new file mode 100755 index 0000000..4d2b45e --- /dev/null +++ b/extras/html/themes/fix_css_validation.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 + +import os +import re +from pathlib import Path + +def hex_to_rgba(hex_color): + """Convert hex color with alpha to rgba format""" + if len(hex_color) == 9: # #RRGGBBAA + r = int(hex_color[1:3], 16) + g = int(hex_color[3:5], 16) + b = int(hex_color[5:7], 16) + a = int(hex_color[7:9], 16) / 255 + return f"rgba({r}, {g}, {b}, {a:.2f})" + elif len(hex_color) == 5: # #RGBA + r = int(hex_color[1] * 2, 16) + g = int(hex_color[2] * 2, 16) + b = int(hex_color[3] * 2, 16) + a = int(hex_color[4] * 2, 16) / 255 + return f"rgba({r}, {g}, {b}, {a:.2f})" + return hex_color + +def fix_css_file(css_path): + """Fix CSS validation issues in a single file""" + with open(css_path, 'r') as f: + content = f.read() + + original_content = content + + # Fix hex colors with alpha channel (8 digits or 4 digits) + # Match #RRGGBBAA or #RGBA patterns + hex_alpha_pattern = r'#([0-9a-fA-F]{8}|[0-9a-fA-F]{4})\b' + + def replace_hex_alpha(match): + return hex_to_rgba(match.group(0)) + + content = re.sub(hex_alpha_pattern, replace_hex_alpha, content) + + # Fix color + percentage opacity patterns like {colors['text']}0A + opacity_pattern = r'\{[^}]+\}([0-9A-Fa-f]{2})\b' + content = re.sub(opacity_pattern, lambda m: f"rgba(0, 0, 0, {int(m.group(1), 16) / 255:.2f})", content) + + # Fix vendor prefixes - ensure they're properly formatted + content = re.sub(r'-webkit-font-smoothing:\s*antialiased;', '-webkit-font-smoothing: antialiased;', content) + content = re.sub(r'-moz-osx-font-smoothing:\s*grayscale;', '-moz-osx-font-smoothing: grayscale;', content) + + # Fix word-wrap (deprecated) to overflow-wrap + content = re.sub(r'word-wrap:\s*break-word;', 'overflow-wrap: break-word;', content) + + # Fix any background-clip issues + content = re.sub(r'-webkit-background-clip:\s*text;', '-webkit-background-clip: text;', content) + content = re.sub(r'background-clip:\s*text;', '', content) # Remove non-webkit background-clip: text + + # Fix -webkit-text-fill-color + content = re.sub(r'-webkit-text-fill-color:\s*transparent;', '-webkit-text-fill-color: transparent;', content) + + # Ensure proper CSS comments + content = re.sub(r'/\*([^*]|\*[^/])*\*/', lambda m: m.group(0), content) + + # Fix @import statements if they exist + content = re.sub(r'@import\s+url\([\'"]([^\'")]+)[\'"]\)([^;]*);', r'@import url("\1")\2;', content) + + # Fix any remaining color notations that might be invalid + # Look for patterns like #0f01 and convert to rgba + short_alpha_pattern = r'#([0-9a-fA-F]{3})([0-9a-fA-F]{1})\b' + + def replace_short_alpha(match): + rgb = match.group(1) + alpha = match.group(2) + r = int(rgb[0] * 2, 16) + g = int(rgb[1] * 2, 16) + b = int(rgb[2] * 2, 16) + a = int(alpha * 2, 16) / 255 + return f"rgba({r}, {g}, {b}, {a:.2f})" + + content = re.sub(short_alpha_pattern, replace_short_alpha, content) + + # Ensure all font-family declarations have proper fallbacks + content = re.sub(r'font-family:\s*([\'"]?)text\1,\s*sans-serif;', 'font-family: text, sans-serif;', content) + content = re.sub(r'font-family:\s*([\'"]?)heading\1,\s*serif;', 'font-family: heading, serif;', content) + content = re.sub(r'font-family:\s*([\'"]?)code\1,\s*monospace;', 'font-family: code, monospace;', content) + content = re.sub(r'font-family:\s*([\'"]?)handnotes\1,\s*cursive;', 'font-family: handnotes, cursive;', content) + + # Fix any clip-path issues + content = re.sub(r'clip-path:\s*polygon\(([^)]+)\);', lambda m: f'clip-path: polygon({m.group(1)});', content) + + # Fix multi-line background declarations + # Find patterns where background or background-image spans multiple lines + lines = content.split('\n') + fixed_lines = [] + i = 0 + while i < len(lines): + line = lines[i] + # Check if this line contains background/background-image without semicolon + if ('background:' in line or 'background-image:' in line) and not line.strip().endswith(';'): + # Collect all lines until we find a semicolon + combined_line = line + i += 1 + while i < len(lines) and not lines[i-1].strip().endswith(';'): + combined_line += ' ' + lines[i].strip() + i += 1 + fixed_lines.append(combined_line) + else: + fixed_lines.append(line) + i += 1 + + content = '\n'.join(fixed_lines) + + # Fix specific SVG data URLs in background-image + content = re.sub(r'background-image:\s*url\(\'data:image/svg\+xml;utf8,([^\']+)\'\)\s*,', + r"background-image: url('data:image/svg+xml;utf8,\1'),", content) + + # Fix text-shadow and box-shadow declarations spanning multiple lines + content = re.sub(r'text-shadow:\s*\n\s*', 'text-shadow: ', content) + content = re.sub(r'box-shadow:\s*\n\s*', 'box-shadow: ', content) + + # Write back only if changes were made + if content != original_content: + with open(css_path, 'w') as f: + f.write(content) + return True + return False + +def fix_all_themes(): + """Fix CSS validation issues in all theme files""" + themes_dir = Path("/home/paul/git/gemtexter/extras/html/themes") + fixed_count = 0 + + # Get all theme directories + theme_dirs = [d for d in themes_dir.iterdir() if d.is_dir() and d.name not in ['screenshots', '.git']] + + print(f"Fixing CSS validation issues in {len(theme_dirs)} themes...") + + for theme_dir in theme_dirs: + css_file = theme_dir / "style.css" + if css_file.exists(): + if fix_css_file(css_file): + fixed_count += 1 + print(f"Fixed: {theme_dir.name}") + else: + print(f"No fixes needed: {theme_dir.name}") + + # Also check for override CSS files + for override_css in theme_dir.glob("*-override.css"): + if fix_css_file(override_css): + print(f"Fixed override: {override_css.name}") + + print(f"\nFixed {fixed_count} theme CSS files") + + # Also fix the main theme CSS files if they exist + main_themes = ["default", "simple", "business", "future", "retrosimple"] + for theme_name in main_themes: + theme_css = themes_dir / theme_name / "style.css" + if theme_css.exists(): + if fix_css_file(theme_css): + print(f"Fixed main theme: {theme_name}") + +def validate_css_syntax(css_path): + """Basic CSS syntax validation""" + with open(css_path, 'r') as f: + content = f.read() + + issues = [] + + # Check for unclosed brackets + open_braces = content.count('{') + close_braces = content.count('}') + if open_braces != close_braces: + issues.append(f"Mismatched braces: {open_braces} open, {close_braces} close") + + # Check for missing semicolons (basic check) + lines = content.split('\n') + for i, line in enumerate(lines): + line = line.strip() + if line and not line.startswith('/*') and not line.endswith(('{', '}', ';', '*/')): + if ':' in line and not line.startswith('@'): + issues.append(f"Line {i+1}: Missing semicolon? '{line}'") + + return issues + +if __name__ == "__main__": + fix_all_themes() + + # Optional: Run basic validation + print("\nRunning basic CSS validation...") + themes_dir = Path("/home/paul/git/gemtexter/extras/html/themes") + theme_dirs = [d for d in themes_dir.iterdir() if d.is_dir() and d.name not in ['screenshots', '.git']] + + for theme_dir in theme_dirs: + css_file = theme_dir / "style.css" + if css_file.exists(): + issues = validate_css_syntax(css_file) + if issues: + print(f"\n{theme_dir.name}:") + for issue in issues: + print(f" - {issue}")
\ No newline at end of file |
