# md2doc — design spec **Data:** 2026-07-27 **Autor:** Sebastian Petrescu **Stare:** draft (pending empirical review) --- ## 1. Obiectiv Construieste `md2doc.py` — un utilitar single-file Python care converteste Markdown la `.docx` (Microsoft Word OOXML), simetric cu `md2pdf.py` existent. In acelasi plan: bug-fixes pentru `md2pdf.py` identificate in timpul brainstorming-ului. ## 2. Context `md2pdf.py` exista, functioneaza (verified: rularea pe `pluxee-todo.md` produce PDF byte-identic cu cel committed), dar are 2 bug-uri: - **B1.** `convert_md_to_pdf()` accepta parametru `footer`, dar argparse nu expune flag-ul `--footer`. Functionalitate moarta in CLI. - **B2.** `--forms` ruleaza fara eroare, dar nu produce campuri AcroForm. Verificare: PDF-ul rezultat nu contine `/AcroForm` si nici anotari `/Widget`. `weasyprint` nu genereaza campuri de formular din `` HTML in mod automat. Pentru md2doc: niciun work anterior in AW pe conversie md→docx. Research extern (web-search-prime) arata 3 abordari posibile: Pandoc (CLI/subprocess), pypandoc (wrapper), sau python-docx cu parsare manuala. Am ales a treia pentru a pastra controlul fin asupra stilurilor si consistenta cu md2pdf.py. ## 3. Arhitectura ### 3.1 Layout repository ``` tools/ ├── md2pdf.py (existing — bug fixes only, scope-limited) ├── md2doc.py (new — sibling, mirrors md2pdf structure) └── docs/specs/2026-07-27-md2doc-design.md ``` md2doc.py este single-file, simetric cu md2pdf.py. Nu se face refactor spre package comun — YAGNI pentru 2 fisiere. ### 3.2 Structura md2doc.py Pattern duplicat din md2pdf.py: ```python #!/usr/bin/env python3 import argparse, sys from pathlib import Path import markdown from bs4 import BeautifulSoup from docx import Document from docx.shared import Pt, RGBColor, Cm STYLES = { "elegant": {...}, # default "report": {...}, "default": {...}, "dark": {...}, "mono": {...}, } COMMON = {...} # reguli comune, ca si COMMON_CSS in md2pdf def convert_md_to_doc(md_file, output_file, style="elegant", footer=""): ... def main(): parser = argparse.ArgumentParser(...) ... ``` ### 3.3 Pipeline conversie ``` md text ↓ _bulletize() (reutilizat din md2pdf — scos ca util comun) ↓ markdown.markdown() (extensii: tables, fenced_code, toc, attr_list, nl2br) ↓ BeautifulSoup HTML ↓ walk DOM, emit docx elements .docx file ``` `_bulletize()` e duplicated logic between md2pdf and md2doc. Pentru scope-ul curent (2 fisiere), se copiaza. Daca pe viitor se adauga un al 3-lea converter, se refactor la un `md_utils.py` comun. ## 4. Mapare stiluri (md → docx) Fiecare stil din `STYLES` defineste echivalentele Word ale CSS-ului md2pdf. Mapping-ul e manual, element cu element. | Element MD | md2pdf (CSS) | md2doc (Word) | |---|---|---| | h1 | font-size + border-bottom | Heading 1 style + paragraph bottom border | | h2/h3/h4 | font-size + color | Heading 2/3/4 style | | p | line-height + color | paragraph format | | strong | font-weight 700 | run.bold = True | | em | italic | run.italic = True | | code (inline) | monospace + bg | run.font.name = mono + run highlight | | pre | block + border-left | paragraph + indent + shading | | blockquote | border-left + bg | paragraph left indent + italic + shading | | table | borders + header bg | docx Table + cell shading + borders | | ul/ol | list markers | List Bullet / List Number paragraph styles | | a | color + underline | run.color + run.underline | | hr | border-top | paragraph bottom border | ### 4.1 Ipoteze de verificat (HARD CLAIMS — astea trebuie validate empiric inainte de implementare) Urmatoarele afirmatii despre python-docx sunt **ipoteze**, nu fapte, pana cand reviewer-ul empiric le rupe sau le confirma cu comenzi reale: - **H1.** python-docx poate adauga bottom border pe un paragraph Heading 1 (necesita manipulare XML — Issue python-docx #105 sugereaza ca nu exista API direct). - **H2.** python-docx poate aplica shading pe un paragraph (pentru code/blockquote) — documentatia oficiala nu listeaza API;StackTrace-ul sugereaza XML manipulation cu `OxmlElement('w:shd')`. - **H3.** python-docx poate crea liste nested (Level 2, Level 3) care se randeaza corect in Word SI Google Docs. Issue #122 si thread-urile Latenate/Reddit sugereaza probleme cunoscute aici. - **H4.** Cell shading (`w:shd` pe `w:tcPr`) functioneaza pentru background de header row intr-un tabel. - **H5.** `style = document.styles['List Bullet']` exista intr-un Document nou creat de python-docx (sau trebuie adaugat manual). Pana nu se valideaza H1-H5 cu comenzi reale (run python-docx, deschide output in Word/Pages, verifica vizual), mapping-ul din tabel este plan, nu fapt. ## 5. CLI interface Identica cu md2pdf.py: ``` md2doc.py input.md # → input.docx md2doc.py input.md -o output.docx md2doc.py docs/ # converteste toate .md din docs/ md2doc.py input.md --style mono md2doc.py input.md -q # quiet ``` ## 6. Bug-fixes md2pdf.py (scope-limited) ### 6.1 B1: expunere `--footer` Adauga argument argparse: ```python parser.add_argument('--footer', help='Custom footer text (right-aligned)') ``` Si paseaza-l in apelul `convert_md_to_pdf(md_file, pdf_file, args.style, forms=args.forms, footer=args.footer)`. ### 6.2 B2: `--forms` nu produce AcroForm Fapt stabilit (verified): weasyprint 68.1 nu genereaza campuri de formular din `` sau `