|
| 1 | +"""Minimal OOXML artifact builders for SourceOS Office Plane. |
| 2 | +
|
| 3 | +These helpers intentionally use only Python's standard library. They create |
| 4 | +small, deterministic-enough DOCX/XLSX/PPTX ZIP packages for guarded local |
| 5 | +artifact generation. They are not a replacement for LibreOffice, Collabora, |
| 6 | +ONLYOFFICE, or a full template engine; they are the safe local bootstrap path |
| 7 | +for simple agent-authored workroom artifacts. |
| 8 | +""" |
| 9 | + |
| 10 | +from __future__ import annotations |
| 11 | + |
| 12 | +from html import escape |
| 13 | +from pathlib import Path |
| 14 | +from zipfile import ZIP_DEFLATED, ZipFile |
| 15 | + |
| 16 | + |
| 17 | +OOXML_GENERATION_FORMATS = {"docx", "xlsx", "pptx"} |
| 18 | + |
| 19 | + |
| 20 | +def _xml(text: str) -> str: |
| 21 | + return escape(text, quote=True) |
| 22 | + |
| 23 | + |
| 24 | +def _write_zip(path: Path, files: dict[str, str]) -> None: |
| 25 | + path.parent.mkdir(parents=True, exist_ok=True) |
| 26 | + with ZipFile(path, "w", ZIP_DEFLATED) as zf: |
| 27 | + for name in sorted(files): |
| 28 | + zf.writestr(name, files[name]) |
| 29 | + |
| 30 | + |
| 31 | +def write_ooxml_artifact( |
| 32 | + *, |
| 33 | + fmt: str, |
| 34 | + path: Path, |
| 35 | + title: str, |
| 36 | + workroom_id: str, |
| 37 | + artifact_id: str, |
| 38 | +) -> None: |
| 39 | + """Write a minimal OOXML artifact to path. |
| 40 | +
|
| 41 | + Args: |
| 42 | + fmt: one of docx, xlsx, pptx. |
| 43 | + path: output path. |
| 44 | + title: human-readable artifact title. |
| 45 | + workroom_id: Professional Workroom id. |
| 46 | + artifact_id: OfficeArtifact id. |
| 47 | + """ |
| 48 | + if fmt == "docx": |
| 49 | + _write_docx(path=path, title=title, workroom_id=workroom_id, artifact_id=artifact_id) |
| 50 | + return |
| 51 | + if fmt == "xlsx": |
| 52 | + _write_xlsx(path=path, title=title, workroom_id=workroom_id, artifact_id=artifact_id) |
| 53 | + return |
| 54 | + if fmt == "pptx": |
| 55 | + _write_pptx(path=path, title=title, workroom_id=workroom_id, artifact_id=artifact_id) |
| 56 | + return |
| 57 | + raise ValueError(f"unsupported OOXML generation format: {fmt}") |
| 58 | + |
| 59 | + |
| 60 | +def _write_docx(*, path: Path, title: str, workroom_id: str, artifact_id: str) -> None: |
| 61 | + document = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| 62 | +<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> |
| 63 | + <w:body> |
| 64 | + <w:p><w:r><w:t>{_xml(title)}</w:t></w:r></w:p> |
| 65 | + <w:p><w:r><w:t>Generated by SourceOS Office Plane guarded OOXML generation.</w:t></w:r></w:p> |
| 66 | + <w:p><w:r><w:t>Workroom: {_xml(workroom_id)}</w:t></w:r></w:p> |
| 67 | + <w:p><w:r><w:t>Artifact: {_xml(artifact_id)}</w:t></w:r></w:p> |
| 68 | + <w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"/></w:sectPr> |
| 69 | + </w:body> |
| 70 | +</w:document> |
| 71 | +''' |
| 72 | + files = { |
| 73 | + "[Content_Types].xml": '''<?xml version="1.0" encoding="UTF-8"?> |
| 74 | +<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> |
| 75 | + <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> |
| 76 | + <Default Extension="xml" ContentType="application/xml"/> |
| 77 | + <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/> |
| 78 | +</Types> |
| 79 | +''', |
| 80 | + "_rels/.rels": '''<?xml version="1.0" encoding="UTF-8"?> |
| 81 | +<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> |
| 82 | + <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/> |
| 83 | +</Relationships> |
| 84 | +''', |
| 85 | + "word/document.xml": document, |
| 86 | + } |
| 87 | + _write_zip(path, files) |
| 88 | + |
| 89 | + |
| 90 | +def _write_xlsx(*, path: Path, title: str, workroom_id: str, artifact_id: str) -> None: |
| 91 | + rows = [ |
| 92 | + ("Title", title), |
| 93 | + ("Generated By", "SourceOS Office Plane guarded OOXML generation"), |
| 94 | + ("Workroom", workroom_id), |
| 95 | + ("Artifact", artifact_id), |
| 96 | + ] |
| 97 | + row_xml = [] |
| 98 | + for idx, (key, value) in enumerate(rows, start=1): |
| 99 | + row_xml.append( |
| 100 | + f'''<row r="{idx}"><c r="A{idx}" t="inlineStr"><is><t>{_xml(key)}</t></is></c><c r="B{idx}" t="inlineStr"><is><t>{_xml(value)}</t></is></c></row>''' |
| 101 | + ) |
| 102 | + sheet = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| 103 | +<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> |
| 104 | + <sheetData> |
| 105 | + {''.join(row_xml)} |
| 106 | + </sheetData> |
| 107 | +</worksheet> |
| 108 | +''' |
| 109 | + files = { |
| 110 | + "[Content_Types].xml": '''<?xml version="1.0" encoding="UTF-8"?> |
| 111 | +<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> |
| 112 | + <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> |
| 113 | + <Default Extension="xml" ContentType="application/xml"/> |
| 114 | + <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/> |
| 115 | + <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/> |
| 116 | +</Types> |
| 117 | +''', |
| 118 | + "_rels/.rels": '''<?xml version="1.0" encoding="UTF-8"?> |
| 119 | +<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> |
| 120 | + <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/> |
| 121 | +</Relationships> |
| 122 | +''', |
| 123 | + "xl/workbook.xml": '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| 124 | +<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> |
| 125 | + <sheets><sheet name="SourceOS" sheetId="1" r:id="rId1"/></sheets> |
| 126 | +</workbook> |
| 127 | +''', |
| 128 | + "xl/_rels/workbook.xml.rels": '''<?xml version="1.0" encoding="UTF-8"?> |
| 129 | +<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> |
| 130 | + <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/> |
| 131 | +</Relationships> |
| 132 | +''', |
| 133 | + "xl/worksheets/sheet1.xml": sheet, |
| 134 | + } |
| 135 | + _write_zip(path, files) |
| 136 | + |
| 137 | + |
| 138 | +def _write_pptx(*, path: Path, title: str, workroom_id: str, artifact_id: str) -> None: |
| 139 | + slide = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| 140 | +<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"> |
| 141 | + <p:cSld> |
| 142 | + <p:spTree> |
| 143 | + <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr> |
| 144 | + <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr> |
| 145 | + <p:sp> |
| 146 | + <p:nvSpPr><p:cNvPr id="2" name="Title"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr> |
| 147 | + <p:spPr><a:xfrm><a:off x="914400" y="914400"/><a:ext cx="7315200" cy="914400"/></a:xfrm></p:spPr> |
| 148 | + <p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>{_xml(title)}</a:t></a:r></a:p></p:txBody> |
| 149 | + </p:sp> |
| 150 | + <p:sp> |
| 151 | + <p:nvSpPr><p:cNvPr id="3" name="Body"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr> |
| 152 | + <p:spPr><a:xfrm><a:off x="914400" y="2133600"/><a:ext cx="7315200" cy="2743200"/></a:xfrm></p:spPr> |
| 153 | + <p:txBody><a:bodyPr/><a:lstStyle/> |
| 154 | + <a:p><a:r><a:t>Generated by SourceOS Office Plane guarded OOXML generation.</a:t></a:r></a:p> |
| 155 | + <a:p><a:r><a:t>Workroom: {_xml(workroom_id)}</a:t></a:r></a:p> |
| 156 | + <a:p><a:r><a:t>Artifact: {_xml(artifact_id)}</a:t></a:r></a:p> |
| 157 | + </p:txBody> |
| 158 | + </p:sp> |
| 159 | + </p:spTree> |
| 160 | + </p:cSld> |
| 161 | + <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr> |
| 162 | +</p:sld> |
| 163 | +''' |
| 164 | + files = { |
| 165 | + "[Content_Types].xml": '''<?xml version="1.0" encoding="UTF-8"?> |
| 166 | +<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> |
| 167 | + <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> |
| 168 | + <Default Extension="xml" ContentType="application/xml"/> |
| 169 | + <Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/> |
| 170 | + <Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/> |
| 171 | +</Types> |
| 172 | +''', |
| 173 | + "_rels/.rels": '''<?xml version="1.0" encoding="UTF-8"?> |
| 174 | +<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> |
| 175 | + <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/> |
| 176 | +</Relationships> |
| 177 | +''', |
| 178 | + "ppt/presentation.xml": '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| 179 | +<p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> |
| 180 | + <p:sldIdLst><p:sldId id="256" r:id="rId1"/></p:sldIdLst> |
| 181 | + <p:sldSz cx="9144000" cy="6858000" type="screen4x3"/> |
| 182 | + <p:notesSz cx="6858000" cy="9144000"/> |
| 183 | +</p:presentation> |
| 184 | +''', |
| 185 | + "ppt/_rels/presentation.xml.rels": '''<?xml version="1.0" encoding="UTF-8"?> |
| 186 | +<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> |
| 187 | + <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/> |
| 188 | +</Relationships> |
| 189 | +''', |
| 190 | + "ppt/slides/slide1.xml": slide, |
| 191 | + } |
| 192 | + _write_zip(path, files) |
0 commit comments