80 lines
3.0 KiB
Python
80 lines
3.0 KiB
Python
"""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 <w:smartTag> 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 <w:r> 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"</?w:smartTag(?:Pr)?\b[^>]*>|<w:attr\b[^>]*/>", "", 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)
|