From a806cf9cae4aa664d74c128997abe870015e13c4 Mon Sep 17 00:00:00 2001 From: inma Date: Tue, 18 Aug 2026 09:24:55 +0000 Subject: [PATCH] doc_convert.py: conversor .doc->.docx de Fray Marcos, con el arreglo de los smartTag (#209) --- scripts/doc_convert.py | 79 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 scripts/doc_convert.py diff --git a/scripts/doc_convert.py b/scripts/doc_convert.py new file mode 100644 index 0000000..77cdb47 --- /dev/null +++ b/scripts/doc_convert.py @@ -0,0 +1,79 @@ +"""doc_convert.py — convierte un .doc viejo a .docx usando Microsoft Word (COM). + +Necesario para Marcos (marroro@telefonica.net), que envía .doc (Word antiguo) y +python-docx no sabe leer .doc. El contenido ya viene en EML (lo etiqueta Inma al +revisar); aquí solo cambiamos el FORMATO de archivo. + +Requiere Microsoft Word instalado + pywin32 (igual que marcos_review). +API: doc_to_docx(path, force=False) -> Path | None +""" +from __future__ import annotations + +from pathlib import Path + +WD_FORMAT_DOCX = 16 # wdFormatDocumentDefault (.docx) + + +def doc_to_docx(path, force: bool = False): + """Convierte un .doc a .docx con Word COM. Idempotente: si el .docx ya existe y + no force, lo devuelve sin reconvertir. Devuelve la ruta .docx, o None si falla.""" + path = Path(path) + if path.suffix.lower() != ".doc": + return None + out = path.with_suffix(".docx") + if out.exists() and not force: + return out + try: + import win32com.client # type: ignore + except ImportError: + return None + word = win32com.client.Dispatch("Word.Application") + word.Visible = False + try: + d = word.Documents.Open(str(path)) + d.SaveAs(str(out), FileFormat=WD_FORMAT_DOCX) + d.Close(SaveChanges=False) + finally: + word.Quit() + if out.exists(): + quitar_smarttags(out) + return out if out.exists() else None + + +def quitar_smarttags(docx_path) -> int: + """Desenvuelve los del .docx. Devuelve cuántos ha quitado. + + ⚠️ CRÍTICO, no es cosmético. Los .doc de Fray Marcos traen `smartTag` (el viejo + reconocimiento de nombres de Word) envolviendo expresiones como "la Iglesia" o + "la Cananea". **python-docx solo lee los que cuelgan directamente del párrafo, + así que el texto de dentro del smartTag DESAPARECE sin dar error**: se publicaron + frases mutiladas en las cartas 736, 737 y 739 ("...no va dirigida a los apóstoles, + sino a dureza de la respuesta..."). Detectado el 12-ago-2026. + """ + import re, shutil, zipfile + from pathlib import Path + + docx_path = Path(docx_path) + with zipfile.ZipFile(docx_path) as z: + piezas = {n: z.read(n) for n in z.namelist()} + xml = piezas.get("word/document.xml", b"").decode("utf-8", "replace") + nuevo, n = re.subn(r"]*>|]*/>", "", xml) + if not n: + return 0 + piezas["word/document.xml"] = nuevo.encode("utf-8") + tmp = docx_path.with_suffix(".docx.tmp") + with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as z: + for nombre, datos in piezas.items(): + z.writestr(nombre, datos) + shutil.move(str(tmp), str(docx_path)) + return n + + +if __name__ == "__main__": + import sys + if len(sys.argv) < 2: + print("uso: python doc_convert.py path/al/archivo.doc", file=sys.stderr) + sys.exit(2) + res = doc_to_docx(Path(sys.argv[1]), force="--force" in sys.argv) + print(f"OK: {res}" if res else "FALLO (¿Word/pywin32?)") + sys.exit(0 if res else 1)