@json-to-office/core-docx 3.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blueprints/index.d.ts +45 -0
- package/dist/blueprints/index.d.ts.map +1 -0
- package/dist/core/structure.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1135 -8
- package/dist/index.js.map +1 -1
- package/dist/plugin/example/index.js +220 -4
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/quality/facts.d.ts +53 -1
- package/dist/quality/facts.d.ts.map +1 -1
- package/dist/quality/preflight.d.ts.map +1 -1
- package/dist/quality/rules.d.ts +77 -0
- package/dist/quality/rules.d.ts.map +1 -1
- package/dist/templates/blueprints/client-report.docx.blueprint.json +473 -0
- package/dist/templates/themes/consulting.docx.theme.json +1 -0
- package/dist/templates/themes/devportal.docx.theme.json +1 -0
- package/dist/templates/themes/index.d.ts +5 -0
- package/dist/templates/themes/index.d.ts.map +1 -1
- package/dist/templates/themes/minimal.docx.theme.json +1 -0
- package/dist/templates/themes/vermilion.docx.theme.json +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/serviceClient.d.ts +6 -0
- package/dist/utils/serviceClient.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -246,6 +246,7 @@ var init_minimal_docx_theme = __esm({
|
|
|
246
246
|
name: "minimal",
|
|
247
247
|
displayName: "Minimal Clean",
|
|
248
248
|
description: "A quiet, warm minimal theme \u2014 Calibri throughout, sage-green ink on ivory neutrals, a large tracked-tight bold title, wide margins and hairline rules",
|
|
249
|
+
whenToUse: "Quiet documents where the type carries the tone: notes, briefs, drafts and internal papers that want warmth without decoration.",
|
|
249
250
|
version: "4.0.0",
|
|
250
251
|
colors: {
|
|
251
252
|
primary: "#2B302B",
|
|
@@ -435,6 +436,7 @@ var init_devportal_docx_theme = __esm({
|
|
|
435
436
|
name: "devportal",
|
|
436
437
|
displayName: "Field Editorial",
|
|
437
438
|
description: "A compact editorial theme \u2014 Helvetica in near-black ink with a burnt-orange accent, tight condensed titles, letterspaced labels, warm tinted fills and hairline rules",
|
|
439
|
+
whenToUse: "Developer and product documentation, API guides and technical handbooks: dense text, code and tables that need compact, letterspaced structure.",
|
|
438
440
|
version: "4.0.0",
|
|
439
441
|
colors: {
|
|
440
442
|
primary: "#12191F",
|
|
@@ -633,6 +635,7 @@ var init_vermilion_docx_theme = __esm({
|
|
|
633
635
|
name: "vermilion",
|
|
634
636
|
displayName: "Vermilion Editorial",
|
|
635
637
|
description: "Poster-red editorial system \u2014 vermilion display headings, ink text, warm creams and hairline rules",
|
|
638
|
+
whenToUse: "Editorial and annual-report work that wants a strong red display voice: covers, feature-style sections, reports built around a few large headings.",
|
|
636
639
|
version: "1.0.0",
|
|
637
640
|
colors: {
|
|
638
641
|
primary: "#282829",
|
|
@@ -851,6 +854,7 @@ var init_consulting_docx_theme = __esm({
|
|
|
851
854
|
name: "consulting",
|
|
852
855
|
displayName: "Consulting House",
|
|
853
856
|
description: "The house style for client and technical reports \u2014 near-black ink, three greys and one deep-blue accent, Calibri body under Arial headings, hairline rules, no fills behind body text, safe fonts only",
|
|
857
|
+
whenToUse: "Client and technical reports, memos and anything a client will read as the house's own work; the default choice for a report unless the brief names a brand.",
|
|
854
858
|
version: "1.0.0",
|
|
855
859
|
colors: {
|
|
856
860
|
primary: "#1A1F26",
|
|
@@ -5051,6 +5055,10 @@ function resolveServiceUrl(propsUrl, servicesUrl, defaultUrl) {
|
|
|
5051
5055
|
const withScheme = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
5052
5056
|
return withScheme.replace(/\/+$/, "");
|
|
5053
5057
|
}
|
|
5058
|
+
var SERVICE_UNAVAILABLE_CODE = "SERVICE_UNAVAILABLE";
|
|
5059
|
+
function serviceUnavailable(message) {
|
|
5060
|
+
return Object.assign(new Error(message), { code: SERVICE_UNAVAILABLE_CODE });
|
|
5061
|
+
}
|
|
5054
5062
|
async function postJsonToService(opts) {
|
|
5055
5063
|
const resolvedHeaders = typeof opts.headers === "function" ? await opts.headers(opts.body) : opts.headers;
|
|
5056
5064
|
const headers = {
|
|
@@ -5070,12 +5078,12 @@ async function postJsonToService(opts) {
|
|
|
5070
5078
|
});
|
|
5071
5079
|
} catch (error) {
|
|
5072
5080
|
if (error?.name === "AbortError") {
|
|
5073
|
-
throw
|
|
5081
|
+
throw serviceUnavailable(
|
|
5074
5082
|
`${opts.serviceLabel} timed out after ${timeoutMs}ms at ${opts.url}.`
|
|
5075
5083
|
);
|
|
5076
5084
|
}
|
|
5077
5085
|
const cause = error instanceof Error ? error.message : String(error);
|
|
5078
|
-
throw
|
|
5086
|
+
throw serviceUnavailable(opts.onUnreachable(opts.url, cause));
|
|
5079
5087
|
} finally {
|
|
5080
5088
|
clearTimeout(timer);
|
|
5081
5089
|
}
|
|
@@ -11770,6 +11778,7 @@ async function processResolvedDocument(document, resolved, themeName, generation
|
|
|
11770
11778
|
};
|
|
11771
11779
|
}
|
|
11772
11780
|
function createDocumentMetadata(props, generationDate = /* @__PURE__ */ new Date()) {
|
|
11781
|
+
const parsed = props.metadata?.date ? new Date(props.metadata.date) : void 0;
|
|
11773
11782
|
return {
|
|
11774
11783
|
title: props.metadata?.title,
|
|
11775
11784
|
subtitle: props.metadata?.subtitle,
|
|
@@ -11778,7 +11787,7 @@ function createDocumentMetadata(props, generationDate = /* @__PURE__ */ new Date
|
|
|
11778
11787
|
company: props.metadata?.company,
|
|
11779
11788
|
version: props.metadata?.version,
|
|
11780
11789
|
tags: props.metadata?.tags,
|
|
11781
|
-
date:
|
|
11790
|
+
date: parsed && !Number.isNaN(parsed.getTime()) ? parsed : generationDate
|
|
11782
11791
|
};
|
|
11783
11792
|
}
|
|
11784
11793
|
async function extractSections(components, context) {
|
|
@@ -11870,7 +11879,12 @@ import {
|
|
|
11870
11879
|
normalizeHighchartsChart
|
|
11871
11880
|
} from "@json-to-office/quality";
|
|
11872
11881
|
import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
|
|
11873
|
-
import {
|
|
11882
|
+
import {
|
|
11883
|
+
designCanvas as designCanvas2,
|
|
11884
|
+
designColors as designColors2,
|
|
11885
|
+
resolveDesignColor as resolveDesignColor2,
|
|
11886
|
+
typeScaleSizes
|
|
11887
|
+
} from "@json-to-office/shared";
|
|
11874
11888
|
|
|
11875
11889
|
// src/core/generationContext.ts
|
|
11876
11890
|
import { applyExportMode } from "@json-to-office/shared";
|
|
@@ -12809,6 +12823,93 @@ function walkActive(node, path4, page, visit) {
|
|
|
12809
12823
|
(child, index) => walkActive(child, `${path4}/children/${index}`, page, visit)
|
|
12810
12824
|
);
|
|
12811
12825
|
}
|
|
12826
|
+
function textSizeFact(node, props, path4, typography, role) {
|
|
12827
|
+
const size = effectiveFontSize(node, props, typography);
|
|
12828
|
+
if (size === void 0) return void 0;
|
|
12829
|
+
return {
|
|
12830
|
+
id: `docx:text-size:${path4}`,
|
|
12831
|
+
kind: "docx/text-size",
|
|
12832
|
+
path: path4,
|
|
12833
|
+
role: role ?? styleKey(node, props),
|
|
12834
|
+
fontSizePt: size.fontSizePt,
|
|
12835
|
+
authored: size.authored,
|
|
12836
|
+
...size.authored && { sizePath: `${path4}/props/font/size` }
|
|
12837
|
+
};
|
|
12838
|
+
}
|
|
12839
|
+
function tableTextSizeFacts(props, authored, path4, typography, page) {
|
|
12840
|
+
const facts = [];
|
|
12841
|
+
const sizeOf = (holder) => finiteNumber(asRecord(asRecord(holder)?.font)?.size);
|
|
12842
|
+
const authoredSize = (holder, pointer, role) => {
|
|
12843
|
+
const size = sizeOf(holder);
|
|
12844
|
+
if (size === void 0) return;
|
|
12845
|
+
facts.push({
|
|
12846
|
+
id: `docx:text-size:${pointer}`,
|
|
12847
|
+
kind: "docx/text-size",
|
|
12848
|
+
path: pointer,
|
|
12849
|
+
role,
|
|
12850
|
+
fontSizePt: size,
|
|
12851
|
+
authored: true,
|
|
12852
|
+
sizePath: `${pointer}/font/size`
|
|
12853
|
+
});
|
|
12854
|
+
};
|
|
12855
|
+
const cellContent = (holder, pointer, role) => {
|
|
12856
|
+
const content = asRecord(holder)?.content;
|
|
12857
|
+
if (asRecord(content) === void 0) return;
|
|
12858
|
+
walkActive(content, `${pointer}/content`, page, (node, nodePath) => {
|
|
12859
|
+
if (node.name !== "paragraph" && node.name !== "heading") return;
|
|
12860
|
+
const nodeProps = asRecord(node.props) ?? {};
|
|
12861
|
+
if (typeof nodeProps.text !== "string" || nodeProps.text.trim() === "")
|
|
12862
|
+
return;
|
|
12863
|
+
const fact = textSizeFact(node, nodeProps, nodePath, typography, role);
|
|
12864
|
+
if (fact) facts.push(fact);
|
|
12865
|
+
});
|
|
12866
|
+
};
|
|
12867
|
+
if (authored) {
|
|
12868
|
+
authoredSize(
|
|
12869
|
+
authored.cellDefaults,
|
|
12870
|
+
`${path4}/props/cellDefaults`,
|
|
12871
|
+
"tableCell"
|
|
12872
|
+
);
|
|
12873
|
+
authoredSize(
|
|
12874
|
+
authored.headerCellDefaults,
|
|
12875
|
+
`${path4}/props/headerCellDefaults`,
|
|
12876
|
+
"tableHeader"
|
|
12877
|
+
);
|
|
12878
|
+
const columns = Array.isArray(authored.columns) ? authored.columns : [];
|
|
12879
|
+
columns.forEach((column, index) => {
|
|
12880
|
+
const record = asRecord(column) ?? {};
|
|
12881
|
+
const columnPath = `${path4}/props/columns/${index}`;
|
|
12882
|
+
authoredSize(
|
|
12883
|
+
record.cellDefaults,
|
|
12884
|
+
`${columnPath}/cellDefaults`,
|
|
12885
|
+
"tableCell"
|
|
12886
|
+
);
|
|
12887
|
+
authoredSize(record.header, `${columnPath}/header`, "tableHeader");
|
|
12888
|
+
cellContent(record.header, `${columnPath}/header`, "tableHeader");
|
|
12889
|
+
const cells = Array.isArray(record.cells) ? record.cells : [];
|
|
12890
|
+
cells.forEach((cell, cellIndex) => {
|
|
12891
|
+
authoredSize(cell, `${columnPath}/cells/${cellIndex}`, "tableCell");
|
|
12892
|
+
cellContent(cell, `${columnPath}/cells/${cellIndex}`, "tableCell");
|
|
12893
|
+
});
|
|
12894
|
+
});
|
|
12895
|
+
}
|
|
12896
|
+
for (const [key, role] of [
|
|
12897
|
+
["cellDefaults", "tableCell"],
|
|
12898
|
+
["headerCellDefaults", "tableHeader"]
|
|
12899
|
+
]) {
|
|
12900
|
+
const size = sizeOf(props[key]);
|
|
12901
|
+
if (size === void 0) continue;
|
|
12902
|
+
facts.push({
|
|
12903
|
+
id: `docx:text-size:${path4}:${role}`,
|
|
12904
|
+
kind: "docx/text-size",
|
|
12905
|
+
path: path4,
|
|
12906
|
+
role,
|
|
12907
|
+
fontSizePt: size,
|
|
12908
|
+
authored: false
|
|
12909
|
+
});
|
|
12910
|
+
}
|
|
12911
|
+
return facts;
|
|
12912
|
+
}
|
|
12812
12913
|
function prepareDocxQualityDocument(document, options = {}) {
|
|
12813
12914
|
const themed = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
|
|
12814
12915
|
customThemes: options.customThemes,
|
|
@@ -12856,6 +12957,75 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12856
12957
|
...budget
|
|
12857
12958
|
});
|
|
12858
12959
|
}
|
|
12960
|
+
for (const role of blockSlotRoles2(themed.document, expanded.blocks)) {
|
|
12961
|
+
addFact({
|
|
12962
|
+
id: `docx:chrome-slot:${role.path}`,
|
|
12963
|
+
kind: "docx/chrome-slot",
|
|
12964
|
+
path: role.path,
|
|
12965
|
+
relatedPaths: [role.invocation],
|
|
12966
|
+
block: role.block,
|
|
12967
|
+
slot: role.slot,
|
|
12968
|
+
role: role.role,
|
|
12969
|
+
present: slotIsFilled(role.value),
|
|
12970
|
+
invocation: role.invocation
|
|
12971
|
+
});
|
|
12972
|
+
}
|
|
12973
|
+
const topLevel = Array.isArray(context.document.children) ? context.document.children : [];
|
|
12974
|
+
let inherited = {};
|
|
12975
|
+
topLevel.forEach((node, index) => {
|
|
12976
|
+
if (node?.name !== "section") return;
|
|
12977
|
+
const props = asRecord(node.props) ?? {};
|
|
12978
|
+
const part = (kind) => props[kind] === "linkToPrevious" ? inherited[kind] : props[kind];
|
|
12979
|
+
const header = part("header");
|
|
12980
|
+
const footer = part("footer");
|
|
12981
|
+
inherited = { header, footer };
|
|
12982
|
+
for (const kind of ["header", "footer"]) {
|
|
12983
|
+
if (!Array.isArray(props[kind])) continue;
|
|
12984
|
+
const drawnByBlock = !Array.isArray(
|
|
12985
|
+
asRecord(
|
|
12986
|
+
asRecord(
|
|
12987
|
+
(Array.isArray(themed.document.children) ? themed.document.children : [])[index]
|
|
12988
|
+
)?.props
|
|
12989
|
+
)?.[kind]
|
|
12990
|
+
);
|
|
12991
|
+
const part2 = props[kind];
|
|
12992
|
+
const visitChrome = (child, childPath) => {
|
|
12993
|
+
if (child.name !== "paragraph" && child.name !== "heading") return;
|
|
12994
|
+
const childProps = asRecord(child.props) ?? {};
|
|
12995
|
+
if (typeof childProps.text !== "string" || childProps.text.trim() === "")
|
|
12996
|
+
return;
|
|
12997
|
+
const fact = textSizeFact(
|
|
12998
|
+
child,
|
|
12999
|
+
childProps,
|
|
13000
|
+
childPath,
|
|
13001
|
+
typography,
|
|
13002
|
+
kind
|
|
13003
|
+
);
|
|
13004
|
+
if (fact)
|
|
13005
|
+
addFact({
|
|
13006
|
+
...fact,
|
|
13007
|
+
generated: drawnByBlock || authoredPath(childPath) !== childPath
|
|
13008
|
+
});
|
|
13009
|
+
};
|
|
13010
|
+
part2.forEach(
|
|
13011
|
+
(child, childIndex) => walkActive(
|
|
13012
|
+
child,
|
|
13013
|
+
`/children/${index}/props/${kind}/${childIndex}`,
|
|
13014
|
+
basePage,
|
|
13015
|
+
visitChrome
|
|
13016
|
+
)
|
|
13017
|
+
);
|
|
13018
|
+
}
|
|
13019
|
+
addFact({
|
|
13020
|
+
id: `docx:section-chrome:${index}`,
|
|
13021
|
+
kind: "docx/section-chrome",
|
|
13022
|
+
path: `/children/${index}`,
|
|
13023
|
+
index,
|
|
13024
|
+
header: partPresent(header),
|
|
13025
|
+
footer: partPresent(footer),
|
|
13026
|
+
pageNumber: hasPageField(header) || hasPageField(footer)
|
|
13027
|
+
});
|
|
13028
|
+
});
|
|
12859
13029
|
const paletteHexes = {};
|
|
12860
13030
|
const visualColors = designColors2(
|
|
12861
13031
|
resolved.theme.colors,
|
|
@@ -12879,12 +13049,34 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12879
13049
|
(value) => `#${resolveDesignColor2(value, visualColors)}`
|
|
12880
13050
|
) ?? SERIES_COLOR_TOKENS.filter((token) => paletteHexes[token] !== void 0);
|
|
12881
13051
|
const authoredPropsAt = (pointer) => asRecord(asRecord(nodeAtPointer(context.document, pointer))?.props);
|
|
13052
|
+
const roleSizesPt = {};
|
|
13053
|
+
for (const key of Object.keys(typography.styles)) {
|
|
13054
|
+
const size = effectiveFontSize(
|
|
13055
|
+
{ name: "paragraph" },
|
|
13056
|
+
{ themeStyle: key },
|
|
13057
|
+
typography
|
|
13058
|
+
);
|
|
13059
|
+
if (size) roleSizesPt[key] = size.fontSizePt;
|
|
13060
|
+
}
|
|
13061
|
+
const scale = resolved.theme.typography?.scale?.[designCanvas2("docx", resolved.theme.page?.size)];
|
|
13062
|
+
const typeScalePt = [
|
|
13063
|
+
.../* @__PURE__ */ new Set([
|
|
13064
|
+
...Object.values(roleSizesPt),
|
|
13065
|
+
...["heading", "body", "mono", "light"].flatMap((role) => {
|
|
13066
|
+
const size = resolveFontSize(resolved.theme, role);
|
|
13067
|
+
return size === void 0 ? [] : [size];
|
|
13068
|
+
}),
|
|
13069
|
+
...scale ? typeScaleSizes(scale) : []
|
|
13070
|
+
])
|
|
13071
|
+
].sort((a, b) => a - b);
|
|
12882
13072
|
addFact({
|
|
12883
13073
|
id: "docx:theme",
|
|
12884
13074
|
kind: "docx/theme",
|
|
12885
13075
|
path: "/props",
|
|
12886
13076
|
themeName: context.themeName,
|
|
12887
13077
|
paletteHexes,
|
|
13078
|
+
typeScalePt,
|
|
13079
|
+
roleSizesPt,
|
|
12888
13080
|
// `heading` and `body` only. A theme also names `mono` and `light`, but
|
|
12889
13081
|
// those paint nothing until a component asks for them — counting an
|
|
12890
13082
|
// unused `Courier New` against a document's family budget would flag a
|
|
@@ -12937,6 +13129,15 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12937
13129
|
if (node.name === "table") {
|
|
12938
13130
|
const fact = tableFact(props, path4, page.availableWidthTwips);
|
|
12939
13131
|
if (fact) addFact(fact);
|
|
13132
|
+
for (const size of tableTextSizeFacts(
|
|
13133
|
+
props,
|
|
13134
|
+
authoredPropsAt(path4),
|
|
13135
|
+
path4,
|
|
13136
|
+
typography,
|
|
13137
|
+
page
|
|
13138
|
+
)) {
|
|
13139
|
+
addFact({ ...size, generated: authoredPath(size.path) !== size.path });
|
|
13140
|
+
}
|
|
12940
13141
|
const design = tableDesignFact(
|
|
12941
13142
|
props,
|
|
12942
13143
|
path4,
|
|
@@ -12995,6 +13196,10 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12995
13196
|
if (node.name === "paragraph" || node.name === "heading") {
|
|
12996
13197
|
const fact = lineBoxFact(node, props, path4, typography, context.document);
|
|
12997
13198
|
if (fact) addFact(fact);
|
|
13199
|
+
if (typeof props.text === "string" && props.text.trim() !== "") {
|
|
13200
|
+
const fact2 = textSizeFact(node, props, path4, typography);
|
|
13201
|
+
if (fact2) addFact({ ...fact2, generated: authoredPath(path4) !== path4 });
|
|
13202
|
+
}
|
|
12998
13203
|
}
|
|
12999
13204
|
if (node.name === "image" || node.name === "visual") {
|
|
13000
13205
|
for (const fact of svgTextFacts(props, path4)) addFact(fact);
|
|
@@ -13050,6 +13255,17 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13050
13255
|
}
|
|
13051
13256
|
};
|
|
13052
13257
|
}
|
|
13258
|
+
function slotIsFilled(value) {
|
|
13259
|
+
if (typeof value === "string") return value.trim() !== "";
|
|
13260
|
+
return value !== void 0 && value !== null && value !== false && (!Array.isArray(value) || value.length > 0);
|
|
13261
|
+
}
|
|
13262
|
+
function partPresent(part) {
|
|
13263
|
+
return Array.isArray(part) && part.length > 0;
|
|
13264
|
+
}
|
|
13265
|
+
var PAGE_FIELD = /\{(PAGE|PAGE_NUMBER|TOTAL_PAGES|NUMPAGES)\}/;
|
|
13266
|
+
function hasPageField(part) {
|
|
13267
|
+
return Array.isArray(part) && PAGE_FIELD.test(JSON.stringify(part));
|
|
13268
|
+
}
|
|
13053
13269
|
|
|
13054
13270
|
// src/core/generateFromIr.ts
|
|
13055
13271
|
var UncompiledComponentError = class extends Error {
|
|
@@ -13556,6 +13772,7 @@ function numberParameter(parameters, name, fallback) {
|
|
|
13556
13772
|
}
|
|
13557
13773
|
var docxTableWidthRule = {
|
|
13558
13774
|
id: "docx/table-width",
|
|
13775
|
+
description: "Explicit column widths that sum past the usable width of their section.",
|
|
13559
13776
|
code: QUALITY_CODES.TABLE_WIDTH_OVERFLOW,
|
|
13560
13777
|
category: "integrity",
|
|
13561
13778
|
defaultSeverity: "warning",
|
|
@@ -13603,6 +13820,7 @@ var docxTableWidthRule = {
|
|
|
13603
13820
|
};
|
|
13604
13821
|
var docxHeadingHierarchyRule = {
|
|
13605
13822
|
id: "docx/heading-hierarchy",
|
|
13823
|
+
description: "A heading that skips a level and breaks the outline.",
|
|
13606
13824
|
code: QUALITY_CODES.HEADING_SKIP,
|
|
13607
13825
|
category: "hierarchy",
|
|
13608
13826
|
defaultSeverity: "info",
|
|
@@ -13646,6 +13864,7 @@ function frameTextFacts(facts) {
|
|
|
13646
13864
|
}
|
|
13647
13865
|
var docxTextFitRule = {
|
|
13648
13866
|
id: "docx/text-fit",
|
|
13867
|
+
description: "A word too wide for its floating frame, or a frame whose wrapped text runs off the sheet.",
|
|
13649
13868
|
code: QUALITY_CODES.TEXT_OVERFLOW,
|
|
13650
13869
|
category: "integrity",
|
|
13651
13870
|
defaultSeverity: "warning",
|
|
@@ -13719,6 +13938,7 @@ var docxTextFitRule = {
|
|
|
13719
13938
|
var FRAME_COLLISION_MIN_WIDTH_TWIPS = 240;
|
|
13720
13939
|
var docxFrameCollisionRule = {
|
|
13721
13940
|
id: "docx/frame-collision",
|
|
13941
|
+
description: "Two page-anchored frames whose estimated text lands on the same region of a page.",
|
|
13722
13942
|
code: QUALITY_CODES.FRAME_COLLISION,
|
|
13723
13943
|
category: "integrity",
|
|
13724
13944
|
defaultSeverity: "warning",
|
|
@@ -13815,6 +14035,7 @@ var docxFrameCollisionRule = {
|
|
|
13815
14035
|
};
|
|
13816
14036
|
var docxSvgTextBoundsRule = {
|
|
13817
14037
|
id: "docx/svg-text-bounds",
|
|
14038
|
+
description: "A text baseline outside an inline SVG\u2019s viewBox, so the words are never painted.",
|
|
13818
14039
|
code: QUALITY_CODES.SVG_TEXT_CLIPPED,
|
|
13819
14040
|
category: "integrity",
|
|
13820
14041
|
defaultSeverity: "warning",
|
|
@@ -13850,6 +14071,7 @@ function tenths(value) {
|
|
|
13850
14071
|
}
|
|
13851
14072
|
var docxLineBoxRule = {
|
|
13852
14073
|
id: "docx/line-box",
|
|
14074
|
+
description: "An `exactly` line box shorter than the capitals it has to hold.",
|
|
13853
14075
|
code: QUALITY_CODES.LINE_BOX_COLLAPSE,
|
|
13854
14076
|
category: "legibility",
|
|
13855
14077
|
defaultSeverity: "warning",
|
|
@@ -13903,6 +14125,7 @@ var docxLineBoxRule = {
|
|
|
13903
14125
|
};
|
|
13904
14126
|
var docxPlaceholderRule = {
|
|
13905
14127
|
id: "docx/placeholder-text",
|
|
14128
|
+
description: "An unfilled scaffold slot, or leftover filler copy.",
|
|
13906
14129
|
code: QUALITY_CODES.PLACEHOLDER_TEXT,
|
|
13907
14130
|
category: "integrity",
|
|
13908
14131
|
defaultSeverity: "warning",
|
|
@@ -13921,6 +14144,7 @@ var docxPlaceholderRule = {
|
|
|
13921
14144
|
};
|
|
13922
14145
|
var docxSlotBudgetRule = {
|
|
13923
14146
|
id: "docx/slot-budget",
|
|
14147
|
+
description: "A block slot holding more words than its budget allows \u2014 a takeaway past the word count the block sets.",
|
|
13924
14148
|
code: QUALITY_CODES.SLOT_BUDGET,
|
|
13925
14149
|
category: "composition",
|
|
13926
14150
|
defaultSeverity: "warning",
|
|
@@ -13944,6 +14168,7 @@ var docxSlotBudgetRule = {
|
|
|
13944
14168
|
var DEFAULT_MAX_FONT_FAMILIES = 3;
|
|
13945
14169
|
var docxFontCountRule = {
|
|
13946
14170
|
id: "docx/font-count",
|
|
14171
|
+
description: "Distinct font families the document can paint.",
|
|
13947
14172
|
code: QUALITY_CODES.FONT_COUNT,
|
|
13948
14173
|
category: "brand",
|
|
13949
14174
|
defaultSeverity: "warning",
|
|
@@ -13980,6 +14205,7 @@ var docxFontCountRule = {
|
|
|
13980
14205
|
};
|
|
13981
14206
|
var docxPaletteRule = {
|
|
13982
14207
|
id: "docx/palette-adherence",
|
|
14208
|
+
description: "A literal colour the resolved theme does not define.",
|
|
13983
14209
|
code: QUALITY_CODES.OFF_PALETTE,
|
|
13984
14210
|
category: "brand",
|
|
13985
14211
|
defaultSeverity: "info",
|
|
@@ -14005,6 +14231,7 @@ var docxPaletteRule = {
|
|
|
14005
14231
|
var DEFAULT_MAX_TABLE_ROWS_PER_PAGE = 25;
|
|
14006
14232
|
var docxChartRule = {
|
|
14007
14233
|
id: "docx/chart-design",
|
|
14234
|
+
description: "What a chart claims about its numbers: the comparison, the palette, the unit and the caption.",
|
|
14008
14235
|
code: QUALITY_CODES.CHART_OVERLOADED,
|
|
14009
14236
|
category: "information-design",
|
|
14010
14237
|
defaultSeverity: "warning",
|
|
@@ -14046,6 +14273,7 @@ function seriesColorFix(fact) {
|
|
|
14046
14273
|
}
|
|
14047
14274
|
var docxTableDesignRule = {
|
|
14048
14275
|
id: "docx/table-design",
|
|
14276
|
+
description: "How a table lays its numbers out: alignment, rounding, rules and length.",
|
|
14049
14277
|
code: QUALITY_CODES.TABLE_NUMERIC_ALIGN,
|
|
14050
14278
|
category: "information-design",
|
|
14051
14279
|
defaultSeverity: "warning",
|
|
@@ -14097,6 +14325,233 @@ function alignColumnRight(column) {
|
|
|
14097
14325
|
}
|
|
14098
14326
|
return operations;
|
|
14099
14327
|
}
|
|
14328
|
+
function stringListParameter(parameters, name) {
|
|
14329
|
+
const value = parameters[name];
|
|
14330
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
14331
|
+
}
|
|
14332
|
+
var docxRequiredChromeRule = {
|
|
14333
|
+
id: "docx/required-chrome",
|
|
14334
|
+
description: "A block slot with a role a profile or policy requires \u2014 a takeaway, a source \u2014 left empty. Off until one names roles.",
|
|
14335
|
+
code: QUALITY_CODES.CHROME_MISSING,
|
|
14336
|
+
category: "consistency",
|
|
14337
|
+
defaultSeverity: "warning",
|
|
14338
|
+
defaultCertainty: "deterministic",
|
|
14339
|
+
formats: ["docx"],
|
|
14340
|
+
defaultParameters: { required: [] },
|
|
14341
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
14342
|
+
const required = stringListParameter(configuration.parameters, "required");
|
|
14343
|
+
if (required.length === 0) return [];
|
|
14344
|
+
return facts.filter(
|
|
14345
|
+
(fact) => fact.kind === "docx/chrome-slot"
|
|
14346
|
+
).filter((fact) => required.includes(fact.role) && !fact.present).map((fact) => ({
|
|
14347
|
+
path: fact.path,
|
|
14348
|
+
relatedPaths: [fact.invocation],
|
|
14349
|
+
message: `${fact.block} states no ${fact.role} in its "${fact.slot}" slot; the ${profile?.id ?? "selected"} profile expects one on every ${fact.block}.`,
|
|
14350
|
+
suggestion: `Fill the "${fact.slot}" slot. The theme already styles it.`,
|
|
14351
|
+
context: { block: fact.block, slot: fact.slot, role: fact.role }
|
|
14352
|
+
}));
|
|
14353
|
+
}
|
|
14354
|
+
};
|
|
14355
|
+
var SECTION_CHROME_PARTS = ["header", "footer", "pageNumber"];
|
|
14356
|
+
var docxRunningHeadRule = {
|
|
14357
|
+
id: "docx/running-head",
|
|
14358
|
+
description: "A body section without the running head a profile or policy expects. Off until one names parts.",
|
|
14359
|
+
code: QUALITY_CODES.CHROME_MISSING,
|
|
14360
|
+
category: "consistency",
|
|
14361
|
+
defaultSeverity: "warning",
|
|
14362
|
+
defaultCertainty: "deterministic",
|
|
14363
|
+
formats: ["docx"],
|
|
14364
|
+
defaultParameters: { required: [], fromSection: 1 },
|
|
14365
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
14366
|
+
const required = stringListParameter(
|
|
14367
|
+
configuration.parameters,
|
|
14368
|
+
"required"
|
|
14369
|
+
).filter(
|
|
14370
|
+
(part) => SECTION_CHROME_PARTS.includes(part)
|
|
14371
|
+
);
|
|
14372
|
+
if (required.length === 0) return [];
|
|
14373
|
+
const from = numberParameter(configuration.parameters, "fromSection", 1);
|
|
14374
|
+
return facts.filter(
|
|
14375
|
+
(fact) => fact.kind === "docx/section-chrome" && fact.index >= from
|
|
14376
|
+
).flatMap((fact) => {
|
|
14377
|
+
const missing = required.filter((part) => !fact[part]);
|
|
14378
|
+
if (missing.length === 0) return [];
|
|
14379
|
+
const parts = missing.map((part) => part === "pageNumber" ? "page-number field" : part).join(", ");
|
|
14380
|
+
return [
|
|
14381
|
+
{
|
|
14382
|
+
path: fact.path,
|
|
14383
|
+
message: `Section ${fact.index + 1} carries no ${parts}; the ${profile?.id ?? "selected"} profile expects a running head on every section after the cover.`,
|
|
14384
|
+
suggestion: "Invoke a running-head block at the top of the first body section: its section effect fills every later section with the tracker and n / N.",
|
|
14385
|
+
context: { section: fact.index, missing }
|
|
14386
|
+
}
|
|
14387
|
+
];
|
|
14388
|
+
});
|
|
14389
|
+
}
|
|
14390
|
+
};
|
|
14391
|
+
var SIZE_TOLERANCE_PT = 0.25;
|
|
14392
|
+
function themeFact(facts) {
|
|
14393
|
+
return facts.find(
|
|
14394
|
+
(fact) => fact.kind === "docx/theme"
|
|
14395
|
+
);
|
|
14396
|
+
}
|
|
14397
|
+
function textSizeFacts(facts) {
|
|
14398
|
+
return facts.filter(
|
|
14399
|
+
(fact) => fact.kind === "docx/text-size"
|
|
14400
|
+
);
|
|
14401
|
+
}
|
|
14402
|
+
function roleDriftFacts(facts, theme) {
|
|
14403
|
+
const byRole = /* @__PURE__ */ new Map();
|
|
14404
|
+
for (const fact of textSizeFacts(facts)) {
|
|
14405
|
+
byRole.set(fact.role, [...byRole.get(fact.role) ?? [], fact]);
|
|
14406
|
+
}
|
|
14407
|
+
const drifting = /* @__PURE__ */ new Map();
|
|
14408
|
+
for (const [role, members] of byRole) {
|
|
14409
|
+
const sizes = [...new Set(members.map((fact) => fact.fontSizePt))].sort(
|
|
14410
|
+
(a, b) => a - b
|
|
14411
|
+
);
|
|
14412
|
+
if (sizes.length < 2) continue;
|
|
14413
|
+
const expected = theme?.roleSizesPt[role];
|
|
14414
|
+
if (expected === void 0) continue;
|
|
14415
|
+
for (const fact of members) {
|
|
14416
|
+
if (fact.sizePath !== void 0 && !fact.generated && Math.abs(fact.fontSizePt - expected) > SIZE_TOLERANCE_PT)
|
|
14417
|
+
drifting.set(fact, { expected, sizes });
|
|
14418
|
+
}
|
|
14419
|
+
}
|
|
14420
|
+
return drifting;
|
|
14421
|
+
}
|
|
14422
|
+
function nearestSize(size, scale) {
|
|
14423
|
+
let best;
|
|
14424
|
+
for (const candidate of scale) {
|
|
14425
|
+
if (best === void 0 || Math.abs(candidate - size) < Math.abs(best - size))
|
|
14426
|
+
best = candidate;
|
|
14427
|
+
}
|
|
14428
|
+
return best;
|
|
14429
|
+
}
|
|
14430
|
+
var docxTypeScaleRule = {
|
|
14431
|
+
id: "docx/type-scale",
|
|
14432
|
+
description: "An authored size the theme never paints: not a style, not a font role, not a step of its scale. Off until a profile or policy enables it.",
|
|
14433
|
+
code: QUALITY_CODES.TYPE_OFF_SCALE,
|
|
14434
|
+
category: "consistency",
|
|
14435
|
+
defaultSeverity: "warning",
|
|
14436
|
+
defaultCertainty: "deterministic",
|
|
14437
|
+
formats: ["docx"],
|
|
14438
|
+
defaultEnabled: false,
|
|
14439
|
+
evaluate: ({ facts }) => {
|
|
14440
|
+
const theme = themeFact(facts);
|
|
14441
|
+
const scale = theme?.typeScalePt ?? [];
|
|
14442
|
+
if (scale.length === 0) return [];
|
|
14443
|
+
const drifting = roleDriftFacts(facts, theme);
|
|
14444
|
+
const offScale = textSizeFacts(facts).filter(
|
|
14445
|
+
(fact) => fact.sizePath !== void 0 && !fact.generated && !drifting.has(fact) && !scale.some(
|
|
14446
|
+
(size) => Math.abs(size - fact.fontSizePt) <= SIZE_TOLERANCE_PT
|
|
14447
|
+
)
|
|
14448
|
+
);
|
|
14449
|
+
const groups = /* @__PURE__ */ new Map();
|
|
14450
|
+
for (const fact of offScale) {
|
|
14451
|
+
const key = `${fact.role}@${fact.fontSizePt}`;
|
|
14452
|
+
groups.set(key, [...groups.get(key) ?? [], fact]);
|
|
14453
|
+
}
|
|
14454
|
+
return [...groups.values()].map((members) => {
|
|
14455
|
+
const [first] = members;
|
|
14456
|
+
const nearest = nearestSize(first.fontSizePt, scale);
|
|
14457
|
+
const sizePaths = members.map((fact) => fact.sizePath);
|
|
14458
|
+
const count = members.length === 1 ? "" : ` (${members.length} places, patched together)`;
|
|
14459
|
+
return {
|
|
14460
|
+
path: sizePaths[0],
|
|
14461
|
+
...sizePaths.length > 1 && { relatedPaths: sizePaths.slice(1) },
|
|
14462
|
+
message: `${first.fontSizePt}pt is not a size the ${theme.themeName} theme paints; the nearest on its scale is ${nearest}pt${count}.`,
|
|
14463
|
+
suggestion: `Use ${nearest}pt, or drop the size and let the "${first.role}" style set it.`,
|
|
14464
|
+
context: { role: first.role, scale, paths: sizePaths },
|
|
14465
|
+
evidence: {
|
|
14466
|
+
actual: first.fontSizePt,
|
|
14467
|
+
expected: nearest,
|
|
14468
|
+
unit: "pt",
|
|
14469
|
+
values: { source: "theme" }
|
|
14470
|
+
},
|
|
14471
|
+
fixes: sizePaths.map((path4) => ({
|
|
14472
|
+
op: "replace",
|
|
14473
|
+
path: path4,
|
|
14474
|
+
value: nearest
|
|
14475
|
+
}))
|
|
14476
|
+
};
|
|
14477
|
+
});
|
|
14478
|
+
}
|
|
14479
|
+
};
|
|
14480
|
+
var docxSizeCountRule = {
|
|
14481
|
+
id: "docx/size-count",
|
|
14482
|
+
description: "More distinct text sizes than maximumSizes allows, blocks included. Off until a profile or policy enables it.",
|
|
14483
|
+
code: QUALITY_CODES.TYPE_SIZE_COUNT,
|
|
14484
|
+
category: "consistency",
|
|
14485
|
+
defaultSeverity: "warning",
|
|
14486
|
+
defaultCertainty: "deterministic",
|
|
14487
|
+
formats: ["docx"],
|
|
14488
|
+
defaultEnabled: false,
|
|
14489
|
+
defaultParameters: { maximumSizes: 8 },
|
|
14490
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
14491
|
+
const maximum = numberParameter(
|
|
14492
|
+
configuration.parameters,
|
|
14493
|
+
"maximumSizes",
|
|
14494
|
+
8
|
|
14495
|
+
);
|
|
14496
|
+
const firstPathBySize = /* @__PURE__ */ new Map();
|
|
14497
|
+
for (const fact of textSizeFacts(facts)) {
|
|
14498
|
+
const size = Math.round(fact.fontSizePt * 4) / 4;
|
|
14499
|
+
if (!firstPathBySize.has(size)) firstPathBySize.set(size, fact.path);
|
|
14500
|
+
}
|
|
14501
|
+
if (firstPathBySize.size <= maximum) return [];
|
|
14502
|
+
const sizes = [...firstPathBySize.keys()].sort((a, b) => a - b);
|
|
14503
|
+
return [
|
|
14504
|
+
{
|
|
14505
|
+
path: themeFact(facts)?.path ?? "/props",
|
|
14506
|
+
relatedPaths: sizes.map((size) => firstPathBySize.get(size)),
|
|
14507
|
+
message: `The document paints ${sizes.length} distinct text sizes (${sizes.join(", ")}pt); the ${profile?.id ?? "selected"} profile allows ${maximum}.`,
|
|
14508
|
+
suggestion: "Keep to the theme styles \u2014 title, headings, body, label, source \u2014 and drop the ad-hoc sizes.",
|
|
14509
|
+
context: { sizes, maximum },
|
|
14510
|
+
evidence: {
|
|
14511
|
+
actual: sizes.length,
|
|
14512
|
+
expected: maximum,
|
|
14513
|
+
values: { source: "profile" }
|
|
14514
|
+
}
|
|
14515
|
+
}
|
|
14516
|
+
];
|
|
14517
|
+
}
|
|
14518
|
+
};
|
|
14519
|
+
var docxRoleDriftRule = {
|
|
14520
|
+
id: "docx/role-drift",
|
|
14521
|
+
description: "One heading level, table role or paragraph style painted at two sizes; the theme size is the fix. Off until a profile or policy enables it.",
|
|
14522
|
+
code: QUALITY_CODES.TYPE_ROLE_DRIFT,
|
|
14523
|
+
category: "consistency",
|
|
14524
|
+
defaultSeverity: "warning",
|
|
14525
|
+
defaultCertainty: "deterministic",
|
|
14526
|
+
formats: ["docx"],
|
|
14527
|
+
defaultEnabled: false,
|
|
14528
|
+
evaluate: ({ facts }) => {
|
|
14529
|
+
const theme = themeFact(facts);
|
|
14530
|
+
const findings = [];
|
|
14531
|
+
for (const [fact, { expected, sizes }] of roleDriftFacts(facts, theme)) {
|
|
14532
|
+
const keeper = textSizeFacts(facts).find(
|
|
14533
|
+
(member) => member.role === fact.role && Math.abs(member.fontSizePt - expected) <= SIZE_TOLERANCE_PT
|
|
14534
|
+
);
|
|
14535
|
+
const others = sizes.filter((size) => size !== fact.fontSizePt);
|
|
14536
|
+
const sizePath = fact.sizePath;
|
|
14537
|
+
findings.push({
|
|
14538
|
+
path: sizePath,
|
|
14539
|
+
...keeper && { relatedPaths: [keeper.path] },
|
|
14540
|
+
message: `"${fact.role}" is painted at ${fact.fontSizePt}pt here and at ${others.join("pt, ")}pt elsewhere; the theme sets it at ${expected}pt.`,
|
|
14541
|
+
suggestion: `Drop the size so "${fact.role}" paints at the theme's ${expected}pt.`,
|
|
14542
|
+
context: { role: fact.role, sizes },
|
|
14543
|
+
evidence: {
|
|
14544
|
+
actual: fact.fontSizePt,
|
|
14545
|
+
expected,
|
|
14546
|
+
unit: "pt",
|
|
14547
|
+
values: { role: fact.role, source: "theme" }
|
|
14548
|
+
},
|
|
14549
|
+
fixes: [{ op: "replace", path: sizePath, value: expected }]
|
|
14550
|
+
});
|
|
14551
|
+
}
|
|
14552
|
+
return findings;
|
|
14553
|
+
}
|
|
14554
|
+
};
|
|
14100
14555
|
var DOCX_QUALITY_RULES = {
|
|
14101
14556
|
id: "docx/default",
|
|
14102
14557
|
rules: [
|
|
@@ -14111,10 +14566,35 @@ var DOCX_QUALITY_RULES = {
|
|
|
14111
14566
|
docxChartRule,
|
|
14112
14567
|
docxTableDesignRule,
|
|
14113
14568
|
docxFontCountRule,
|
|
14114
|
-
docxPaletteRule
|
|
14569
|
+
docxPaletteRule,
|
|
14570
|
+
docxRequiredChromeRule,
|
|
14571
|
+
docxRunningHeadRule,
|
|
14572
|
+
docxTypeScaleRule,
|
|
14573
|
+
docxSizeCountRule,
|
|
14574
|
+
docxRoleDriftRule
|
|
14115
14575
|
]
|
|
14116
14576
|
};
|
|
14117
14577
|
var DOCX_QUALITY_PROFILES = {
|
|
14578
|
+
"client-report": {
|
|
14579
|
+
id: "client-report",
|
|
14580
|
+
formats: ["docx"],
|
|
14581
|
+
description: "Client or public-administration report: a running head with page numbers on every section after the cover, a takeaway and a source wherever a block declares them, no heading skipped, and every size on the theme scale with at most eight in play.",
|
|
14582
|
+
rules: {
|
|
14583
|
+
"docx/required-chrome": {
|
|
14584
|
+
parameters: { required: ["takeaway", "source"] }
|
|
14585
|
+
},
|
|
14586
|
+
"docx/running-head": {
|
|
14587
|
+
parameters: {
|
|
14588
|
+
required: ["header", "footer", "pageNumber"],
|
|
14589
|
+
fromSection: 1
|
|
14590
|
+
}
|
|
14591
|
+
},
|
|
14592
|
+
"docx/heading-hierarchy": { severity: "warning" },
|
|
14593
|
+
"docx/type-scale": { enabled: true },
|
|
14594
|
+
"docx/size-count": { enabled: true, parameters: { maximumSizes: 8 } },
|
|
14595
|
+
"docx/role-drift": { enabled: true }
|
|
14596
|
+
}
|
|
14597
|
+
},
|
|
14118
14598
|
"executive-report": {
|
|
14119
14599
|
id: "executive-report",
|
|
14120
14600
|
formats: ["docx"],
|
|
@@ -14136,6 +14616,11 @@ var DOCX_QUALITY_PROFILES = {
|
|
|
14136
14616
|
};
|
|
14137
14617
|
var DOCX_DEFAULT_QUALITY_PROFILE = DOCX_QUALITY_PROFILES["technical-report"];
|
|
14138
14618
|
var DOCX_PROFILES_BY_ID = DOCX_QUALITY_PROFILES;
|
|
14619
|
+
function declaredDocxQualityProfile(document) {
|
|
14620
|
+
const props = document?.props;
|
|
14621
|
+
const id = props?.qualityProfile;
|
|
14622
|
+
return typeof id === "string" && Object.prototype.hasOwnProperty.call(DOCX_PROFILES_BY_ID, id) ? DOCX_PROFILES_BY_ID[id] : void 0;
|
|
14623
|
+
}
|
|
14139
14624
|
function resolveDocxQualityProfile(requested) {
|
|
14140
14625
|
if (!requested) return void 0;
|
|
14141
14626
|
const registered = DOCX_PROFILES_BY_ID[requested.id];
|
|
@@ -14183,7 +14668,7 @@ function analyzeDocxQuality(doc, options = {}) {
|
|
|
14183
14668
|
);
|
|
14184
14669
|
}
|
|
14185
14670
|
return docxQualityEngine.analyzeSync(prepared, {
|
|
14186
|
-
profile: resolveDocxQualityProfile(options.profile) ?? DOCX_DEFAULT_QUALITY_PROFILE,
|
|
14671
|
+
profile: resolveDocxQualityProfile(options.profile) ?? declaredDocxQualityProfile(doc) ?? DOCX_DEFAULT_QUALITY_PROFILE,
|
|
14187
14672
|
policy: options.policy
|
|
14188
14673
|
});
|
|
14189
14674
|
}
|
|
@@ -14293,17 +14778,655 @@ function formatWarningsText(warnings) {
|
|
|
14293
14778
|
|
|
14294
14779
|
// src/index.ts
|
|
14295
14780
|
init_styles();
|
|
14781
|
+
|
|
14782
|
+
// src/blueprints/index.ts
|
|
14783
|
+
import {
|
|
14784
|
+
blockDependencies,
|
|
14785
|
+
validateBlueprint
|
|
14786
|
+
} from "@json-to-office/shared";
|
|
14787
|
+
import { collectPlaceholders as collectPlaceholders2 } from "@json-to-office/quality";
|
|
14788
|
+
import { existsSync, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
14789
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14790
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
14791
|
+
|
|
14792
|
+
// src/templates/blueprints/client-report.docx.blueprint.json
|
|
14793
|
+
var client_report_docx_blueprint_default = {
|
|
14794
|
+
id: "client-report",
|
|
14795
|
+
format: "docx",
|
|
14796
|
+
title: "Client report",
|
|
14797
|
+
description: "A report for a client or a public administration: a cover, key takeaways, KPIs, chart-first sections with a takeaway and a source under every figure, data tables, notes and sources, under a running head that tracks the section and numbers the pages.",
|
|
14798
|
+
whenToUse: "A periodic or one-off report to a client, a board or a public body that reads for the conclusion first and checks the evidence second. Not for a memo or a technical specification.",
|
|
14799
|
+
theme: "consulting",
|
|
14800
|
+
profile: "client-report",
|
|
14801
|
+
definitions: "client-report-blocks.docx.json",
|
|
14802
|
+
numbering: "sections",
|
|
14803
|
+
toc: false,
|
|
14804
|
+
variants: {
|
|
14805
|
+
"data-heavy": {
|
|
14806
|
+
description: "Evidence-led: a KPI row under the takeaways, a chart with its takeaway in the second section, a data table with a note in the third, next steps last.",
|
|
14807
|
+
whenToUse: "The brief comes with numbers: performance, finance, operations, anything the reader will check against a figure.",
|
|
14808
|
+
pages: {
|
|
14809
|
+
min: 4,
|
|
14810
|
+
max: 8
|
|
14811
|
+
},
|
|
14812
|
+
metadata: {
|
|
14813
|
+
title: "{{Title: as on the cover}}",
|
|
14814
|
+
author: "{{Author or team}}",
|
|
14815
|
+
company: "{{Client name}}",
|
|
14816
|
+
date: "{{Month YYYY}}"
|
|
14817
|
+
},
|
|
14818
|
+
children: [
|
|
14819
|
+
{
|
|
14820
|
+
name: "section",
|
|
14821
|
+
children: [
|
|
14822
|
+
{
|
|
14823
|
+
name: "block",
|
|
14824
|
+
props: {
|
|
14825
|
+
ref: "cover",
|
|
14826
|
+
slots: {
|
|
14827
|
+
title: "{{Title: the report's conclusion in one sentence}}",
|
|
14828
|
+
subtitle: "{{Subtitle: what the report answers, for whom}}",
|
|
14829
|
+
client: "{{Client name}}",
|
|
14830
|
+
date: "{{Month YYYY}}",
|
|
14831
|
+
confidentiality: "{{Confidentiality: Confidential, or For internal use}}"
|
|
14832
|
+
}
|
|
14833
|
+
}
|
|
14834
|
+
}
|
|
14835
|
+
]
|
|
14836
|
+
},
|
|
14837
|
+
{
|
|
14838
|
+
name: "section",
|
|
14839
|
+
children: [
|
|
14840
|
+
{
|
|
14841
|
+
name: "block",
|
|
14842
|
+
props: {
|
|
14843
|
+
ref: "running-head",
|
|
14844
|
+
slots: {
|
|
14845
|
+
confidentiality: "{{Confidentiality: as on the cover}}",
|
|
14846
|
+
date: "{{Month YYYY}}"
|
|
14847
|
+
}
|
|
14848
|
+
}
|
|
14849
|
+
},
|
|
14850
|
+
{
|
|
14851
|
+
name: "block",
|
|
14852
|
+
props: {
|
|
14853
|
+
ref: "section-opener",
|
|
14854
|
+
slots: {
|
|
14855
|
+
number: "01",
|
|
14856
|
+
title: "{{Section title: the finding, as a statement}}",
|
|
14857
|
+
tracker: "{{Tracker: one or two words}}"
|
|
14858
|
+
}
|
|
14859
|
+
}
|
|
14860
|
+
},
|
|
14861
|
+
{
|
|
14862
|
+
name: "block",
|
|
14863
|
+
props: {
|
|
14864
|
+
ref: "key-takeaways",
|
|
14865
|
+
slots: {
|
|
14866
|
+
items: [
|
|
14867
|
+
"{{Takeaway 1: one claim, one sentence, with its number}}",
|
|
14868
|
+
"{{Takeaway 2: one claim, one sentence, with its number}}",
|
|
14869
|
+
"{{Takeaway 3: the recommendation that follows}}"
|
|
14870
|
+
]
|
|
14871
|
+
}
|
|
14872
|
+
}
|
|
14873
|
+
},
|
|
14874
|
+
{
|
|
14875
|
+
name: "block",
|
|
14876
|
+
props: {
|
|
14877
|
+
ref: "kpi-row",
|
|
14878
|
+
slots: {
|
|
14879
|
+
items: [
|
|
14880
|
+
{
|
|
14881
|
+
value: "{{0.0}}",
|
|
14882
|
+
label: "{{What it measures}}"
|
|
14883
|
+
},
|
|
14884
|
+
{
|
|
14885
|
+
value: "{{0.0}}",
|
|
14886
|
+
label: "{{What it measures}}"
|
|
14887
|
+
},
|
|
14888
|
+
{
|
|
14889
|
+
value: "{{0.0}}",
|
|
14890
|
+
label: "{{What it measures}}"
|
|
14891
|
+
}
|
|
14892
|
+
],
|
|
14893
|
+
source: "{{Source: system or document, date}}"
|
|
14894
|
+
}
|
|
14895
|
+
}
|
|
14896
|
+
},
|
|
14897
|
+
{
|
|
14898
|
+
name: "paragraph",
|
|
14899
|
+
props: {
|
|
14900
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
14901
|
+
}
|
|
14902
|
+
}
|
|
14903
|
+
]
|
|
14904
|
+
},
|
|
14905
|
+
{
|
|
14906
|
+
name: "section",
|
|
14907
|
+
children: [
|
|
14908
|
+
{
|
|
14909
|
+
name: "block",
|
|
14910
|
+
props: {
|
|
14911
|
+
ref: "section-opener",
|
|
14912
|
+
slots: {
|
|
14913
|
+
number: "02",
|
|
14914
|
+
title: "{{Section title: the finding, as a statement}}",
|
|
14915
|
+
tracker: "{{Tracker: one or two words}}"
|
|
14916
|
+
}
|
|
14917
|
+
}
|
|
14918
|
+
},
|
|
14919
|
+
{
|
|
14920
|
+
name: "paragraph",
|
|
14921
|
+
props: {
|
|
14922
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
14923
|
+
}
|
|
14924
|
+
},
|
|
14925
|
+
{
|
|
14926
|
+
name: "block",
|
|
14927
|
+
props: {
|
|
14928
|
+
ref: "chart-figure",
|
|
14929
|
+
slots: {
|
|
14930
|
+
chart: {
|
|
14931
|
+
name: "highcharts",
|
|
14932
|
+
props: {
|
|
14933
|
+
width: "100%",
|
|
14934
|
+
options: {
|
|
14935
|
+
chart: {
|
|
14936
|
+
type: "column",
|
|
14937
|
+
width: 900,
|
|
14938
|
+
height: 460
|
|
14939
|
+
},
|
|
14940
|
+
title: {
|
|
14941
|
+
text: null
|
|
14942
|
+
},
|
|
14943
|
+
xAxis: {
|
|
14944
|
+
categories: [
|
|
14945
|
+
"{{Period 1}}",
|
|
14946
|
+
"{{Period 2}}",
|
|
14947
|
+
"{{Period 3}}"
|
|
14948
|
+
]
|
|
14949
|
+
},
|
|
14950
|
+
yAxis: {
|
|
14951
|
+
title: {
|
|
14952
|
+
text: "{{Measure (unit)}}"
|
|
14953
|
+
}
|
|
14954
|
+
},
|
|
14955
|
+
series: [
|
|
14956
|
+
{
|
|
14957
|
+
name: "{{Series name}}",
|
|
14958
|
+
data: [0, 0, 0]
|
|
14959
|
+
}
|
|
14960
|
+
]
|
|
14961
|
+
}
|
|
14962
|
+
}
|
|
14963
|
+
},
|
|
14964
|
+
caption: "{{Caption: what the chart shows, as a statement}}",
|
|
14965
|
+
takeaway: "{{Takeaway: the one sentence the reader should keep}}",
|
|
14966
|
+
source: "{{Source: system or document, date}}"
|
|
14967
|
+
}
|
|
14968
|
+
}
|
|
14969
|
+
},
|
|
14970
|
+
{
|
|
14971
|
+
name: "paragraph",
|
|
14972
|
+
props: {
|
|
14973
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
14974
|
+
}
|
|
14975
|
+
}
|
|
14976
|
+
]
|
|
14977
|
+
},
|
|
14978
|
+
{
|
|
14979
|
+
name: "section",
|
|
14980
|
+
children: [
|
|
14981
|
+
{
|
|
14982
|
+
name: "block",
|
|
14983
|
+
props: {
|
|
14984
|
+
ref: "section-opener",
|
|
14985
|
+
slots: {
|
|
14986
|
+
number: "03",
|
|
14987
|
+
title: "{{Section title: the finding, as a statement}}",
|
|
14988
|
+
tracker: "{{Tracker: one or two words}}"
|
|
14989
|
+
}
|
|
14990
|
+
}
|
|
14991
|
+
},
|
|
14992
|
+
{
|
|
14993
|
+
name: "paragraph",
|
|
14994
|
+
props: {
|
|
14995
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
14996
|
+
}
|
|
14997
|
+
},
|
|
14998
|
+
{
|
|
14999
|
+
name: "block",
|
|
15000
|
+
props: {
|
|
15001
|
+
ref: "data-table",
|
|
15002
|
+
slots: {
|
|
15003
|
+
title: "{{Table title: what it compares, and the period}}",
|
|
15004
|
+
labelHeader: "{{Row label}}",
|
|
15005
|
+
labels: ["{{Row 1}}", "{{Row 2}}", "{{Row 3}}"],
|
|
15006
|
+
columns: [
|
|
15007
|
+
{
|
|
15008
|
+
header: "{{Measure (unit)}}",
|
|
15009
|
+
cells: ["{{0.0}}", "{{0.0}}", "{{0.0}}"]
|
|
15010
|
+
},
|
|
15011
|
+
{
|
|
15012
|
+
header: "{{Change}}",
|
|
15013
|
+
cells: ["{{0.0}}", "{{0.0}}", "{{0.0}}"]
|
|
15014
|
+
}
|
|
15015
|
+
],
|
|
15016
|
+
source: "{{Source: system or document, date}}"
|
|
15017
|
+
}
|
|
15018
|
+
}
|
|
15019
|
+
},
|
|
15020
|
+
{
|
|
15021
|
+
name: "block",
|
|
15022
|
+
props: {
|
|
15023
|
+
ref: "callout",
|
|
15024
|
+
slots: {
|
|
15025
|
+
label: "{{Note label}}",
|
|
15026
|
+
text: "{{Note: a caveat about the numbers, a definition or the method}}"
|
|
15027
|
+
}
|
|
15028
|
+
}
|
|
15029
|
+
}
|
|
15030
|
+
]
|
|
15031
|
+
},
|
|
15032
|
+
{
|
|
15033
|
+
name: "section",
|
|
15034
|
+
children: [
|
|
15035
|
+
{
|
|
15036
|
+
name: "block",
|
|
15037
|
+
props: {
|
|
15038
|
+
ref: "section-opener",
|
|
15039
|
+
slots: {
|
|
15040
|
+
number: "04",
|
|
15041
|
+
title: "{{Section title: the finding, as a statement}}",
|
|
15042
|
+
tracker: "{{Tracker: one or two words}}"
|
|
15043
|
+
}
|
|
15044
|
+
}
|
|
15045
|
+
},
|
|
15046
|
+
{
|
|
15047
|
+
name: "paragraph",
|
|
15048
|
+
props: {
|
|
15049
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
15050
|
+
}
|
|
15051
|
+
},
|
|
15052
|
+
{
|
|
15053
|
+
name: "block",
|
|
15054
|
+
props: {
|
|
15055
|
+
ref: "footnotes"
|
|
15056
|
+
}
|
|
15057
|
+
}
|
|
15058
|
+
]
|
|
15059
|
+
}
|
|
15060
|
+
]
|
|
15061
|
+
},
|
|
15062
|
+
narrative: {
|
|
15063
|
+
description: "Argument-led: takeaways, then three sections of prose with one KPI row and one note as evidence, next steps last.",
|
|
15064
|
+
whenToUse: "The brief is a position, an assessment or a recommendation with few numbers; the reader follows an argument.",
|
|
15065
|
+
pages: {
|
|
15066
|
+
min: 3,
|
|
15067
|
+
max: 6
|
|
15068
|
+
},
|
|
15069
|
+
metadata: {
|
|
15070
|
+
title: "{{Title: as on the cover}}",
|
|
15071
|
+
author: "{{Author or team}}",
|
|
15072
|
+
company: "{{Client name}}",
|
|
15073
|
+
date: "{{Month YYYY}}"
|
|
15074
|
+
},
|
|
15075
|
+
children: [
|
|
15076
|
+
{
|
|
15077
|
+
name: "section",
|
|
15078
|
+
children: [
|
|
15079
|
+
{
|
|
15080
|
+
name: "block",
|
|
15081
|
+
props: {
|
|
15082
|
+
ref: "cover",
|
|
15083
|
+
slots: {
|
|
15084
|
+
title: "{{Title: the report's conclusion in one sentence}}",
|
|
15085
|
+
subtitle: "{{Subtitle: what the report answers, for whom}}",
|
|
15086
|
+
client: "{{Client name}}",
|
|
15087
|
+
date: "{{Month YYYY}}",
|
|
15088
|
+
confidentiality: "{{Confidentiality: Confidential, or For internal use}}"
|
|
15089
|
+
}
|
|
15090
|
+
}
|
|
15091
|
+
}
|
|
15092
|
+
]
|
|
15093
|
+
},
|
|
15094
|
+
{
|
|
15095
|
+
name: "section",
|
|
15096
|
+
children: [
|
|
15097
|
+
{
|
|
15098
|
+
name: "block",
|
|
15099
|
+
props: {
|
|
15100
|
+
ref: "running-head",
|
|
15101
|
+
slots: {
|
|
15102
|
+
confidentiality: "{{Confidentiality: as on the cover}}",
|
|
15103
|
+
date: "{{Month YYYY}}"
|
|
15104
|
+
}
|
|
15105
|
+
}
|
|
15106
|
+
},
|
|
15107
|
+
{
|
|
15108
|
+
name: "block",
|
|
15109
|
+
props: {
|
|
15110
|
+
ref: "section-opener",
|
|
15111
|
+
slots: {
|
|
15112
|
+
number: "01",
|
|
15113
|
+
title: "{{Section title: the finding, as a statement}}",
|
|
15114
|
+
tracker: "{{Tracker: one or two words}}"
|
|
15115
|
+
}
|
|
15116
|
+
}
|
|
15117
|
+
},
|
|
15118
|
+
{
|
|
15119
|
+
name: "block",
|
|
15120
|
+
props: {
|
|
15121
|
+
ref: "key-takeaways",
|
|
15122
|
+
slots: {
|
|
15123
|
+
items: [
|
|
15124
|
+
"{{Takeaway 1: one claim, one sentence, with its number}}",
|
|
15125
|
+
"{{Takeaway 2: one claim, one sentence, with its number}}",
|
|
15126
|
+
"{{Takeaway 3: the recommendation that follows}}"
|
|
15127
|
+
]
|
|
15128
|
+
}
|
|
15129
|
+
}
|
|
15130
|
+
},
|
|
15131
|
+
{
|
|
15132
|
+
name: "paragraph",
|
|
15133
|
+
props: {
|
|
15134
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
15135
|
+
}
|
|
15136
|
+
},
|
|
15137
|
+
{
|
|
15138
|
+
name: "paragraph",
|
|
15139
|
+
props: {
|
|
15140
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
15141
|
+
}
|
|
15142
|
+
}
|
|
15143
|
+
]
|
|
15144
|
+
},
|
|
15145
|
+
{
|
|
15146
|
+
name: "section",
|
|
15147
|
+
children: [
|
|
15148
|
+
{
|
|
15149
|
+
name: "block",
|
|
15150
|
+
props: {
|
|
15151
|
+
ref: "section-opener",
|
|
15152
|
+
slots: {
|
|
15153
|
+
number: "02",
|
|
15154
|
+
title: "{{Section title: the finding, as a statement}}",
|
|
15155
|
+
tracker: "{{Tracker: one or two words}}"
|
|
15156
|
+
}
|
|
15157
|
+
}
|
|
15158
|
+
},
|
|
15159
|
+
{
|
|
15160
|
+
name: "paragraph",
|
|
15161
|
+
props: {
|
|
15162
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
15163
|
+
}
|
|
15164
|
+
},
|
|
15165
|
+
{
|
|
15166
|
+
name: "block",
|
|
15167
|
+
props: {
|
|
15168
|
+
ref: "callout",
|
|
15169
|
+
slots: {
|
|
15170
|
+
label: "{{Note label}}",
|
|
15171
|
+
text: "{{Note: a caveat about the numbers, a definition or the method}}"
|
|
15172
|
+
}
|
|
15173
|
+
}
|
|
15174
|
+
},
|
|
15175
|
+
{
|
|
15176
|
+
name: "paragraph",
|
|
15177
|
+
props: {
|
|
15178
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
15179
|
+
}
|
|
15180
|
+
}
|
|
15181
|
+
]
|
|
15182
|
+
},
|
|
15183
|
+
{
|
|
15184
|
+
name: "section",
|
|
15185
|
+
children: [
|
|
15186
|
+
{
|
|
15187
|
+
name: "block",
|
|
15188
|
+
props: {
|
|
15189
|
+
ref: "section-opener",
|
|
15190
|
+
slots: {
|
|
15191
|
+
number: "03",
|
|
15192
|
+
title: "{{Section title: the finding, as a statement}}",
|
|
15193
|
+
tracker: "{{Tracker: one or two words}}"
|
|
15194
|
+
}
|
|
15195
|
+
}
|
|
15196
|
+
},
|
|
15197
|
+
{
|
|
15198
|
+
name: "paragraph",
|
|
15199
|
+
props: {
|
|
15200
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
15201
|
+
}
|
|
15202
|
+
},
|
|
15203
|
+
{
|
|
15204
|
+
name: "block",
|
|
15205
|
+
props: {
|
|
15206
|
+
ref: "kpi-row",
|
|
15207
|
+
slots: {
|
|
15208
|
+
items: [
|
|
15209
|
+
{
|
|
15210
|
+
value: "{{0.0}}",
|
|
15211
|
+
label: "{{What it measures}}"
|
|
15212
|
+
},
|
|
15213
|
+
{
|
|
15214
|
+
value: "{{0.0}}",
|
|
15215
|
+
label: "{{What it measures}}"
|
|
15216
|
+
},
|
|
15217
|
+
{
|
|
15218
|
+
value: "{{0.0}}",
|
|
15219
|
+
label: "{{What it measures}}"
|
|
15220
|
+
}
|
|
15221
|
+
],
|
|
15222
|
+
source: "{{Source: system or document, date}}"
|
|
15223
|
+
}
|
|
15224
|
+
}
|
|
15225
|
+
},
|
|
15226
|
+
{
|
|
15227
|
+
name: "paragraph",
|
|
15228
|
+
props: {
|
|
15229
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
15230
|
+
}
|
|
15231
|
+
}
|
|
15232
|
+
]
|
|
15233
|
+
},
|
|
15234
|
+
{
|
|
15235
|
+
name: "section",
|
|
15236
|
+
children: [
|
|
15237
|
+
{
|
|
15238
|
+
name: "block",
|
|
15239
|
+
props: {
|
|
15240
|
+
ref: "section-opener",
|
|
15241
|
+
slots: {
|
|
15242
|
+
number: "04",
|
|
15243
|
+
title: "{{Section title: the finding, as a statement}}",
|
|
15244
|
+
tracker: "{{Tracker: one or two words}}"
|
|
15245
|
+
}
|
|
15246
|
+
}
|
|
15247
|
+
},
|
|
15248
|
+
{
|
|
15249
|
+
name: "paragraph",
|
|
15250
|
+
props: {
|
|
15251
|
+
text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
|
|
15252
|
+
}
|
|
15253
|
+
},
|
|
15254
|
+
{
|
|
15255
|
+
name: "block",
|
|
15256
|
+
props: {
|
|
15257
|
+
ref: "footnotes"
|
|
15258
|
+
}
|
|
15259
|
+
}
|
|
15260
|
+
]
|
|
15261
|
+
}
|
|
15262
|
+
]
|
|
15263
|
+
}
|
|
15264
|
+
}
|
|
15265
|
+
};
|
|
15266
|
+
|
|
15267
|
+
// src/blueprints/index.ts
|
|
15268
|
+
function register(...candidates) {
|
|
15269
|
+
const registry2 = {};
|
|
15270
|
+
for (const candidate of candidates) {
|
|
15271
|
+
const issues = validateBlueprint(candidate);
|
|
15272
|
+
if (issues.length > 0)
|
|
15273
|
+
throw new Error(
|
|
15274
|
+
`Invalid bundled blueprint: ${issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`
|
|
15275
|
+
);
|
|
15276
|
+
const blueprint = candidate;
|
|
15277
|
+
if (blueprint.format !== "docx")
|
|
15278
|
+
throw new Error(`Blueprint ${blueprint.id} is not a DOCX blueprint.`);
|
|
15279
|
+
if (registry2[blueprint.id])
|
|
15280
|
+
throw new Error(
|
|
15281
|
+
`Two bundled blueprints share the id "${blueprint.id}"; the later one would silently replace the earlier.`
|
|
15282
|
+
);
|
|
15283
|
+
registry2[blueprint.id] = blueprint;
|
|
15284
|
+
}
|
|
15285
|
+
return registry2;
|
|
15286
|
+
}
|
|
15287
|
+
function scanned(known) {
|
|
15288
|
+
let here;
|
|
15289
|
+
try {
|
|
15290
|
+
here = dirname2(fileURLToPath2(import.meta.url));
|
|
15291
|
+
} catch {
|
|
15292
|
+
return [];
|
|
15293
|
+
}
|
|
15294
|
+
const directory = [
|
|
15295
|
+
join2(here, "../templates/blueprints"),
|
|
15296
|
+
join2(here, "templates/blueprints")
|
|
15297
|
+
].find((path4) => existsSync(path4));
|
|
15298
|
+
if (!directory) return [];
|
|
15299
|
+
return readdirSync(directory).filter((file) => file.endsWith(".docx.blueprint.json")).map((file) => JSON.parse(readFileSync2(join2(directory, file), "utf8"))).filter((candidate) => {
|
|
15300
|
+
const id = candidate?.id;
|
|
15301
|
+
return !(typeof id === "string" && id in known);
|
|
15302
|
+
});
|
|
15303
|
+
}
|
|
15304
|
+
var bundled = register(client_report_docx_blueprint_default);
|
|
15305
|
+
var DOCX_BLUEPRINTS = {
|
|
15306
|
+
...bundled,
|
|
15307
|
+
...register(...scanned(bundled))
|
|
15308
|
+
};
|
|
15309
|
+
function docxBlueprint(id) {
|
|
15310
|
+
return DOCX_BLUEPRINTS[id];
|
|
15311
|
+
}
|
|
15312
|
+
var MARKER = /^\{\{\s*([\s\S]*?)\s*\}\}$/;
|
|
15313
|
+
function instantiateDocxBlueprint(blueprint, options) {
|
|
15314
|
+
if (blueprint.format !== "docx")
|
|
15315
|
+
throw new Error(
|
|
15316
|
+
`Blueprint ${blueprint.id} is a ${blueprint.format} blueprint; this instantiates DOCX ones.`
|
|
15317
|
+
);
|
|
15318
|
+
const variantId = options.variant ?? Object.keys(blueprint.variants)[0];
|
|
15319
|
+
const variant = blueprint.variants[variantId];
|
|
15320
|
+
if (!variant)
|
|
15321
|
+
throw new Error(
|
|
15322
|
+
`Blueprint ${blueprint.id} has no variant "${variantId}"; it has ${Object.keys(
|
|
15323
|
+
blueprint.variants
|
|
15324
|
+
).join(", ")}.`
|
|
15325
|
+
);
|
|
15326
|
+
const children = structuredClone(variant.children);
|
|
15327
|
+
const blocks = definitionsFor(children, options.definitions, blueprint);
|
|
15328
|
+
const document = {
|
|
15329
|
+
name: "docx",
|
|
15330
|
+
props: {
|
|
15331
|
+
theme: options.theme ?? blueprint.theme,
|
|
15332
|
+
qualityProfile: blueprint.profile,
|
|
15333
|
+
...Object.keys(blocks).length > 0 && { blocks },
|
|
15334
|
+
metadata: { ...variant.metadata, ...options.metadata }
|
|
15335
|
+
},
|
|
15336
|
+
children
|
|
15337
|
+
};
|
|
15338
|
+
return { document, fillMap: fillMap(document, blocks), variant: variantId };
|
|
15339
|
+
}
|
|
15340
|
+
function definitionsFor(children, available, blueprint) {
|
|
15341
|
+
const refs = /* @__PURE__ */ new Set();
|
|
15342
|
+
const visit = (node) => {
|
|
15343
|
+
if (Array.isArray(node)) return node.forEach(visit);
|
|
15344
|
+
if (!node || typeof node !== "object") return;
|
|
15345
|
+
const record = node;
|
|
15346
|
+
const props = record.props;
|
|
15347
|
+
if (record.name === "block" && typeof props?.ref === "string")
|
|
15348
|
+
refs.add(props.ref);
|
|
15349
|
+
Object.values(record).forEach(visit);
|
|
15350
|
+
};
|
|
15351
|
+
visit(children);
|
|
15352
|
+
const result = {};
|
|
15353
|
+
for (const ref of refs) {
|
|
15354
|
+
if (!available[ref])
|
|
15355
|
+
throw new Error(
|
|
15356
|
+
`Blueprint ${blueprint.id} invokes "${ref}", which ${blueprint.definitions} does not define.`
|
|
15357
|
+
);
|
|
15358
|
+
for (const name of [...blockDependencies(available, ref), ref])
|
|
15359
|
+
result[name] ??= structuredClone(available[name]);
|
|
15360
|
+
}
|
|
15361
|
+
return result;
|
|
15362
|
+
}
|
|
15363
|
+
function fillMap(document, blocks) {
|
|
15364
|
+
return collectPlaceholders2(document).filter((occurrence) => occurrence.match.kind === "scaffold-marker").map((occurrence) => {
|
|
15365
|
+
const guidance = occurrence.text.match(MARKER)?.[1] ?? occurrence.text;
|
|
15366
|
+
const base = { path: occurrence.path, marker: occurrence.text, guidance };
|
|
15367
|
+
if (occurrence.path.startsWith("/props/metadata/"))
|
|
15368
|
+
return { ...base, kind: "metadata" };
|
|
15369
|
+
const slot = slotAt(document, occurrence.path, blocks);
|
|
15370
|
+
return slot ? { ...base, kind: "slot", ...slot } : { ...base, kind: "text" };
|
|
15371
|
+
});
|
|
15372
|
+
}
|
|
15373
|
+
function slotAt(document, pointer, blocks) {
|
|
15374
|
+
const at = pointer.lastIndexOf("/props/slots/");
|
|
15375
|
+
if (at < 0) return void 0;
|
|
15376
|
+
const invocation = valueAt(document, pointer.slice(0, at));
|
|
15377
|
+
const ref = invocation?.props?.ref;
|
|
15378
|
+
if (typeof ref !== "string" || !blocks[ref]) return void 0;
|
|
15379
|
+
const segments = pointer.slice(at + "/props/slots/".length).split("/").map(unescape);
|
|
15380
|
+
let slot = blocks[ref].slots[segments[0]];
|
|
15381
|
+
const names = [segments[0]];
|
|
15382
|
+
for (const segment of segments.slice(1)) {
|
|
15383
|
+
if (!slot) break;
|
|
15384
|
+
if (slot.type === "array" && /^\d+$/.test(segment)) slot = slot.items;
|
|
15385
|
+
else if (slot.type === "object") {
|
|
15386
|
+
slot = slot.properties?.[segment];
|
|
15387
|
+
names.push(segment);
|
|
15388
|
+
} else if (slot.type === "component")
|
|
15389
|
+
break;
|
|
15390
|
+
else slot = void 0;
|
|
15391
|
+
}
|
|
15392
|
+
if (!slot) return void 0;
|
|
15393
|
+
return {
|
|
15394
|
+
block: ref,
|
|
15395
|
+
slot: names.join("."),
|
|
15396
|
+
type: slot.type,
|
|
15397
|
+
...slot.maxWords !== void 0 && { maxWords: slot.maxWords },
|
|
15398
|
+
...slot.maxLength !== void 0 && { maxLength: slot.maxLength },
|
|
15399
|
+
...slot.oneLine !== void 0 && { oneLine: slot.oneLine },
|
|
15400
|
+
...slot.required !== void 0 && { required: slot.required }
|
|
15401
|
+
};
|
|
15402
|
+
}
|
|
15403
|
+
function unescape(segment) {
|
|
15404
|
+
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
15405
|
+
}
|
|
15406
|
+
function valueAt(root, pointer) {
|
|
15407
|
+
if (pointer === "") return root;
|
|
15408
|
+
let current = root;
|
|
15409
|
+
for (const segment of pointer.split("/").slice(1).map(unescape)) {
|
|
15410
|
+
if (Array.isArray(current)) current = current[Number(segment)];
|
|
15411
|
+
else if (current && typeof current === "object")
|
|
15412
|
+
current = current[segment];
|
|
15413
|
+
else return void 0;
|
|
15414
|
+
}
|
|
15415
|
+
return current;
|
|
15416
|
+
}
|
|
15417
|
+
|
|
15418
|
+
// src/index.ts
|
|
14296
15419
|
init_json();
|
|
14297
15420
|
|
|
14298
15421
|
// src/templates/documents/index.ts
|
|
14299
15422
|
import fs4 from "fs";
|
|
14300
15423
|
import path2 from "path";
|
|
14301
|
-
import { fileURLToPath as
|
|
15424
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14302
15425
|
var _dirname = null;
|
|
14303
15426
|
function getDirname() {
|
|
14304
15427
|
if (_dirname === null) {
|
|
14305
15428
|
try {
|
|
14306
|
-
_dirname = path2.dirname(
|
|
15429
|
+
_dirname = path2.dirname(fileURLToPath3(import.meta.url));
|
|
14307
15430
|
} catch {
|
|
14308
15431
|
throw new Error(
|
|
14309
15432
|
"Cannot resolve file paths in a bundled environment. Example loading from disk is not available."
|
|
@@ -15189,6 +16312,7 @@ export {
|
|
|
15189
16312
|
ComponentValidationError2 as ComponentValidationError,
|
|
15190
16313
|
DocumentGenerator as CoreDocumentGenerator,
|
|
15191
16314
|
DEFAULT_DOCX_RENDERER_ID,
|
|
16315
|
+
DOCX_BLUEPRINTS,
|
|
15192
16316
|
DOCX_DEFAULT_QUALITY_PROFILE,
|
|
15193
16317
|
DOCX_QUALITY_PROFILES,
|
|
15194
16318
|
DOCX_QUALITY_RULES,
|
|
@@ -15205,7 +16329,9 @@ export {
|
|
|
15205
16329
|
createDocumentGenerator,
|
|
15206
16330
|
createMinimalTheme,
|
|
15207
16331
|
createVersion,
|
|
16332
|
+
declaredDocxQualityProfile,
|
|
15208
16333
|
devportalTheme,
|
|
16334
|
+
docxBlueprint,
|
|
15209
16335
|
docxQualityEngine,
|
|
15210
16336
|
docxRendererIds,
|
|
15211
16337
|
docxRendererStatuses,
|
|
@@ -15230,6 +16356,7 @@ export {
|
|
|
15230
16356
|
getExampleNames,
|
|
15231
16357
|
getVisualPrepassStats,
|
|
15232
16358
|
hasNodeBuiltins,
|
|
16359
|
+
instantiateDocxBlueprint,
|
|
15233
16360
|
isColumnsComponent,
|
|
15234
16361
|
isDocxRendererId,
|
|
15235
16362
|
isHeadingComponent,
|