scripts/docx_create.py
scripts/docx_create.pyBrowse 12 files
1,704 tokens
6,387 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32# MIT License. Part of the Hermes docx skill.3"""Create a .docx document from a JSON spec.4 5Usage: docx_create.py spec.json output.docx6Run with --help for the spec format summary.7 8Spec (JSON object):9{10 "page": {"width_mm": 210, "height_mm": 297,11 "margins_mm": {"top": 25, "bottom": 25, "left": 20, "right": 20}},12 "header": "text shown in page header",13 "footer": "text shown in page footer",14 "styles": [{"name": "MyStyle", "base": "Normal", "font": "Arial",15 "size_pt": 12, "bold": true, "color": "1F4E79"}],16 "blocks": [17 {"type": "heading", "text": "Title", "level": 1},18 {"type": "paragraph", "style": "MyStyle", "runs": [19 {"text": "plain "}, {"text": "bold", "bold": true},20 {"text": " italic", "italic": true},21 {"text": " under", "underline": true}]},22 {"type": "paragraph", "text": "shortcut: single plain run"},23 {"type": "bullet_list", "items": ["a", "b"]},24 {"type": "numbered_list", "items": ["one", "two"]},25 {"type": "table", "header": ["Col1", "Col2"],26 "rows": [["1", "2"]], "style": "Light Grid Accent 1",27 "header_bold": true},28 {"type": "image", "path": "pic.png", "width_mm": 60},29 {"type": "page_break"},30 {"type": "toc"}31 ]32}33 34Extras: `"footer_page_numbers": true` at the top level adds a35"Page X of Y" footer built from PAGE/NUMPAGES fields, and a `toc` block36inserts a Table of Contents field. Field results are computed by37Word/LibreOffice when the file is opened, not by python-docx.38"""39from __future__ import annotations40 41import argparse42import json43import sys44 45from docx import Document46from docx.enum.style import WD_STYLE_TYPE47from docx.enum.text import WD_BREAK48from docx.shared import Mm, Pt, RGBColor49 50 51def apply_page(doc, page: dict) -> None:52 section = doc.sections[0]53 if "width_mm" in page:54 section.page_width = Mm(page["width_mm"])55 if "height_mm" in page:56 section.page_height = Mm(page["height_mm"])57 m = page.get("margins_mm", {})58 for side in ("top", "bottom", "left", "right"):59 if side in m:60 setattr(section, f"{side}_margin", Mm(m[side]))61 62 63def add_styles(doc, styles: list) -> None:64 for s in styles:65 style = doc.styles.add_style(s["name"], WD_STYLE_TYPE.PARAGRAPH)66 if s.get("base"):67 style.base_style = doc.styles[s["base"]]68 font = style.font69 if s.get("font"):70 font.name = s["font"]71 if s.get("size_pt"):72 font.size = Pt(s["size_pt"])73 if s.get("bold") is not None:74 font.bold = s["bold"]75 if s.get("italic") is not None:76 font.italic = s["italic"]77 if s.get("color"):78 font.color.rgb = RGBColor.from_string(s["color"])79 80 81def add_runs(para, block: dict) -> None:82 runs = block.get("runs")83 if runs is None:84 runs = [{"text": block.get("text", "")}]85 for r in runs:86 run = para.add_run(r.get("text", ""))87 if r.get("bold"):88 run.bold = True89 if r.get("italic"):90 run.italic = True91 if r.get("underline"):92 run.underline = True93 94 95def add_block(doc, block: dict) -> None:96 btype = block["type"]97 if btype == "heading":98 doc.add_heading(block.get("text", ""), level=block.get("level", 1))99 elif btype == "paragraph":100 para = doc.add_paragraph(style=block.get("style"))101 add_runs(para, block)102 elif btype == "bullet_list":103 for item in block.get("items", []):104 doc.add_paragraph(item, style="List Bullet")105 elif btype == "numbered_list":106 for item in block.get("items", []):107 doc.add_paragraph(item, style="List Number")108 elif btype == "table":109 header = block.get("header", [])110 rows = block.get("rows", [])111 ncols = len(header) if header else (len(rows[0]) if rows else 1)112 table = doc.add_table(rows=0, cols=ncols)113 table.style = block.get("style", "Table Grid")114 if header:115 cells = table.add_row().cells116 for i, text in enumerate(header):117 cells[i].text = str(text)118 if block.get("header_bold", True):119 for para in cells[i].paragraphs:120 for run in para.runs:121 run.bold = True122 for row in rows:123 cells = table.add_row().cells124 for i, text in enumerate(row):125 cells[i].text = str(text)126 elif btype == "image":127 width = Mm(block["width_mm"]) if block.get("width_mm") else None128 doc.add_picture(block["path"], width=width)129 elif btype == "page_break":130 doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)131 elif btype == "toc":132 from docx_edit import _add_field133 para = doc.add_paragraph()134 _add_field(para, r' TOC \o "1-3" \h \z \u ',135 "Table of contents - open in Word/LibreOffice and "136 "update fields to populate.")137 else:138 raise ValueError(f"unknown block type: {btype}")139 140 141def main() -> int:142 ap = argparse.ArgumentParser(143 description="Create a .docx from a JSON spec.",144 epilog="See the module docstring (top of this file) for the spec format.")145 ap.add_argument("spec", help="path to JSON spec file")146 ap.add_argument("output", help="path of .docx to write")147 args = ap.parse_args()148 149 with open(args.spec, encoding="utf-8") as f:150 spec = json.load(f)151 152 doc = Document()153 if spec.get("page"):154 apply_page(doc, spec["page"])155 if spec.get("styles"):156 add_styles(doc, spec["styles"])157 if spec.get("header"):158 doc.sections[0].header.paragraphs[0].text = spec["header"]159 if spec.get("footer"):160 doc.sections[0].footer.paragraphs[0].text = spec["footer"]161 for block in spec.get("blocks", []):162 add_block(doc, block)163 if spec.get("footer_page_numbers"):164 from docx_edit import _add_field165 para = doc.sections[0].footer.paragraphs[0]166 para.add_run("Page ")167 _add_field(para, " PAGE ", "1")168 para.add_run(" of ")169 _add_field(para, " NUMPAGES ", "1")170 doc.save(args.output)171 print(json.dumps({"ok": True, "output": args.output,172 "blocks": len(spec.get("blocks", []))}))173 return 0174 175 176if __name__ == "__main__":177 sys.exit(main())178 Referenced from SKILL.md
SKILL.mdView in source ↗
Source excerpt starting at line 50.SKILL.mdView in source ↗50```bash51python scripts/docx_create.py spec.json out.docx52python scripts/docx_read.py out.docx --text
Source excerpt starting at line 89.SKILL.mdView in source ↗891. **Create.** Write a JSON spec with `write_file`, then run90 `scripts/docx_create.py`. The spec supports: `page` (size + margins in91 mm), `header`/`footer` strings, `footer_page_numbers` (adds a
Source excerpt starting at line 98.98 `toc` (Table of Contents field), and `page_break`. The full spec99 format is documented at the top of `scripts/docx_create.py`.1002. **Read.** Use `scripts/docx_read.py` with exactly one mode flag.