#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ fix_malformed.py — Corrige ficheiros .po com msgstr mal formados. Problemas detectados e corrigidos: 1. Aspa de fecho escapada (linha termina com \") 2. Sequência de escape inválida (backslash + espaço) 3. Aspas internas não escapadas Se existirem linhas de continuação, usa-as como tradução correcta. Se não houver continuação, fecha a aspa em falta. Uso: python3 fix_malformed.py plugin-pt_PT.po [plugin2-pt_PT.po ...] find /path/to/library -name "*-pt_PT.po" | xargs python3 fix_malformed.py Author: Descomplicar® Version: 1.1.0 """ import sys import re import shutil import subprocess from pathlib import Path def last_quote_is_escaped(s: str) -> bool: """Verifica se a última aspas está escapada com backslash.""" if not s.endswith('"'): return False count = 0 i = len(s) - 2 while i >= 0 and s[i] == '\\': count += 1 i -= 1 return count % 2 == 1 def has_invalid_escape(s: str) -> bool: """Detecta sequências de escape inválidas (backslash + espaço/tab).""" return bool(re.search(r'\\[ \t]', s)) def has_unescaped_internal_quote(s: str) -> bool: """Detecta aspas não escapadas no interior da string.""" i = 0 while i < len(s): if s[i] == '\\': i += 2 continue if s[i] == '"': return True i += 1 return False def is_malformed(line: str) -> bool: """Determina se uma linha msgstr está mal formada.""" if not line.startswith('msgstr "'): return False content = line[7:] # Vazio é válido if content == '""': return False # Não termina em aspas, ou a aspas final está escapada if not content.endswith('"') or last_quote_is_escaped(content): return True # Escape inválido no interior if has_invalid_escape(content): return True # Aspas internas não escapadas inner = content[1:-1] if has_unescaped_internal_quote(inner): return True return False def process_file(filepath: str) -> tuple[int, bool]: """ Processa um ficheiro .po e corrige malformações. Devolve (número de correcções, sucesso do .mo). """ path = Path(filepath) if not path.exists(): print(f" ERRO: ficheiro não encontrado: {filepath}", file=sys.stderr) return 0, False with open(filepath, 'r', encoding='utf-8') as f: lines = f.readlines() new_lines = [] i = 0 fixes = 0 while i < len(lines): line = lines[i].rstrip('\n') if is_malformed(line): # Verificar se há linhas de continuação continuation = [] j = i + 1 while j < len(lines) and lines[j].rstrip('\n').startswith('"'): continuation.append(lines[j]) j += 1 if continuation: # Usar continuação como tradução correcta new_lines.append('msgstr ""\n') for c in continuation: new_lines.append(c) fixes += 1 i = j continue elif not line.endswith('"') or last_quote_is_escaped(line): # Fechar aspa em falta new_lines.append(line + '"\n') fixes += 1 i += 1 continue new_lines.append(lines[i]) i += 1 if fixes > 0: shutil.copy2(filepath, filepath + '.bak_malformed') with open(filepath, 'w', encoding='utf-8') as f: f.writelines(new_lines) mo_path = filepath.replace('.po', '.mo') r = subprocess.run( ['msgfmt', filepath, '-o', mo_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) mo_ok = r.returncode == 0 mo_status = "OK" if mo_ok else f"ERRO: {r.stderr.decode()[:80]}" print(f" {path.name}: {fixes} correcção(ões) | .mo: {mo_status}") return fixes, mo_ok else: print(f" {path.name}: sem malformações") return 0, True def main(): if len(sys.argv) < 2: print("Uso: python3 fix_malformed.py [...]") print(" find /path -name '*-pt_PT.po' | xargs python3 fix_malformed.py") sys.exit(1) total_fixes = 0 total_files = 0 errors = 0 for filepath in sys.argv[1:]: total_files += 1 fixes, ok = process_file(filepath) total_fixes += fixes if not ok: errors += 1 print(f"\nTotal: {total_fixes} correcções em {total_files} ficheiro(s) | Erros .mo: {errors}") if __name__ == '__main__': main()