"""Reproduce topic-specific Markdown -> DOCX structural experiments, offline. Use the preinstalled Python environment. Output files stay beside this script. Counts inspect OOXML, not Word layout or visual rendering quality. """ from pathlib import Path from collections import Counter import hashlib import json import platform import subprocess import sys import time from zipfile import ZipFile, ZIP_DEFLATED from lxml import etree as ET ROOT = Path(__file__).resolve().parent W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main' M = 'http://schemas.openxmlformats.org/officeDocument/2006/math' NS = {'w': W, 'm': M} def run(argv, *, stdin=None): start = time.perf_counter() p = subprocess.run(argv, input=stdin, capture_output=True) return p, round(time.perf_counter() - start, 6) def nodes(value): if isinstance(value, dict): if 't' in value: yield value for v in value.values(): yield from nodes(v) elif isinstance(value, list): for v in value: yield from nodes(v) def inspect_docx(path): with ZipFile(path) as z: raw = z.read('word/document.xml') doc = ET.fromstring(raw) styles = ET.fromstring(z.read('word/styles.xml')) numbering = ET.fromstring(z.read('word/numbering.xml')) # Publish-safe exact raw XML as .txt, no whitespace rewriting. path.with_suffix('.xml.txt').write_bytes(raw) text = ''.join(doc.xpath('//w:t/text()', namespaces=NS)) h1 = styles.xpath('//w:style[@w:styleId="Heading1"]/w:rPr/w:sz/@w:val', namespaces=NS) return { 'headings': len(doc.xpath('//w:pStyle[starts-with(@w:val,"Heading")]', namespaces=NS)), 'tables': len(doc.xpath('//w:tbl', namespaces=NS)), 'rows': len(doc.xpath('//w:tbl/w:tr', namespaces=NS)), 'cells': len(doc.xpath('//w:tbl/w:tr/w:tc', namespaces=NS)), 'math_objects': len(doc.xpath('//m:oMath', namespaces=NS)), 'line_breaks': len(doc.xpath('//w:br', namespaces=NS)), 'numbered_paragraphs': len(doc.xpath('//w:numPr', namespaces=NS)), 'code_paragraphs': len(doc.xpath('//w:pStyle[@w:val="SourceCode"]', namespaces=NS)), 'hyperlinks': len(doc.xpath('//w:hyperlink', namespaces=NS)), 'heading1_half_points': h1, 'numbering_starts': numbering.xpath('//w:start/@w:val', namespaces=NS), 'html_table_content_present': 'Markdown' in text and 'Author' in text, 'reference_body_present': 'REFERENCE_BODY_MUST_NOT_COPY' in text, 'body_text': text, 'sha256': hashlib.sha256(path.read_bytes()).hexdigest(), } def main(): version = subprocess.check_output(['pandoc', '--version'], text=True).splitlines()[0] ext = subprocess.check_output(['pandoc', '--list-extensions=gfm'], text=True) (ROOT / 'gfm-extensions.txt').write_text(ext) p, _ = run(['pandoc', '-o', str(ROOT / 'reference.docx'), '--print-default-data-file', 'reference.docx']) assert p.returncode == 0, p.stderr ref = ROOT / 'reference.docx' with ZipFile(ref) as z: files = {i.filename: z.read(i.filename) for i in z.infolist()} st = ET.fromstring(files['word/styles.xml']) for size in st.xpath('//w:style[@w:styleId="Heading1"]/w:rPr/w:sz', namespaces=NS): size.set('{'+W+'}val', '56') files['word/styles.xml'] = ET.tostring(st, xml_declaration=True, encoding='UTF-8', standalone=True) doc = ET.fromstring(files['word/document.xml']) body = doc.find('{'+W+'}body') ptag = ET.Element('{'+W+'}p') ET.SubElement(ET.SubElement(ptag, '{'+W+'}r'), '{'+W+'}t').text = 'REFERENCE_BODY_MUST_NOT_COPY' body.insert(0, ptag) files['word/document.xml'] = ET.tostring(doc, xml_declaration=True, encoding='UTF-8', standalone=True) with ZipFile(ref, 'w', ZIP_DEFLATED) as z: for name, content in files.items(): z.writestr(name, content) rows = [] for sample in ('native', 'math', 'html', 'style'): src = ROOT / f'{sample}.md' for route, reader, extra in ( ('gfm', 'gfm', []), ('commonmark', 'commonmark', []), ('reference', 'gfm', ['--reference-doc', str(ref)]), ('html-bridge', 'gfm', []), ): outdir = ROOT / route outdir.mkdir(exist_ok=True) target = outdir / f'{sample}.docx' commands = [] if route == 'html-bridge': argv = ['pandoc', '-f', 'gfm', '-t', 'html', '--mathml', str(src)] p, _ = run(argv) assert p.returncode == 0, p.stderr html = outdir / f'{sample}.html.txt' html.write_bytes(p.stdout) commands.append(argv) argv = ['pandoc', '-f', 'html', '-t', 'json', str(html)] ast_p, _ = run(argv) commands.append(argv) argv = ['pandoc', '-f', 'html', '-t', 'docx', str(html), '-o', str(target)] else: argv = ['pandoc', '-f', reader, '-t', 'json', str(src)] ast_p, _ = run(argv) commands.append(argv) argv = ['pandoc', '-f', reader, str(src), '-o', str(target), *extra] assert ast_p.returncode == 0, ast_p.stderr ast_path = outdir / f'{sample}.ast.json' ast_path.write_bytes(ast_p.stdout) ast = json.loads(ast_p.stdout) counts = Counter(n['t'] for n in nodes(ast)) commands.append(argv) p, elapsed = run(argv) (outdir / f'{sample}.stderr.txt').write_bytes(p.stderr) assert p.returncode == 0, p.stderr metrics = inspect_docx(target) # Raw reverse Markdown is only a readable witness, not a layout score. argv = ['pandoc', '-f', 'docx', '-t', 'gfm', '--wrap=none', str(target)] rev, _ = run(argv) commands.append(argv) assert rev.returncode == 0, rev.stderr (outdir / f'{sample}.md').write_bytes(rev.stdout) rows.append({'tool': 'pandoc', 'route': route, 'sample': sample, 'reader': 'html' if route == 'html-bridge' else reader, 'commands': commands, 'elapsed_s': elapsed, 'ast_nodes': dict(counts), 'metrics': metrics, 'output': str(target.relative_to(ROOT)), 'evidence': [str(ast_path.relative_to(ROOT)), str(target.with_suffix('.xml.txt').relative_to(ROOT)), f'{route}/{sample}.md']}) # A second writer is attempted, never equated with a successful benchmark. html_path = ROOT / 'html-bridge/native.html.txt' textutil_path = ROOT / 'textutil-native.docx' p, elapsed = run(['/usr/bin/textutil', '-convert', 'docx', '-format', 'html', '-output', str(textutil_path), str(html_path)]) (ROOT / 'textutil.stderr.txt').write_bytes(p.stderr) alt = {'tool': 'macOS textutil', 'returncode': p.returncode, 'elapsed_s': elapsed, 'stderr': p.stderr.decode(errors='replace'), 'executed': True, 'output_exists': textutil_path.exists()} if p.returncode == 0 and textutil_path.exists(): alt['metrics'] = inspect_docx(textutil_path) result = {'kind': 'markdown-rendering', 'target': 'docx', 'date': '2026-10-29', 'python': sys.version.split()[0], 'platform': platform.platform(), 'pandoc': version, 'limits': ['One engine with four configurations, not a two-tool comparison', 'OOXML structure only; no Word/Pages/LibreOffice layout acceptance', 'Synthetic fixtures; no PDF/OCR benchmarks used'], 'definitions': { 'tables/rows/cells': 'Counts of w:tbl / direct w:tr / direct w:tc in word/document.xml', 'math_objects': 'Count of m:oMath in word/document.xml; no equation correctness score', 'headings': 'Count of paragraph style references whose IDs start with Heading', 'line_breaks': 'Count of w:br; compare the HTML-break sample only', 'heading1_half_points': 'Direct Heading1 style w:sz value; no visual/font availability claim', 'ast_nodes': 'Recursive counts of Pandoc JSON nodes before the DOCX writer', 'elapsed_s': 'Single subprocess wall time, not a performance comparison'}, 'rows': rows, 'second_writer_attempt': alt} (ROOT / 'results.json').write_text(json.dumps(result, indent=2) + '\n') print(json.dumps({'pandoc': version, 'rows': len(rows), 'second_writer': alt}, indent=2)) if __name__ == '__main__': main()