@json-to-office/core-docx 3.2.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
@@ -5051,6 +5051,10 @@ function resolveServiceUrl(propsUrl, servicesUrl, defaultUrl) {
5051
5051
  const withScheme = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
5052
5052
  return withScheme.replace(/\/+$/, "");
5053
5053
  }
5054
+ var SERVICE_UNAVAILABLE_CODE = "SERVICE_UNAVAILABLE";
5055
+ function serviceUnavailable(message) {
5056
+ return Object.assign(new Error(message), { code: SERVICE_UNAVAILABLE_CODE });
5057
+ }
5054
5058
  async function postJsonToService(opts) {
5055
5059
  const resolvedHeaders = typeof opts.headers === "function" ? await opts.headers(opts.body) : opts.headers;
5056
5060
  const headers = {
@@ -5070,12 +5074,12 @@ async function postJsonToService(opts) {
5070
5074
  });
5071
5075
  } catch (error) {
5072
5076
  if (error?.name === "AbortError") {
5073
- throw new Error(
5077
+ throw serviceUnavailable(
5074
5078
  `${opts.serviceLabel} timed out after ${timeoutMs}ms at ${opts.url}.`
5075
5079
  );
5076
5080
  }
5077
5081
  const cause = error instanceof Error ? error.message : String(error);
5078
- throw new Error(opts.onUnreachable(opts.url, cause));
5082
+ throw serviceUnavailable(opts.onUnreachable(opts.url, cause));
5079
5083
  } finally {
5080
5084
  clearTimeout(timer);
5081
5085
  }
@@ -11770,6 +11774,7 @@ async function processResolvedDocument(document, resolved, themeName, generation
11770
11774
  };
11771
11775
  }
11772
11776
  function createDocumentMetadata(props, generationDate = /* @__PURE__ */ new Date()) {
11777
+ const parsed = props.metadata?.date ? new Date(props.metadata.date) : void 0;
11773
11778
  return {
11774
11779
  title: props.metadata?.title,
11775
11780
  subtitle: props.metadata?.subtitle,
@@ -11778,7 +11783,7 @@ function createDocumentMetadata(props, generationDate = /* @__PURE__ */ new Date
11778
11783
  company: props.metadata?.company,
11779
11784
  version: props.metadata?.version,
11780
11785
  tags: props.metadata?.tags,
11781
- date: props.metadata?.date ? new Date(props.metadata.date) : generationDate
11786
+ date: parsed && !Number.isNaN(parsed.getTime()) ? parsed : generationDate
11782
11787
  };
11783
11788
  }
11784
11789
  async function extractSections(components, context) {
@@ -11870,7 +11875,10 @@ import {
11870
11875
  normalizeHighchartsChart
11871
11876
  } from "@json-to-office/quality";
11872
11877
  import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
11873
- 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";
11874
11882
 
11875
11883
  // src/core/generationContext.ts
11876
11884
  import { applyExportMode } from "@json-to-office/shared";
@@ -12856,6 +12864,38 @@ function prepareDocxQualityDocument(document, options = {}) {
12856
12864
  ...budget
12857
12865
  });
12858
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
+ });
12859
12899
  const paletteHexes = {};
12860
12900
  const visualColors = designColors2(
12861
12901
  resolved.theme.colors,
@@ -13050,6 +13090,17 @@ function prepareDocxQualityDocument(document, options = {}) {
13050
13090
  }
13051
13091
  };
13052
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
+ }
13053
13104
 
13054
13105
  // src/core/generateFromIr.ts
