initial MVP
This commit is contained in:
0
src/core/__init__.py
Normal file
0
src/core/__init__.py
Normal file
30
src/core/converter.py
Normal file
30
src/core/converter.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from pathlib import Path
|
||||
from docx import Document
|
||||
from .style_registry import StyleRegistry
|
||||
from .renderer import render_markdown_to_docx
|
||||
|
||||
def process_document(input_dir: Path, output_dir: Path, style_config_path: Path):
|
||||
style_reg = StyleRegistry(style_config_path)
|
||||
|
||||
for doc_dir in input_dir.iterdir():
|
||||
if not doc_dir.is_dir():
|
||||
continue
|
||||
doc_name = doc_dir.name
|
||||
md_path = doc_dir / "document.md"
|
||||
images_dir = doc_dir / "images"
|
||||
|
||||
if not md_path.exists():
|
||||
print(f"⚠️ Пропущено: {doc_name} — нет document.md")
|
||||
continue
|
||||
|
||||
with open(md_path, encoding="utf-8") as f:
|
||||
md_content = f.read()
|
||||
|
||||
doc = Document()
|
||||
style_reg.ensure_styles_in_doc(doc) # добавляем стили
|
||||
render_markdown_to_docx(doc, md_content, images_dir, style_reg)
|
||||
|
||||
output_path = output_dir / f"{doc_name}.docx"
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
doc.save(output_path)
|
||||
print(f"✅ {doc_name} → {output_path}")
|
||||
17
src/core/renderer.py
Normal file
17
src/core/renderer.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import mistune
|
||||
from .style_registry import StyleRegistry
|
||||
from ..handlers import get_handler
|
||||
|
||||
def render_markdown_to_docx(doc, md_content: str, images_dir, style_reg: StyleRegistry):
|
||||
markdown = mistune.create_markdown(renderer=None)
|
||||
ast = markdown(md_content)
|
||||
|
||||
def walk(node):
|
||||
handler = get_handler(node["type"])
|
||||
if handler:
|
||||
handler(node, doc, images_dir, style_reg)
|
||||
elif node.get("children"):
|
||||
for child in node["children"]:
|
||||
walk(child)
|
||||
|
||||
walk(ast)
|
||||
56
src/core/style_registry.py
Normal file
56
src/core/style_registry.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import yaml
|
||||
from docx import Document
|
||||
from docx.enum.style import WD_STYLE_TYPE
|
||||
from docx.shared import Pt, RGBColor
|
||||
from docx.enum.text import WD_LINE_SPACING, WD_ALIGN_PARAGRAPH
|
||||
from docx.styles.style import ParagraphStyle
|
||||
|
||||
class StyleRegistry:
|
||||
def __init__(self, config_path):
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
self.config = yaml.safe_load(f)
|
||||
|
||||
def ensure_styles_in_doc(self, doc: Document):
|
||||
"""Создаёт недостающие стили в документе, если их нет."""
|
||||
for name, cfg in self.config["styles"].items():
|
||||
try:
|
||||
doc.styles[name]
|
||||
except KeyError:
|
||||
# Создаём стиль
|
||||
style = doc.styles.add_style(name, WD_STYLE_TYPE.PARAGRAPH)
|
||||
self.apply_style(style, name)
|
||||
|
||||
def apply_style(self, style, style_name: str):
|
||||
cfg = self.config["styles"].get(style_name)
|
||||
if not cfg:
|
||||
return
|
||||
|
||||
font_cfg = cfg.get("font", {})
|
||||
para_cfg = cfg.get("paragraph", {})
|
||||
|
||||
if font_cfg:
|
||||
font = style.font
|
||||
font.name = font_cfg.get("name", font.name)
|
||||
if "size" in font_cfg:
|
||||
font.size = Pt(font_cfg["size"])
|
||||
font.bold = font_cfg.get("bold", font.bold)
|
||||
font.italic = font_cfg.get("italic", font.italic)
|
||||
|
||||
if para_cfg:
|
||||
pf = style.paragraph_format
|
||||
if "space_before" in para_cfg:
|
||||
pf.space_before = Pt(para_cfg["space_before"])
|
||||
if "space_after" in para_cfg:
|
||||
pf.space_after = Pt(para_cfg["space_after"])
|
||||
if "line_spacing" in para_cfg:
|
||||
pf.line_spacing = para_cfg["line_spacing"]
|
||||
pf.line_spacing_rule = WD_LINE_SPACING.MULTIPLE
|
||||
if "alignment" in para_cfg:
|
||||
alignment_map = {"center": WD_ALIGN_PARAGRAPH.CENTER}
|
||||
align = alignment_map.get(para_cfg["alignment"])
|
||||
if align:
|
||||
pf.alignment = align
|
||||
|
||||
def get_color(self, key: str):
|
||||
hex_color = self.config.get("colors", {}).get(key, "000000")
|
||||
return RGBColor.from_string(hex_color)
|
||||
Reference in New Issue
Block a user