Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
# MIT License. Part of the Hermes docx skill.
|
||||
"""Create a .docx document from a JSON spec.
|
||||
|
||||
Usage: docx_create.py spec.json output.docx
|
||||
Run with --help for the spec format summary.
|
||||
|
||||
Spec (JSON object):
|
||||
{
|
||||
"page": {"width_mm": 210, "height_mm": 297,
|
||||
"margins_mm": {"top": 25, "bottom": 25, "left": 20, "right": 20}},
|
||||
"header": "text shown in page header",
|
||||
"footer": "text shown in page footer",
|
||||
"styles": [{"name": "MyStyle", "base": "Normal", "font": "Arial",
|
||||
"size_pt": 12, "bold": true, "color": "1F4E79"}],
|
||||
"blocks": [
|
||||
{"type": "heading", "text": "Title", "level": 1},
|
||||
{"type": "paragraph", "style": "MyStyle", "runs": [
|
||||
{"text": "plain "}, {"text": "bold", "bold": true},
|
||||
{"text": " italic", "italic": true},
|
||||
{"text": " under", "underline": true}]},
|
||||
{"type": "paragraph", "text": "shortcut: single plain run"},
|
||||
{"type": "bullet_list", "items": ["a", "b"]},
|
||||
{"type": "numbered_list", "items": ["one", "two"]},
|
||||
{"type": "table", "header": ["Col1", "Col2"],
|
||||
"rows": [["1", "2"]], "style": "Light Grid Accent 1",
|
||||
"header_bold": true},
|
||||
{"type": "image", "path": "pic.png", "width_mm": 60},
|
||||
{"type": "page_break"},
|
||||
{"type": "toc"}
|
||||
]
|
||||
}
|
||||
|
||||
Extras: `"footer_page_numbers": true` at the top level adds a
|
||||
"Page X of Y" footer built from PAGE/NUMPAGES fields, and a `toc` block
|
||||
inserts a Table of Contents field. Field results are computed by
|
||||
Word/LibreOffice when the file is opened, not by python-docx.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
from docx import Document
|
||||
from docx.enum.style import WD_STYLE_TYPE
|
||||
from docx.enum.text import WD_BREAK
|
||||
from docx.shared import Mm, Pt, RGBColor
|
||||
|
||||
|
||||
def apply_page(doc, page: dict) -> None:
|
||||
section = doc.sections[0]
|
||||
if "width_mm" in page:
|
||||
section.page_width = Mm(page["width_mm"])
|
||||
if "height_mm" in page:
|
||||
section.page_height = Mm(page["height_mm"])
|
||||
m = page.get("margins_mm", {})
|
||||
for side in ("top", "bottom", "left", "right"):
|
||||
if side in m:
|
||||
setattr(section, f"{side}_margin", Mm(m[side]))
|
||||
|
||||
|
||||
def add_styles(doc, styles: list) -> None:
|
||||
for s in styles:
|
||||
style = doc.styles.add_style(s["name"], WD_STYLE_TYPE.PARAGRAPH)
|
||||
if s.get("base"):
|
||||
style.base_style = doc.styles[s["base"]]
|
||||
font = style.font
|
||||
if s.get("font"):
|
||||
font.name = s["font"]
|
||||
if s.get("size_pt"):
|
||||
font.size = Pt(s["size_pt"])
|
||||
if s.get("bold") is not None:
|
||||
font.bold = s["bold"]
|
||||
if s.get("italic") is not None:
|
||||
font.italic = s["italic"]
|
||||
if s.get("color"):
|
||||
font.color.rgb = RGBColor.from_string(s["color"])
|
||||
|
||||
|
||||
def add_runs(para, block: dict) -> None:
|
||||
runs = block.get("runs")
|
||||
if runs is None:
|
||||
runs = [{"text": block.get("text", "")}]
|
||||
for r in runs:
|
||||
run = para.add_run(r.get("text", ""))
|
||||
if r.get("bold"):
|
||||
run.bold = True
|
||||
if r.get("italic"):
|
||||
run.italic = True
|
||||
if r.get("underline"):
|
||||
run.underline = True
|
||||
|
||||
|
||||
def add_block(doc, block: dict) -> None:
|
||||
btype = block["type"]
|
||||
if btype == "heading":
|
||||
doc.add_heading(block.get("text", ""), level=block.get("level", 1))
|
||||
elif btype == "paragraph":
|
||||
para = doc.add_paragraph(style=block.get("style"))
|
||||
add_runs(para, block)
|
||||
elif btype == "bullet_list":
|
||||
for item in block.get("items", []):
|
||||
doc.add_paragraph(item, style="List Bullet")
|
||||
elif btype == "numbered_list":
|
||||
for item in block.get("items", []):
|
||||
doc.add_paragraph(item, style="List Number")
|
||||
elif btype == "table":
|
||||
header = block.get("header", [])
|
||||
rows = block.get("rows", [])
|
||||
ncols = len(header) if header else (len(rows[0]) if rows else 1)
|
||||
table = doc.add_table(rows=0, cols=ncols)
|
||||
table.style = block.get("style", "Table Grid")
|
||||
if header:
|
||||
cells = table.add_row().cells
|
||||
for i, text in enumerate(header):
|
||||
cells[i].text = str(text)
|
||||
if block.get("header_bold", True):
|
||||
for para in cells[i].paragraphs:
|
||||
for run in para.runs:
|
||||
run.bold = True
|
||||
for row in rows:
|
||||
cells = table.add_row().cells
|
||||
for i, text in enumerate(row):
|
||||
cells[i].text = str(text)
|
||||
elif btype == "image":
|
||||
width = Mm(block["width_mm"]) if block.get("width_mm") else None
|
||||
doc.add_picture(block["path"], width=width)
|
||||
elif btype == "page_break":
|
||||
doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)
|
||||
elif btype == "toc":
|
||||
from docx_edit import _add_field
|
||||
para = doc.add_paragraph()
|
||||
_add_field(para, r' TOC \o "1-3" \h \z \u ',
|
||||
"Table of contents - open in Word/LibreOffice and "
|
||||
"update fields to populate.")
|
||||
else:
|
||||
raise ValueError(f"unknown block type: {btype}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Create a .docx from a JSON spec.",
|
||||
epilog="See the module docstring (top of this file) for the spec format.")
|
||||
ap.add_argument("spec", help="path to JSON spec file")
|
||||
ap.add_argument("output", help="path of .docx to write")
|
||||
args = ap.parse_args()
|
||||
|
||||
with open(args.spec, encoding="utf-8") as f:
|
||||
spec = json.load(f)
|
||||
|
||||
doc = Document()
|
||||
if spec.get("page"):
|
||||
apply_page(doc, spec["page"])
|
||||
if spec.get("styles"):
|
||||
add_styles(doc, spec["styles"])
|
||||
if spec.get("header"):
|
||||
doc.sections[0].header.paragraphs[0].text = spec["header"]
|
||||
if spec.get("footer"):
|
||||
doc.sections[0].footer.paragraphs[0].text = spec["footer"]
|
||||
for block in spec.get("blocks", []):
|
||||
add_block(doc, block)
|
||||
if spec.get("footer_page_numbers"):
|
||||
from docx_edit import _add_field
|
||||
para = doc.sections[0].footer.paragraphs[0]
|
||||
para.add_run("Page ")
|
||||
_add_field(para, " PAGE ", "1")
|
||||
para.add_run(" of ")
|
||||
_add_field(para, " NUMPAGES ", "1")
|
||||
doc.save(args.output)
|
||||
print(json.dumps({"ok": True, "output": args.output,
|
||||
"blocks": len(spec.get("blocks", []))}))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user