13055
13106
  var UncompiledComponentError = class extends Error {
@@ -14097,6 +14148,67 @@ function alignColumnRight(column) {
14097
14148
  }
14098
14149
  return operations;
14099
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
+ };
14100
14212
  var DOCX_QUALITY_RULES = {
14101
14213
  id: "docx/default",
14102
14214
  rules: [
@@ -14111,10 +14223,29 @@ var DOCX_QUALITY_RULES = {
14111
14223
  docxChartRule,
14112
14224
  docxTableDesignRule,
14113
14225
  docxFontCountRule,
14114
- docxPaletteRule
14226
+ docxPaletteRule,
14227
+ docxRequiredChromeRule,
14228
+ docxRunningHeadRule
14115
14229
  ]
14116
14230
  };
14117
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
+ },
14118
14249
  "executive-report": {
14119
14250
  id: "executive-report",
14120
14251
  formats: ["docx"],
@@ -14136,6 +14267,11 @@ var DOCX_QUALITY_PROFILES = {
14136
14267
  };
14137
14268
  var DOCX_DEFAULT_QUALITY_PROFILE = DOCX_QUALITY_PROFILES["technical-report"];
14138
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
+ }
14139
14275
  function resolveDocxQualityProfile(requested) {
14140
14276
  if (!requested) return void 0;
14141
14277
  const registered = DOCX_PROFILES_BY_ID[requested.id];
@@ -14183,7 +14319,7 @@ function analyzeDocxQuality(doc, options = {}) {
14183
14319
  );
14184
14320
  }
14185
14321
  return docxQualityEngine.analyzeSync(prepared, {
14186
- profile: resolveDocxQualityProfile(options.profile) ?? DOCX_DEFAULT_QUALITY_PROFILE,
14322
+ profile: resolveDocxQualityProfile(options.profile) ?? declaredDocxQualityProfile(doc) ?? DOCX_DEFAULT_QUALITY_PROFILE,
14187
14323
  policy: options.policy
14188
14324
  });
14189
14325
  }
@@ -14293,17 +14429,655 @@ function formatWarningsText(warnings) {
14293
14429
 
14294
14430
  // src/index.ts
14295
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
14296
15070
  init_json();
14297
15071
 
14298
15072
  // src/templates/documents/index.ts
14299
15073
  import fs4 from "fs";
14300
15074
  import path2 from "path";
14301
- import { fileURLToPath as fileURLToPath2 } from "url";
15075
+ import { fileURLToPath as fileURLToPath3 } from "url";
14302
15076
  var _dirname = null;
14303
15077
  function getDirname() {
14304
15078
  if (_dirname === null) {
14305
15079
  try {
14306
- _dirname = path2.dirname(fileURLToPath2(import.meta.url));
15080
+ _dirname = path2.dirname(fileURLToPath3(import.meta.url));
14307
15081
  } catch {
14308
15082
  throw new Error(
14309
15083
  "Cannot resolve file paths in a bundled environment. Example loading from disk is not available."
@@ -15189,6 +15963,7 @@ export {
15189
15963
  ComponentValidationError2 as ComponentValidationError,
15190
15964
  DocumentGenerator as CoreDocumentGenerator,
15191
15965
  DEFAULT_DOCX_RENDERER_ID,
15966
+ DOCX_BLUEPRINTS,
15192
15967
  DOCX_DEFAULT_QUALITY_PROFILE,
15193
15968
  DOCX_QUALITY_PROFILES,
15194
15969
  DOCX_QUALITY_RULES,
@@ -15205,7 +15980,9 @@ export {
15205
15980
  createDocumentGenerator,
15206
15981
  createMinimalTheme,
15207
15982
  createVersion,
15983
+ declaredDocxQualityProfile,
15208
15984
  devportalTheme,
15985
+ docxBlueprint,
15209
15986
  docxQualityEngine,
15210
15987
  docxRendererIds,
15211
15988
  docxRendererStatuses,
@@ -15230,6 +16007,7 @@ export {
15230
16007
  getExampleNames,
15231
16008
  getVisualPrepassStats,
15232
16009
  hasNodeBuiltins,
16010
+ instantiateDocxBlueprint,
15233
16011
  isColumnsComponent,
15234
16012
  isDocxRendererId,
15235
16013
  isHeadingComponent,