init: scripts diversos (crawlers, conversores, scrapers)
This commit is contained in:
165
translate-wp-plugin/fix_malformed.py
Executable file
165
translate-wp-plugin/fix_malformed.py
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/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 <ficheiro.po> [...]")
|
||||
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()
|
||||
Reference in New Issue
Block a user