180 lines
6.1 KiB
Python
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()
|