@json-to-office/core-docx 3.1.0 → 3.3.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/index.js CHANGED
@@ -2376,7 +2376,7 @@ import {
2376
2376
  WpsShapeRun
2377
2377
  } from "docx";
2378
2378
  import { assertNever } from "@json-to-office/shared/rendering";
2379
- function complexField(instruction, formatting, leading) {
2379
+ function complexField(instruction, formatting, leading, cachedText) {
2380
2380
  const options = runOptions(formatting);
2381
2381
  const fieldChar = (type, extra = {}) => new TextRun({
2382
2382
  children: [
@@ -2395,6 +2395,9 @@ function complexField(instruction, formatting, leading) {
2395
2395
  fieldChar("begin", leading),
2396
2396
  new TextRun({ children: [instrText], ...options }),
2397
2397
  fieldChar("separate"),
2398
+ // A sequence number is the one field this pipeline can compute, so its
2399
+ // result is written: a reader that never updates fields shows the count.
2400
+ ...cachedText !== void 0 ? [new TextRun({ text: cachedText, ...options })] : [],
2398
2401
  fieldChar("end")
2399
2402
  ];
2400
2403
  }
@@ -2505,13 +2508,18 @@ function inlineChildren(children, resources = /* @__PURE__ */ new Map()) {
2505
2508
  out.push(new SimpleField(child.instruction, child.cachedText));
2506
2509
  break;
2507
2510
  }
2508
- if (!PAGE_FIELDS.has(child.instruction)) {
2511
+ if (!PAGE_FIELDS.has(child.instruction) && !isSequenceField(child.instruction)) {
2509
2512
  throw new Error(
2510
2513
  `the docxjs renderer has no emitter for the field "${child.instruction}"`
2511
2514
  );
2512
2515
  }
2513
2516
  out.push(
2514
- ...complexField(child.instruction, child.formatting, breakOption())
2517
+ ...complexField(
2518
+ child.instruction,
2519
+ child.formatting,
2520
+ breakOption(),
2521
+ child.cachedText
2522
+ )
2515
2523
  );
2516
2524
  break;
2517
2525
  }
@@ -2885,7 +2893,7 @@ function emitBorder(border3) {
2885
2893
  ...border3.spacePoints !== void 0 ? { space: border3.spacePoints } : {}
2886
2894
  };
2887
2895
  }
2888
- var ALIGNMENT2, PAGE_FIELDS, WRAP_TYPE, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
2896
+ var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
2889
2897
  var init_emit = __esm({
2890
2898
  "src/renderers/docxjs/emit.ts"() {
2891
2899
  "use strict";
@@ -2898,6 +2906,7 @@ var init_emit = __esm({
2898
2906
  end: AlignmentType.END
2899
2907
  };
2900
2908
  PAGE_FIELDS = /* @__PURE__ */ new Set(["PAGE", "NUMPAGES"]);
2909
+ isSequenceField = (instruction) => /^SEQ [A-Za-z][\w-]* \\\* ARABIC$/.test(instruction);
2901
2910
  WRAP_TYPE = {
2902
2911
  none: TextWrappingType.NONE,
2903
2912
  square: TextWrappingType.SQUARE,
@@ -5042,6 +5051,10 @@ function resolveServiceUrl(propsUrl, servicesUrl, defaultUrl) {
5042
5051
  const withScheme = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
5043
5052
  return withScheme.replace(/\/+$/, "");
5044
5053
  }
5054
+ var SERVICE_UNAVAILABLE_CODE = "SERVICE_UNAVAILABLE";
5055
+ function serviceUnavailable(message) {
5056
+ return Object.assign(new Error(message), { code: SERVICE_UNAVAILABLE_CODE });
5057
+ }
5045
5058
  async function postJsonToService(opts) {
5046
5059
  const resolvedHeaders = typeof opts.headers === "function" ? await opts.headers(opts.body) : opts.headers;
5047
5060
  const headers = {
@@ -5061,12 +5074,12 @@ async function postJsonToService(opts) {
5061
5074
  });
5062
5075
  } catch (error) {
5063
5076
  if (error?.name === "AbortError") {
5064
- throw new Error(
5077
+ throw serviceUnavailable(
5065
5078
  `${opts.serviceLabel} timed out after ${timeoutMs}ms at ${opts.url}.`
5066
5079
  );
5067
5080
  }
5068
5081
  const cause = error instanceof Error ? error.message : String(error);
5069
- throw new Error(opts.onUnreachable(opts.url, cause));
5082
+ throw serviceUnavailable(opts.onUnreachable(opts.url, cause));
5070
5083
  } finally {
5071
5084
  clearTimeout(timer);
5072
5085
  }
@@ -5521,13 +5534,25 @@ init_colorUtils();
5521
5534
  init_layoutUtils();
5522
5535
  import {
5523
5536
  chartFamilyResolver,
5537
+ remoteExportNotice,
5538
+ REMOTE_EXPORT_WARNING,
5524
5539
  chartPointsPerPixel,
5525
5540
  POINTS_PER_PIXEL_96DPI,
5526
5541
  withChartFontFaceCss,
5527
5542
  withChartTypography
5528
5543
  } from "@json-to-office/shared";
5529
5544
  var DEFAULT_EXPORT_SERVER_URL = "http://localhost:7801";
5530
- async function generateChart(config, servicesConfig) {
5545
+ function assertExportServerAllowed(serverUrl, servicesConfig, warnings) {
5546
+ const notice = remoteExportNotice(serverUrl, servicesConfig?.allowRemote);
5547
+ if (notice)
5548
+ warnings?.push({
5549
+ component: "highcharts",
5550
+ severity: "warning",
5551
+ message: notice,
5552
+ context: { code: REMOTE_EXPORT_WARNING, serverUrl }
5553
+ });
5554
+ }
5555
+ async function generateChart(config, servicesConfig, warnings) {
5531
5556
  if (!isNodeEnvironment()) {
5532
5557
  throw new Error(
5533
5558
  "Highcharts export server requires a Node.js environment. Chart generation is not available in browser environments."
@@ -5538,6 +5563,7 @@ async function generateChart(config, servicesConfig) {
5538
5563
  servicesConfig?.serverUrl,
5539
5564
  DEFAULT_EXPORT_SERVER_URL
5540
5565
  );
5566
+ assertExportServerAllowed(serverUrl, servicesConfig, warnings);
5541
5567
  const requestBody = {
5542
5568
  infile: config.options,
5543
5569
  type: "png",
@@ -5650,13 +5676,13 @@ function withChartFontFaces(config, theme, faces) {
5650
5676
  theme.fonts.heading.family
5651
5677
  ]);
5652
5678
  }
5653
- async function renderChartToImageProps(props, theme, servicesConfig, chartFonts) {
5679
+ async function renderChartToImageProps(props, theme, servicesConfig, chartFonts, warnings) {
5654
5680
  const config = withChartFontFaces(
5655
5681
  withThemeTypography(withThemeColors(props, theme), theme),
5656
5682
  theme,
5657
5683
  chartFonts
5658
5684
  );
5659
- const chart = await generateChart(config, servicesConfig);
5685
+ const chart = await generateChart(config, servicesConfig, warnings);
5660
5686
  const hasConfigDimensions = config.width !== void 0 || config.height !== void 0;
5661
5687
  return {
5662
5688
  base64: chart.base64DataUri,
@@ -5696,7 +5722,8 @@ async function desugarExternals(document, options) {
5696
5722
  node.props,
5697
5723
  options.theme,
5698
5724
  options.services?.highcharts,
5699
- options.chartFonts
5725
+ options.chartFonts,
5726
+ options.warnings
5700
5727
  )
5701
5728
  });
5702
5729
  }
@@ -8247,6 +8274,9 @@ function parsePlaceholders(normalized, parseOptions) {
8247
8274
  out.push({
8248
8275
  kind: "field",
8249
8276
  instruction: resolved.instruction,
8277
+ ...resolved.cachedText !== void 0 && {
8278
+ cachedText: resolved.cachedText
8279
+ },
8250
8280
  ...formatting ? { formatting } : {}
8251
8281
  });
8252
8282
  }
@@ -8473,6 +8503,7 @@ function compileDocument(structure, layout, warnings = [], images = /* @__PURE__
8473
8503
  nextBookmarkId: CONTENT_BOOKMARK_ID_BASE + 1,
8474
8504
  bookmarkNames: /* @__PURE__ */ new Set(),
8475
8505
  usesStatistic: false,
8506
+ sequences: /* @__PURE__ */ new Map(),
8476
8507
  styleIds: /* @__PURE__ */ new Set([
8477
8508
  ...styles.paragraph.map((s) => s.id),
8478
8509
  ...styles.character.map((s) => s.id)
@@ -9868,7 +9899,20 @@ function compileRuns(props, text, ctx, path4, mode = "inline") {
9868
9899
  return children;
9869
9900
  }
9870
9901
  function placeholderResolver(ctx) {
9871
- return (name) => resolvePlaceholder(name, ctx.generatedAt);
9902
+ return (name) => {
9903
+ const sequence = /^SEQ:([A-Za-z][\w-]*)$/i.exec(name);
9904
+ if (sequence) {
9905
+ const key = sequence[1];
9906
+ const count = (ctx.sequences.get(key) ?? 0) + 1;
9907
+ ctx.sequences.set(key, count);
9908
+ return {
9909
+ kind: "field",
9910
+ instruction: `SEQ ${key} \\* ARABIC`,
9911
+ cachedText: String(count)
9912
+ };
9913
+ }
9914
+ return resolvePlaceholder(name, ctx.generatedAt);
9915
+ };
9872
9916
  }
9873
9917
  function crossReferenceResolver(ctx, path4) {
9874
9918
  return (id, format2, token) => resolveCrossReference(id, format2, token, ctx, path4);
@@ -11730,6 +11774,7 @@ async function processResolvedDocument(document, resolved, themeName, generation
11730
11774
  };
11731
11775
  }
11732
11776
  function createDocumentMetadata(props, generationDate = /* @__PURE__ */ new Date()) {
11777
+ const parsed = props.metadata?.date ? new Date(props.metadata.date) : void 0;
11733
11778
  return {
11734
11779
  title: props.metadata?.title,
11735
11780
  subtitle: props.metadata?.subtitle,
@@ -11738,7 +11783,7 @@ function createDocumentMetadata(props, generationDate = /* @__PURE__ */ new Date
11738
11783
  company: props.metadata?.company,
11739
11784
  version: props.metadata?.version,
11740
11785
  tags: props.metadata?.tags,
11741
- date: props.metadata?.date ? new Date(props.metadata.date) : generationDate
11786
+ date: parsed && !Number.isNaN(parsed.getTime()) ? parsed : generationDate
11742
11787
  };
11743
11788
  }
11744
11789
  async function extractSections(components, context) {
@@ -11830,7 +11875,10 @@ import {
11830
11875
  normalizeHighchartsChart
11831
11876
  } from "@json-to-office/quality";
11832
11877
  import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
11833
- import { designColors as designColors2, resolveDesignColor as resolveDesignColor2 } from "@json-to-office/shared";
11878
+ import {
11879
+ designColors as designColors2,
11880
+ resolveDesignColor as resolveDesignColor2
11881
+ } from "@json-to-office/shared";
11834
11882
 
11835
11883
  // src/core/generationContext.ts
11836
11884
  import { applyExportMode } from "@json-to-office/shared";
@@ -12094,12 +12142,41 @@ import {
12094
12142
  readBlockDefinitions,
12095
12143
  blockValueAt,
12096
12144
  isBlockRecord,
12097
- toAuthoredBlockPointer
12145
+ toAuthoredBlockPointer,
12146
+ blockPointerKey,
12147
+ blockSlotRoles
12098
12148
  } from "@json-to-office/shared";
12099
- import { blockSlotBudgets } from "@json-to-office/shared";
12149
+ import { blockSlotBudgets, blockSlotRoles as blockSlotRoles2 } from "@json-to-office/shared";
12100
12150
  import { validateDocument } from "@json-to-office/shared-docx";
12101
12151
  var toAuthoredPointer = toAuthoredBlockPointer;
12102
- function docxBlockContext(document, theme, path4 = "") {
12152
+ function documentSourceLines(document) {
12153
+ if (!isBlockRecord(document)) return [];
12154
+ const invocations = [];
12155
+ const walk2 = (value, path4) => {
12156
+ if (Array.isArray(value)) {
12157
+ value.forEach((item, index) => walk2(item, `${path4}/${index}`));
12158
+ return;
12159
+ }
12160
+ if (!isBlockRecord(value) || value.enabled === false) return;
12161
+ if (value.name === "block") invocations.push(path4);
12162
+ for (const [key, item] of Object.entries(value)) {
12163
+ if (path4 === "/props" && key === "blocks") continue;
12164
+ walk2(item, `${path4}/${blockPointerKey(key)}`);
12165
+ }
12166
+ };
12167
+ walk2(document, "");
12168
+ const seen = /* @__PURE__ */ new Set();
12169
+ const lines = [];
12170
+ for (const slot of blockSlotRoles(document, invocations)) {
12171
+ if (slot.role !== "source" || typeof slot.value !== "string") continue;
12172
+ const text = slot.value.trim();
12173
+ if (text === "" || seen.has(text)) continue;
12174
+ seen.add(text);
12175
+ lines.push({ text, path: slot.path });
12176
+ }
12177
+ return lines;
12178
+ }
12179
+ function docxBlockContext(document, theme, path4, sources) {
12103
12180
  const index = /^\/children\/(\d+)/.exec(path4)?.[1];
12104
12181
  const section2 = index === void 0 ? void 0 : blockValueAt(document, `/children/${index}`);
12105
12182
  const page = isBlockRecord(section2) && isBlockRecord(section2.props) && isBlockRecord(section2.props.page) ? section2.props.page : {};
@@ -12119,26 +12196,46 @@ function docxBlockContext(document, theme, path4 = "") {
12119
12196
  return {
12120
12197
  document: blockValueAt(document, "/props/metadata") ?? {},
12121
12198
  page: { width, height },
12122
- section: {}
12199
+ section: {},
12200
+ sources: sources.map((line) => line.text)
12123
12201
  };
12124
12202
  }
12125
- function createDocxBlockEvaluator(document, theme, extra = {}) {
12203
+ function docxBlockContextSources(sources) {
12204
+ return {
12205
+ "/document": "/props/metadata",
12206
+ ...Object.fromEntries(
12207
+ sources.map((line, index) => [`/sources/${index}`, line.path])
12208
+ )
12209
+ };
12210
+ }
12211
+ function createDocxBlockEvaluator(document, theme, extra = {}, sources = documentSourceLines(document)) {
12126
12212
  return new JsonBlockEvaluator(readBlockDefinitions(document), {
12127
12213
  format: "docx",
12128
12214
  theme,
12129
- contextAt: (path4) => docxBlockContext(document, theme, path4),
12130
- contextSources: { "/document": "/props/metadata" },
12215
+ contextAt: (path4) => docxBlockContext(document, theme, path4, sources),
12216
+ contextSources: docxBlockContextSources(sources),
12131
12217
  measure: (axis, unit, context) => Number(blockValueAt(context, `/page/${axis}`)) / (unit === "twip" ? 1 : unit === "in" ? 1440 : 20),
12132
12218
  ...extra
12133
12219
  });
12134
12220
  }
12135
12221
  function expandBlocks(document, theme) {
12136
12222
  const effects = [];
12137
- const evaluator = createDocxBlockEvaluator(document, theme, {
12138
- onSection: (effect) => effects.push(effect)
12139
- });
12223
+ const sources = documentSourceLines(document);
12224
+ const evaluator = createDocxBlockEvaluator(
12225
+ document,
12226
+ theme,
12227
+ { onSection: (effect) => effects.push(effect) },
12228
+ sources
12229
+ );
12140
12230
  const expanded = evaluator.expand(document);
12141
- return finishDocxBlocks(expanded, document, theme, evaluator, effects);
12231
+ return finishDocxBlocks(
12232
+ expanded,
12233
+ document,
12234
+ theme,
12235
+ evaluator,
12236
+ effects,
12237
+ sources
12238
+ );
12142
12239
  }
12143
12240
  function validateEffects(document, effects) {
12144
12241
  for (const effect of effects) {
@@ -12159,7 +12256,7 @@ function validateEffects(document, effects) {
12159
12256
  ]);
12160
12257
  }
12161
12258
  }
12162
- function finishDocxBlocks(expanded, document, theme, evaluator, effects, validate = true) {
12259
+ function finishDocxBlocks(expanded, document, theme, evaluator, effects, sources, validate = true) {
12163
12260
  if (isBlockRecord(expanded) && Array.isArray(expanded.children)) {
12164
12261
  let inherited;
12165
12262
  expanded.children.forEach((section2, index) => {
@@ -12197,11 +12294,11 @@ function finishDocxBlocks(expanded, document, theme, evaluator, effects, validat
12197
12294
  const environment = {
12198
12295
  ...chrome.environment,
12199
12296
  context: {
12200
- ...docxBlockContext(document, theme, path4),
12297
+ ...docxBlockContext(document, theme, path4, sources),
12201
12298
  section: { tracker }
12202
12299
  },
12203
12300
  contextSources: {
12204
- "/document": "/props/metadata",
12301
+ ...docxBlockContextSources(sources),
12205
12302
  "/section/tracker": trackerSource
12206
12303
  }
12207
12304
  };
@@ -12245,10 +12342,16 @@ function finishDocxBlocks(expanded, document, theme, evaluator, effects, validat
12245
12342
  }
12246
12343
  async function expandBlocksWithPlugins(document, theme, plugins, render, preserve = /* @__PURE__ */ new Set()) {
12247
12344
  const effects = [];
12248
- const evaluator = createDocxBlockEvaluator(document, theme, {
12249
- reservedNames: [...plugins],
12250
- onSection: (effect) => effects.push(effect)
12251
- });
12345
+ const sources = documentSourceLines(document);
12346
+ const evaluator = createDocxBlockEvaluator(
12347
+ document,
12348
+ theme,
12349
+ {
12350
+ reservedNames: [...plugins],
12351
+ onSection: (effect) => effects.push(effect)
12352
+ },
12353
+ sources
12354
+ );
12252
12355
  const walk2 = (value) => composeBlocksWithPlugins(evaluator, value, {
12253
12356
  plugins,
12254
12357
  render,
@@ -12261,6 +12364,7 @@ async function expandBlocksWithPlugins(document, theme, plugins, render, preserv
12261
12364
  theme,
12262
12365
  evaluator,
12263
12366
  effects,
12367
+ sources,
12264
12368
  false
12265
12369
  );
12266
12370
  const second = await walk2(finished.document);
@@ -12360,7 +12464,8 @@ function chartFact(node, props, path4, paletteTokens) {
12360
12464
  kind: "docx/chart",
12361
12465
  path: path4,
12362
12466
  componentName: typeof node.name === "string" ? node.name : "",
12363
- paletteTokens
12467
+ paletteTokens,
12468
+ generated: false
12364
12469
  };
12365
12470
  if (node.name === "highcharts") {
12366
12471
  const shape = normalizeHighchartsChart(props.options);
@@ -12371,7 +12476,11 @@ function chartFact(node, props, path4, paletteTokens) {
12371
12476
  threeD: shape.threeD,
12372
12477
  seriesCount: shape.seriesCount,
12373
12478
  categoryCount: shape.categoryCount,
12374
- seriesColorsStated: shape.seriesColorsStated,
12479
+ // A config that names no `colors` is painted in the theme's chart
12480
+ // palette at render, so the series are the document's whenever the
12481
+ // theme has a palette to give; only a theme with none leaves them to
12482
+ // the export server's default.
12483
+ seriesColorsStated: shape.seriesColorsStated || paletteTokens.length > 0,
12375
12484
  seriesColorsPath: `${path4}/props/options`,
12376
12485
  ...shape.valueAxisMin !== void 0 && {
12377
12486
  valueAxisMin: shape.valueAxisMin
@@ -12743,6 +12852,11 @@ function prepareDocxQualityDocument(document, options = {}) {
12743
12852
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
12744
12853
  };
12745
12854
  };
12855
+ const annotatedInvocations = new Set(
12856
+ blockSlotRoles2(themed.document, expanded.blocks).filter(
12857
+ (slot) => (slot.role === "takeaway" || slot.role === "source") && typeof slot.value === "string" && slot.value.trim() !== ""
12858
+ ).map((slot) => slot.invocation)
12859
+ );
12746
12860
  for (const budget of blockSlotBudgets(themed.document, expanded.blocks)) {
12747
12861
  addFact({
12748
12862
  id: `docx:block-slot:${budget.path}`,
@@ -12750,6 +12864,38 @@ function prepareDocxQualityDocument(document, options = {}) {
12750
12864
  ...budget
12751
12865
  });
12752
12866
  }
12867
+ for (const role of blockSlotRoles2(themed.document, expanded.blocks)) {
12868
+ addFact({
12869
+ id: `docx:chrome-slot:${role.path}`,
12870
+ kind: "docx/chrome-slot",
12871
+ path: role.path,
12872
+ relatedPaths: [role.invocation],
12873
+ block: role.block,
12874
+ slot: role.slot,
12875
+ role: role.role,
12876
+ present: slotIsFilled(role.value),
12877
+ invocation: role.invocation
12878
+ });
12879
+ }
12880
+ const topLevel = Array.isArray(context.document.children) ? context.document.children : [];
12881
+ let inherited = {};
12882
+ topLevel.forEach((node, index) => {
12883
+ if (node?.name !== "section") return;
12884
+ const props = asRecord(node.props) ?? {};
12885
+ const part = (kind) => props[kind] === "linkToPrevious" ? inherited[kind] : props[kind];
12886
+ const header = part("header");
12887
+ const footer = part("footer");
12888
+ inherited = { header, footer };
12889
+ addFact({
12890
+ id: `docx:section-chrome:${index}`,
12891
+ kind: "docx/section-chrome",
12892
+ path: `/children/${index}`,
12893
+ index,
12894
+ header: partPresent(header),
12895
+ footer: partPresent(footer),
12896
+ pageNumber: hasPageField(header) || hasPageField(footer)
12897
+ });
12898
+ });
12753
12899
  const paletteHexes = {};
12754
12900
  const visualColors = designColors2(
12755
12901
  resolved.theme.colors,
@@ -12854,7 +13000,27 @@ function prepareDocxQualityDocument(document, options = {}) {
12854
13000
  }
12855
13001
  if (node.name === "chart" || node.name === "highcharts") {
12856
13002
  const fact = chartFact(node, props, path4, paletteTokens);
12857
- if (fact) addFact(fact);
13003
+ if (fact) {
13004
+ const authored = authoredPath(path4);
13005
+ const invocation = expanded.blocks.filter(
13006
+ (pointer) => authored === pointer || authored.startsWith(`${pointer}/`)
13007
+ ).sort((a, b) => b.length - a.length)[0];
13008
+ const annotated = invocation !== void 0 && annotatedInvocations.has(invocation);
13009
+ addFact({
13010
+ ...fact,
13011
+ generated: !["chart", "highcharts"].includes(
13012
+ String(asRecord(nodeAtPointer(themed.document, authored))?.name)
13013
+ ),
13014
+ seriesColorsPath: authoredPath(fact.seriesColorsPath),
13015
+ ...fact.annotation && {
13016
+ annotation: {
13017
+ ...fact.annotation,
13018
+ path: authoredPath(fact.annotation.path),
13019
+ stated: fact.annotation.stated || annotated
13020
+ }
13021
+ }
13022
+ });
13023
+ }
12858
13024
  }
12859
13025
  if (node.name === "paragraph" || node.name === "text-box") {
12860
13026
  const floating = asRecord(props.floating);
@@ -12924,6 +13090,17 @@ function prepareDocxQualityDocument(document, options = {}) {
12924
13090
  }
12925
13091
  };
12926
13092
  }
13093
+ function slotIsFilled(value) {
13094
+ if (typeof value === "string") return value.trim() !== "";
13095
+ return value !== void 0 && value !== null && value !== false && (!Array.isArray(value) || value.length > 0);
13096
+ }
13097
+ function partPresent(part) {
13098
+ return Array.isArray(part) && part.length > 0;
13099
+ }
13100
+ var PAGE_FIELD = /\{(PAGE|PAGE_NUMBER|TOTAL_PAGES|NUMPAGES)\}/;
13101
+ function hasPageField(part) {
13102
+ return Array.isArray(part) && PAGE_FIELD.test(JSON.stringify(part));
13103
+ }
12927
13104
 
12928
13105
  // src/core/generateFromIr.ts
12929
13106
  var UncompiledComponentError = class extends Error {
@@ -12976,7 +13153,8 @@ async function compileDocumentScoped(document, options, warnings) {
12976
13153
  ...options.services ? { services: options.services } : {},
12977
13154
  ...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
12978
13155
  ...visualFonts.length > 0 ? { visualFonts } : {},
12979
- ...chartFonts.length > 0 ? { chartFonts } : {}
13156
+ ...chartFonts.length > 0 ? { chartFonts } : {},
13157
+ warnings
12980
13158
  });
12981
13159
  const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
12982
13160
  const structure = await processResolvedDocument(
@@ -13909,7 +14087,7 @@ var docxChartRule = {
13909
14087
  }
13910
14088
  };
13911
14089
  function seriesColorFix(fact) {
13912
- if (fact.componentName !== "chart") return void 0;
14090
+ if (fact.componentName !== "chart" || fact.generated) return void 0;
13913
14091
  if (fact.seriesCount < 1 || fact.paletteTokens.length === 0) return void 0;
13914
14092
  const tokens = Array.from(
13915
14093
  { length: fact.seriesCount },
@@ -13970,6 +14148,67 @@ function alignColumnRight(column) {
13970
14148
  }
13971
14149
  return operations;
13972
14150
  }
14151
+ function stringListParameter(parameters, name) {
14152
+ const value = parameters[name];
14153
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
14154
+ }
14155
+ var docxRequiredChromeRule = {
14156
+ id: "docx/required-chrome",
14157
+ code: QUALITY_CODES.CHROME_MISSING,
14158
+ category: "consistency",
14159
+ defaultSeverity: "warning",
14160
+ defaultCertainty: "deterministic",
14161
+ formats: ["docx"],
14162
+ defaultParameters: { required: [] },
14163
+ evaluate: ({ facts, configuration, profile }) => {
14164
+ const required = stringListParameter(configuration.parameters, "required");
14165
+ if (required.length === 0) return [];
14166
+ return facts.filter(
14167
+ (fact) => fact.kind === "docx/chrome-slot"
14168
+ ).filter((fact) => required.includes(fact.role) && !fact.present).map((fact) => ({
14169
+ path: fact.path,
14170
+ relatedPaths: [fact.invocation],
14171
+ message: `${fact.block} states no ${fact.role} in its "${fact.slot}" slot; the ${profile?.id ?? "selected"} profile expects one on every ${fact.block}.`,
14172
+ suggestion: `Fill the "${fact.slot}" slot. The theme already styles it.`,
14173
+ context: { block: fact.block, slot: fact.slot, role: fact.role }
14174
+ }));
14175
+ }
14176
+ };
14177
+ var SECTION_CHROME_PARTS = ["header", "footer", "pageNumber"];
14178
+ var docxRunningHeadRule = {
14179
+ id: "docx/running-head",
14180
+ code: QUALITY_CODES.CHROME_MISSING,
14181
+ category: "consistency",
14182
+ defaultSeverity: "warning",
14183
+ defaultCertainty: "deterministic",
14184
+ formats: ["docx"],
14185
+ defaultParameters: { required: [], fromSection: 1 },
14186
+ evaluate: ({ facts, configuration, profile }) => {
14187
+ const required = stringListParameter(
14188
+ configuration.parameters,
14189
+ "required"
14190
+ ).filter(
14191
+ (part) => SECTION_CHROME_PARTS.includes(part)
14192
+ );
14193
+ if (required.length === 0) return [];
14194
+ const from = numberParameter(configuration.parameters, "fromSection", 1);
14195
+ return facts.filter(
14196
+ (fact) => fact.kind === "docx/section-chrome" && fact.index >= from
14197
+ ).flatMap((fact) => {
14198
+ const missing = required.filter((part) => !fact[part]);
14199
+ if (missing.length === 0) return [];
14200
+ const parts = missing.map((part) => part === "pageNumber" ? "page-number field" : part).join(", ");
14201
+ return [
14202
+ {
14203
+ path: fact.path,
14204
+ message: `Section ${fact.index + 1} carries no ${parts}; the ${profile?.id ?? "selected"} profile expects a running head on every section after the cover.`,
14205
+ 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.",
14206
+ context: { section: fact.index, missing }
14207
+ }
14208
+ ];
14209
+ });
14210
+ }
14211
+ };
13973
14212
  var DOCX_QUALITY_RULES = {
13974
14213
  id: "docx/default",
13975
14214
  rules: [
@@ -13984,10 +14223,29 @@ var DOCX_QUALITY_RULES = {
13984
14223
  docxChartRule,
13985
14224
  docxTableDesignRule,
13986
14225
  docxFontCountRule,
13987
- docxPaletteRule
14226
+ docxPaletteRule,
14227
+ docxRequiredChromeRule,
14228
+ docxRunningHeadRule
13988
14229
  ]
13989
14230
  };
13990
14231
  var DOCX_QUALITY_PROFILES = {
14232
+ "client-report": {
14233
+ id: "client-report",
14234
+ formats: ["docx"],
14235
+ 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.",
14236
+ rules: {
14237
+ "docx/required-chrome": {
14238
+ parameters: { required: ["takeaway", "source"] }
14239
+ },
14240
+ "docx/running-head": {
14241
+ parameters: {
14242
+ required: ["header", "footer", "pageNumber"],
14243
+ fromSection: 1
14244
+ }
14245
+ },
14246
+ "docx/heading-hierarchy": { severity: "warning" }
14247
+ }
14248
+ },
13991
14249
  "executive-report": {
13992
14250
  id: "executive-report",
13993
14251
  formats: ["docx"],
@@ -14009,6 +14267,11 @@ var DOCX_QUALITY_PROFILES = {
14009
14267
  };
14010
14268
  var DOCX_DEFAULT_QUALITY_PROFILE = DOCX_QUALITY_PROFILES["technical-report"];
14011
14269
  var DOCX_PROFILES_BY_ID = DOCX_QUALITY_PROFILES;
14270
+ function declaredDocxQualityProfile(document) {
14271
+ const props = document?.props;
14272
+ const id = props?.qualityProfile;
14273
+ return typeof id === "string" && Object.prototype.hasOwnProperty.call(DOCX_PROFILES_BY_ID, id) ? DOCX_PROFILES_BY_ID[id] : void 0;
14274
+ }
14012
14275
  function resolveDocxQualityProfile(requested) {
14013
14276
  if (!requested) return void 0;
14014
14277
  const registered = DOCX_PROFILES_BY_ID[requested.id];
@@ -14056,7 +14319,7 @@ function analyzeDocxQuality(doc, options = {}) {
14056
14319
  );
14057
14320
  }
14058
14321
  return docxQualityEngine.analyzeSync(prepared, {
14059
- profile: resolveDocxQualityProfile(options.profile) ?? DOCX_DEFAULT_QUALITY_PROFILE,
14322
+ profile: resolveDocxQualityProfile(options.profile) ?? declaredDocxQualityProfile(doc) ?? DOCX_DEFAULT_QUALITY_PROFILE,
14060
14323
  policy: options.policy
14061
14324
  });
14062
14325
  }
@@ -14166,17 +14429,655 @@ function formatWarningsText(warnings) {
14166
14429
 
14167
14430
  // src/index.ts
14168
14431
  init_styles();
14432
+
14433
+ // src/blueprints/index.ts
14434
+ import {
14435
+ blockDependencies,
14436
+ validateBlueprint
14437
+ } from "@json-to-office/shared";
14438
+ import { collectPlaceholders as collectPlaceholders2 } from "@json-to-office/quality";
14439
+ import { existsSync, readdirSync, readFileSync as readFileSync2 } from "fs";
14440
+ import { fileURLToPath as fileURLToPath2 } from "url";
14441
+ import { dirname as dirname2, join as join2 } from "path";
14442
+
14443
+ // src/templates/blueprints/client-report.docx.blueprint.json
14444
+ var client_report_docx_blueprint_default = {
14445
+ id: "client-report",
14446
+ format: "docx",
14447
+ title: "Client report",
14448
+ 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.",
14449
+ 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.",
14450
+ theme: "consulting",
14451
+ profile: "client-report",
14452
+ definitions: "client-report-blocks.docx.json",
14453
+ numbering: "sections",
14454
+ toc: false,
14455
+ variants: {
14456
+ "data-heavy": {
14457
+ 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.",
14458
+ whenToUse: "The brief comes with numbers: performance, finance, operations, anything the reader will check against a figure.",
14459
+ pages: {
14460
+ min: 4,
14461
+ max: 8
14462
+ },
14463
+ metadata: {
14464
+ title: "{{Title: as on the cover}}",
14465
+ author: "{{Author or team}}",
14466
+ company: "{{Client name}}",
14467
+ date: "{{Month YYYY}}"
14468
+ },
14469
+ children: [
14470
+ {
14471
+ name: "section",
14472
+ children: [
14473
+ {
14474
+ name: "block",
14475
+ props: {
14476
+ ref: "cover",
14477
+ slots: {
14478
+ title: "{{Title: the report's conclusion in one sentence}}",
14479
+ subtitle: "{{Subtitle: what the report answers, for whom}}",
14480
+ client: "{{Client name}}",
14481
+ date: "{{Month YYYY}}",
14482
+ confidentiality: "{{Confidentiality: Confidential, or For internal use}}"
14483
+ }
14484
+ }
14485
+ }
14486
+ ]
14487
+ },
14488
+ {
14489
+ name: "section",
14490
+ children: [
14491
+ {
14492
+ name: "block",
14493
+ props: {
14494
+ ref: "running-head",
14495
+ slots: {
14496
+ confidentiality: "{{Confidentiality: as on the cover}}",
14497
+ date: "{{Month YYYY}}"
14498
+ }
14499
+ }
14500
+ },
14501
+ {
14502
+ name: "block",
14503
+ props: {
14504
+ ref: "section-opener",
14505
+ slots: {
14506
+ number: "01",
14507
+ title: "{{Section title: the finding, as a statement}}",
14508
+ tracker: "{{Tracker: one or two words}}"
14509
+ }
14510
+ }
14511
+ },
14512
+ {
14513
+ name: "block",
14514
+ props: {
14515
+ ref: "key-takeaways",
14516
+ slots: {
14517
+ items: [
14518
+ "{{Takeaway 1: one claim, one sentence, with its number}}",
14519
+ "{{Takeaway 2: one claim, one sentence, with its number}}",
14520
+ "{{Takeaway 3: the recommendation that follows}}"
14521
+ ]
14522
+ }
14523
+ }
14524
+ },
14525
+ {
14526
+ name: "block",
14527
+ props: {
14528
+ ref: "kpi-row",
14529
+ slots: {
14530
+ items: [
14531
+ {
14532
+ value: "{{0.0}}",
14533
+ label: "{{What it measures}}"
14534
+ },
14535
+ {
14536
+ value: "{{0.0}}",
14537
+ label: "{{What it measures}}"
14538
+ },
14539
+ {
14540
+ value: "{{0.0}}",
14541
+ label: "{{What it measures}}"
14542
+ }
14543
+ ],
14544
+ source: "{{Source: system or document, date}}"
14545
+ }
14546
+ }
14547
+ },
14548
+ {
14549
+ name: "paragraph",
14550
+ props: {
14551
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14552
+ }
14553
+ }
14554
+ ]
14555
+ },
14556
+ {
14557
+ name: "section",
14558
+ children: [
14559
+ {
14560
+ name: "block",
14561
+ props: {
14562
+ ref: "section-opener",
14563
+ slots: {
14564
+ number: "02",
14565
+ title: "{{Section title: the finding, as a statement}}",
14566
+ tracker: "{{Tracker: one or two words}}"
14567
+ }
14568
+ }
14569
+ },
14570
+ {
14571
+ name: "paragraph",
14572
+ props: {
14573
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14574
+ }
14575
+ },
14576
+ {
14577
+ name: "block",
14578
+ props: {
14579
+ ref: "chart-figure",
14580
+ slots: {
14581
+ chart: {
14582
+ name: "highcharts",
14583
+ props: {
14584
+ width: "100%",
14585
+ options: {
14586
+ chart: {
14587
+ type: "column",
14588
+ width: 900,
14589
+ height: 460
14590
+ },
14591
+ title: {
14592
+ text: null
14593
+ },
14594
+ xAxis: {
14595
+ categories: [
14596
+ "{{Period 1}}",
14597
+ "{{Period 2}}",
14598
+ "{{Period 3}}"
14599
+ ]
14600
+ },
14601
+ yAxis: {
14602
+ title: {
14603
+ text: "{{Measure (unit)}}"
14604
+ }
14605
+ },
14606
+ series: [
14607
+ {
14608
+ name: "{{Series name}}",
14609
+ data: [0, 0, 0]
14610
+ }
14611
+ ]
14612
+ }
14613
+ }
14614
+ },
14615
+ caption: "{{Caption: what the chart shows, as a statement}}",
14616
+ takeaway: "{{Takeaway: the one sentence the reader should keep}}",
14617
+ source: "{{Source: system or document, date}}"
14618
+ }
14619
+ }
14620
+ },
14621
+ {
14622
+ name: "paragraph",
14623
+ props: {
14624
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14625
+ }
14626
+ }
14627
+ ]
14628
+ },
14629
+ {
14630
+ name: "section",
14631
+ children: [
14632
+ {
14633
+ name: "block",
14634
+ props: {
14635
+ ref: "section-opener",
14636
+ slots: {
14637
+ number: "03",
14638
+ title: "{{Section title: the finding, as a statement}}",
14639
+ tracker: "{{Tracker: one or two words}}"
14640
+ }
14641
+ }
14642
+ },
14643
+ {
14644
+ name: "paragraph",
14645
+ props: {
14646
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14647
+ }
14648
+ },
14649
+ {
14650
+ name: "block",
14651
+ props: {
14652
+ ref: "data-table",
14653
+ slots: {
14654
+ title: "{{Table title: what it compares, and the period}}",
14655
+ labelHeader: "{{Row label}}",
14656
+ labels: ["{{Row 1}}", "{{Row 2}}", "{{Row 3}}"],
14657
+ columns: [
14658
+ {
14659
+ header: "{{Measure (unit)}}",
14660
+ cells: ["{{0.0}}", "{{0.0}}", "{{0.0}}"]
14661
+ },
14662
+ {
14663
+ header: "{{Change}}",
14664
+ cells: ["{{0.0}}", "{{0.0}}", "{{0.0}}"]
14665
+ }
14666
+ ],
14667
+ source: "{{Source: system or document, date}}"
14668
+ }
14669
+ }
14670
+ },
14671
+ {
14672
+ name: "block",
14673
+ props: {
14674
+ ref: "callout",
14675
+ slots: {
14676
+ label: "{{Note label}}",
14677
+ text: "{{Note: a caveat about the numbers, a definition or the method}}"
14678
+ }
14679
+ }
14680
+ }
14681
+ ]
14682
+ },
14683
+ {
14684
+ name: "section",
14685
+ children: [
14686
+ {
14687
+ name: "block",
14688
+ props: {
14689
+ ref: "section-opener",
14690
+ slots: {
14691
+ number: "04",
14692
+ title: "{{Section title: the finding, as a statement}}",
14693
+ tracker: "{{Tracker: one or two words}}"
14694
+ }
14695
+ }
14696
+ },
14697
+ {
14698
+ name: "paragraph",
14699
+ props: {
14700
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14701
+ }
14702
+ },
14703
+ {
14704
+ name: "block",
14705
+ props: {
14706
+ ref: "footnotes"
14707
+ }
14708
+ }
14709
+ ]
14710
+ }
14711
+ ]
14712
+ },
14713
+ narrative: {
14714
+ description: "Argument-led: takeaways, then three sections of prose with one KPI row and one note as evidence, next steps last.",
14715
+ whenToUse: "The brief is a position, an assessment or a recommendation with few numbers; the reader follows an argument.",
14716
+ pages: {
14717
+ min: 3,
14718
+ max: 6
14719
+ },
14720
+ metadata: {
14721
+ title: "{{Title: as on the cover}}",
14722
+ author: "{{Author or team}}",
14723
+ company: "{{Client name}}",
14724
+ date: "{{Month YYYY}}"
14725
+ },
14726
+ children: [
14727
+ {
14728
+ name: "section",
14729
+ children: [
14730
+ {
14731
+ name: "block",
14732
+ props: {
14733
+ ref: "cover",
14734
+ slots: {
14735
+ title: "{{Title: the report's conclusion in one sentence}}",
14736
+ subtitle: "{{Subtitle: what the report answers, for whom}}",
14737
+ client: "{{Client name}}",
14738
+ date: "{{Month YYYY}}",
14739
+ confidentiality: "{{Confidentiality: Confidential, or For internal use}}"
14740
+ }
14741
+ }
14742
+ }
14743
+ ]
14744
+ },
14745
+ {
14746
+ name: "section",
14747
+ children: [
14748
+ {
14749
+ name: "block",
14750
+ props: {
14751
+ ref: "running-head",
14752
+ slots: {
14753
+ confidentiality: "{{Confidentiality: as on the cover}}",
14754
+ date: "{{Month YYYY}}"
14755
+ }
14756
+ }
14757
+ },
14758
+ {
14759
+ name: "block",
14760
+ props: {
14761
+ ref: "section-opener",
14762
+ slots: {
14763
+ number: "01",
14764
+ title: "{{Section title: the finding, as a statement}}",
14765
+ tracker: "{{Tracker: one or two words}}"
14766
+ }
14767
+ }
14768
+ },
14769
+ {
14770
+ name: "block",
14771
+ props: {
14772
+ ref: "key-takeaways",
14773
+ slots: {
14774
+ items: [
14775
+ "{{Takeaway 1: one claim, one sentence, with its number}}",
14776
+ "{{Takeaway 2: one claim, one sentence, with its number}}",
14777
+ "{{Takeaway 3: the recommendation that follows}}"
14778
+ ]
14779
+ }
14780
+ }
14781
+ },
14782
+ {
14783
+ name: "paragraph",
14784
+ props: {
14785
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14786
+ }
14787
+ },
14788
+ {
14789
+ name: "paragraph",
14790
+ props: {
14791
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14792
+ }
14793
+ }
14794
+ ]
14795
+ },
14796
+ {
14797
+ name: "section",
14798
+ children: [
14799
+ {
14800
+ name: "block",
14801
+ props: {
14802
+ ref: "section-opener",
14803
+ slots: {
14804
+ number: "02",
14805
+ title: "{{Section title: the finding, as a statement}}",
14806
+ tracker: "{{Tracker: one or two words}}"
14807
+ }
14808
+ }
14809
+ },
14810
+ {
14811
+ name: "paragraph",
14812
+ props: {
14813
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14814
+ }
14815
+ },
14816
+ {
14817
+ name: "block",
14818
+ props: {
14819
+ ref: "callout",
14820
+ slots: {
14821
+ label: "{{Note label}}",
14822
+ text: "{{Note: a caveat about the numbers, a definition or the method}}"
14823
+ }
14824
+ }
14825
+ },
14826
+ {
14827
+ name: "paragraph",
14828
+ props: {
14829
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14830
+ }
14831
+ }
14832
+ ]
14833
+ },
14834
+ {
14835
+ name: "section",
14836
+ children: [
14837
+ {
14838
+ name: "block",
14839
+ props: {
14840
+ ref: "section-opener",
14841
+ slots: {
14842
+ number: "03",
14843
+ title: "{{Section title: the finding, as a statement}}",
14844
+ tracker: "{{Tracker: one or two words}}"
14845
+ }
14846
+ }
14847
+ },
14848
+ {
14849
+ name: "paragraph",
14850
+ props: {
14851
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14852
+ }
14853
+ },
14854
+ {
14855
+ name: "block",
14856
+ props: {
14857
+ ref: "kpi-row",
14858
+ slots: {
14859
+ items: [
14860
+ {
14861
+ value: "{{0.0}}",
14862
+ label: "{{What it measures}}"
14863
+ },
14864
+ {
14865
+ value: "{{0.0}}",
14866
+ label: "{{What it measures}}"
14867
+ },
14868
+ {
14869
+ value: "{{0.0}}",
14870
+ label: "{{What it measures}}"
14871
+ }
14872
+ ],
14873
+ source: "{{Source: system or document, date}}"
14874
+ }
14875
+ }
14876
+ },
14877
+ {
14878
+ name: "paragraph",
14879
+ props: {
14880
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14881
+ }
14882
+ }
14883
+ ]
14884
+ },
14885
+ {
14886
+ name: "section",
14887
+ children: [
14888
+ {
14889
+ name: "block",
14890
+ props: {
14891
+ ref: "section-opener",
14892
+ slots: {
14893
+ number: "04",
14894
+ title: "{{Section title: the finding, as a statement}}",
14895
+ tracker: "{{Tracker: one or two words}}"
14896
+ }
14897
+ }
14898
+ },
14899
+ {
14900
+ name: "paragraph",
14901
+ props: {
14902
+ text: "{{Body: two or three short paragraphs of evidence for the section title, each opening with its claim}}"
14903
+ }
14904
+ },
14905
+ {
14906
+ name: "block",
14907
+ props: {
14908
+ ref: "footnotes"
14909
+ }
14910
+ }
14911
+ ]
14912
+ }
14913
+ ]
14914
+ }
14915
+ }
14916
+ };
14917
+
14918
+ // src/blueprints/index.ts
14919
+ function register(...candidates) {
14920
+ const registry2 = {};
14921
+ for (const candidate of candidates) {
14922
+ const issues = validateBlueprint(candidate);
14923
+ if (issues.length > 0)
14924
+ throw new Error(
14925
+ `Invalid bundled blueprint: ${issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`
14926
+ );
14927
+ const blueprint = candidate;
14928
+ if (blueprint.format !== "docx")
14929
+ throw new Error(`Blueprint ${blueprint.id} is not a DOCX blueprint.`);
14930
+ if (registry2[blueprint.id])
14931
+ throw new Error(
14932
+ `Two bundled blueprints share the id "${blueprint.id}"; the later one would silently replace the earlier.`
14933
+ );
14934
+ registry2[blueprint.id] = blueprint;
14935
+ }
14936
+ return registry2;
14937
+ }
14938
+ function scanned(known) {
14939
+ let here;
14940
+ try {
14941
+ here = dirname2(fileURLToPath2(import.meta.url));
14942
+ } catch {
14943
+ return [];
14944
+ }
14945
+ const directory = [
14946
+ join2(here, "../templates/blueprints"),
14947
+ join2(here, "templates/blueprints")
14948
+ ].find((path4) => existsSync(path4));
14949
+ if (!directory) return [];
14950
+ return readdirSync(directory).filter((file) => file.endsWith(".docx.blueprint.json")).map((file) => JSON.parse(readFileSync2(join2(directory, file), "utf8"))).filter((candidate) => {
14951
+ const id = candidate?.id;
14952
+ return !(typeof id === "string" && id in known);
14953
+ });
14954
+ }
14955
+ var bundled = register(client_report_docx_blueprint_default);
14956
+ var DOCX_BLUEPRINTS = {
14957
+ ...bundled,
14958
+ ...register(...scanned(bundled))
14959
+ };
14960
+ function docxBlueprint(id) {
14961
+ return DOCX_BLUEPRINTS[id];
14962
+ }
14963
+ var MARKER = /^\{\{\s*([\s\S]*?)\s*\}\}$/;
14964
+ function instantiateDocxBlueprint(blueprint, options) {
14965
+ if (blueprint.format !== "docx")
14966
+ throw new Error(
14967
+ `Blueprint ${blueprint.id} is a ${blueprint.format} blueprint; this instantiates DOCX ones.`
14968
+ );
14969
+ const variantId = options.variant ?? Object.keys(blueprint.variants)[0];
14970
+ const variant = blueprint.variants[variantId];
14971
+ if (!variant)
14972
+ throw new Error(
14973
+ `Blueprint ${blueprint.id} has no variant "${variantId}"; it has ${Object.keys(
14974
+ blueprint.variants
14975
+ ).join(", ")}.`
14976
+ );
14977
+ const children = structuredClone(variant.children);
14978
+ const blocks = definitionsFor(children, options.definitions, blueprint);
14979
+ const document = {
14980
+ name: "docx",
14981
+ props: {
14982
+ theme: options.theme ?? blueprint.theme,
14983
+ qualityProfile: blueprint.profile,
14984
+ ...Object.keys(blocks).length > 0 && { blocks },
14985
+ metadata: { ...variant.metadata, ...options.metadata }
14986
+ },
14987
+ children
14988
+ };
14989
+ return { document, fillMap: fillMap(document, blocks), variant: variantId };
14990
+ }
14991
+ function definitionsFor(children, available, blueprint) {
14992
+ const refs = /* @__PURE__ */ new Set();
14993
+ const visit = (node) => {
14994
+ if (Array.isArray(node)) return node.forEach(visit);
14995
+ if (!node || typeof node !== "object") return;
14996
+ const record = node;
14997
+ const props = record.props;
14998
+ if (record.name === "block" && typeof props?.ref === "string")
14999
+ refs.add(props.ref);
15000
+ Object.values(record).forEach(visit);
15001
+ };
15002
+ visit(children);
15003
+ const result = {};
15004
+ for (const ref of refs) {
15005
+ if (!available[ref])
15006
+ throw new Error(
15007
+ `Blueprint ${blueprint.id} invokes "${ref}", which ${blueprint.definitions} does not define.`
15008
+ );
15009
+ for (const name of [...blockDependencies(available, ref), ref])
15010
+ result[name] ??= structuredClone(available[name]);
15011
+ }
15012
+ return result;
15013
+ }
15014
+ function fillMap(document, blocks) {
15015
+ return collectPlaceholders2(document).filter((occurrence) => occurrence.match.kind === "scaffold-marker").map((occurrence) => {
15016
+ const guidance = occurrence.text.match(MARKER)?.[1] ?? occurrence.text;
15017
+ const base = { path: occurrence.path, marker: occurrence.text, guidance };
15018
+ if (occurrence.path.startsWith("/props/metadata/"))
15019
+ return { ...base, kind: "metadata" };
15020
+ const slot = slotAt(document, occurrence.path, blocks);
15021
+ return slot ? { ...base, kind: "slot", ...slot } : { ...base, kind: "text" };
15022
+ });
15023
+ }
15024
+ function slotAt(document, pointer, blocks) {
15025
+ const at = pointer.lastIndexOf("/props/slots/");
15026
+ if (at < 0) return void 0;
15027
+ const invocation = valueAt(document, pointer.slice(0, at));
15028
+ const ref = invocation?.props?.ref;
15029
+ if (typeof ref !== "string" || !blocks[ref]) return void 0;
15030
+ const segments = pointer.slice(at + "/props/slots/".length).split("/").map(unescape);
15031
+ let slot = blocks[ref].slots[segments[0]];
15032
+ const names = [segments[0]];
15033
+ for (const segment of segments.slice(1)) {
15034
+ if (!slot) break;
15035
+ if (slot.type === "array" && /^\d+$/.test(segment)) slot = slot.items;
15036
+ else if (slot.type === "object") {
15037
+ slot = slot.properties?.[segment];
15038
+ names.push(segment);
15039
+ } else if (slot.type === "component")
15040
+ break;
15041
+ else slot = void 0;
15042
+ }
15043
+ if (!slot) return void 0;
15044
+ return {
15045
+ block: ref,
15046
+ slot: names.join("."),
15047
+ type: slot.type,
15048
+ ...slot.maxWords !== void 0 && { maxWords: slot.maxWords },
15049
+ ...slot.maxLength !== void 0 && { maxLength: slot.maxLength },
15050
+ ...slot.oneLine !== void 0 && { oneLine: slot.oneLine },
15051
+ ...slot.required !== void 0 && { required: slot.required }
15052
+ };
15053
+ }
15054
+ function unescape(segment) {
15055
+ return segment.replace(/~1/g, "/").replace(/~0/g, "~");
15056
+ }
15057
+ function valueAt(root, pointer) {
15058
+ if (pointer === "") return root;
15059
+ let current = root;
15060
+ for (const segment of pointer.split("/").slice(1).map(unescape)) {
15061
+ if (Array.isArray(current)) current = current[Number(segment)];
15062
+ else if (current && typeof current === "object")
15063
+ current = current[segment];
15064
+ else return void 0;
15065
+ }
15066
+ return current;
15067
+ }
15068
+
15069
+ // src/index.ts
14169
15070
  init_json();
14170
15071
 
14171
15072
  // src/templates/documents/index.ts
14172
15073
  import fs4 from "fs";
14173
15074
  import path2 from "path";
14174
- import { fileURLToPath as fileURLToPath2 } from "url";
15075
+ import { fileURLToPath as fileURLToPath3 } from "url";
14175
15076
  var _dirname = null;
14176
15077
  function getDirname() {
14177
15078
  if (_dirname === null) {
14178
15079
  try {
14179
- _dirname = path2.dirname(fileURLToPath2(import.meta.url));
15080
+ _dirname = path2.dirname(fileURLToPath3(import.meta.url));
14180
15081
  } catch {
14181
15082
  throw new Error(
14182
15083
  "Cannot resolve file paths in a bundled environment. Example loading from disk is not available."
@@ -15062,6 +15963,7 @@ export {
15062
15963
  ComponentValidationError2 as ComponentValidationError,
15063
15964
  DocumentGenerator as CoreDocumentGenerator,
15064
15965
  DEFAULT_DOCX_RENDERER_ID,
15966
+ DOCX_BLUEPRINTS,
15065
15967
  DOCX_DEFAULT_QUALITY_PROFILE,
15066
15968
  DOCX_QUALITY_PROFILES,
15067
15969
  DOCX_QUALITY_RULES,
@@ -15078,7 +15980,9 @@ export {
15078
15980
  createDocumentGenerator,
15079
15981
  createMinimalTheme,
15080
15982
  createVersion,
15983
+ declaredDocxQualityProfile,
15081
15984
  devportalTheme,
15985
+ docxBlueprint,
15082
15986
  docxQualityEngine,
15083
15987
  docxRendererIds,
15084
15988
  docxRendererStatuses,
@@ -15103,6 +16007,7 @@ export {
15103
16007
  getExampleNames,
15104
16008
  getVisualPrepassStats,
15105
16009
  hasNodeBuiltins,
16010
+ instantiateDocxBlueprint,
15106
16011
  isColumnsComponent,
15107
16012
  isDocxRendererId,
15108
16013
  isHeadingComponent,