Files
md_to_gost/word2md/pipeline.py
T
Igor20264 510f7e7adf
Python application / build (push) Waiting to run
v0.5.2
Что то сделал
2026-09-08 19:37:54 +03:00

111 lines
3.4 KiB
Python

"""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,
)