/** * Regenerate the classified reference documents (issue #209, ADR 0022): * `apps/api/assets/reference-vs-nfd.docx` / `.odt`. * * The DOCX/ODT export of a classified page passes these to pandoc via * `--reference-doc`; pandoc copies the reference's page setup — including * headers and footers — into its output, which is how the VS-NfD marking * repeats on every page in Word and LibreOffice without being deletable * body text. * * The binaries are DERIVED files: base = the default reference documents of * the PINNED pandoc (`pandoc/core:3.6`, the exact sidecar the stages run), * plus a header and footer carrying the marking. Never edit the binaries by * hand — edit this script and re-run it (Docker required): * * node apps/api/scripts/gen-classified-reference-docs.mjs * * The marking wording comes from @dorfteich/shared (single source, ADR * 0022); the shared package must be built (`pnpm --filter @dorfteich/shared * build`). */ import { execFileSync } from 'node:child_process'; import { mkdirSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { classificationMarking } from '@dorfteich/shared'; import { strToU8, strFromU8, unzipSync, zipSync } from 'fflate'; const PANDOC_IMAGE = 'pandoc/core:3.6'; const MARKING = classificationMarking('vs_nfd'); const outDir = join(dirname(fileURLToPath(import.meta.url)), '../assets'); function defaultReference(name) { return execFileSync('docker', ['run', '--rm', PANDOC_IMAGE, '--print-default-data-file', name], { maxBuffer: 64 * 1024 * 1024, }); } function escapeXml(value) { return value.replace(/&/g, '&').replace(//g, '>'); } /** DOCX: add word/header1.xml + word/footer1.xml, register them in the * content types and document relationships, and reference them from the * document's sectPr — Word repeats them on every page. */ function patchDocx(bytes) { const zip = unzipSync(new Uint8Array(bytes)); const marking = escapeXml(MARKING); const partXml = (root) => `\n` + `` + `` + `${marking}` + ``; zip['word/header1.xml'] = strToU8(partXml('hdr')); zip['word/footer1.xml'] = strToU8(partXml('ftr')); const types = strFromU8(zip['[Content_Types].xml']); zip['[Content_Types].xml'] = strToU8( types.replace( '', '' + '' + '', ), ); const rels = strFromU8(zip['word/_rels/document.xml.rels']); zip['word/_rels/document.xml.rels'] = strToU8( rels.replace( '', '' + '' + '', ), ); const doc = strFromU8(zip['word/document.xml']); if (!doc.includes('')) throw new Error('reference.docx has no sectPr'); zip['word/document.xml'] = strToU8( doc.replace( '', '' + '' + '', ), ); return zipSync(zip); } /** ODT: give the Standard master page a header with the marking and put the * marking next to the existing page number in its footer — LibreOffice * repeats master-page headers/footers on every page. */ function patchOdt(bytes) { const zip = unzipSync(new Uint8Array(bytes)); const marking = escapeXml(MARKING); const styles = strFromU8(zip['styles.xml']); if (!styles.includes('')) throw new Error('reference.odt has no footer'); const patched = styles .replace( '', `${marking}`, ) .replace( '\n ', `\n ${marking} · `, ); zip['styles.xml'] = strToU8(patched); return zipSync(zip); } mkdirSync(outDir, { recursive: true }); writeFileSync(join(outDir, 'reference-vs-nfd.docx'), patchDocx(defaultReference('reference.docx'))); writeFileSync(join(outDir, 'reference-vs-nfd.odt'), patchOdt(defaultReference('reference.odt'))); console.log(`generated reference-vs-nfd.docx/.odt in ${outDir} (marking: ${MARKING})`);