Files
html/api/ambre-tool-docx-render.py

180 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""
ambre-tool-docx-render.py — Render JSON to premium docx
Usage: python3 ambre-tool-docx-render.py <input.json> <output.docx>
"""
import sys, json
from docx import Document
from docx.shared import Pt, RGBColor, Inches, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_ALIGN_VERTICAL
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from datetime import datetime
def add_border(cell, color="4f46e5"):
tc_pr = cell._tc.get_or_add_tcPr()
borders = OxmlElement('w:tcBorders')
for side in ('top','left','bottom','right'):
b = OxmlElement(f'w:{side}')
b.set(qn('w:val'), 'single')
b.set(qn('w:sz'), '4')
b.set(qn('w:color'), color)
borders.append(b)
tc_pr.append(borders)
def shade_cell(cell, color):
tc_pr = cell._tc.get_or_add_tcPr()
shd = OxmlElement('w:shd')
shd.set(qn('w:val'), 'clear')
shd.set(qn('w:color'), 'auto')
shd.set(qn('w:fill'), color)
tc_pr.append(shd)
def main():
if len(sys.argv) < 3:
print("Usage: render <input.json> <output.docx>"); sys.exit(1)
with open(sys.argv[1], 'r', encoding='utf-8') as f:
doc_data = json.load(f)
doc = Document()
# Page setup
for section in doc.sections:
section.top_margin = Cm(2.2)
section.bottom_margin = Cm(2.2)
section.left_margin = Cm(2.5)
section.right_margin = Cm(2.5)
# Style base font
style = doc.styles['Normal']
style.font.name = 'Calibri'
style.font.size = Pt(11)
# Title
title_p = doc.add_paragraph()
title_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
title_r = title_p.add_run(doc_data.get('title', 'Document'))
title_r.font.size = Pt(28)
title_r.font.bold = True
title_r.font.color.rgb = RGBColor(0x1e, 0x3a, 0x8a) # deep blue
# Subtitle
if doc_data.get('subtitle'):
sub_p = doc.add_paragraph()
sub_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
sub_r = sub_p.add_run(doc_data['subtitle'])
sub_r.font.size = Pt(14)
sub_r.font.italic = True
sub_r.font.color.rgb = RGBColor(0x64, 0x74, 0x8b)
# Author + date
meta_p = doc.add_paragraph()
meta_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
meta_r = meta_p.add_run(f"{doc_data.get('author', 'WEVAL Consulting')} | {datetime.now().strftime('%d %B %Y')}")
meta_r.font.size = Pt(10)
meta_r.font.color.rgb = RGBColor(0x94, 0xa3, 0xb8)
doc.add_paragraph() # spacer
# Executive Summary with box
if doc_data.get('executive_summary'):
exec_h = doc.add_heading('Synthese Executive', level=1)
for run in exec_h.runs:
run.font.color.rgb = RGBColor(0x4f, 0x46, 0xe5)
# Put exec summary in a 1-cell table for box style
t = doc.add_table(rows=1, cols=1)
t.alignment = WD_ALIGN_PARAGRAPH.CENTER
cell = t.cell(0, 0)
shade_cell(cell, 'f0f4ff')
add_border(cell, '4f46e5')
cell_p = cell.paragraphs[0]
cell_r = cell_p.add_run(doc_data['executive_summary'])
cell_r.font.size = Pt(11)
cell_r.font.italic = True
doc.add_paragraph()
# Sections
for section_data in doc_data.get('sections', []):
# Heading
h = doc.add_heading(section_data.get('heading', 'Section'), level=1)
for run in h.runs:
run.font.color.rgb = RGBColor(0x4f, 0x46, 0xe5)
run.font.size = Pt(18)
# Paragraphs
for para in section_data.get('paragraphs', []):
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(8)
p.paragraph_format.line_spacing = 1.4
r = p.add_run(para)
r.font.size = Pt(11)
# Bullets
bullets = section_data.get('bullets', [])
if bullets:
for b in bullets:
bp = doc.add_paragraph(b, style='List Bullet')
bp.paragraph_format.space_after = Pt(4)
# Table
table_data = section_data.get('table')
if table_data and table_data.get('headers') and table_data.get('rows'):
headers = table_data['headers']
rows = table_data['rows']
t = doc.add_table(rows=1+len(rows), cols=len(headers))
t.alignment = WD_ALIGN_PARAGRAPH.CENTER
# Header row
for i, h_text in enumerate(headers):
cell = t.cell(0, i)
shade_cell(cell, '4f46e5')
add_border(cell, '4f46e5')
cell_p = cell.paragraphs[0]
cell_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = cell_p.add_run(str(h_text))
run.font.bold = True
run.font.color.rgb = RGBColor(0xff, 0xff, 0xff)
run.font.size = Pt(11)
# Data rows
for r_idx, row in enumerate(rows):
for c_idx, val in enumerate(row[:len(headers)]):
cell = t.cell(r_idx+1, c_idx)
add_border(cell, 'cbd5e1')
if r_idx % 2 == 0:
shade_cell(cell, 'f8fafc')
cell_p = cell.paragraphs[0]
run = cell_p.add_run(str(val))
run.font.size = Pt(10)
doc.add_paragraph()
# Conclusion
if doc_data.get('conclusion'):
h = doc.add_heading('Conclusion', level=1)
for run in h.runs:
run.font.color.rgb = RGBColor(0x4f, 0x46, 0xe5)
p = doc.add_paragraph()
p.paragraph_format.line_spacing = 1.4
r = p.add_run(doc_data['conclusion'])
r.font.size = Pt(11)
# Footer
doc.add_paragraph()
footer_p = doc.add_paragraph()
footer_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
footer_r = footer_p.add_run(f"Document genere par WEVAL Consulting - {datetime.now().strftime('%Y-%m-%d %H:%M')}")
footer_r.font.size = Pt(8)
footer_r.font.italic = True
footer_r.font.color.rgb = RGBColor(0x94, 0xa3, 0xb8)
doc.save(sys.argv[2])
print(f"OK: {sys.argv[2]}")
if __name__ == '__main__':
main()