feat(canary): docx template (binary blob + build helper)
Phase 4 Task 4.1+4.2 — pure-Go OOXML template builder under
backend/cmd/builddocxtemplate/. Run:
go run ./cmd/builddocxtemplate \
-out ./internal/token/generators/docx/template/template.docx
produces a minimal valid .docx (5 zip entries, mixed STORE/DEFLATE
compression) whose word/footer2.xml carries an INCLUDEPICTURE field
referencing the literal placeholder string HONEY_TRACK_URL with the
\\d switch (forces fetch-on-open, no local cache).
The committed template.docx is the embedded source for the Phase 4
docx generator (next commit). Mixed compression methods in the template
make Task 4.3's TestGenerate_PreservesCompressionMethods a meaningful
regression guard — a hardcode-DEFLATE generator would now mismatch on
[Content_Types].xml + word/_rels/document.xml.rels (both STORE).
cmd/builddocxtemplate is excluded from the production binary (separate
cmd/ subdir from cmd/canary).
This commit is contained in:
parent
fe7d641af8
commit
f36cce9a01
|
|
@ -0,0 +1,127 @@
|
||||||
|
// ©AngelaMos | 2026
|
||||||
|
// main.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
contentTypesXML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||||
|
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||||
|
<Default Extension="xml" ContentType="application/xml"/>
|
||||||
|
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
||||||
|
<Override PartName="/word/footer2.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>
|
||||||
|
</Types>
|
||||||
|
`
|
||||||
|
|
||||||
|
packageRelsXML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
||||||
|
</Relationships>
|
||||||
|
`
|
||||||
|
|
||||||
|
documentXML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||||
|
<w:body>
|
||||||
|
<w:p><w:r><w:t xml:space="preserve">Internal document - confidential. Do not redistribute.</w:t></w:r></w:p>
|
||||||
|
<w:sectPr>
|
||||||
|
<w:footerReference w:type="default" r:id="rIdFooter"/>
|
||||||
|
<w:pgSz w:w="12240" w:h="15840"/>
|
||||||
|
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/>
|
||||||
|
</w:sectPr>
|
||||||
|
</w:body>
|
||||||
|
</w:document>
|
||||||
|
`
|
||||||
|
|
||||||
|
documentRelsXML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rIdFooter" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer2.xml"/>
|
||||||
|
</Relationships>
|
||||||
|
`
|
||||||
|
|
||||||
|
footer2XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||||
|
<w:p>
|
||||||
|
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
|
||||||
|
<w:r><w:instrText xml:space="preserve"> INCLUDEPICTURE "HONEY_TRACK_URL" \d \* MERGEFORMAT </w:instrText></w:r>
|
||||||
|
<w:r><w:fldChar w:fldCharType="end"/></w:r>
|
||||||
|
</w:p>
|
||||||
|
</w:ftr>
|
||||||
|
`
|
||||||
|
|
||||||
|
pathContentTypes = "[Content_Types].xml"
|
||||||
|
pathPackageRels = "_rels/.rels"
|
||||||
|
pathDocument = "word/document.xml"
|
||||||
|
pathDocumentRels = "word/_rels/document.xml.rels"
|
||||||
|
pathFooter2 = "word/footer2.xml"
|
||||||
|
|
||||||
|
dirPerm os.FileMode = 0o755
|
||||||
|
filePerm os.FileMode = 0o644
|
||||||
|
)
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
name string
|
||||||
|
body string
|
||||||
|
method uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func entries() []entry {
|
||||||
|
return []entry{
|
||||||
|
{name: pathContentTypes, body: contentTypesXML, method: zip.Store},
|
||||||
|
{name: pathPackageRels, body: packageRelsXML, method: zip.Deflate},
|
||||||
|
{name: pathDocument, body: documentXML, method: zip.Deflate},
|
||||||
|
{name: pathDocumentRels, body: documentRelsXML, method: zip.Store},
|
||||||
|
{name: pathFooter2, body: footer2XML, method: zip.Deflate},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
out := flag.String("out", "", "output path for template.docx")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *out == "" {
|
||||||
|
log.Fatal("usage: builddocxtemplate -out <path>")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := buildTemplate(*out); err != nil {
|
||||||
|
log.Fatalf("build docx template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("wrote %s\n", *out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTemplate(out string) error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(out), dirPerm); err != nil {
|
||||||
|
return fmt.Errorf("mkdir parent: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(out, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, filePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open output: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
w := zip.NewWriter(f)
|
||||||
|
for _, e := range entries() {
|
||||||
|
hdr := &zip.FileHeader{Name: e.name, Method: e.method}
|
||||||
|
fw, cErr := w.CreateHeader(hdr)
|
||||||
|
if cErr != nil {
|
||||||
|
return fmt.Errorf("create %s: %w", e.name, cErr)
|
||||||
|
}
|
||||||
|
if _, wErr := fw.Write([]byte(e.body)); wErr != nil {
|
||||||
|
return fmt.Errorf("write %s: %w", e.name, wErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
return fmt.Errorf("close zip writer: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
Reference in New Issue