- 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:
@@ -1,5 +1,8 @@
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libreoffice-writer \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
COPY site /app/site
|
COPY site /app/site
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ site/app.py ──→ Flask (create_app)
|
|||||||
| **`.pdf`** | → Markdown | Текст + таблицы (`pdfplumber`), без форматирования |
|
| **`.pdf`** | → Markdown | Текст + таблицы (`pdfplumber`), без форматирования |
|
||||||
| **`.txt`** и прочие текстовые | → как есть | Декодируется UTF-8, ошибки заменяются `�` |
|
| **`.txt`** и прочие текстовые | → как есть | Декодируется UTF-8, ошибки заменяются `�` |
|
||||||
| **`.zip`** | → распаковка | Файлы внутри обрабатываются рекурсивно. Защита от ZIP-бомб: ≤500 файлов, ratio ≤100:1, ≤500 MB |
|
| **`.zip`** | → распаковка | Файлы внутри обрабатываются рекурсивно. Защита от ZIP-бомб: ≤500 файлов, ratio ≤100:1, ≤500 MB |
|
||||||
| **`.doc`** (бинарный) | ❌ не поддерживается | Возвращает заглушку `[DOC binary — not supported. Convert to .docx first.]` |
|
| **`.doc`** (бинарный) | → Docx → Markdown | LibreOffice headless: .doc → .docx → штатный `docx_to_markdown()` (таблицы, форматирование). Требует `libreoffice-writer` в контейнере |
|
||||||
|
|
||||||
Ограничение на размер загрузки: **200 MB** (`MAX_CONTENT_LENGTH`).
|
Ограничение на размер загрузки: **200 MB** (`MAX_CONTENT_LENGTH`).
|
||||||
|
|
||||||
|
|||||||
+51
-3
@@ -4,7 +4,7 @@
|
|||||||
Поддерживает:
|
Поддерживает:
|
||||||
- .docx → MD (python-docx: стили, жирный/курсив, таблицы)
|
- .docx → MD (python-docx: стили, жирный/курсив, таблицы)
|
||||||
- .pdf → MD (pdfplumber: текст + таблицы, без форматирования)
|
- .pdf → MD (pdfplumber: текст + таблицы, без форматирования)
|
||||||
- .doc (бинарный — не парсится, возвращает заглушку)
|
- .doc → MD (LibreOffice headless: .doc → .docx → штатный docx_to_markdown)
|
||||||
- .txt и прочие → как есть
|
- .txt и прочие → как есть
|
||||||
|
|
||||||
Также содержит:
|
Также содержит:
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
|
|
||||||
@@ -145,6 +148,51 @@ def pdf_to_markdown(content: bytes) -> str:
|
|||||||
return "\n".join(lines)
|
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:
|
except ImportError:
|
||||||
return content.decode('utf-8', errors='replace')
|
return content.decode('utf-8', errors='replace')
|
||||||
|
|
||||||
# ── .doc: бинарный — пока не поддерживается ──
|
# ── .doc: бинарный → LibreOffice → DOCX → Markdown ──
|
||||||
elif ext == '.doc':
|
elif ext == '.doc':
|
||||||
return "[DOC binary — not supported. Convert to .docx first.]"
|
return doc_to_markdown(content)
|
||||||
|
|
||||||
# ── .txt и прочие ──
|
# ── .txt и прочие ──
|
||||||
else:
|
else:
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ if _sys_path_root not in sys.path:
|
|||||||
sys.path.insert(0, _sys_path_root)
|
sys.path.insert(0, _sys_path_root)
|
||||||
|
|
||||||
# Версия приложения (меняется при изменениях)
|
# Версия приложения (меняется при изменениях)
|
||||||
VERSION = "0.0.16"
|
VERSION = "0.0.17"
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|||||||
Reference in New Issue
Block a user