feat: поддержка .doc через LibreOffice headless
Deploy drhider / validate (push) Waiting to run

- extractor.py: добавлена doc_to_markdown() — .doc → .docx (soffice) → docx_to_markdown()
- Dockerfile: apt install libreoffice-writer
- ARCHITECTURE.md: обновлён статус .doc
- bump: 0.0.16 → 0.0.17
This commit is contained in:
2026-07-14 07:21:39 +04:00
parent 444ef4b732
commit 00d54dbfb6
4 changed files with 56 additions and 5 deletions
+51 -3
View File
@@ -4,7 +4,7 @@
Поддерживает:
- .docx → MD (python-docx: стили, жирный/курсив, таблицы)
- .pdf → MD (pdfplumber: текст + таблицы, без форматирования)
- .doc (бинарный — не парсится, возвращает заглушку)
- .doc → MD (LibreOffice headless: .doc → .docx → штатный docx_to_markdown)
- .txt и прочие → как есть
Также содержит:
@@ -14,6 +14,9 @@
import io
import os
import zipfile
import tempfile
import shutil
import subprocess
import logging
from typing import List, Tuple
@@ -145,6 +148,51 @@ def pdf_to_markdown(content: bytes) -> str:
return "\n".join(lines)
# ═══════════════════════════════════════════════════════════════════════════
# DOC → Markdown (через LibreOffice)
# ═══════════════════════════════════════════════════════════════════════════
def doc_to_markdown(content: bytes) -> str:
"""Конвертировать бинарный .doc → .docx (LibreOffice) → Markdown.
Использует soffice --headless --convert-to docx, затем прогоняет
через штатный docx_to_markdown() с полным форматированием и таблицами.
Args:
content: Бинарное содержимое .doc файла
Returns:
Строка в формате Markdown (или сообщение об ошибке)
"""
tmpdir = tempfile.mkdtemp()
doc_path = os.path.join(tmpdir, "input.doc")
try:
with open(doc_path, 'wb') as f:
f.write(content)
result = subprocess.run(
["soffice", "--headless", "--convert-to", "docx",
"--outdir", tmpdir, doc_path],
timeout=60, capture_output=True
)
docx_path = os.path.join(tmpdir, "input.docx")
if os.path.exists(docx_path):
with open(docx_path, 'rb') as f:
return docx_to_markdown(f.read())
stderr = result.stderr.decode('utf-8', errors='replace').strip()
log.warning("LibreOffice conversion failed: %s", stderr)
return f"[DOC — LibreOffice conversion failed: {stderr}]"
except FileNotFoundError:
return "[DOC — LibreOffice not installed. Install: apt install libreoffice-writer]"
except subprocess.TimeoutExpired:
return "[DOC — conversion timed out (>60s)]"
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
# ═══════════════════════════════════════════════════════════════════════════
# Единая точка входа
# ═══════════════════════════════════════════════════════════════════════════
@@ -176,9 +224,9 @@ def extract_text(fname: str, content: bytes, ctype: str = "") -> str:
except ImportError:
return content.decode('utf-8', errors='replace')
# ── .doc: бинарный — пока не поддерживается ──
# ── .doc: бинарный → LibreOffice → DOCX → Markdown ──
elif ext == '.doc':
return "[DOC binary — not supported. Convert to .docx first.]"
return doc_to_markdown(content)
# ── .txt и прочие ──
else: