Design for new md2doc.py (Markdown to DOCX converter) plus two bug-fixes for md2pdf.py (--footer dead in CLI, --forms produces no AcroForm). Spec includes 5 empirical hypotheses (H1-H5) about python-docx capabilities that must be validated before implementation.
7.3 KiB
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 parametrufooter, dar argparse nu expune flag-ul--footer. Functionalitate moarta in CLI. - B2.
--formsruleaza fara eroare, dar nu produce campuri AcroForm. Verificare: PDF-ul rezultat nu contine/AcroFormsi nici anotari/Widget.weasyprintnu genereaza campuri de formular din<input>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:
#!/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:shdpew: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:
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 <input type="text"> sau <textarea> din HTML-ul stringify-uit. Markul
HTML e prezent (verified: markdown.markdown() produce <input ...> corect),
dar weasyprint il randeaza ca text static.
Ipoteza (de verificat): weasyprint necesita ca HTML-ul sa fie incarcat
ca string cu base_url setat, sau tag-urile <form> sa fie wrappate explicit,
sau exista un flag documentat presentational_hints=True.
Alternativ: daca weasyprint 68.1 nu suporta forms deloc, atunci flag-ul
--forms trebuie eliminat din md2pdf (YAGNI) sau documentat ca no-op cu
eroare loud.
Inainte de implementare, reviewer-ul empiric verifica care varianta e adevarata.
7. Dependencies
md2doc.py:
python-docx(NOU —pip install python-docx)markdown(deja instalat, v3.10.2)beautifulsoup4(trebuie verificat daca e instalat)lxml(parser pentru BeautifulSoup, mai rapid decat html.parser)
md2pdf.py:
- existente, niciun nou dep.
8. Error handling
Fail-loud, niciodata silent. Pe orice eroare de parsare/conversie:
- print la stderr cu context (file, line if available)
- sys.exit(1) cu mesaj
- nu se scrie fisier de output partial
9. Testing
- md2doc: PT urmeaza pattern-ul md2pdf — nu exista test suite inca.
Pentru MVP: convertim
pluxee-todo.mdsi un edge-case file, verificam ca output .docx se deschide in Word/Pages fara erori si ca structura (headings, lists, tables, code blocks) e corecta vizual. - md2pdf bug-fixes: dupa B1, rulam
md2pdf.py input.md --footer "Test"si verificam ca footer-ul apare in PDF. Pentru B2, depinde de verdictul empiric.
10. Out of scope
- Refactor md2pdf + md2doc la un package comun (YAGNI pentru 2 fisiere)
- Stiluri noi peste cele 5 existente
- Suport pentru images embedded in markdown (de tratat separat)
- Suport pentru syntax highlighting in code blocks (Pygments integration)
.doc(format vechi binar) — doar.docx- Integrare cu reference.docx templates (pandoc-style)