#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Export MTMS ↔ M18 mapping docs to Word (.docx) and Excel (.xlsx). Prereqs: pip install python-docx openpyxl Usage (from repo root): python scripts/export_m18_mapping_office.py Outputs: docs/exports/MTMS_M18_DATA_MAPPING.docx docs/exports/MTMS_M18_DATA_MAPPING.xlsx """ from __future__ import annotations import re from datetime import datetime, timezone from pathlib import Path from docx import Document from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.oxml.ns import qn from docx.shared import Cm, Pt, RGBColor from openpyxl import Workbook from openpyxl.styles import Alignment, Border, Font, PatternFill, Side from openpyxl.utils import get_column_letter ROOT = Path(__file__).resolve().parents[1] DOCS = ROOT / "docs" GEN = DOCS / "generated" OUT = DOCS / "exports" HANDBOOK = DOCS / "MTMS_M18_DATA_MAPPING.md" ITEM_TYPE_MD = GEN / "m18-item-type-mapping.md" STSEARCH_MD = GEN / "m18-stsearch-types.md" def strip_md_inline(s: str) -> str: s = s.strip() s = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", s) # links s = s.replace("**", "").replace("`", "").replace("*", "") return s.strip() def parse_md_tables(text: str) -> list[tuple[str, list[str], list[list[str]]]]: """ Return list of (section_title, headers, rows) for each markdown table. section_title = nearest preceding ## / ### heading. """ lines = text.splitlines() current_h = "" tables: list[tuple[str, list[str], list[list[str]]]] = [] i = 0 while i < len(lines): line = lines[i] if line.startswith("#"): current_h = strip_md_inline(re.sub(r"^#+\s*", "", line)) i += 1 continue if line.strip().startswith("|") and i + 1 < len(lines) and re.match( r"^\|[\s\-:|]+\|$", lines[i + 1].strip() ): header = [strip_md_inline(c) for c in line.strip().strip("|").split("|")] i += 2 rows: list[list[str]] = [] while i < len(lines) and lines[i].strip().startswith("|"): row = [strip_md_inline(c) for c in lines[i].strip().strip("|").split("|")] rows.append(row) i += 1 tables.append((current_h, header, rows)) continue i += 1 return tables def add_runs_with_code(paragraph, text: str) -> None: """Simple split on backticks for monospace-ish plain text.""" parts = re.split(r"`([^`]+)`", text) for idx, part in enumerate(parts): if not part: continue run = paragraph.add_run(part) run.font.name = "Calibri" run._element.rPr.rFonts.set(qn("w:eastAsia"), "Microsoft JhengHei") if idx % 2 == 1: run.font.name = "Consolas" run.font.size = Pt(9) def md_to_docx(md_path: Path, out_path: Path, extra_md_files: list[Path] | None = None) -> None: doc = Document() section = doc.sections[0] section.top_margin = Cm(2) section.bottom_margin = Cm(2) section.left_margin = Cm(2.2) section.right_margin = Cm(2.2) style = doc.styles["Normal"] style.font.name = "Calibri" style.font.size = Pt(11) style._element.rPr.rFonts.set(qn("w:eastAsia"), "Microsoft JhengHei") files = [md_path] + (extra_md_files or []) first = True for path in files: if not path.is_file(): continue if not first: doc.add_page_break() first = False _append_md_file(doc, path) footer = doc.sections[0].footer.paragraphs[0] footer.text = ( f"MTMS ↔ M18 mapping · exported {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}" ) footer.alignment = WD_ALIGN_PARAGRAPH.CENTER out_path.parent.mkdir(parents=True, exist_ok=True) doc.save(out_path) print(f"Wrote {out_path.relative_to(ROOT)}") def _append_md_file(doc: Document, path: Path) -> None: lines = path.read_text(encoding="utf-8").splitlines() i = 0 in_code = False code_buf: list[str] = [] while i < len(lines): line = lines[i] if line.startswith("