|
|
|
|
@@ -0,0 +1,450 @@
|
|
|
|
|
// HL-firm skeleton submission template generator (t-paliad-275).
|
|
|
|
|
//
|
|
|
|
|
// Reads HLC's "HL Patents Style" .dotm letterhead, strips its VBA
|
|
|
|
|
// macros and template-only artifacts, then emits a clean .docx that:
|
|
|
|
|
//
|
|
|
|
|
// 1. Preserves every HL paragraph + character style (HLpat-Heading-H1,
|
|
|
|
|
// HLpat-Body-B0, HLpat-Signature, HLpat-Table-Recitals-*, …) by
|
|
|
|
|
// keeping word/styles.xml, word/theme/*, word/numbering.xml,
|
|
|
|
|
// word/fontTable.xml, settings.xml, footnotes/endnotes from the
|
|
|
|
|
// source .dotm untouched.
|
|
|
|
|
// 2. Preserves the firm letterhead (logo header + firm-address footer)
|
|
|
|
|
// by keeping word/header[12].xml + word/footer[12].xml and the
|
|
|
|
|
// sectPr that references them.
|
|
|
|
|
// 3. Replaces word/document.xml with a Schriftsatz-shaped body that
|
|
|
|
|
// exercises every SubmissionVarsService placeholder (firm.*,
|
|
|
|
|
// today.*, user.*, project.*, parties.*, procedural_event.*, rule.*,
|
|
|
|
|
// deadline.*) — applying HL paragraph/character styles to each
|
|
|
|
|
// section so the rendered output reads as a real HL submission with
|
|
|
|
|
// variables substituted.
|
|
|
|
|
//
|
|
|
|
|
// Drop the output into HL/mWorkRepo at
|
|
|
|
|
//
|
|
|
|
|
// 6 - material/Templates/Word/Paliad/HLC/_firm-skeleton.docx
|
|
|
|
|
//
|
|
|
|
|
// so paliad's submission generator picks it up via the fallback chain.
|
|
|
|
|
// Lookup order after this CL: per-firm per-code → _firm-skeleton.docx
|
|
|
|
|
// (THIS file — HL formatting + placeholders) → universal _skeleton.docx
|
|
|
|
|
// (generic skeleton from t-paliad-259) → bare HL Patents Style .dotm
|
|
|
|
|
// (no placeholders). See internal/handlers/submission_drafts.go
|
|
|
|
|
// resolveSubmissionTemplate.
|
|
|
|
|
//
|
|
|
|
|
// Why this is firm-specific: the .dotm carries HL-licensed fonts,
|
|
|
|
|
// HL-branded logo media, and HLpat-prefixed style IDs. The output lives
|
|
|
|
|
// under the firm-namespaced directory in mWorkRepo so a future firm gets
|
|
|
|
|
// its own equivalent file generated against its own .dotm.
|
|
|
|
|
//
|
|
|
|
|
// Run:
|
|
|
|
|
//
|
|
|
|
|
// go run ./scripts/gen-hl-skeleton-template \
|
|
|
|
|
// -in /tmp/hl-patents-style.dotm \
|
|
|
|
|
// -out /tmp/_firm-skeleton.docx
|
|
|
|
|
//
|
|
|
|
|
// Output is byte-stable across runs for a given input (zip mtimes
|
|
|
|
|
// pinned).
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"archive/zip"
|
|
|
|
|
"bytes"
|
|
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
in := flag.String("in", "", "path to source HL Patents Style .dotm (required)")
|
|
|
|
|
out := flag.String("out", "_firm-skeleton.docx", "output .docx path")
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
|
|
if *in == "" {
|
|
|
|
|
fmt.Fprintln(os.Stderr, "gen-hl-skeleton-template: -in is required (path to HL Patents Style .dotm)")
|
|
|
|
|
os.Exit(2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
srcBytes, err := os.ReadFile(*in)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Fprintln(os.Stderr, "gen-hl-skeleton-template: read source:", err)
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
docx, err := buildDocx(srcBytes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Fprintln(os.Stderr, "gen-hl-skeleton-template:", err)
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
if err := os.WriteFile(*out, docx, 0o644); err != nil {
|
|
|
|
|
fmt.Fprintln(os.Stderr, "gen-hl-skeleton-template: write:", err)
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("wrote %s (%d bytes)\n", *out, len(docx))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fixedTime pins every zip entry's mtime so successive runs over the
|
|
|
|
|
// same .dotm produce byte-stable output. Useful for diffing the
|
|
|
|
|
// generated file in PR review.
|
|
|
|
|
var fixedTime = time.Date(2026, 5, 25, 0, 0, 0, 0, time.UTC)
|
|
|
|
|
|
|
|
|
|
// dropPaths lists zip entries removed during the .dotm → .docx
|
|
|
|
|
// conversion. VBA macros + their keymap binding + the template-only
|
|
|
|
|
// glossary parts and ribbon customizations are all dead weight (and
|
|
|
|
|
// some actively trigger Word's macro-security warning) — none of them
|
|
|
|
|
// add anything to a placeholder-rich Schriftsatz starter.
|
|
|
|
|
var dropPaths = map[string]bool{
|
|
|
|
|
"word/vbaProject.bin": true,
|
|
|
|
|
"word/vbaData.xml": true,
|
|
|
|
|
"word/customizations.xml": true,
|
|
|
|
|
"userCustomization/customUI.xml": true,
|
|
|
|
|
"customUI/customUI14.xml": true,
|
|
|
|
|
"word/glossary/document.xml": true,
|
|
|
|
|
"word/glossary/_rels/document.xml.rels": true,
|
|
|
|
|
"word/glossary/fontTable.xml": true,
|
|
|
|
|
"word/glossary/numbering.xml": true,
|
|
|
|
|
"word/glossary/settings.xml": true,
|
|
|
|
|
"word/glossary/styles.xml": true,
|
|
|
|
|
"word/glossary/webSettings.xml": true,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// rIdsToDrop names the document-rel ids whose targets are stripped
|
|
|
|
|
// from the package (vbaProject, customizations.xml, glossary). They
|
|
|
|
|
// must vanish from word/_rels/document.xml.rels so Word doesn't choke
|
|
|
|
|
// on a dangling reference.
|
|
|
|
|
var rIdsToDrop = map[string]bool{
|
|
|
|
|
"rId1": true, // vbaProject.bin
|
|
|
|
|
"rId2": true, // customizations.xml (keymap to VBA)
|
|
|
|
|
"rId21": true, // glossary/document.xml
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func buildDocx(src []byte) ([]byte, error) {
|
|
|
|
|
zr, err := zip.NewReader(bytes.NewReader(src), int64(len(src)))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("open source zip: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
zw := zip.NewWriter(&buf)
|
|
|
|
|
|
|
|
|
|
for _, f := range zr.File {
|
|
|
|
|
name := f.Name
|
|
|
|
|
if dropPaths[name] {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body, err := readZipEntry(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("read %s: %w", name, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch name {
|
|
|
|
|
case "[Content_Types].xml":
|
|
|
|
|
body = []byte(patchContentTypes(string(body)))
|
|
|
|
|
case "_rels/.rels":
|
|
|
|
|
body = []byte(patchRootRels(string(body)))
|
|
|
|
|
case "word/_rels/document.xml.rels":
|
|
|
|
|
body = []byte(patchDocumentRels(string(body)))
|
|
|
|
|
case "word/document.xml":
|
|
|
|
|
body = []byte(buildDocumentXML())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hdr := &zip.FileHeader{
|
|
|
|
|
Name: name,
|
|
|
|
|
Method: zip.Deflate,
|
|
|
|
|
Modified: fixedTime,
|
|
|
|
|
}
|
|
|
|
|
w, err := zw.CreateHeader(hdr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("create %s: %w", name, err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := w.Write(body); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("write %s: %w", name, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := zw.Close(); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("finalise zip: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func readZipEntry(f *zip.File) ([]byte, error) {
|
|
|
|
|
rc, err := f.Open()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer rc.Close()
|
|
|
|
|
return io.ReadAll(rc)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// patchContentTypes rewrites the macroEnabledTemplate part type to the
|
|
|
|
|
// regular wordprocessingml.document type (a .dotm carries the macro
|
|
|
|
|
// part type even on the body part), and removes Default/Override
|
|
|
|
|
// entries that target now-deleted parts (vba binary, customizations,
|
|
|
|
|
// glossary).
|
|
|
|
|
func patchContentTypes(in string) string {
|
|
|
|
|
out := in
|
|
|
|
|
out = strings.ReplaceAll(out,
|
|
|
|
|
`<Override PartName="/word/document.xml" ContentType="application/vnd.ms-word.template.macroEnabledTemplate.main+xml"/>`,
|
|
|
|
|
`<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>`)
|
|
|
|
|
|
|
|
|
|
removals := []string{
|
|
|
|
|
`<Default Extension="bin" ContentType="application/vnd.ms-office.vbaProject"/>`,
|
|
|
|
|
`<Override PartName="/word/vbaData.xml" ContentType="application/vnd.ms-word.vbaData+xml"/>`,
|
|
|
|
|
`<Override PartName="/word/customizations.xml" ContentType="application/vnd.ms-word.keyMapCustomizations+xml"/>`,
|
|
|
|
|
`<Override PartName="/word/glossary/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml"/>`,
|
|
|
|
|
`<Override PartName="/word/glossary/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>`,
|
|
|
|
|
`<Override PartName="/word/glossary/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>`,
|
|
|
|
|
`<Override PartName="/word/glossary/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/>`,
|
|
|
|
|
`<Override PartName="/word/glossary/webSettings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"/>`,
|
|
|
|
|
`<Override PartName="/word/glossary/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"/>`,
|
|
|
|
|
}
|
|
|
|
|
for _, r := range removals {
|
|
|
|
|
out = strings.ReplaceAll(out, r, "")
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// patchRootRels drops the userCustomization (ribbon mini-tab) and the
|
|
|
|
|
// customUI14 extensibility relationships — both reference VBA-backed
|
|
|
|
|
// UI we don't ship.
|
|
|
|
|
func patchRootRels(in string) string {
|
|
|
|
|
out := in
|
|
|
|
|
out = stripRelByPrefix(out, `<Relationship Id="rId2" Type="http://schemas.microsoft.com/office/2006/relationships/ui/userCustomization"`)
|
|
|
|
|
out = stripRelByPrefix(out, `<Relationship Id="Rf8f70ab1afd0469a" Type="http://schemas.microsoft.com/office/2007/relationships/ui/extensibility"`)
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// patchDocumentRels drops the document-level rels whose targets we
|
|
|
|
|
// stripped (vbaProject, customizations.xml, glossaryDocument).
|
|
|
|
|
func patchDocumentRels(in string) string {
|
|
|
|
|
out := in
|
|
|
|
|
for rid := range rIdsToDrop {
|
|
|
|
|
needle := `<Relationship Id="` + rid + `" `
|
|
|
|
|
out = stripRelByPrefix(out, needle)
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// stripRelByPrefix removes the full <Relationship .../> element whose
|
|
|
|
|
// open tag starts with the given prefix. Tolerates either a regular
|
|
|
|
|
// closing tag (</Relationship>) or the more common self-closing form.
|
|
|
|
|
func stripRelByPrefix(s, prefix string) string {
|
|
|
|
|
for {
|
|
|
|
|
start := strings.Index(s, prefix)
|
|
|
|
|
if start < 0 {
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
// Find end of this element (next "/>"). The .dotm always uses the
|
|
|
|
|
// self-closing form for Relationship elements.
|
|
|
|
|
end := strings.Index(s[start:], "/>")
|
|
|
|
|
if end < 0 {
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
s = s[:start] + s[start+end+2:]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// buildDocumentXML emits a Schriftsatz skeleton that exercises every
|
|
|
|
|
// SubmissionVarsService placeholder (the canonical 48-key v1 contract
|
|
|
|
|
// + the procedural_event.* canonical names + their rule.* legacy
|
|
|
|
|
// aliases). The structure mirrors a real DE/UPC submission — title
|
|
|
|
|
// block → court → rubrum → patent reference → submission title →
|
|
|
|
|
// legal grounds → Sachverhalt/Anträge/Rechtsausführungen/Beweis →
|
|
|
|
|
// signature → locale-variant verification footer.
|
|
|
|
|
//
|
|
|
|
|
// Each placeholder lives in its own <w:r> run so the renderer's pass-1
|
|
|
|
|
// (format-preserving single-run replace) catches every key. HL
|
|
|
|
|
// paragraph styles (HLpat-Heading-H1, HLpat-Header-Section, etc.) are
|
|
|
|
|
// applied via pStyle, character styles via rStyle.
|
|
|
|
|
//
|
|
|
|
|
// The sectPr at the bottom is copied verbatim from the source .dotm
|
|
|
|
|
// so the firm header/footer references (rId16=header1, rId17=footer1,
|
|
|
|
|
// rId18=header2 first-page, rId19=footer2 first-page) keep resolving
|
|
|
|
|
// after we replace the body. pgSz/pgMar/cols/docGrid match the .dotm
|
|
|
|
|
// exactly — a lawyer printing this gets the same A4 layout the .dotm
|
|
|
|
|
// produces.
|
|
|
|
|
func buildDocumentXML() string {
|
|
|
|
|
var b strings.Builder
|
|
|
|
|
b.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`)
|
|
|
|
|
b.WriteString(`<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">`)
|
|
|
|
|
b.WriteString(`<w:body>`)
|
|
|
|
|
|
|
|
|
|
skeletonBanner(&b)
|
|
|
|
|
|
|
|
|
|
heading(&b, "HLpat-Heading-H1", "{{firm.name}}")
|
|
|
|
|
body0(&b, "Bearbeiter: {{user.display_name}}")
|
|
|
|
|
body0(&b, "E-Mail: {{user.email}} · Büro: {{user.office}}")
|
|
|
|
|
body0(&b, "Datum: {{today.long_de}} ({{today.iso}})")
|
|
|
|
|
body0(&b, "{{firm.signature_block}}")
|
|
|
|
|
|
|
|
|
|
headerSection(&b, "{{project.court}}")
|
|
|
|
|
body0(&b, "Aktenzeichen: {{project.case_number}}")
|
|
|
|
|
body0(&b, "Verfahrensart: {{project.proceeding.name}} ({{project.proceeding.code}})")
|
|
|
|
|
body0(&b, "Instanz: {{project.instance_level}}")
|
|
|
|
|
|
|
|
|
|
headerSubsection(&b, "In der Sache")
|
|
|
|
|
|
|
|
|
|
recitalsParty(&b, "{{parties.claimant.name}}")
|
|
|
|
|
recitalsPartyDetails(&b, "vertreten durch {{parties.claimant.representative}}")
|
|
|
|
|
recitalsRoles(&b, "— Klägerin / Patentinhaberin / Anmelderin —")
|
|
|
|
|
|
|
|
|
|
recitalsSequencer(&b, "gegen")
|
|
|
|
|
|
|
|
|
|
recitalsParty(&b, "{{parties.defendant.name}}")
|
|
|
|
|
recitalsPartyDetails(&b, "vertreten durch {{parties.defendant.representative}}")
|
|
|
|
|
recitalsRoles(&b, "— Beklagte / Einsprechende / Beschwerdegegnerin —")
|
|
|
|
|
|
|
|
|
|
recitalsSequencer(&b, "sowie")
|
|
|
|
|
|
|
|
|
|
recitalsParty(&b, "{{parties.other.name}}")
|
|
|
|
|
recitalsPartyDetails(&b, "vertreten durch {{parties.other.representative}}")
|
|
|
|
|
recitalsRoles(&b, "— Weitere Beteiligte —")
|
|
|
|
|
|
|
|
|
|
headerSubsection(&b, "Betreff")
|
|
|
|
|
body0(&b, "Streitpatent: {{project.patent_number}} (UPC-Schreibweise: {{project.patent_number_upc}})")
|
|
|
|
|
body0(&b, "Anmeldung: {{project.filing_date}} · Erteilung: {{project.grant_date}}")
|
|
|
|
|
body0(&b, "Projekttitel: {{project.title}}")
|
|
|
|
|
body0(&b, "Unsere Seite: {{project.our_side_de}} ({{project.our_side}})")
|
|
|
|
|
body0(&b, "Mandant: {{project.client_number}} · Matter: {{project.matter_number}}")
|
|
|
|
|
body0(&b, "Internes Aktenzeichen: {{project.reference}}")
|
|
|
|
|
|
|
|
|
|
heading(&b, "HLpat-Heading-H1", "{{procedural_event.name}}")
|
|
|
|
|
body0(&b, "(Schriftsatz-Code: {{procedural_event.code}})")
|
|
|
|
|
body0(&b, "Rechtsgrundlage: {{procedural_event.legal_source_pretty}} ({{procedural_event.legal_source}})")
|
|
|
|
|
body0(&b, "Typische Partei: {{procedural_event.primary_party}} · Schriftsatz-Typ: {{procedural_event.event_kind}}")
|
|
|
|
|
|
|
|
|
|
headerSubsection(&b, "Frist")
|
|
|
|
|
body0(&b, "Frist-Bezeichnung: {{deadline.title}}")
|
|
|
|
|
body0(&b, "Fälligkeit: {{deadline.due_date_long_de}} ({{deadline.due_date}})")
|
|
|
|
|
body0(&b, "Ursprüngliche Frist: {{deadline.original_due_date}}")
|
|
|
|
|
body0(&b, "Berechnet aus: {{deadline.computed_from}} · Quelle: {{deadline.source}}")
|
|
|
|
|
|
|
|
|
|
heading(&b, "HLpat-Heading-H2", "I. Sachverhalt")
|
|
|
|
|
body0(&b, "[Hier folgt der Sachverhalt. Diese Vorlage ist eine Skelett-Fassung — bitte gemäß Schriftsatz-Typ ({{procedural_event.name}}) ausformulieren.]")
|
|
|
|
|
|
|
|
|
|
heading(&b, "HLpat-Heading-H2", "II. Anträge")
|
|
|
|
|
requestsIntro(&b, "Es wird beantragt:")
|
|
|
|
|
requestsLevel1(&b, "[Antrag 1 — gemäß {{procedural_event.legal_source_pretty}}]")
|
|
|
|
|
requestsLevel1(&b, "[Antrag 2]")
|
|
|
|
|
|
|
|
|
|
heading(&b, "HLpat-Heading-H2", "III. Rechtsausführungen")
|
|
|
|
|
body0(&b, "[Hier folgen die Rechtsausführungen.]")
|
|
|
|
|
|
|
|
|
|
heading(&b, "HLpat-Heading-H2", "IV. Beweis")
|
|
|
|
|
evidenceOffering(&b, "Beweis: [Beweismittel — z. B. Anlage K1: {{project.patent_number}}]")
|
|
|
|
|
|
|
|
|
|
heading(&b, "HLpat-Heading-H2", "Schlussformel")
|
|
|
|
|
signature(&b, "{{today.long_de}}")
|
|
|
|
|
signature(&b, "")
|
|
|
|
|
signature(&b, "{{user.display_name}}")
|
|
|
|
|
signature(&b, "{{firm.name}}")
|
|
|
|
|
|
|
|
|
|
// Locale-aware verification block — exercises every EN/DE alias the
|
|
|
|
|
// variable bag carries plus the rule.* legacy aliases so a lawyer
|
|
|
|
|
// editing the template sees that both surfaces resolve. A real
|
|
|
|
|
// submission deletes this section after sanity-checking the render.
|
|
|
|
|
heading(&b, "HLpat-Heading-H3", "Locale-Varianten & Legacy-Aliase (SKELETON)")
|
|
|
|
|
body1(&b, "EN long date: {{today.long_en}} · Today (bare alias): {{today}}")
|
|
|
|
|
body1(&b, "Project our side (EN): {{project.our_side_en}} · Proceeding (EN): {{project.proceeding.name_en}}")
|
|
|
|
|
body1(&b, "Proceeding (DE): {{project.proceeding.name_de}}")
|
|
|
|
|
body1(&b, "Deadline EN long: {{deadline.due_date_long_en}}")
|
|
|
|
|
body1(&b, "Procedural event name (DE): {{procedural_event.name_de}} · (EN): {{procedural_event.name_en}}")
|
|
|
|
|
body1(&b, "Rule legacy aliases — name: {{rule.name}}, name_de: {{rule.name_de}}, name_en: {{rule.name_en}}")
|
|
|
|
|
body1(&b, "Rule legacy aliases — code: {{rule.submission_code}}, legal_source: {{rule.legal_source}}, legal_source_pretty: {{rule.legal_source_pretty}}")
|
|
|
|
|
body1(&b, "Rule legacy aliases — primary_party: {{rule.primary_party}}, event_type: {{rule.event_type}}")
|
|
|
|
|
|
|
|
|
|
// sectPr — copied verbatim from the source .dotm. Keeps the firm
|
|
|
|
|
// letterhead header (rId16=header1.xml, rId18=header2.xml first-page)
|
|
|
|
|
// and the firm-address footer (rId17, rId19) on every printed page.
|
|
|
|
|
b.WriteString(sectPrXML)
|
|
|
|
|
|
|
|
|
|
b.WriteString(`</w:body></w:document>`)
|
|
|
|
|
return b.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sectPrXML matches the source .dotm's section properties exactly so
|
|
|
|
|
// the firm header/footer refs and A4 page geometry round-trip.
|
|
|
|
|
const sectPrXML = `<w:sectPr><w:headerReference w:type="default" r:id="rId16"/><w:footerReference w:type="default" r:id="rId17"/><w:headerReference w:type="first" r:id="rId18"/><w:footerReference w:type="first" r:id="rId19"/><w:pgSz w:w="11906" w:h="16838" w:code="9"/><w:pgMar w:top="567" w:right="1418" w:bottom="567" w:left="1418" w:header="284" w:footer="284" w:gutter="0"/><w:cols w:space="720"/><w:titlePg/><w:docGrid w:linePitch="286"/></w:sectPr>`
|
|
|
|
|
|
|
|
|
|
func skeletonBanner(b *strings.Builder) {
|
|
|
|
|
b.WriteString(`<w:p><w:pPr><w:pStyle w:val="HLpat-Heading-H1"/></w:pPr><w:r><w:rPr><w:b/><w:color w:val="C00000"/></w:rPr><w:t xml:space="preserve">SKELETON — HL Patents Style mit Platzhaltern (nicht freigegeben)</w:t></w:r></w:p>`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func heading(b *strings.Builder, style, text string) { styledPara(b, style, "", text) }
|
|
|
|
|
func headerSection(b *strings.Builder, text string) { styledPara(b, "HLpat-Header-Section", "", text) }
|
|
|
|
|
func headerSubsection(b *strings.Builder, text string) { styledPara(b, "HLpat-Header-Subsection", "", text) }
|
|
|
|
|
func body0(b *strings.Builder, text string) { styledPara(b, "HLpat-Body-B0", "", text) }
|
|
|
|
|
func body1(b *strings.Builder, text string) { styledPara(b, "HLpat-Body-B1", "", text) }
|
|
|
|
|
func recitalsParty(b *strings.Builder, text string) { styledPara(b, "HLpat-Table-Recitals-Party", "", text) }
|
|
|
|
|
func recitalsPartyDetails(b *strings.Builder, text string) { styledPara(b, "HLpat-Table-Recitals-PartyDetails", "", text) }
|
|
|
|
|
func recitalsRoles(b *strings.Builder, text string) { styledPara(b, "HLpat-Table-Recitals-PartyRoles", "", text) }
|
|
|
|
|
func recitalsSequencer(b *strings.Builder, text string) { styledPara(b, "HLpat-Table-Recitals-Sequencers", "", text) }
|
|
|
|
|
func requestsIntro(b *strings.Builder, text string) { styledPara(b, "HLpat-Requests-Intro", "", text) }
|
|
|
|
|
func requestsLevel1(b *strings.Builder, text string) { styledPara(b, "HLpat-Requests-Level1", "", text) }
|
|
|
|
|
func evidenceOffering(b *strings.Builder, text string) { styledPara(b, "HLpat-EvidenceOffering", "", text) }
|
|
|
|
|
func signature(b *strings.Builder, text string) { styledPara(b, "HLpat-Signature", "", text) }
|
|
|
|
|
|
|
|
|
|
// styledPara writes one paragraph with the given pStyle (paragraph
|
|
|
|
|
// style id) and optional rStyle (character style applied to every run).
|
|
|
|
|
// Empty style ids drop the corresponding wrapper. Placeholders inside
|
|
|
|
|
// `text` are split into their own runs so the renderer's pass-1
|
|
|
|
|
// single-run replace catches each one independently.
|
|
|
|
|
func styledPara(b *strings.Builder, pStyle, rStyle, text string) {
|
|
|
|
|
b.WriteString(`<w:p>`)
|
|
|
|
|
if pStyle != "" {
|
|
|
|
|
b.WriteString(`<w:pPr><w:pStyle w:val="`)
|
|
|
|
|
b.WriteString(pStyle)
|
|
|
|
|
b.WriteString(`"/></w:pPr>`)
|
|
|
|
|
}
|
|
|
|
|
for _, seg := range splitOnPlaceholders(text) {
|
|
|
|
|
b.WriteString(`<w:r>`)
|
|
|
|
|
if rStyle != "" {
|
|
|
|
|
b.WriteString(`<w:rPr><w:rStyle w:val="`)
|
|
|
|
|
b.WriteString(rStyle)
|
|
|
|
|
b.WriteString(`"/></w:rPr>`)
|
|
|
|
|
}
|
|
|
|
|
b.WriteString(`<w:t xml:space="preserve">`)
|
|
|
|
|
b.WriteString(xmlEscape(seg))
|
|
|
|
|
b.WriteString(`</w:t></w:r>`)
|
|
|
|
|
}
|
|
|
|
|
b.WriteString(`</w:p>`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func splitOnPlaceholders(s string) []string {
|
|
|
|
|
if s == "" {
|
|
|
|
|
return []string{""}
|
|
|
|
|
}
|
|
|
|
|
var out []string
|
|
|
|
|
for {
|
|
|
|
|
open := strings.Index(s, "{{")
|
|
|
|
|
if open < 0 {
|
|
|
|
|
out = append(out, s)
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
close := strings.Index(s[open:], "}}")
|
|
|
|
|
if close < 0 {
|
|
|
|
|
out = append(out, s)
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
end := open + close + 2
|
|
|
|
|
if open > 0 {
|
|
|
|
|
out = append(out, s[:open])
|
|
|
|
|
}
|
|
|
|
|
out = append(out, s[open:end])
|
|
|
|
|
s = s[end:]
|
|
|
|
|
if s == "" {
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func xmlEscape(s string) string {
|
|
|
|
|
s = strings.ReplaceAll(s, "&", "&")
|
|
|
|
|
s = strings.ReplaceAll(s, "<", "<")
|
|
|
|
|
s = strings.ReplaceAll(s, ">", ">")
|
|
|
|
|
s = strings.ReplaceAll(s, `"`, """)
|
|
|
|
|
s = strings.ReplaceAll(s, "'", "'")
|
|
|
|
|
return s
|
|
|
|
|
}
|