"""DOCX → md2gost Markdown pipeline.""" from __future__ import annotations import traceback from dataclasses import dataclass from pathlib import Path from typing import Callable from docx import Document from .emit import emit_markdown from .media import MediaExtractor from .refs import apply_reference_pass from .walker import DocxWalker, postprocess_blocks LogFn = Callable[[str], None] @dataclass class ImportRequest: filename: str = "" output: str | None = None media_dir: str | None = None keep_toc_pages: bool = False emit_page_breaks: bool = False @dataclass class ImportResult: ok: bool exit_code: int = 0 output_path: str | None = None media_dir: str | None = None message: str = "" def default_output_path(docx_path: str | Path) -> Path: p = Path(docx_path) return p.with_suffix(".md") def default_media_dir(md_path: Path) -> Path: return md_path.parent / f"{md_path.stem}_media" def convert_docx(req: ImportRequest, log: LogFn | None = None) -> ImportResult: """Convert DOCX to md2gost dialect Markdown. Errors go into ImportResult.""" emit: LogFn = log if log is not None else print def fail(code: int, message: str) -> ImportResult: emit(message) return ImportResult(False, code, message=message) filename = (req.filename or "").strip() if not filename: return fail(2, "Укажите исходный .docx") path = Path(filename) if not path.is_file(): return fail(2, f"Файл не найден: {path}") if path.suffix.lower() != ".docx": return fail(1, "Исходный файл должен быть в формате .docx") out = Path(req.output) if req.output else default_output_path(path) if out.suffix.lower() != ".md": out = out.with_suffix(".md") out.parent.mkdir(parents=True, exist_ok=True) media_path = Path(req.media_dir) if req.media_dir else default_media_dir(out) rel_prefix = media_path.name # If media dir is not sibling of md, use relative path from md parent try: rel_prefix = str(media_path.resolve().relative_to(out.parent.resolve())).replace("\\", "/") except ValueError: rel_prefix = str(media_path).replace("\\", "/") try: document = Document(str(path)) except Exception as exc: return fail(1, f"Не удалось открыть DOCX: {exc}") try: with MediaExtractor(path, media_path, rel_prefix=rel_prefix) as media: walker = DocxWalker( document, media, keep_toc_pages=req.keep_toc_pages, emit_page_breaks=req.emit_page_breaks, log=emit, ) blocks = walker.walk() blocks = postprocess_blocks(blocks) blocks = apply_reference_pass(blocks) md_text = emit_markdown(blocks, emit_page_breaks=req.emit_page_breaks) out.write_text(md_text, encoding="utf-8") except Exception as exc: emit(traceback.format_exc()) return fail(1, f"Ошибка импорта: {exc}") msg = f"Markdown: {out}" emit(msg) if media_path.is_dir() and any(media_path.iterdir()): emit(f"Медиа: {media_path}") return ImportResult( True, 0, output_path=str(out.resolve()), media_dir=str(media_path.resolve()) if media_path.exists() else None, message=msg, )