@openpresentation/opf 0.26.1 → 0.26.3

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.
Files changed (52) hide show
  1. package/README.md +4 -0
  2. package/dist/browser.js +13 -14
  3. package/dist/{catalog-refs-CSHevLn1.d.ts → catalog-refs-BSfGELpt.d.ts} +1 -1
  4. package/dist/catalog-schemas-DoxA2BgY.d.ts +19 -0
  5. package/dist/{chunk-IC6OPLMU.js → chunk-2ZQBMZTZ.js} +2 -2
  6. package/dist/{chunk-JIHHLV47.js → chunk-34LIUOT7.js} +6 -6
  7. package/dist/{chunk-FITOGMR3.js → chunk-54OV2WQC.js} +1 -1
  8. package/dist/{chunk-IWFXFWGD.js → chunk-7RY7QWWJ.js} +1 -1
  9. package/dist/{chunk-GS37EBPQ.js → chunk-DEI2M2H6.js} +1 -1
  10. package/dist/{chunk-3OE6PZIC.js → chunk-FXNUM2PN.js} +4 -4
  11. package/dist/{chunk-WW62PPYV.js → chunk-GLLR3AQ5.js} +1 -2
  12. package/dist/{chunk-P7S3ORB4.js → chunk-NJWI6MPT.js} +1827 -68
  13. package/dist/{chunk-F5ABHDUX.js → chunk-PB5X2VSV.js} +4 -4
  14. package/dist/{chunk-LU6XM5JQ.js → chunk-PXOXTJTN.js} +1 -1
  15. package/dist/{chunk-MJ57RSYO.js → chunk-WASENX2R.js} +4 -4
  16. package/dist/{chunk-X7RANZWP.js → chunk-YVNFACTC.js} +1 -1
  17. package/dist/{composition-Daqvi-GV.d.ts → composition-DIftf6iP.d.ts} +1 -1
  18. package/dist/composition.d.ts +2 -2
  19. package/dist/{conversion-BFr5USlh.d.ts → conversion-BjsKgxBZ.d.ts} +5 -5
  20. package/dist/conversion-F4UCOGR7.js +18 -0
  21. package/dist/convert.d.ts +3 -3
  22. package/dist/convert.js +5 -5
  23. package/dist/{deck-report-DHaboWbM.d.ts → deck-report-5vwMGcQz.d.ts} +2 -2
  24. package/dist/deck.d.ts +5 -5
  25. package/dist/deck.js +8 -8
  26. package/dist/diff.js +2 -3
  27. package/dist/docs.js +6 -6
  28. package/dist/index.d.ts +13 -12
  29. package/dist/index.js +17 -18
  30. package/dist/markdown.d.ts +3 -3
  31. package/dist/markdown.js +5 -5
  32. package/dist/node-engine.d.ts +7 -7
  33. package/dist/node-engine.js +17 -18
  34. package/dist/{pagination-R8YFwVj7.d.ts → pagination-D_kJe4jH.d.ts} +2 -2
  35. package/dist/pagination.d.ts +3 -3
  36. package/dist/pagination.js +2 -2
  37. package/dist/patch.js +1 -2
  38. package/dist/repo-readme.js +1 -1
  39. package/dist/schema.d.ts +165 -0
  40. package/dist/schema.js +3 -0
  41. package/dist/source.d.ts +97 -0
  42. package/dist/source.js +90 -0
  43. package/dist/types.d.ts +1 -1
  44. package/dist/{validation-types-JW8e06ee.d.ts → validation-types-3ARvTsB0.d.ts} +2 -2
  45. package/dist/validator.d.ts +54 -6
  46. package/dist/validator.js +1 -1
  47. package/dist/yaml.d.ts +3 -3
  48. package/dist/yaml.js +5 -5
  49. package/package.json +13 -5
  50. package/dist/chunk-IY5SCRTV.js +0 -261
  51. package/dist/conversion-ETAHLWAH.js +0 -19
  52. package/dist/validator-Dym2zYVK.d.ts +0 -67
@@ -1311,8 +1311,8 @@ function colorChoice(levels, scheme, variables = {}) {
1311
1311
  const variant = normalizeColorVariant(level.variant);
1312
1312
  if (variant) {
1313
1313
  const fill = definition !== void 0 && !variantOnly && !themeSlot && !isPicture ? decisionColor(definition, scheme, variables) : void 0;
1314
- const own2 = definition !== void 0 && !variantOnly && !themeSlot;
1315
- return { variant, ...fill ? { background: fill } : {}, drawn: variantOnly ? picture : { definition: own2 ? definition : void 0 } };
1314
+ const own4 = definition !== void 0 && !variantOnly && !themeSlot;
1315
+ return { variant, ...fill ? { background: fill } : {}, drawn: variantOnly ? picture : { definition: own4 ? definition : void 0 } };
1316
1316
  }
1317
1317
  if (variantOnly || definition === void 0 || definition === null || definition === false) continue;
1318
1318
  if (themeSlot) return { variant: { background: themeSlot } };
@@ -1328,20 +1328,20 @@ function colorChoice(levels, scheme, variables = {}) {
1328
1328
  return {};
1329
1329
  }
1330
1330
  function resolveDesign(document, index, context) {
1331
- const deck = rec(document.design), own2 = rec(rec(rec(document.slides)[index]).design);
1331
+ const deck = rec(document.design), own4 = rec(rec(rec(document.slides)[index]).design);
1332
1332
  const { options, diagnostics, resolved } = context;
1333
1333
  const { theme, colorScheme, fontScheme, fontSchemePath } = resolved;
1334
1334
  const fontPath = `/${fontSchemePath.split(".").join("/")}`;
1335
1335
  const fontDiagnostic = diagnostics.find((diagnostic) => diagnostic.kind === "fontSchemes");
1336
1336
  const dimensions = { width: options.width, height: options.height };
1337
1337
  const variables = rec(document.variables);
1338
- const choice = colorChoice([own2, deck, theme], colorScheme, variables);
1338
+ const choice = colorChoice([own4, deck, theme], colorScheme, variables);
1339
1339
  const roles = resolveColorRoles(colorScheme, { background: choice.background, variant: choice.variant });
1340
1340
  const dark = roles.dark;
1341
1341
  const text = roles.text;
1342
- const backdrop = backdropOf(choice.drawn ? choice.drawn.definition : own2.background ?? deck.background ?? theme.background, colorScheme, text, variables, roles.map.bg1 === "light1" ? void 0 : roles.background);
1343
- const picture = imageBackground(choice.drawn ? choice.drawn.definition : own2.background ?? deck.background ?? theme.background);
1344
- const backgroundImage = picture && own2.background !== void 0 ? { source: picture.src, fit: picture.fit, path: `/slides/${index}/design/background` } : picture && deck.background !== void 0 ? { source: picture.src, fit: picture.fit, path: "/design/background" } : void 0;
1342
+ const backdrop = backdropOf(choice.drawn ? choice.drawn.definition : own4.background ?? deck.background ?? theme.background, colorScheme, text, variables, roles.map.bg1 === "light1" ? void 0 : roles.background);
1343
+ const picture = imageBackground(choice.drawn ? choice.drawn.definition : own4.background ?? deck.background ?? theme.background);
1344
+ const backgroundImage = picture && own4.background !== void 0 ? { source: picture.src, fit: picture.fit, path: `/slides/${index}/design/background` } : picture && deck.background !== void 0 ? { source: picture.src, fit: picture.fit, path: "/design/background" } : void 0;
1345
1345
  return {
1346
1346
  theme,
1347
1347
  colorScheme,
@@ -1525,8 +1525,8 @@ function runsOf(tv) {
1525
1525
  }));
1526
1526
  }
1527
1527
  function altOf(value, assets) {
1528
- const own2 = typeof value === "object" && value !== null ? value.alt : void 0;
1529
- if (typeof own2 === "string") return own2;
1528
+ const own4 = typeof value === "object" && value !== null ? value.alt : void 0;
1529
+ if (typeof own4 === "string") return own4;
1530
1530
  let source = typeof value === "string" ? value : rec(value).src;
1531
1531
  for (let hop = 0; typeof source === "string" && source.startsWith("asset:") && hop < 8; hop++) {
1532
1532
  const entry = rec(assets)[source.slice(6)];
@@ -2695,7 +2695,7 @@ var formatRules = [
2695
2695
  "error",
2696
2696
  "The text is not strict JSON.",
2697
2697
  "Every OPF tool reads JSON. A misplaced comma, quote, comment or trailing comma makes the file unreadable, and the range says where.",
2698
- { cost: "syntax", standard: "RFC 8259 (strict JSON: no comments, no trailing commas)", approximations: "Only text input is checked. A string is always read as JSON; a text that looks like YAML or Markdown gets a hint to use fromYaml or fromMarkdown first." }
2698
+ { cost: "syntax", standard: "RFC 8259 (strict JSON: no comments, no trailing commas)", approximations: "Only text input is checked. A string is always read as JSON. One finding per root cause, in position order and at most five (a key with no value, a missing comma, a bracket never closed; not the damage each one causes further on): `message` says what is wrong, `help` what to do next and `location` is the range to underline. A text that looks like YAML or Markdown gets one finding with a hint to use fromYaml or fromMarkdown first. Source editors get the same causes with their quick fixes from analyzeJsonSyntax (@openpresentation/opf/source)." }
2699
2699
  ),
2700
2700
  rule(
2701
2701
  "duplicate-key",
@@ -2932,6 +2932,7 @@ function issueFinding(issue2, ruleId = issueRuleId(issue2), schemaName = "presen
2932
2932
  else if (issue2.keyword === "opf")
2933
2933
  help = "Resolve the reported structural conflict while preserving every content field. Preview the result after editing.";
2934
2934
  const allowed = Array.isArray(issue2.params.allowedValues) ? issue2.params.allowedValues.filter(primitive) : [];
2935
+ const nearest = issue2.keyword === "additionalProperties" ? nearestProperties(issue2, entry.schema, precise?.pointer, precise?.schemaName) : [];
2935
2936
  return finding(ruleId, {
2936
2937
  ...severity ? { severity } : {},
2937
2938
  path,
@@ -2948,9 +2949,29 @@ function issueFinding(issue2, ruleId = issueRuleId(issue2), schemaName = "presen
2948
2949
  origin: "schema",
2949
2950
  definition
2950
2951
  }))
2951
- } : {}
2952
+ } : nearest.length ? { suggestions: nearest } : {}
2952
2953
  });
2953
2954
  }
2955
+ var NEAREST_PROPERTIES = 3;
2956
+ function nearestProperties(issue2, fallbackRoot, pointerToKeyword, schemaName) {
2957
+ const typed = issue2.params.additionalProperty;
2958
+ if (typeof typed !== "string" || pointerToKeyword === void 0) return [];
2959
+ const rootEntry = schemaEntries.find((entry) => entry.name === schemaName);
2960
+ let node = rootEntry?.schema ?? fallbackRoot;
2961
+ const base = pointerToKeyword.split("/").slice(0, -1);
2962
+ for (const token of base.slice(1)) {
2963
+ if (!isRecord(node)) return [];
2964
+ node = node[token.replaceAll("~1", "/").replaceAll("~0", "~")];
2965
+ }
2966
+ if (!isRecord(node) || !isRecord(node.properties)) return [];
2967
+ const root = rootEntry?.schema ?? fallbackRoot, limit = typed.length <= 3 ? 1 : 2, lower = typed.toLowerCase();
2968
+ return Object.keys(node.properties).map((name) => ({ name, away: distance(lower, name.toLowerCase()) })).filter(({ name, away }) => name !== typed && away <= limit).sort((a, b) => a.away - b.away || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)).slice(0, NEAREST_PROPERTIES).map(({ name }) => ({
2969
+ value: name,
2970
+ label: name,
2971
+ origin: "schema",
2972
+ definition: `${String(root.$id)}#${base.join("/")}/properties/${name.replaceAll("~", "~0").replaceAll("/", "~1")}`
2973
+ }));
2974
+ }
2954
2975
  function resolve(cursor, seen = /* @__PURE__ */ new Set()) {
2955
2976
  const ref = cursor.schema.$ref;
2956
2977
  if (typeof ref !== "string") return cursor;
@@ -3224,7 +3245,7 @@ function variableFindings(document, engine, schemaValid) {
3224
3245
  })
3225
3246
  );
3226
3247
  if (engine.template !== void 0 || !schemaValid) return findings;
3227
- const walk = (value, path, depth2) => {
3248
+ const walk2 = (value, path, depth2) => {
3228
3249
  if (depth2 > 64) return;
3229
3250
  if (typeof value === "string") {
3230
3251
  if (value.startsWith("data:") || !value.includes("{{")) return;
@@ -3242,17 +3263,17 @@ function variableFindings(document, engine, schemaValid) {
3242
3263
  }
3243
3264
  if (Array.isArray(value)) {
3244
3265
  value.forEach((entry, index) => {
3245
- walk(entry, [...path, String(index)], depth2 + 1);
3266
+ walk2(entry, [...path, String(index)], depth2 + 1);
3246
3267
  });
3247
3268
  return;
3248
3269
  }
3249
3270
  if (value && typeof value === "object")
3250
3271
  for (const [key2, entry] of Object.entries(value)) {
3251
3272
  if (path.length === 0 && ["variables", "extensions", "catalogs", "assets"].includes(key2)) continue;
3252
- walk(entry, [...path, key2], depth2 + 1);
3273
+ walk2(entry, [...path, key2], depth2 + 1);
3253
3274
  }
3254
3275
  };
3255
- walk(document, [], 0);
3276
+ walk2(document, [], 0);
3256
3277
  return findings;
3257
3278
  }
3258
3279
  function undeclaredCatalogFindings(document) {
@@ -3475,15 +3496,1766 @@ function contractFindings(document, contracts) {
3475
3496
  }
3476
3497
  return findings;
3477
3498
  }
3499
+
3500
+ // src/patch.ts
3501
+ var OPFPatchError = class extends Error {
3502
+ code;
3503
+ /** The JSON Pointer the failing operation addressed, when known. */
3504
+ path;
3505
+ /** The index of the failing operation within the patch, when known. */
3506
+ index;
3507
+ /** The failing operation, when known. */
3508
+ operation;
3509
+ constructor(code, message, details = {}) {
3510
+ super(message);
3511
+ this.name = "OPFPatchError";
3512
+ this.code = code;
3513
+ if (details.path !== void 0) this.path = details.path;
3514
+ if (details.index !== void 0) this.index = details.index;
3515
+ if (details.operation !== void 0) this.operation = details.operation;
3516
+ }
3517
+ };
3518
+ var OPFPatchValidationError = class extends OPFPatchError {
3519
+ validation;
3520
+ constructor(validation) {
3521
+ super("patch-invalid-document", "The patched document is not a valid OPF presentation.");
3522
+ this.name = "OPFPatchValidationError";
3523
+ this.validation = validation;
3524
+ }
3525
+ };
3526
+ var own2 = (value, key2) => Object.prototype.hasOwnProperty.call(value, key2);
3527
+ var isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
3528
+ var clone = (value) => structuredClone(value);
3529
+ function jsonEqual(a, b) {
3530
+ if (a === b) return true;
3531
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
3532
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
3533
+ if (Array.isArray(a)) {
3534
+ const other = b;
3535
+ return a.length === other.length && a.every((item, i) => jsonEqual(item, other[i]));
3536
+ }
3537
+ const left = a, right = b;
3538
+ const keys = Object.keys(left);
3539
+ return keys.length === Object.keys(right).length && keys.every((key2) => own2(right, key2) && jsonEqual(left[key2], right[key2]));
3540
+ }
3541
+ function escapePointerToken(token) {
3542
+ return String(token).replaceAll("~", "~0").replaceAll("/", "~1");
3543
+ }
3544
+ function unescapePointerToken(token) {
3545
+ return token.replaceAll("~1", "/").replaceAll("~0", "~");
3546
+ }
3547
+ function parsePointer(pointer4) {
3548
+ if (typeof pointer4 !== "string" || pointer4 !== "" && !pointer4.startsWith("/") || /~(?![01])/u.test(pointer4)) {
3549
+ throw new OPFPatchError("invalid-json-pointer", "Expected a JSON Pointer (empty string or /path with ~0 and ~1 escapes).", { path: typeof pointer4 === "string" ? pointer4 : void 0 });
3550
+ }
3551
+ return pointer4 === "" ? [] : pointer4.slice(1).split("/").map(unescapePointerToken);
3552
+ }
3553
+ function formatPointer(tokens) {
3554
+ return tokens.length ? `/${tokens.map(escapePointerToken).join("/")}` : "";
3555
+ }
3556
+ function pointerFromPath(path) {
3557
+ if (Array.isArray(path)) return formatPointer(path);
3558
+ if (typeof path !== "string") throw new OPFPatchError("invalid-json-pointer", "A path must be a string or an array of segments.");
3559
+ if (path === "") return "";
3560
+ if (path.startsWith("/")) {
3561
+ parsePointer(path);
3562
+ return path;
3563
+ }
3564
+ return formatPointer(path.split(".").filter(Boolean));
3565
+ }
3566
+ function splitPointer2(pointer4) {
3567
+ const tokens = parsePointer(pointer4);
3568
+ if (!tokens.length) throw new OPFPatchError("invalid-json-pointer", "The root pointer has no parent.", { path: pointer4 });
3569
+ return { parent: formatPointer(tokens.slice(0, -1)), token: tokens[tokens.length - 1] };
3570
+ }
3571
+ function readPointer(document, pointer4) {
3572
+ const tokens = typeof pointer4 === "string" ? parsePointer(pointer4) : pointer4;
3573
+ let value = document;
3574
+ for (const token of tokens) {
3575
+ if (Array.isArray(value)) {
3576
+ if (!/^(0|[1-9][0-9]*)$/.test(token)) return { found: false };
3577
+ const at2 = Number(token);
3578
+ if (!Number.isSafeInteger(at2) || at2 >= value.length) return { found: false };
3579
+ value = value[at2];
3580
+ } else if (value && typeof value === "object" && own2(value, token)) value = value[token];
3581
+ else return { found: false };
3582
+ }
3583
+ return { found: true, value };
3584
+ }
3585
+ function getAtPointer(document, pointer4) {
3586
+ const result = readPointer(document, pointer4);
3587
+ if (!result.found) {
3588
+ const shown = typeof pointer4 === "string" ? pointer4 : formatPointer(pointer4);
3589
+ throw new OPFPatchError("patch-path-missing", `Patch path does not exist: ${shown || "/"}`, { path: shown });
3590
+ }
3591
+ return result.value;
3592
+ }
3593
+ var hasPointer = (document, pointer4) => readPointer(document, pointer4).found;
3594
+ function arrayIndex(token, length, allowEnd, path) {
3595
+ if (allowEnd && token === "-") return length;
3596
+ if (!/^(0|[1-9][0-9]*)$/.test(token)) throw new OPFPatchError("invalid-array-index", `Invalid array index: ${token}`, { path });
3597
+ const at2 = Number(token);
3598
+ if (!Number.isSafeInteger(at2) || at2 > length || !allowEnd && at2 >= length) throw new OPFPatchError("invalid-array-index", `Array index out of bounds: ${token}`, { path });
3599
+ return at2;
3600
+ }
3601
+ function normalizePatch(patch) {
3602
+ if (!Array.isArray(patch)) throw new OPFPatchError("invalid-patch", "A JSON Patch must be an array of operations.");
3603
+ return patch.map((operation, index) => normalizeOperation(operation, index));
3604
+ }
3605
+ function normalizeOperation(operation, index) {
3606
+ const fail = (code, message, path) => new OPFPatchError(code, index === void 0 ? message : `Operation ${index}: ${message}`, { index, operation, path });
3607
+ if (!isObject(operation)) throw fail("invalid-patch-operation", "Expected an operation object.");
3608
+ const op = operation.op;
3609
+ if (typeof op !== "string" || !["add", "remove", "replace", "move", "copy", "test"].includes(op)) {
3610
+ throw fail("unsupported-patch-operation", `Unknown operation: ${String(op)}`);
3611
+ }
3612
+ if (typeof operation.path !== "string") throw fail("invalid-patch-operation", "An operation needs a string path.");
3613
+ try {
3614
+ parsePointer(operation.path);
3615
+ } catch (error) {
3616
+ throw fail("invalid-json-pointer", error.message, operation.path);
3617
+ }
3618
+ if (op === "move" || op === "copy") {
3619
+ if (typeof operation.from !== "string") throw fail("invalid-patch-operation", `${op} requires a string from.`, operation.path);
3620
+ try {
3621
+ parsePointer(operation.from);
3622
+ } catch (error) {
3623
+ throw fail("invalid-json-pointer", error.message, operation.from);
3624
+ }
3625
+ return { op, from: operation.from, path: operation.path };
3626
+ }
3627
+ if (op === "remove") return { op, path: operation.path };
3628
+ if (!own2(operation, "value")) throw fail("invalid-patch-operation", `${op} requires value.`, operation.path);
3629
+ return { op, path: operation.path, value: clone(operation.value) };
3630
+ }
3631
+ function parentOf(document, tokens, path) {
3632
+ const key2 = tokens[tokens.length - 1];
3633
+ const found = readPointer(document, tokens.slice(0, -1));
3634
+ if (!found.found || !found.value || typeof found.value !== "object") {
3635
+ throw new OPFPatchError("patch-parent-missing", `Patch parent does not exist for ${path}.`, { path });
3636
+ }
3637
+ return { parent: found.value, key: key2 };
3638
+ }
3639
+ var NONE = /* @__PURE__ */ Symbol("none");
3640
+ function addAt(document, path, value) {
3641
+ const tokens = parsePointer(path);
3642
+ if (!tokens.length) return { document: clone(value), inverse: { op: "replace", path: "", value: clone(document) } };
3643
+ const { parent, key: key2 } = parentOf(document, tokens, path);
3644
+ if (Array.isArray(parent)) {
3645
+ const at2 = arrayIndex(key2, parent.length, true, path);
3646
+ parent.splice(at2, 0, clone(value));
3647
+ return { document, inverse: { op: "remove", path: formatPointer([...tokens.slice(0, -1), at2]) } };
3648
+ }
3649
+ const record = parent;
3650
+ const previous = own2(record, key2) ? record[key2] : NONE;
3651
+ Object.defineProperty(record, key2, { value: clone(value), enumerable: true, configurable: true, writable: true });
3652
+ return { document, inverse: previous === NONE ? { op: "remove", path } : { op: "replace", path, value: clone(previous) } };
3653
+ }
3654
+ function replaceAt(document, path, value) {
3655
+ const tokens = parsePointer(path);
3656
+ if (!tokens.length) return { document: clone(value), inverse: { op: "replace", path: "", value: clone(document) } };
3657
+ const { parent, key: key2 } = parentOf(document, tokens, path);
3658
+ let previous;
3659
+ if (Array.isArray(parent)) {
3660
+ const at2 = arrayIndex(key2, parent.length, false, path);
3661
+ previous = parent[at2];
3662
+ parent[at2] = clone(value);
3663
+ } else {
3664
+ const record = parent;
3665
+ if (!own2(record, key2)) throw new OPFPatchError("patch-path-missing", `Patch path does not exist: ${path}.`, { path });
3666
+ previous = record[key2];
3667
+ Object.defineProperty(record, key2, { value: clone(value), enumerable: true, configurable: true, writable: true });
3668
+ }
3669
+ return { document, inverse: { op: "replace", path, value: clone(previous) } };
3670
+ }
3671
+ function removeAt(document, path) {
3672
+ const tokens = parsePointer(path);
3673
+ if (!tokens.length) throw new OPFPatchError("patch-root-remove", "The document root cannot be removed.", { path });
3674
+ const { parent, key: key2 } = parentOf(document, tokens, path);
3675
+ let previous;
3676
+ if (Array.isArray(parent)) {
3677
+ const at2 = arrayIndex(key2, parent.length, false, path);
3678
+ previous = parent[at2];
3679
+ parent.splice(at2, 1);
3680
+ return { document, inverse: { op: "add", path: formatPointer([...tokens.slice(0, -1), at2]), value: clone(previous) } };
3681
+ }
3682
+ const record = parent;
3683
+ if (!own2(record, key2)) throw new OPFPatchError("patch-path-missing", `Patch path does not exist: ${path}.`, { path });
3684
+ previous = record[key2];
3685
+ delete record[key2];
3686
+ return { document, inverse: { op: "add", path, value: clone(previous) } };
3687
+ }
3688
+ function applyOne(document, operation) {
3689
+ switch (operation.op) {
3690
+ case "add": {
3691
+ const r = addAt(document, operation.path, operation.value);
3692
+ return { document: r.document, inverse: [r.inverse] };
3693
+ }
3694
+ case "replace": {
3695
+ const r = replaceAt(document, operation.path, operation.value);
3696
+ return { document: r.document, inverse: [r.inverse] };
3697
+ }
3698
+ case "remove": {
3699
+ const r = removeAt(document, operation.path);
3700
+ return { document: r.document, inverse: [r.inverse] };
3701
+ }
3702
+ case "test": {
3703
+ const found = readPointer(document, operation.path);
3704
+ if (!found.found || !jsonEqual(found.value, operation.value)) throw new OPFPatchError("patch-test-failed", `Test failed at ${operation.path || "/"}: the value changed.`, { path: operation.path });
3705
+ return { document, inverse: [] };
3706
+ }
3707
+ case "copy": {
3708
+ const value = clone(getAtPointer(document, operation.from));
3709
+ const r = addAt(document, operation.path, value);
3710
+ return { document: r.document, inverse: [r.inverse] };
3711
+ }
3712
+ case "move": {
3713
+ const from = parsePointer(operation.from), to = parsePointer(operation.path);
3714
+ const value = clone(getAtPointer(document, from));
3715
+ if (from.length === to.length && from.every((token, i) => token === to[i])) return { document, inverse: [] };
3716
+ if (to.length > from.length && from.every((token, i) => to[i] === token)) {
3717
+ throw new OPFPatchError("patch-invalid-move", "Cannot move a value into its descendant.", { path: operation.path });
3718
+ }
3719
+ const removed = removeAt(document, operation.from);
3720
+ const added = addAt(removed.document, operation.path, value);
3721
+ return { document: added.document, inverse: [added.inverse, removed.inverse] };
3722
+ }
3723
+ }
3724
+ }
3725
+ function applyPatchWithInverse(document, patch, options = {}) {
3726
+ const operations = normalizePatch(patch);
3727
+ let working = clone(document);
3728
+ const inverse = [];
3729
+ for (const [index, operation] of operations.entries()) {
3730
+ try {
3731
+ const step = applyOne(working, operation);
3732
+ working = step.document;
3733
+ inverse.unshift(...step.inverse);
3734
+ } catch (error) {
3735
+ if (error instanceof OPFPatchError && error.index === void 0) {
3736
+ const wrapped = new OPFPatchError(error.code, `Operation ${index}: ${error.message}`, { path: error.path, index, operation });
3737
+ throw wrapped;
3738
+ }
3739
+ throw error;
3740
+ }
3741
+ }
3742
+ const result = { presentation: working, inverse, patch: operations };
3743
+ if (options.validate) {
3744
+ const validator = typeof options.validate === "function" ? options.validate : (value) => validate(value, { only: options.strict ? ["format", "references"] : ["format"] });
3745
+ const validation = validator(working);
3746
+ if (!validation.valid || options.strict && validation.findings?.some((entry) => entry.severity === "warning")) throw new OPFPatchValidationError(validation);
3747
+ result.validation = validation;
3748
+ }
3749
+ return result;
3750
+ }
3751
+ function applyPatch(document, patch, options = {}) {
3752
+ return applyPatchWithInverse(document, patch, options).presentation;
3753
+ }
3754
+ function invertPatch(document, patch) {
3755
+ return applyPatchWithInverse(document, patch).inverse;
3756
+ }
3757
+
3758
+ // src/schema-catalog-kinds.ts
3759
+ var schemaCatalogKinds = {
3760
+ "presentation#/properties/audience": "audiences",
3761
+ "presentation#/properties/audience/oneOf/2/items": "audiences",
3762
+ "presentation#/properties/purpose": "purposes",
3763
+ "presentation#/properties/tone": "tones",
3764
+ "presentation#/properties/narrative": "narratives",
3765
+ "presentation#/$defs/Audience/properties/id": "audiences",
3766
+ "presentation#/$defs/Purpose/properties/id": "purposes",
3767
+ "presentation#/$defs/Language/properties/fontScheme": "fontSchemes",
3768
+ "presentation#/$defs/Language/properties/googleFontScheme": "fontSchemes",
3769
+ "presentation#/$defs/Tone/properties/id": "tones",
3770
+ "presentation#/$defs/Design/properties/theme": "themes",
3771
+ "presentation#/$defs/Design/properties/colorScheme": "colorSchemes",
3772
+ "presentation#/$defs/Design/properties/fontScheme": "fontSchemes",
3773
+ "presentation#/$defs/ColorScheme/properties/id": "colorSchemes",
3774
+ "presentation#/$defs/FontScheme/properties/id": "fontSchemes",
3775
+ "presentation#/$defs/Slide/properties/layout": "layouts",
3776
+ "theme#/properties/colorScheme": "colorSchemes",
3777
+ "theme#/properties/fontScheme": "fontSchemes"
3778
+ };
3779
+
3780
+ // src/schema-navigation.ts
3781
+ var own3 = (object2, key2) => object2 !== null && typeof object2 === "object" && Object.hasOwn(object2, key2);
3782
+ var documents = new Map(Object.values(schemas).map((schema) => [schema.$id, schema]));
3783
+ var presentationSchema = schemas.presentation;
3784
+ var CATALOG_KIND = /* @__PURE__ */ Symbol.for("@openpresentation/opf:catalog-kind");
3785
+ var catalogKindOfNode = /* @__PURE__ */ new WeakMap();
3786
+ var kindOfNode = (node) => catalogKindOfNode.get(node) ?? node[CATALOG_KIND];
3787
+ function splitOpfPath(path) {
3788
+ if (Array.isArray(path)) return path.map((segment) => String(segment));
3789
+ if (typeof path !== "string") throw new TypeError("An OPF path must be a string or an array of segments.");
3790
+ if (path === "") return [];
3791
+ if (path.startsWith("/")) return parsePointer(path);
3792
+ return path.split(".").filter(Boolean);
3793
+ }
3794
+ function readSchemaPointer(target, pointer4) {
3795
+ let current = target;
3796
+ for (const token of parsePointer(pointer4)) {
3797
+ if (current === null || typeof current !== "object" || !own3(current, token)) return void 0;
3798
+ current = current[token];
3799
+ }
3800
+ return current;
3801
+ }
3802
+ for (const [key2, kind] of Object.entries(schemaCatalogKinds)) {
3803
+ const [name = "", pointer4 = ""] = key2.split("#");
3804
+ const node = readSchemaPointer(schemas[name], pointer4);
3805
+ if (node === void 0 || node === null || typeof node !== "object") throw new Error(`schemaCatalogKinds: ${key2} is not a schema node.`);
3806
+ catalogKindOfNode.set(node, kind);
3807
+ }
3808
+ function absolutize(schema, id) {
3809
+ if (Array.isArray(schema)) return schema.map((item) => absolutize(item, id));
3810
+ if (schema === null || typeof schema !== "object") return schema;
3811
+ const copy = {};
3812
+ const kind = kindOfNode(schema);
3813
+ if (kind !== void 0) copy[CATALOG_KIND] = kind;
3814
+ for (const [key2, value] of Object.entries(schema)) copy[key2] = key2 === "$ref" && typeof value === "string" && value.startsWith("#") ? `${id}${value}` : absolutize(value, id);
3815
+ return copy;
3816
+ }
3817
+ function mergeAllOf(schema, root, seen) {
3818
+ if (!Array.isArray(schema.allOf)) return schema;
3819
+ const { allOf, ...base } = schema;
3820
+ let merged = base;
3821
+ for (const member of allOf) {
3822
+ const resolved = resolveSchema(member, root, new Set(seen));
3823
+ if (!resolved.properties && !resolved.type && !resolved.oneOf && !resolved.anyOf) continue;
3824
+ merged = {
3825
+ ...resolved,
3826
+ ...merged,
3827
+ ...resolved.properties || merged.properties ? { properties: { ...resolved.properties, ...merged.properties } } : {}
3828
+ };
3829
+ }
3830
+ return merged;
3831
+ }
3832
+ function resolveSchema(schema, root = presentationSchema, seen = /* @__PURE__ */ new Set()) {
3833
+ const resolved = resolveNode(schema, root, seen);
3834
+ const kind = schema && typeof schema === "object" ? kindOfNode(schema) : void 0;
3835
+ return kind === void 0 ? resolved : { ...resolved, [CATALOG_KIND]: kind };
3836
+ }
3837
+ function resolveNode(schema, root, seen) {
3838
+ if (!schema || typeof schema !== "object") return {};
3839
+ if (!schema.$ref) return mergeAllOf(schema, root, seen);
3840
+ const ref = schema.$ref;
3841
+ if (seen.has(ref)) return {};
3842
+ seen.add(ref);
3843
+ const [id = "", fragment = ""] = ref.split("#");
3844
+ const target = id ? documents.get(id) : root;
3845
+ if (!target) throw new Error(`Unresolved OPF schema: ${ref}`);
3846
+ let resolved = fragment ? readSchemaPointer(target, fragment) : target;
3847
+ if (!resolved) throw new Error(`Unresolved OPF schema: ${ref}`);
3848
+ if (id && target !== root) resolved = absolutize(resolved, id);
3849
+ const { $ref: _ref, ...rest } = schema;
3850
+ return mergeAllOf({ ...resolveSchema(resolved, target, seen), ...rest }, target, seen);
3851
+ }
3852
+ function schemaVariants(schema, root = presentationSchema) {
3853
+ const resolved = resolveSchema(schema, root);
3854
+ if (Array.isArray(resolved.type)) return resolved.type.flatMap((type) => schemaVariants({ ...resolved, type }, root));
3855
+ const variants = resolved.oneOf ?? resolved.anyOf;
3856
+ if (!variants) return [resolved];
3857
+ const { $ref: _ref, oneOf: _oneOf, anyOf: _anyOf, ...base } = resolved;
3858
+ return variants.flatMap(
3859
+ (child) => schemaVariants(child, root).map((variant) => {
3860
+ const requiredOnly = !variant.type && !variant.properties && !variant.$ref && Array.isArray(variant.required);
3861
+ return {
3862
+ ...base,
3863
+ ...variant,
3864
+ properties: base.properties || variant.properties ? { ...base.properties, ...variant.properties } : void 0,
3865
+ ...base.required || variant.required ? { required: [.../* @__PURE__ */ new Set([...base.required ?? [], ...variant.required ?? []])] } : {},
3866
+ ...requiredOnly && !variant.title ? { title: variant.required.join(" + ") } : {}
3867
+ };
3868
+ })
3869
+ );
3870
+ }
3871
+ function schemaType(schema, value) {
3872
+ return schema.type ?? (schema.properties || schema.additionalProperties ? "object" : schema.items ? "array" : schema.const !== void 0 ? typeof schema.const : value === null ? "null" : Array.isArray(value) ? "array" : typeof value === "undefined" ? "string" : typeof value);
3873
+ }
3874
+ function score2(schema, value) {
3875
+ const type = schemaType(schema, value);
3876
+ if (type === "integer" ? !Number.isInteger(value) : type === "array" ? !Array.isArray(value) : type === "null" ? value !== null : typeof value !== type || type === "object" && (value === null || Array.isArray(value))) return -100;
3877
+ if (own3(schema, "const")) return JSON.stringify(schema.const) === JSON.stringify(value) ? 100 : -100;
3878
+ if (schema.enum && !schema.enum.includes(value)) return -100;
3879
+ let result = 1;
3880
+ const record = value;
3881
+ for (const [key2, child] of Object.entries(schema.properties ?? {})) {
3882
+ if (!record || !own3(record, key2)) continue;
3883
+ if (own3(child, "const")) result += record[key2] === child.const ? 100 : -1e3;
3884
+ else result += 1;
3885
+ }
3886
+ for (const key2 of schema.required ?? []) if (!record || !own3(record, key2)) result -= 2;
3887
+ return result;
3888
+ }
3889
+ function activeSchema(schema, value, root = presentationSchema) {
3890
+ return schemaVariants(schema, root).sort((a, b) => score2(b, value) - score2(a, value))[0] ?? {};
3891
+ }
3892
+ var INDEX = /^(?:0|[1-9][0-9]*|-)$/;
3893
+ function patternChild(schema, key2) {
3894
+ for (const [pattern, child] of Object.entries(schema.patternProperties ?? {})) {
3895
+ try {
3896
+ if (new RegExp(pattern).test(key2)) return child;
3897
+ } catch {
3898
+ }
3899
+ }
3900
+ return void 0;
3901
+ }
3902
+ function childSchema(schema, key2, value) {
3903
+ if (Array.isArray(value) || schema.type === "array") return schema.items ?? void 0;
3904
+ return schema.properties?.[key2] ?? patternChild(schema, key2) ?? (typeof schema.additionalProperties === "object" ? schema.additionalProperties : void 0);
3905
+ }
3906
+ function stepSchema(schema, value, key2, root) {
3907
+ const variants = schemaVariants(schema, root).sort((a, b) => score2(b, value) - score2(a, value));
3908
+ const first = variants[0] ?? {};
3909
+ if (value !== void 0 || variants.length < 2 || childSchema(first, key2, value) !== void 0) return first;
3910
+ return variants.find((variant) => variant.type === "array" ? INDEX.test(key2) && childSchema(variant, key2, value) !== void 0 : childSchema(variant, key2, value) !== void 0) ?? first;
3911
+ }
3912
+ function walk(presentation, parts2, rootSchema) {
3913
+ let current = rootSchema;
3914
+ let root = rootSchema;
3915
+ let value = presentation;
3916
+ let parent;
3917
+ let known = true;
3918
+ for (let index = 0; index < parts2.length; index++) {
3919
+ const key2 = parts2[index];
3920
+ const resolved = stepSchema(current, value, key2, root);
3921
+ parent = resolved;
3922
+ let next;
3923
+ const recordSchema = parts2[0] === "catalogs" && index === 3 ? catalogSchemaNames[parts2[2]] : void 0;
3924
+ if (recordSchema) {
3925
+ next = schemas[recordSchema] ?? {};
3926
+ root = next;
3927
+ } else next = childSchema(resolved, key2, value);
3928
+ if (next === void 0) {
3929
+ known = false;
3930
+ next = {};
3931
+ }
3932
+ current = next;
3933
+ value = value !== null && typeof value === "object" ? value[key2] : void 0;
3934
+ }
3935
+ return { raw: current, schema: resolveSchema(current, root), root, parent, value, known };
3936
+ }
3937
+ function schemaAtPath(presentation, path, root = presentationSchema) {
3938
+ return walk(presentation, splitOpfPath(path), root).schema;
3939
+ }
3940
+ function schemaLabel(schema, index = 0) {
3941
+ const type = schemaType(schema);
3942
+ return schema.title ?? schema.properties?.type?.const ?? (schema.const !== void 0 ? String(schema.const) : `${type}${schema.properties ? ` \xB7 ${Object.keys(schema.properties).slice(0, 3).join(", ")}` : ""}`) ?? `Option ${index + 1}`;
3943
+ }
3944
+ function createSchemaValue(schema, root = presentationSchema, depth2 = 0) {
3945
+ if (depth2 > 20) throw new Error("Choose a shallower initial value for this recursive structure.");
3946
+ const variants = schemaVariants(schema, root);
3947
+ const s = variants[0] ?? {};
3948
+ if (own3(s, "const")) return structuredClone(s.const);
3949
+ if (own3(s, "default")) return structuredClone(s.default);
3950
+ if (s.enum?.length) return structuredClone(s.enum[0]);
3951
+ const type = schemaType(s);
3952
+ if (type === "object") {
3953
+ const value = {};
3954
+ const properties = s.properties ?? {};
3955
+ for (const key2 of /* @__PURE__ */ new Set([...s.required ?? [], ...Object.keys(properties).filter((name) => own3(properties[name], "const"))])) value[key2] = createSchemaValue(properties[key2] ?? {}, root, depth2 + 1);
3956
+ return value;
3957
+ }
3958
+ if (type === "array") return Array.from({ length: Math.min(s.minItems ?? 0, 100) }, () => createSchemaValue(s.items ?? {}, root, depth2 + 1));
3959
+ if (type === "boolean") return false;
3960
+ if (type === "null") return null;
3961
+ if (type === "number" || type === "integer") return s.minimum ?? (s.exclusiveMinimum !== void 0 ? s.exclusiveMinimum + 1 : 0);
3962
+ if (s.examples?.length && typeof s.examples[0] === "string" && !s.examples[0].includes("...")) return s.examples[0];
3963
+ if (s.format === "uri") return "https://example.com";
3964
+ if (s.pattern?.includes("a-z")) return "example";
3965
+ return s.minLength ? "Text" : "";
3966
+ }
3967
+ function listSchemaFields() {
3968
+ const result = [];
3969
+ for (const [name, root] of Object.entries(schemas)) {
3970
+ const visit = (schema, path) => {
3971
+ if (!schema || typeof schema !== "object") return;
3972
+ for (const [key2, child] of Object.entries(schema.properties ?? {})) {
3973
+ result.push({
3974
+ schema: name,
3975
+ path: formatPointer([...path, "properties", key2]),
3976
+ name: key2,
3977
+ description: child.description ?? "",
3978
+ reference: child.$ref ?? null,
3979
+ type: child.type ?? null,
3980
+ values: child.enum ?? (own3(child, "const") ? [child.const] : null)
3981
+ });
3982
+ visit(child, [...path, "properties", key2]);
3983
+ }
3984
+ for (const [key2, child] of Object.entries(schema.$defs ?? {})) visit(child, [...path, "$defs", key2]);
3985
+ for (const keyword of ["oneOf", "anyOf", "allOf"]) {
3986
+ const members = schema[keyword] ?? [];
3987
+ for (let index = 0; index < members.length; index++) visit(members[index], [...path, keyword, String(index)]);
3988
+ }
3989
+ if (schema.items) visit(schema.items, [...path, "items"]);
3990
+ if (typeof schema.additionalProperties === "object") visit(schema.additionalProperties, [...path, "additionalProperties"]);
3991
+ for (const [key2, child] of Object.entries(schema.patternProperties ?? {})) visit(child, [...path, "patternProperties", key2]);
3992
+ };
3993
+ visit(root, []);
3994
+ }
3995
+ return result;
3996
+ }
3997
+ var COMMON_KEYS = Object.freeze({
3998
+ /** The root of a document. */
3999
+ deck: Object.freeze(["name", "slides", "design", "language", "audience", "purpose", "tone", "narrative", "organization", "speaker"]),
4000
+ /** A slide (`slides[n]`). */
4001
+ slide: Object.freeze(["title", "subtitle", "layout", "items", "text", "notes", "image", "chart", "table", "quote", "code", "metric", "timeline", "blocks", "design", "id", "section", "tag", "hidden", "beat"]),
4002
+ /** `design`, on the deck and on a slide. */
4003
+ design: Object.freeze(["theme", "colorScheme", "fontScheme", "dimensions", "background", "header", "footer"])
4004
+ });
4005
+ var UNRANKED_PRIORITY = 1e6;
4006
+ var DEFINITION_LISTS = { Slide: COMMON_KEYS.slide, Design: COMMON_KEYS.design, SlideDesign: COMMON_KEYS.design };
4007
+ var ADVANCED_KEYS = /* @__PURE__ */ new Set(["extensions", "$schema"]);
4008
+ var REGION_KEY = /^(?:(?:top|middle|bottom)(?:\+(?:top|middle|bottom))*)?(?::)?(?:(?:left|center|right)(?:\+(?:left|center|right))*)?$/;
4009
+ function isPromotedRegionKey(key2) {
4010
+ return key2 !== "" && key2 !== ":" && REGION_KEY.test(key2);
4011
+ }
4012
+ var uniq = (items) => [...new Set(items)];
4013
+ function variantType(variant) {
4014
+ if (typeof variant.type === "string") return variant.type;
4015
+ if (variant.properties || variant.additionalProperties) return "object";
4016
+ if (variant.items) return "array";
4017
+ if (variant.const !== void 0) return variant.const === null ? "null" : typeof variant.const;
4018
+ if (Array.isArray(variant.enum) && variant.enum.length) return variant.enum[0] === null ? "null" : typeof variant.enum[0];
4019
+ return "any";
4020
+ }
4021
+ var firstParagraph = (text) => typeof text === "string" && text.trim() ? (text.trim().split(/\n[ \t]*\n/)[0] ?? "").trim() : void 0;
4022
+ var definitionOf = (raw) => typeof raw.$ref === "string" ? /#\/\$defs\/([^/]+)$/.exec(raw.$ref)?.[1] : void 0;
4023
+ function enumValues(variants, inherited) {
4024
+ const seen = /* @__PURE__ */ new Map();
4025
+ const add = (value, description) => {
4026
+ if (!(value === null || ["string", "number", "boolean"].includes(typeof value))) return;
4027
+ const id = JSON.stringify(value);
4028
+ if (!seen.has(id)) seen.set(id, { value, ...description ? { description } : {} });
4029
+ };
4030
+ for (const variant of variants) {
4031
+ const own4 = typeof variant.description === "string" && variant.description !== inherited ? firstParagraph(variant.description) : void 0;
4032
+ if (Array.isArray(variant.enum)) for (const value of variant.enum) add(value);
4033
+ else if (variant.const !== void 0) add(variant.const, own4);
4034
+ }
4035
+ return [...seen.values()];
4036
+ }
4037
+ function catalogKindOf(schema, variants = []) {
4038
+ for (const candidate of [schema, ...variants]) {
4039
+ const kind = kindOfNode(candidate);
4040
+ if (kind !== void 0 && own3(catalogSchemaNames, kind)) return kind;
4041
+ }
4042
+ return void 0;
4043
+ }
4044
+ var formRank = (variant) => {
4045
+ if (Array.isArray(variant.enum) || variant.const !== void 0) return 0;
4046
+ const type = variantType(variant);
4047
+ return type === "string" || type === "boolean" || type === "number" || type === "integer" ? 0 : type === "object" ? 1 : type === "array" ? 2 : 3;
4048
+ };
4049
+ function primaryIndex(variants) {
4050
+ let best = 0;
4051
+ for (let index = 1; index < variants.length; index++) if (formRank(variants[index]) < formRank(variants[best])) best = index;
4052
+ return best;
4053
+ }
4054
+ function typeLabelOf(raw, variants, root) {
4055
+ if (variants.length > 1) return "choice";
4056
+ const variant = variants[0] ?? {};
4057
+ if (Array.isArray(variant.enum) && variant.enum.length > 1) return "enum";
4058
+ const type = variantType(variant);
4059
+ if (type === "array") {
4060
+ const items = variant.items ?? {};
4061
+ const name = definitionOf(items);
4062
+ if (name) return `array of ${name}`;
4063
+ const forms = uniq(schemaVariants(items, root).map(variantType));
4064
+ return `array of ${forms.length === 1 ? forms[0] : forms.length ? "choice" : "any"}`;
4065
+ }
4066
+ if (type === "object") return definitionOf(raw) ?? "object";
4067
+ return type;
4068
+ }
4069
+ function priorityOf(list, key2) {
4070
+ const index = list ? list.indexOf(key2) : -1;
4071
+ return index < 0 ? UNRANKED_PRIORITY : index;
4072
+ }
4073
+ function objectForm(variants, value) {
4074
+ const objects = variants.filter((variant) => variantType(variant) === "object");
4075
+ let used = objects;
4076
+ if (objects.length > 1 && value !== null && typeof value === "object" && !Array.isArray(value)) {
4077
+ const best = [...objects].sort((a, b) => score2(b, value) - score2(a, value))[0];
4078
+ if (best) used = [best];
4079
+ }
4080
+ const properties = /* @__PURE__ */ new Map();
4081
+ for (const variant of used) for (const [key2, child] of Object.entries(variant.properties ?? {})) if (!properties.has(key2)) properties.set(key2, child);
4082
+ const required = used.length ? (used[0]?.required ?? []).filter((key2) => used.every((variant) => (variant.required ?? []).includes(key2))) : [];
4083
+ return { properties, required };
4084
+ }
4085
+ function childrenOf(form, list, isSlide, root) {
4086
+ const children = [];
4087
+ for (const [key2, raw] of form.properties) {
4088
+ const resolved = resolveSchema(raw, root);
4089
+ const variants = schemaVariants(raw, root);
4090
+ const description = firstParagraph(resolved.description);
4091
+ const catalogKind = catalogKindOf(resolved, variants);
4092
+ children.push({
4093
+ key: key2,
4094
+ types: uniq(variants.map(variantType)),
4095
+ typeLabel: typeLabelOf(raw, variants, root),
4096
+ required: form.required.includes(key2),
4097
+ advanced: ADVANCED_KEYS.has(key2) || isSlide && isPromotedRegionKey(key2),
4098
+ priority: priorityOf(list, key2),
4099
+ ...description ? { description } : {},
4100
+ ...catalogKind ? { catalogKind } : {},
4101
+ ...resolved.deprecated === true ? { deprecated: true } : {},
4102
+ hasEnum: variants.some((variant) => Array.isArray(variant.enum) || variant.const !== void 0)
4103
+ });
4104
+ }
4105
+ return children.sort(
4106
+ (a, b) => Number(b.required) - Number(a.required) || Number(a.advanced) - Number(b.advanced) || a.priority - b.priority || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)
4107
+ );
4108
+ }
4109
+ function nodeSegments(path) {
4110
+ const parts2 = splitOpfPath(path);
4111
+ const shown = Array.isArray(path) ? [...path] : parts2.map((part) => /^(?:0|[1-9][0-9]*)$/.test(part) ? Number(part) : part);
4112
+ return { parts: parts2, shown };
4113
+ }
4114
+ var MAX_ITEM_DEPTH = 3;
4115
+ function describeAt(presentation, path, depth2) {
4116
+ let parts2;
4117
+ let shown;
4118
+ try {
4119
+ ({ parts: parts2, shown } = nodeSegments(path));
4120
+ } catch {
4121
+ return unknownField(Array.isArray(path) ? [...path] : [], "");
4122
+ }
4123
+ const pointer4 = formatPointer(parts2);
4124
+ const found = walk(presentation, parts2, presentationSchema);
4125
+ if (!found.known) return unknownField(shown, pointer4);
4126
+ const { raw, schema, root, parent, value } = found;
4127
+ const variants = schemaVariants(raw, root);
4128
+ const inherited = schema.description;
4129
+ const last = parts2.at(-1);
4130
+ const parentIsArray = parent !== void 0 && variantType(parent) === "array";
4131
+ const key2 = last !== void 0 && !parentIsArray ? last : void 0;
4132
+ const definition = definitionOf(raw);
4133
+ const description = firstParagraph(schema.description);
4134
+ const enumList = enumValues(variants, inherited);
4135
+ const primary = primaryIndex(variants);
4136
+ const catalogKind = catalogKindOf(schema, variants);
4137
+ const form = objectForm(variants, value);
4138
+ const isRoot = parts2.length === 0;
4139
+ const list = isRoot ? COMMON_KEYS.deck : definition ? DEFINITION_LISTS[definition] : void 0;
4140
+ const infos2 = variants.map((variant) => {
4141
+ const type = variantType(variant);
4142
+ const items = type === "array" ? variant.items ?? {} : void 0;
4143
+ const variantEnum = enumValues([variant], inherited);
4144
+ return {
4145
+ type,
4146
+ ...typeof variant.title === "string" ? { title: variant.title } : {},
4147
+ ...variantEnum.length ? { enum: variantEnum } : {},
4148
+ ...variant.const !== void 0 ? { const: variant.const } : {},
4149
+ requiredChildren: [...variant.required ?? []],
4150
+ properties: Object.keys(variant.properties ?? {}),
4151
+ ...items ? { itemsType: uniq(schemaVariants(items, root).map(variantType)).join(" | ") || "any" } : {},
4152
+ ...items && definitionOf(items) ? { itemsDefinition: definitionOf(items) } : {}
4153
+ };
4154
+ });
4155
+ const primaryVariant = variants[primary] ?? {};
4156
+ const freeText = variantType(primaryVariant) === "string" && primaryVariant.enum === void 0 && primaryVariant.const === void 0 && primaryVariant.pattern === void 0 && primaryVariant.format === void 0 && catalogKind === void 0;
4157
+ const info = {
4158
+ known: true,
4159
+ path: shown,
4160
+ pointer: pointer4,
4161
+ ...key2 !== void 0 ? { key: key2 } : {},
4162
+ types: uniq(infos2.map((variant) => variant.type)),
4163
+ ...description ? { description } : {},
4164
+ ...typeof schema.description === "string" && schema.description.trim() ? { fullDescription: schema.description.trim() } : {},
4165
+ ...definition ? { definition } : {},
4166
+ required: key2 !== void 0 && Array.isArray(parent?.required) && parent.required.includes(key2),
4167
+ ...schema.deprecated === true ? { deprecated: true } : {},
4168
+ examples: Array.isArray(schema.examples) ? [...schema.examples] : [],
4169
+ ...own3(schema, "default") ? { default: schema.default } : {},
4170
+ ...enumList.length ? { enum: enumList } : {},
4171
+ ...catalogKind ? { catalogKind } : {},
4172
+ variants: infos2,
4173
+ primary,
4174
+ children: childrenOf(form, list, definition === "Slide", root),
4175
+ requiredChildren: form.required,
4176
+ freeText
4177
+ };
4178
+ const arrayVariant = variants.find((variant) => variantType(variant) === "array") ?? void 0;
4179
+ if (arrayVariant && depth2 < MAX_ITEM_DEPTH) {
4180
+ const next = Array.isArray(value) ? value.length : 0;
4181
+ info.itemInfo = describeAt(presentation, [...parts2, String(next)], depth2 + 1);
4182
+ info.itemInfo.path = [...shown, next];
4183
+ }
4184
+ const mapVariant = variants.find((variant) => variantType(variant) === "object" && typeof variant.additionalProperties === "object");
4185
+ if (mapVariant && depth2 < MAX_ITEM_DEPTH) info.entryInfo = describeAt(presentation, [...parts2, "*"], depth2 + 1);
4186
+ if (info.entryInfo) info.entryInfo.path = [...shown, "*"];
4187
+ return info;
4188
+ }
4189
+ function unknownField(path, pointer4) {
4190
+ return { known: false, path, pointer: pointer4, types: [], required: false, examples: [], variants: [], primary: 0, children: [], requiredChildren: [], freeText: false };
4191
+ }
4192
+ function describeField(path, document) {
4193
+ return describeAt(document, path, 0);
4194
+ }
4195
+ function catalogKindAt(path, document) {
4196
+ let parts2;
4197
+ try {
4198
+ parts2 = splitOpfPath(path);
4199
+ } catch {
4200
+ return void 0;
4201
+ }
4202
+ const found = walk(document, parts2, presentationSchema);
4203
+ return found.known ? catalogKindOf(found.schema, schemaVariants(found.raw, found.root)) : void 0;
4204
+ }
4205
+
4206
+ // src/source-text.ts
4207
+ function createLocator(text) {
4208
+ const lineStarts = [0];
4209
+ for (let index = 0; index < text.length; index++) {
4210
+ const code = text.charCodeAt(index);
4211
+ if (code === 13) {
4212
+ if (text.charCodeAt(index + 1) === 10) index++;
4213
+ lineStarts.push(index + 1);
4214
+ } else if (code === 10) lineStarts.push(index + 1);
4215
+ }
4216
+ return (offset, length) => {
4217
+ let low = 0;
4218
+ let high = lineStarts.length;
4219
+ while (low + 1 < high) {
4220
+ const middle = low + high >> 1;
4221
+ if ((lineStarts[middle] ?? 0) <= offset) low = middle;
4222
+ else high = middle;
4223
+ }
4224
+ return { offset, length, line: low + 1, column: offset - (lineStarts[low] ?? 0) + 1 };
4225
+ };
4226
+ }
4227
+ function applyTextEdits(text, edits) {
4228
+ const ordered = edits.map((edit, index) => ({ edit, index })).sort((a, b) => a.edit.offset - b.edit.offset || a.index - b.index);
4229
+ let result = "";
4230
+ let cursor = 0;
4231
+ for (const { edit } of ordered) {
4232
+ if (edit.offset < cursor) throw new RangeError("Text edits overlap.");
4233
+ result += text.slice(cursor, edit.offset) + edit.insert;
4234
+ cursor = edit.offset + edit.length;
4235
+ }
4236
+ return result + text.slice(cursor);
4237
+ }
4238
+
4239
+ // src/json-syntax.ts
4240
+ var WORD = /[\p{L}\p{N}_$.+\-%]/u;
4241
+ var NUMBER = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/;
4242
+ var TYPOGRAPHIC = /[\u2018-\u201F\u00AB\u00BB]/;
4243
+ var HEX = /^[0-9a-fA-F]{4}$/;
4244
+ var isBreak = (char) => char === "\n" || char === "\r";
4245
+ function tokenize(text) {
4246
+ const tokens = [];
4247
+ const lexical = { escapes: /* @__PURE__ */ new Map(), unicode: /* @__PURE__ */ new Map(), controls: /* @__PURE__ */ new Map() };
4248
+ const length = text.length;
4249
+ let index = text.charCodeAt(0) === 65279 ? 1 : 0;
4250
+ const push = (token) => {
4251
+ const full = { ...token, text: text.slice(token.start, token.end) };
4252
+ tokens.push(full);
4253
+ return full;
4254
+ };
4255
+ while (index < length) {
4256
+ const char = text[index];
4257
+ if (char === " " || char === " " || char === "\n" || char === "\r") {
4258
+ index++;
4259
+ continue;
4260
+ }
4261
+ const start = index;
4262
+ if (char === "{" || char === "}" || char === "[" || char === "]" || char === "," || char === ":") {
4263
+ index++;
4264
+ push({ kind: char === "{" ? "lbrace" : char === "}" ? "rbrace" : char === "[" ? "lbracket" : char === "]" ? "rbracket" : char === "," ? "comma" : "colon", start, end: index });
4265
+ continue;
4266
+ }
4267
+ if (char === '"') {
4268
+ index++;
4269
+ const escapes = [];
4270
+ const unicode = [];
4271
+ const controls = [];
4272
+ let terminated = false;
4273
+ while (index < length) {
4274
+ const current = text[index];
4275
+ if (current === '"') {
4276
+ terminated = true;
4277
+ index++;
4278
+ break;
4279
+ }
4280
+ if (isBreak(current)) break;
4281
+ if (current === "\\") {
4282
+ const following = text[index + 1];
4283
+ if (following === void 0 || isBreak(following)) {
4284
+ escapes.push(index);
4285
+ index++;
4286
+ } else if (following === "u") {
4287
+ if (HEX.test(text.slice(index + 2, index + 6))) index += 6;
4288
+ else {
4289
+ unicode.push(index);
4290
+ index += 2;
4291
+ }
4292
+ } else if ('"\\/bfnrt'.includes(following)) index += 2;
4293
+ else {
4294
+ escapes.push(index);
4295
+ index += 1 + ((following.codePointAt(0) ?? 0) > 65535 ? 2 : 1);
4296
+ }
4297
+ continue;
4298
+ }
4299
+ if (current < " ") controls.push(index);
4300
+ index++;
4301
+ }
4302
+ const token = push({ kind: "string", start, end: index, quote: "double", terminated });
4303
+ if (escapes.length) lexical.escapes.set(token, escapes);
4304
+ if (unicode.length) lexical.unicode.set(token, unicode);
4305
+ if (controls.length) lexical.controls.set(token, controls);
4306
+ continue;
4307
+ }
4308
+ if (char === "'") {
4309
+ index++;
4310
+ let terminated = false;
4311
+ while (index < length) {
4312
+ const current = text[index];
4313
+ if (isBreak(current)) break;
4314
+ if (current === "\\" && index + 1 < length && !isBreak(text[index + 1])) {
4315
+ index += 2;
4316
+ continue;
4317
+ }
4318
+ index++;
4319
+ if (current === "'") {
4320
+ terminated = true;
4321
+ break;
4322
+ }
4323
+ }
4324
+ push({ kind: "string", start, end: index, quote: "single", terminated });
4325
+ continue;
4326
+ }
4327
+ if (TYPOGRAPHIC.test(char)) {
4328
+ index++;
4329
+ let terminated = false;
4330
+ while (index < length) {
4331
+ const current = text[index];
4332
+ if (isBreak(current)) break;
4333
+ index++;
4334
+ if (current === '"' || TYPOGRAPHIC.test(current)) {
4335
+ terminated = true;
4336
+ break;
4337
+ }
4338
+ }
4339
+ push({ kind: "string", start, end: index, quote: "typographic", terminated });
4340
+ continue;
4341
+ }
4342
+ if (char === "/" && text[index + 1] === "/") {
4343
+ while (index < length && !isBreak(text[index])) index++;
4344
+ push({ kind: "comment", start, end: index });
4345
+ continue;
4346
+ }
4347
+ if (char === "/" && text[index + 1] === "*") {
4348
+ const close = text.indexOf("*/", index + 2);
4349
+ index = close < 0 ? length : close + 2;
4350
+ push({ kind: "comment", start, end: index, block: true, terminated: close >= 0 });
4351
+ continue;
4352
+ }
4353
+ if (WORD.test(String.fromCodePoint(text.codePointAt(index) ?? 0))) {
4354
+ while (index < length) {
4355
+ const code = text.codePointAt(index) ?? 0;
4356
+ if (!WORD.test(String.fromCodePoint(code))) break;
4357
+ index += code > 65535 ? 2 : 1;
4358
+ }
4359
+ const lexeme = text.slice(start, index);
4360
+ const kind = NUMBER.test(lexeme) ? "number" : lexeme === "true" || lexeme === "false" || lexeme === "null" ? "literal" : /^[+\-.]?[0-9]|^[+-]$/.test(lexeme) ? "badnumber" : "word";
4361
+ push({ kind, start, end: index });
4362
+ continue;
4363
+ }
4364
+ while (index < length) {
4365
+ const code = text.codePointAt(index) ?? 0;
4366
+ const next = String.fromCodePoint(code);
4367
+ if (/[\s{}[\],:"'/]/.test(next) || TYPOGRAPHIC.test(next) || WORD.test(next)) break;
4368
+ index += code > 65535 ? 2 : 1;
4369
+ }
4370
+ if (index === start) index += (text.codePointAt(index) ?? 0) > 65535 ? 2 : 1;
4371
+ push({ kind: "unknown", start, end: index });
4372
+ }
4373
+ push({ kind: "eof", start: length, end: length });
4374
+ return { tokens, lexical };
4375
+ }
4376
+ var KIND_NAMES = { layouts: "layout", themes: "theme", colorSchemes: "color scheme", fontSchemes: "font scheme", narratives: "narrative", audiences: "audience", purposes: "purpose", tones: "tone" };
4377
+ function clip(value, max = 40) {
4378
+ const collapsed = value.replace(/\s+/g, " ").trim();
4379
+ return collapsed.length > max ? `${collapsed.slice(0, max - 1)}\u2026` : collapsed;
4380
+ }
4381
+ function lineBounds(text, offset) {
4382
+ let start = offset;
4383
+ while (start > 0 && !isBreak(text[start - 1])) start--;
4384
+ let end = offset;
4385
+ while (end < text.length && !isBreak(text[end])) end++;
4386
+ return { start, end };
4387
+ }
4388
+ var indentOf = (text, offset) => /^[ \t]*/.exec(text.slice(lineBounds(text, offset).start))?.[0] ?? "";
4389
+ function unquoteSingle(raw) {
4390
+ const inner = raw.endsWith("'") && raw.length > 1 ? raw.slice(1, -1) : raw.slice(1);
4391
+ let result = "";
4392
+ for (let index = 0; index < inner.length; index++) {
4393
+ const char = inner[index];
4394
+ if (char === "\\" && inner[index + 1] === "'") {
4395
+ result += "'";
4396
+ index++;
4397
+ } else if (char === "\\" && index + 1 < inner.length) {
4398
+ result += char + inner[index + 1];
4399
+ index++;
4400
+ } else if (char === '"') result += '\\"';
4401
+ else result += char;
4402
+ }
4403
+ return `"${result}"`;
4404
+ }
3478
4405
  var YAML_START = /^[A-Za-z_][\w.-]*\s*:(?:\s|$)/;
3479
- function hint(source) {
3480
- const text = source.replace(/^\uFEFF/, "").trimStart();
3481
- if (text.startsWith("{") || text.startsWith("[") || text === "") return "";
3482
- if (text.startsWith("---") || !text.startsWith("#") && YAML_START.test(text))
3483
- return " This looks like YAML: read it with fromYaml, then validate the presentation it returns.";
3484
- if (text.startsWith("#")) return " This looks like Markdown: read it with fromMarkdown, then validate the presentation it returns.";
3485
- return "";
4406
+ var MINIMAL_DECK = '{\n "slides": [\n { "title": "" }\n ]\n}\n';
4407
+ var HINT_FORMS_LIMIT = 2;
4408
+ var Analyzer = class {
4409
+ constructor(text) {
4410
+ this.text = text;
4411
+ const lexed = tokenize(text);
4412
+ this.comments = lexed.tokens.filter((token) => token.kind === "comment");
4413
+ this.tokens = lexed.tokens.filter((token) => token.kind !== "comment");
4414
+ this.lexical = lexed.lexical;
4415
+ this.locate = createLocator(text);
4416
+ }
4417
+ text;
4418
+ problems = [];
4419
+ tokens;
4420
+ comments;
4421
+ lexical;
4422
+ locate;
4423
+ index = 0;
4424
+ muteIndex = -1;
4425
+ last;
4426
+ stack = [];
4427
+ order = 0;
4428
+ // Containers left open at the same place close together: the outer one's fix inserts the inner ones' closers first.
4429
+ cascade = { last: void 0, inserts: [] };
4430
+ // -- reporting --
4431
+ add(problem) {
4432
+ this.problems.push({ problem: { severity: "error", ...problem }, order: this.order++ });
4433
+ }
4434
+ /** A structural problem about the token at the read position: dropped when it is the aftershock of the cause just reported. */
4435
+ structural(problem) {
4436
+ if (this.index <= this.muteIndex) return;
4437
+ this.add(problem);
4438
+ }
4439
+ mute() {
4440
+ this.muteIndex = this.index;
4441
+ }
4442
+ range(start, end) {
4443
+ return this.locate(start, Math.max(0, end - start));
4444
+ }
4445
+ line(offset) {
4446
+ return this.locate(offset, 0).line;
4447
+ }
4448
+ // -- tokens --
4449
+ peek(ahead = 0) {
4450
+ return this.tokens[Math.min(this.index + ahead, this.tokens.length - 1)];
4451
+ }
4452
+ take() {
4453
+ const token = this.peek();
4454
+ if (token.kind !== "eof") {
4455
+ this.index++;
4456
+ this.last = token;
4457
+ }
4458
+ return token;
4459
+ }
4460
+ startsEntry(offset = 0) {
4461
+ const token = this.peek(offset);
4462
+ return token.kind === "string" || token.kind === "word" && this.peek(offset + 1).kind === "colon";
4463
+ }
4464
+ // -- the document --
4465
+ run() {
4466
+ const first = this.peek();
4467
+ if (first.kind !== "eof" && first.kind !== "lbrace" && first.kind !== "lbracket" && this.looksLikeOtherFormat(first)) return;
4468
+ this.lexicalProblems();
4469
+ if (first.kind === "eof") {
4470
+ this.emptyDocument();
4471
+ return;
4472
+ }
4473
+ if (first.kind === "word" || first.kind === "unknown" || first.kind === "colon" || first.kind === "comma" || first.kind === "rbrace" || first.kind === "rbracket") {
4474
+ this.add({
4475
+ code: "invalid-symbol",
4476
+ range: this.range(first.start, first.end),
4477
+ message: `Unexpected ${first.kind === "word" || first.kind === "unknown" ? `\`${clip(first.text, 24)}\`` : `\`${first.text}\``} at the start of the document.`,
4478
+ next: 'start with { for a deck, for example { "slides": [ { "title": "\u2026" } ] }.',
4479
+ fixes: []
4480
+ });
4481
+ return;
4482
+ }
4483
+ const root = this.parseValue([], void 0);
4484
+ if (root) {
4485
+ if (root.kind !== "object") this.notAnObject(root, first);
4486
+ const extra = this.peek();
4487
+ if (extra.kind !== "eof") {
4488
+ const end = this.text.trimEnd().length;
4489
+ const closer = root.kind === "object" ? "}" : root.kind === "array" ? "]" : "value";
4490
+ this.add({
4491
+ code: "content-after-root",
4492
+ range: this.range(extra.start, Math.max(end, extra.end)),
4493
+ message: `Unexpected content after the closing ${closer === "value" ? "value" : `\`${closer}\``} on line ${this.line(Math.max(root.end - 1, 0))}. A JSON file holds one value.`,
4494
+ next: "remove the extra text, or put it inside the object.",
4495
+ fixes: [{ id: "remove-extra", title: "Remove the extra text", safe: false, edits: [{ offset: root.end, length: Math.max(end, extra.end) - root.end, insert: "" }] }]
4496
+ });
4497
+ }
4498
+ }
4499
+ }
4500
+ emptyDocument() {
4501
+ const length = this.text.length;
4502
+ this.add({
4503
+ code: "empty-document",
4504
+ range: this.range(0, length),
4505
+ message: "The document is empty.",
4506
+ next: 'start with { "slides": [ { "title": "\u2026" } ] }.',
4507
+ fixes: [{ id: "insert-minimal-deck", title: "Insert minimal deck", safe: false, edits: [{ offset: 0, length, insert: MINIMAL_DECK }], caret: MINIMAL_DECK.indexOf('""') + 1 }]
4508
+ });
4509
+ }
4510
+ looksLikeOtherFormat(first) {
4511
+ const head = this.text.slice(first.start);
4512
+ const lineEnd = lineBounds(this.text, first.start).end;
4513
+ const range = this.range(first.start, Math.max(first.end, this.text.slice(0, lineEnd).trimEnd().length));
4514
+ if (/^---(?:\s|$)/.test(head) || !head.startsWith("#") && YAML_START.test(head) || /^-\s+\S/.test(head)) {
4515
+ this.add({
4516
+ code: "looks-like-yaml",
4517
+ range,
4518
+ message: "This is not JSON. It looks like YAML.",
4519
+ next: "switch the format to YAML (read it with fromYaml, then validate the presentation it returns), or add braces and quotes to make it JSON.",
4520
+ fixes: [{ id: "switch-yaml", title: "Switch format to YAML", safe: false, edits: [], action: "switch-format", format: "yaml" }]
4521
+ });
4522
+ return true;
4523
+ }
4524
+ if (head.startsWith("#") || head.startsWith("```") || /^>\s/.test(head) || /^[*]\s+\S/.test(head)) {
4525
+ this.add({
4526
+ code: "looks-like-markdown",
4527
+ range,
4528
+ message: "This is not JSON. It looks like Markdown.",
4529
+ next: "switch the format to Markdown (read it with fromMarkdown, then validate the presentation it returns), or write the deck as a JSON object.",
4530
+ fixes: [{ id: "switch-markdown", title: "Switch format to Markdown", safe: false, edits: [], action: "switch-format", format: "md" }]
4531
+ });
4532
+ return true;
4533
+ }
4534
+ return false;
4535
+ }
4536
+ notAnObject(root, first) {
4537
+ const literal = this.text.slice(root.start, root.end);
4538
+ const what = root.kind === "array" ? "an array" : root.kind === "string" ? "a string" : literal === "true" || literal === "false" ? "a boolean" : literal === "null" ? "null" : "a number";
4539
+ const wrap = root.kind === "array" ? [
4540
+ {
4541
+ id: "wrap-slides",
4542
+ title: 'Wrap in { "slides" ... }',
4543
+ safe: false,
4544
+ edits: [
4545
+ { offset: root.start, length: 0, insert: '{ "slides": ' },
4546
+ { offset: root.end, length: 0, insert: " }" }
4547
+ ]
4548
+ }
4549
+ ] : [];
4550
+ this.add({
4551
+ code: "not-an-object",
4552
+ range: this.range(first.start, root.kind === "array" ? first.end : root.end),
4553
+ message: `The top level must be an object { \u2026 }, but this is ${what}.`,
4554
+ next: root.kind === "array" ? 'wrap it as { "slides": [ \u2026 ] }.' : 'write an object such as { "slides": [ { "title": "\u2026" } ] }.',
4555
+ fixes: wrap
4556
+ });
4557
+ }
4558
+ // -- lexical problems (no structure needed) --
4559
+ lexicalProblems() {
4560
+ for (const token of this.comments) {
4561
+ const unclosed = token.block && token.terminated === false;
4562
+ const whole = this.removalOf(token);
4563
+ if (unclosed) {
4564
+ this.add({
4565
+ code: "unclosed-comment",
4566
+ range: this.range(token.start, token.start + 2),
4567
+ message: "This `/*` comment is never closed.",
4568
+ next: "end it with `*/`, or remove it. Everything after `/*` is part of the comment.",
4569
+ fixes: [
4570
+ { id: "close-comment", title: "Close comment", safe: false, edits: [{ offset: this.text.length, length: 0, insert: " */" }] },
4571
+ { id: "remove-comment", title: "Remove comment", safe: false, edits: [whole] }
4572
+ ]
4573
+ });
4574
+ } else {
4575
+ this.add({
4576
+ code: "comment",
4577
+ range: this.range(token.start, token.end),
4578
+ message: "JSON does not allow comments.",
4579
+ next: "remove it, or switch to YAML, where # comments are kept.",
4580
+ fixes: [
4581
+ { id: "remove-comment", title: "Remove comment", safe: true, edits: [whole] },
4582
+ { id: "switch-yaml", title: "Switch format to YAML", safe: false, edits: [], action: "switch-format", format: "yaml" }
4583
+ ]
4584
+ });
4585
+ }
4586
+ }
4587
+ for (const token of this.tokens) {
4588
+ if (token.kind === "string") this.stringProblems(token);
4589
+ else if (token.kind === "badnumber") this.numberProblem(token);
4590
+ else if (token.kind === "unknown") this.unknownProblem(token);
4591
+ }
4592
+ }
4593
+ /** The edit that removes a comment: its whole line when it stands alone on it, else just the comment and the spaces before it. */
4594
+ removalOf(token) {
4595
+ let start = token.start;
4596
+ let end = token.end;
4597
+ while (start > 0 && (this.text[start - 1] === " " || this.text[start - 1] === " ")) start--;
4598
+ const alone = start === 0 || isBreak(this.text[start - 1]);
4599
+ let after = end;
4600
+ while (after < this.text.length && (this.text[after] === " " || this.text[after] === " ")) after++;
4601
+ if (alone && (after >= this.text.length || isBreak(this.text[after]))) {
4602
+ end = after;
4603
+ if (this.text[end] === "\r" && this.text[end + 1] === "\n") end += 2;
4604
+ else if (end < this.text.length) end++;
4605
+ return { offset: start, length: end - start, insert: "" };
4606
+ }
4607
+ return { offset: start, length: end - start, insert: "" };
4608
+ }
4609
+ stringProblems(token) {
4610
+ const { text } = this;
4611
+ if (token.quote === "single") {
4612
+ this.add({
4613
+ code: "single-quotes",
4614
+ range: this.range(token.start, token.end),
4615
+ message: "JSON strings use double quotes, not single quotes.",
4616
+ next: `write ${unquoteSingle(token.text)} instead of ${clip(token.text, 30)}.`,
4617
+ fixes: [{ id: "double-quotes", title: "Use double quotes", safe: true, edits: [{ offset: token.start, length: token.end - token.start, insert: unquoteSingle(token.text) }] }]
4618
+ });
4619
+ return;
4620
+ }
4621
+ if (token.quote === "typographic") {
4622
+ const edits = [{ offset: token.start, length: 1, insert: '"' }];
4623
+ if (token.terminated) edits.push({ offset: token.end - 1, length: 1, insert: '"' });
4624
+ this.add({
4625
+ code: "typographic-quotes",
4626
+ range: this.range(token.start, token.end),
4627
+ message: 'These are typographic quotes; JSON needs straight quotes (").',
4628
+ next: "replace them with straight double quotes. Word processors and chat apps change them as you paste.",
4629
+ fixes: [{ id: "straight-quotes", title: "Replace with straight quotes", safe: token.terminated === true, edits }]
4630
+ });
4631
+ return;
4632
+ }
4633
+ const escapes = this.lexical.escapes.get(token);
4634
+ const unicode = this.lexical.unicode.get(token);
4635
+ const controls = this.lexical.controls.get(token);
4636
+ if (token.terminated === false) {
4637
+ const lineEnd = token.start + clipTrailing(text.slice(token.start, token.end)).length;
4638
+ this.add({
4639
+ code: "unclosed-string",
4640
+ range: this.range(token.start, lineEnd),
4641
+ message: "This string is not closed before the end of the line.",
4642
+ next: "close it with a double quote. JSON strings stay on one line: write a line break as \\n.",
4643
+ fixes: [{ id: "close-string", title: "Close the string", safe: false, edits: [{ offset: lineEnd, length: 0, insert: '"' }] }]
4644
+ });
4645
+ }
4646
+ if (escapes?.length) {
4647
+ const first = escapes[0];
4648
+ const shown = text.slice(first, first + 2);
4649
+ this.add({
4650
+ code: "invalid-escape",
4651
+ range: this.range(first, Math.min(token.end, first + 2)),
4652
+ message: `\`${shown.trim() || "\\"}\` is not a JSON escape.`,
4653
+ next: 'write a backslash as \\\\ (the valid escapes are \\" \\\\ \\/ \\b \\f \\n \\r \\t and \\uXXXX).',
4654
+ fixes: [{ id: "double-backslash", title: escapes.length > 1 ? "Double the backslashes" : "Double the backslash", safe: false, edits: escapes.map((offset) => ({ offset, length: 0, insert: "\\" })) }]
4655
+ });
4656
+ }
4657
+ if (unicode?.length) {
4658
+ const first = unicode[0];
4659
+ this.add({
4660
+ code: "invalid-unicode",
4661
+ range: this.range(first, Math.min(token.end, first + 6)),
4662
+ message: "`\\u` must be followed by four hex digits.",
4663
+ next: "write the character as \\u00e9 (four digits 0 to 9 and a to f), or as the character itself.",
4664
+ fixes: []
4665
+ });
4666
+ }
4667
+ if (controls?.length) {
4668
+ const first = controls[0];
4669
+ this.add({
4670
+ code: "control-character",
4671
+ range: this.range(first, first + 1),
4672
+ message: "A tab or control character sits inside this string.",
4673
+ next: "write it as \\t, or as \\n for a line break.",
4674
+ fixes: [
4675
+ {
4676
+ id: "escape-controls",
4677
+ title: "Replace with escape",
4678
+ safe: true,
4679
+ edits: controls.map((offset) => {
4680
+ const code = text.charCodeAt(offset);
4681
+ const named = { 8: "\\b", 9: "\\t", 12: "\\f" };
4682
+ return { offset, length: 1, insert: named[code] ?? `\\u${code.toString(16).padStart(4, "0")}` };
4683
+ })
4684
+ }
4685
+ ]
4686
+ });
4687
+ }
4688
+ }
4689
+ numberProblem(token) {
4690
+ const word = token.text;
4691
+ const fixes = [];
4692
+ let message = `\`${word}\` is not a JSON number.`;
4693
+ let next = "Numbers have no leading zero, plus sign or trailing dot; put text such as 12% in quotes.";
4694
+ const numeric = (id, title, value) => {
4695
+ fixes.push({ id, title, safe: false, edits: [{ offset: token.start, length: token.end - token.start, insert: value }] });
4696
+ };
4697
+ if (/^0[0-9]+$/.test(word) || /^-0[0-9]+$/.test(word)) {
4698
+ message = `\`${word}\` has a leading zero, which JSON numbers do not allow.`;
4699
+ next = 'remove the leading zero, or put it in quotes if it is an identifier such as "007".';
4700
+ numeric("remove-leading-zero", "Remove leading zeros", word.replace(/^(-?)0+(?=[0-9])/, "$1"));
4701
+ } else if (/^\+[0-9]/.test(word) && NUMBER.test(word.slice(1))) {
4702
+ message = `\`${word}\` has a plus sign, which JSON numbers do not allow.`;
4703
+ next = "remove the plus sign.";
4704
+ numeric("remove-plus", "Remove plus sign", word.slice(1));
4705
+ } else if (/^-?\.[0-9]+(?:[eE][+-]?[0-9]+)?$/.test(word)) {
4706
+ message = `\`${word}\` is missing the digit before the dot.`;
4707
+ next = `write ${word.startsWith("-") ? "-0" : "0"}${word.replace("-", "")}.`;
4708
+ numeric("add-leading-zero", "Add leading zero", `${word.startsWith("-") ? "-0" : "0"}${word.replace("-", "")}`);
4709
+ } else if (/^-?[0-9]+\.(?:[eE][+-]?[0-9]+)?$/.test(word)) {
4710
+ message = `\`${word}\` ends with a dot, which JSON numbers do not allow.`;
4711
+ next = "remove the dot or add digits after it.";
4712
+ numeric("remove-trailing-dot", "Remove trailing dot", word.replace(".", ""));
4713
+ }
4714
+ fixes.push({ id: "quote-value", title: "Quote the value", safe: false, edits: [{ offset: token.start, length: token.end - token.start, insert: JSON.stringify(word) }] });
4715
+ this.add({ code: "invalid-number", range: this.range(token.start, token.end), message, next, fixes });
4716
+ }
4717
+ unknownProblem(token) {
4718
+ if (token === this.tokens[0]) return;
4719
+ const shown = clip(token.text, 12);
4720
+ const invisible = /^[\s\u200B-\u200D\u2060\uFEFF]+$/u.test(token.text);
4721
+ this.add({
4722
+ code: "invalid-symbol",
4723
+ range: this.range(token.start, token.end),
4724
+ message: invisible ? "An invisible or non-standard space sits here." : `Unexpected character \`${shown}\`.`,
4725
+ next: invisible ? "remove it, or replace it with a regular space. JSON allows only space, tab and line breaks between tokens." : "remove it. JSON has no use for this character outside a string.",
4726
+ fixes: [{ id: "remove-character", title: invisible ? "Replace with a space" : "Remove the character", safe: invisible, edits: [{ offset: token.start, length: token.end - token.start, insert: invisible ? " " : "" }] }]
4727
+ });
4728
+ }
4729
+ // -- values --
4730
+ parseValue(path, key2) {
4731
+ const token = this.peek();
4732
+ switch (token.kind) {
4733
+ case "lbrace":
4734
+ return this.parseObject(path);
4735
+ case "lbracket":
4736
+ return this.parseArray(path);
4737
+ case "string": {
4738
+ this.take();
4739
+ if (token.terminated === false) this.mute();
4740
+ return { start: token.start, end: token.end, kind: "string" };
4741
+ }
4742
+ case "number":
4743
+ case "literal":
4744
+ case "badnumber":
4745
+ this.take();
4746
+ return { start: token.start, end: token.end, kind: "scalar" };
4747
+ case "word":
4748
+ this.take();
4749
+ this.bareWord(token, path, key2);
4750
+ return { start: token.start, end: token.end, kind: "scalar" };
4751
+ case "unknown":
4752
+ this.take();
4753
+ return { start: token.start, end: token.end, kind: "scalar" };
4754
+ default:
4755
+ return void 0;
4756
+ }
4757
+ }
4758
+ bareWord(token, path, key2) {
4759
+ const word = token.text;
4760
+ const lower = word.toLowerCase();
4761
+ const edit = (insert) => [{ offset: token.start, length: token.end - token.start, insert }];
4762
+ let message;
4763
+ let next;
4764
+ const fixes = [];
4765
+ if (lower === "true" || lower === "false") {
4766
+ message = `\`${word}\` is not JSON. Write ${lower} in lowercase.`;
4767
+ next = `write ${lower}, or "${word}" in quotes if it is text.`;
4768
+ fixes.push({ id: `use-${lower}`, title: `Use ${lower}`, safe: true, edits: edit(lower) });
4769
+ } else if (["none", "nil", "null", "undefined", "nan", "infinity", "-infinity", "+infinity", "inf"].includes(lower)) {
4770
+ message = lower === "null" ? "`" + word + "` is not JSON. Write null in lowercase." : `\`${word}\` is not JSON. Use null for no value.`;
4771
+ next = "write null, or put it in quotes if it is text. JSON has no NaN, Infinity or undefined.";
4772
+ fixes.push({ id: "use-null", title: "Use null", safe: lower === "null", edits: edit("null") });
4773
+ } else {
4774
+ message = key2 !== void 0 ? `\`${key2}\` needs a quoted string: ${JSON.stringify(word)}.` : `\`${clip(word, 24)}\` needs quotes to be text: ${JSON.stringify(word)}.`;
4775
+ next = `put it in double quotes: ${JSON.stringify(word)}.`;
4776
+ }
4777
+ fixes.push({ id: "quote-value", title: "Quote the value", safe: false, edits: edit(JSON.stringify(word)) });
4778
+ this.add({ code: "bare-word", range: this.range(token.start, token.end), message, next, path: [...path], fixes });
4779
+ }
4780
+ parseObject(path) {
4781
+ const open = this.take();
4782
+ const container = { type: "{", open, path };
4783
+ this.stack.push(container);
4784
+ let expectKey = true;
4785
+ let comma;
4786
+ let lastEntry;
4787
+ let valueEnd = open.end;
4788
+ for (; ; ) {
4789
+ const token = this.peek();
4790
+ if (token.kind === "rbrace") {
4791
+ if (comma && expectKey) this.trailingComma(comma, container, lastEntry);
4792
+ this.take();
4793
+ this.stack.pop();
4794
+ return { start: open.start, end: token.end, kind: "object" };
4795
+ }
4796
+ if (token.kind === "eof") {
4797
+ this.unclosed(container);
4798
+ this.stack.pop();
4799
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "object" };
4800
+ }
4801
+ if (token.kind === "rbracket") {
4802
+ if (this.closeMismatched(container, token)) return { start: open.start, end: token.end, kind: "object" };
4803
+ this.unclosed(container);
4804
+ this.stack.pop();
4805
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "object" };
4806
+ }
4807
+ if (token.kind === "comma") {
4808
+ if (expectKey) this.doubleComma(token, container, comma !== void 0 || lastEntry !== void 0);
4809
+ else comma = token;
4810
+ this.take();
4811
+ expectKey = true;
4812
+ continue;
4813
+ }
4814
+ if (!expectKey) {
4815
+ if (this.startsEntry()) {
4816
+ this.structural({
4817
+ code: "missing-comma",
4818
+ range: this.range(valueEnd - 1, valueEnd),
4819
+ path: [...path],
4820
+ ...this.missingComma(valueEnd, lastEntry)
4821
+ });
4822
+ } else if (token.kind === "lbrace" || token.kind === "lbracket") {
4823
+ this.unclosed(container);
4824
+ this.stack.pop();
4825
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "object" };
4826
+ } else {
4827
+ this.take();
4828
+ this.unexpected(token, "a comma or }");
4829
+ valueEnd = token.end;
4830
+ continue;
4831
+ }
4832
+ }
4833
+ const entryStart = token.start;
4834
+ if (token.kind === "string" || token.kind === "word" && this.peek(1).kind === "colon") {
4835
+ const keyToken = this.take();
4836
+ if (token.kind === "word") this.unquotedKey(keyToken, path);
4837
+ if (keyToken.terminated === false) this.mute();
4838
+ const entryKey = keyToken.kind === "string" ? keyName(keyToken) : keyToken.text;
4839
+ const entryPath = [...path, entryKey];
4840
+ let hasColon = false;
4841
+ let colon;
4842
+ if (this.peek().kind === "colon") {
4843
+ colon = this.take();
4844
+ hasColon = true;
4845
+ } else {
4846
+ const following = this.peek();
4847
+ const valueFollows = following.kind === "lbrace" || following.kind === "lbracket" || following.kind === "number" || following.kind === "literal" || following.kind === "badnumber" || following.kind === "unknown" || (following.kind === "string" || following.kind === "word") && this.peek(1).kind !== "colon";
4848
+ this.missingColon(keyToken, entryKey, entryPath, valueFollows, container);
4849
+ if (!valueFollows) {
4850
+ this.mute();
4851
+ expectKey = false;
4852
+ comma = void 0;
4853
+ valueEnd = keyToken.end;
4854
+ lastEntry = { start: entryStart, text: clip(this.text.slice(entryStart, keyToken.end)) };
4855
+ continue;
4856
+ }
4857
+ }
4858
+ let value;
4859
+ const after = this.peek();
4860
+ const keyIsNext = (after.kind === "string" || after.kind === "word") && this.peek(1).kind === "colon";
4861
+ if (hasColon && (after.kind === "rbrace" || after.kind === "rbracket" || after.kind === "comma" || after.kind === "eof" || keyIsNext)) {
4862
+ this.missingValue(keyToken, colon, entryKey, entryPath);
4863
+ this.mute();
4864
+ valueEnd = colon.end;
4865
+ } else {
4866
+ value = this.parseValue(entryPath, entryKey);
4867
+ valueEnd = value?.end ?? keyToken.end;
4868
+ }
4869
+ const entryEnd = value?.end ?? valueEnd;
4870
+ lastEntry = { start: entryStart, text: entryText(this.text, entryStart, entryEnd, value?.kind) };
4871
+ expectKey = false;
4872
+ comma = void 0;
4873
+ continue;
4874
+ }
4875
+ if (token.kind === "lbrace" || token.kind === "lbracket") {
4876
+ this.unexpected(token, "a property name");
4877
+ valueEnd = this.parseValue([...path], void 0)?.end ?? valueEnd;
4878
+ this.mute();
4879
+ } else {
4880
+ this.take();
4881
+ this.unexpected(token, "a property name (a quoted string)");
4882
+ this.mute();
4883
+ valueEnd = token.end;
4884
+ }
4885
+ expectKey = false;
4886
+ comma = void 0;
4887
+ }
4888
+ }
4889
+ parseArray(path) {
4890
+ const open = this.take();
4891
+ const container = { type: "[", open, path };
4892
+ this.stack.push(container);
4893
+ let expectItem = true;
4894
+ let comma;
4895
+ let count = 0;
4896
+ let valueEnd = open.end;
4897
+ let lastItem;
4898
+ for (; ; ) {
4899
+ const token = this.peek();
4900
+ if (token.kind === "rbracket") {
4901
+ if (comma && expectItem) this.trailingComma(comma, container, lastItem);
4902
+ this.take();
4903
+ this.stack.pop();
4904
+ return { start: open.start, end: token.end, kind: "array" };
4905
+ }
4906
+ if (token.kind === "eof") {
4907
+ this.unclosed(container);
4908
+ this.stack.pop();
4909
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "array" };
4910
+ }
4911
+ if (token.kind === "rbrace") {
4912
+ if (this.closeMismatched(container, token)) return { start: open.start, end: token.end, kind: "array" };
4913
+ this.unclosed(container);
4914
+ this.stack.pop();
4915
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "array" };
4916
+ }
4917
+ if (token.kind === "comma") {
4918
+ if (expectItem) this.doubleComma(token, container, comma !== void 0 || lastItem !== void 0);
4919
+ else comma = token;
4920
+ this.take();
4921
+ expectItem = true;
4922
+ continue;
4923
+ }
4924
+ const startsValue = token.kind === "string" || token.kind === "number" || token.kind === "literal" || token.kind === "badnumber" || token.kind === "word" || token.kind === "lbrace" || token.kind === "lbracket";
4925
+ if (!startsValue) {
4926
+ this.take();
4927
+ this.unexpected(token, "a value");
4928
+ this.mute();
4929
+ valueEnd = token.end;
4930
+ continue;
4931
+ }
4932
+ if (!expectItem) {
4933
+ this.structural({
4934
+ code: "missing-comma",
4935
+ range: this.range(valueEnd - 1, valueEnd),
4936
+ path: [...path],
4937
+ ...this.missingComma(valueEnd, lastItem)
4938
+ });
4939
+ }
4940
+ const itemStart = token.start;
4941
+ const value = this.parseValue([...path, count], void 0);
4942
+ count++;
4943
+ valueEnd = value?.end ?? token.end;
4944
+ lastItem = { start: itemStart, text: entryText(this.text, itemStart, valueEnd, value?.kind) };
4945
+ expectItem = false;
4946
+ comma = void 0;
4947
+ }
4948
+ }
4949
+ // -- structural problems --
4950
+ missingComma(valueEnd, previous) {
4951
+ const line = this.line(Math.max(valueEnd - 1, 0));
4952
+ return {
4953
+ message: `Missing comma after ${previous ? `\`${previous.text}\`` : "the previous value"} (line ${line}).`,
4954
+ next: `add \`,\` right after it, on line ${line}.`,
4955
+ fixes: [{ id: "insert-comma", title: "Insert comma", safe: true, edits: [{ offset: valueEnd, length: 0, insert: "," }], caret: valueEnd + 1 }]
4956
+ };
4957
+ }
4958
+ trailingComma(comma, container, previous) {
4959
+ const what = container.type === "{" ? "property" : "item";
4960
+ this.add({
4961
+ code: "trailing-comma",
4962
+ range: this.range(comma.start, comma.end),
4963
+ path: [...container.path],
4964
+ message: previous ? `Trailing comma after \`${previous.text}\`. JSON does not allow a comma after the last ${what}.` : "Trailing comma. JSON does not allow a comma here.",
4965
+ next: "remove the comma.",
4966
+ fixes: [{ id: "remove-comma", title: "Remove comma", safe: true, edits: [{ offset: comma.start, length: comma.end - comma.start, insert: "" }] }]
4967
+ });
4968
+ }
4969
+ doubleComma(comma, container, afterSomething) {
4970
+ this.add({
4971
+ code: "double-comma",
4972
+ range: this.range(comma.start, comma.end),
4973
+ path: [...container.path],
4974
+ message: afterSomething ? "Two commas in a row." : `This comma has nothing before it in the ${container.type === "{" ? "object" : "list"}.`,
4975
+ next: "remove the extra comma.",
4976
+ fixes: [{ id: "remove-comma", title: "Remove comma", safe: true, edits: [{ offset: comma.start, length: comma.end - comma.start, insert: "" }] }]
4977
+ });
4978
+ }
4979
+ unquotedKey(token, path) {
4980
+ this.add({
4981
+ code: "unquoted-key",
4982
+ range: this.range(token.start, token.end),
4983
+ path: [...path, token.text],
4984
+ message: `Property names need double quotes: ${JSON.stringify(token.text)}.`,
4985
+ next: `write ${JSON.stringify(token.text)} with quotes.`,
4986
+ fixes: [{ id: "quote-key", title: "Quote the property name", safe: true, edits: [{ offset: token.start, length: token.end - token.start, insert: JSON.stringify(token.text) }] }]
4987
+ });
4988
+ }
4989
+ unexpected(token, expected) {
4990
+ this.add({
4991
+ code: "invalid-symbol",
4992
+ range: this.range(token.start, token.end),
4993
+ message: `Unexpected \`${clip(token.text, 20)}\`. Expected ${expected} here.`,
4994
+ next: "remove it, or add the missing punctuation before it.",
4995
+ fixes: [{ id: "remove-token", title: `Remove ${clip(token.text, 12)}`, safe: false, edits: [{ offset: token.start, length: token.end - token.start, insert: "" }] }]
4996
+ });
4997
+ }
4998
+ missingColon(keyToken, key2, path, valueFollows, container) {
4999
+ const keyEnd = keyToken.end;
5000
+ const shown = clip(key2, 30);
5001
+ const removal = this.removeEntry(keyToken.start, keyEnd);
5002
+ this.structural({
5003
+ code: "missing-colon",
5004
+ range: this.range(keyToken.start, keyToken.end),
5005
+ path,
5006
+ message: valueFollows ? `\`${shown}\` is missing a colon.` : `\`${shown}\` has no colon and value.`,
5007
+ next: valueFollows ? "add `:` after the property name." : `write ${JSON.stringify(key2)}: and a value, or remove the property.`,
5008
+ fixes: [
5009
+ { id: "insert-colon", title: "Add colon", safe: valueFollows, edits: [{ offset: keyEnd, length: 0, insert: valueFollows ? ":" : ": " }], caret: keyEnd + (valueFollows ? 1 : 2) },
5010
+ ...valueFollows ? [] : [{ id: "remove-key", title: `Remove ${JSON.stringify(key2)}`, safe: false, edits: removal }]
5011
+ ]
5012
+ });
5013
+ }
5014
+ missingValue(keyToken, colon, key2, path) {
5015
+ const needsComma = (() => {
5016
+ const next = this.peek();
5017
+ return (next.kind === "string" || next.kind === "word") && this.peek(1).kind === "colon";
5018
+ })();
5019
+ const hint = valueHint(path);
5020
+ const gap = this.text[colon.end] === " " || this.text[colon.end] === " " ? 0 : 1;
5021
+ const at2 = colon.end + (gap === 0 ? 1 : 0);
5022
+ const edits = [{ offset: at2, length: 0, insert: `${gap ? " " : ""}${hint.insert}` }];
5023
+ if (needsComma) edits.push({ offset: at2, length: 0, insert: "," });
5024
+ const fixes = [{ id: "add-value", title: hint.title, safe: false, edits, caret: at2 + (gap ? 1 : 0) + hint.caret }];
5025
+ fixes.push({ id: "remove-key", title: `Remove ${JSON.stringify(key2)}`, safe: false, edits: this.removeEntry(keyToken.start, colon.end) });
5026
+ this.structural({
5027
+ code: "missing-value",
5028
+ range: this.range(keyToken.start, colon.end),
5029
+ path,
5030
+ message: `\`${key2}\` needs a value.`,
5031
+ next: `${hint.next} Press Ctrl+Space after the colon for options.`,
5032
+ fixes
5033
+ });
5034
+ }
5035
+ /** The edits that remove an entry whose value is missing (key through colon), and the comma that belonged to it. */
5036
+ removeEntry(start, end) {
5037
+ const { text } = this;
5038
+ const firstFrom = (offset) => {
5039
+ let low = 0;
5040
+ let high = this.tokens.length;
5041
+ while (low < high) {
5042
+ const middle = low + high >> 1;
5043
+ if (this.tokens[middle].start < offset) low = middle + 1;
5044
+ else high = middle;
5045
+ }
5046
+ return low;
5047
+ };
5048
+ const startIndex = firstFrom(start);
5049
+ const before = this.tokens[startIndex - 1];
5050
+ const firstIndex = firstFrom(end);
5051
+ const first = this.tokens[firstIndex];
5052
+ const second = first?.kind === "comma" ? this.tokens[firstIndex + 1] : first;
5053
+ const entryFollows = (token) => token !== void 0 && (token.kind === "string" || token.kind === "word" && this.tokens[firstFrom(token.start) + 1]?.kind === "colon");
5054
+ let from = start;
5055
+ let to = end;
5056
+ if (first?.kind === "comma") to = first.end;
5057
+ else if (!entryFollows(second) && before?.kind === "comma") from = before.start;
5058
+ const alone = from === start && /^[ \t]*$/.test(text.slice(lineBounds(text, start).start, start)) && /^[ \t]*$/.test(text.slice(to, lineBounds(text, Math.max(to - 1, 0)).end));
5059
+ if (alone) {
5060
+ const bounds = lineBounds(text, start);
5061
+ let lineEnd = lineBounds(text, Math.max(to - 1, 0)).end;
5062
+ if (text[lineEnd] === "\r" && text[lineEnd + 1] === "\n") lineEnd += 2;
5063
+ else if (lineEnd < text.length) lineEnd++;
5064
+ return [{ offset: bounds.start, length: lineEnd - bounds.start, insert: "" }];
5065
+ }
5066
+ return [{ offset: from, length: to - from, insert: "" }];
5067
+ }
5068
+ unclosed(container) {
5069
+ const { text } = this;
5070
+ const closer = container.type === "{" ? "}" : "]";
5071
+ const open = container.open;
5072
+ const last = this.last && this.last.start >= open.start ? this.last : open;
5073
+ if (last.kind === "string" && last.terminated === false) {
5074
+ this.mute();
5075
+ return;
5076
+ }
5077
+ const dangling = last.kind === "comma" && last !== open;
5078
+ const insertAt = dangling ? last.start : last.end;
5079
+ const removed = dangling ? last.end - last.start : 0;
5080
+ const sameLine = this.line(open.start) === this.line(Math.max(last.end - 1, 0));
5081
+ const own4 = sameLine ? closer : `
5082
+ ${indentOf(text, open.start)}${closer}`;
5083
+ const inner = this.cascade.last === this.last && removed === 0 ? this.cascade.inserts : [];
5084
+ const insert = inner.join("") + own4;
5085
+ this.cascade = { last: this.last, inserts: [...inner, own4] };
5086
+ const closers = insert.replace(/\s/g, "");
5087
+ const key2 = container.path.at(-1);
5088
+ const entry = typeof key2 === "string" ? `"${key2}": ${open.text}` : open.text;
5089
+ const context = container.type === "{" ? firstEntryOf(text, open) : void 0;
5090
+ const what = container.type === "{" ? "property" : "item";
5091
+ this.add({
5092
+ code: container.type === "{" ? "unclosed-object" : "unclosed-array",
5093
+ range: this.range(open.start, open.end),
5094
+ path: [...container.path],
5095
+ message: container.type === "{" ? `The \`${entry}\` on line ${this.line(open.start)}${context ? ` (starting \`${context}\`)` : ""} is never closed.` : `The list \`${entry}\` on line ${this.line(open.start)} is never closed.`,
5096
+ next: `add \`${closer}\` after its last ${what}${sameLine ? "" : ", on its own line"}.`,
5097
+ fixes: [{ id: "insert-closer", title: `Insert ${closers}`, safe: false, edits: [{ offset: insertAt, length: removed, insert }], caret: insertAt + insert.length }]
5098
+ });
5099
+ this.mute();
5100
+ }
5101
+ /** A closing bracket of the other kind. With an enclosing container of its kind it ends that one (the current is unclosed); alone, it is a typo for the right one. */
5102
+ closeMismatched(container, token) {
5103
+ const wanted = container.type === "{" ? "[" : "{";
5104
+ if (this.stack.some((other) => other !== container && other.type === wanted)) return false;
5105
+ const closer = container.type === "{" ? "}" : "]";
5106
+ const open = container.open;
5107
+ this.take();
5108
+ this.stack.pop();
5109
+ this.add({
5110
+ code: container.type === "{" ? "unclosed-object" : "unclosed-array",
5111
+ range: this.range(open.start, open.end),
5112
+ path: [...container.path],
5113
+ message: `The \`${open.text}\` on line ${this.line(open.start)} is closed with \`${token.text}\`, which does not match.`,
5114
+ next: `end it with \`${closer}\`.`,
5115
+ fixes: [{ id: "replace-closer", title: `Replace ${token.text} with ${closer}`, safe: false, edits: [{ offset: token.start, length: token.end - token.start, insert: closer }] }]
5116
+ });
5117
+ return true;
5118
+ }
5119
+ };
5120
+ function keyName(token) {
5121
+ if (token.quote === "double" && token.terminated !== false) {
5122
+ try {
5123
+ return JSON.parse(token.text);
5124
+ } catch {
5125
+ }
5126
+ }
5127
+ const inner = token.text.slice(1, token.terminated === false ? void 0 : -1);
5128
+ return inner;
5129
+ }
5130
+ function clipTrailing(value) {
5131
+ return value.replace(/[ \t]+$/, "");
5132
+ }
5133
+ function entryText(text, start, end, kind) {
5134
+ const raw = text.slice(start, end);
5135
+ if (kind === "object" || kind === "array") {
5136
+ const head = raw.slice(0, raw.indexOf(kind === "object" ? "{" : "["));
5137
+ return clip(`${head}${kind === "object" ? "{\u2026}" : "[\u2026]"}`, 40);
5138
+ }
5139
+ return clip(raw, 40);
5140
+ }
5141
+ function firstEntryOf(text, open) {
5142
+ const rest = text.slice(open.end, open.end + 200);
5143
+ const match = /^\s*("(?:[^"\\\n]|\\.)*"\s*:\s*(?:"(?:[^"\\\n]|\\.)*"|[^\s,{}[\]]+))/.exec(rest);
5144
+ return match?.[1] ? clip(match[1], 36) : void 0;
3486
5145
  }
5146
+ function jsonShort(value) {
5147
+ const text = JSON.stringify(value);
5148
+ return text !== void 0 && text.length <= 32 ? text : void 0;
5149
+ }
5150
+ function exampleOf(info) {
5151
+ const example = info.examples.map(jsonShort).find((item) => item !== void 0) ?? (info.enum?.[0] ? jsonShort(info.enum[0].value) : void 0);
5152
+ if (example) return example;
5153
+ const type = info.types[0];
5154
+ return type === "boolean" ? "true" : type === "number" || type === "integer" ? "1" : type === "object" ? "{ \u2026 }" : type === "array" ? "[ \u2026 ]" : '"\u2026"';
5155
+ }
5156
+ function listValues(values) {
5157
+ const shown = values.slice(0, 5).map((entry) => String(entry.value));
5158
+ return values.length > 5 ? `one of ${values.length} values (${shown.slice(0, 3).join(", ")}, \u2026)` : `one of ${shown.join(", ")}`;
5159
+ }
5160
+ function joinAlternatives(items) {
5161
+ return items.length <= 1 ? items[0] ?? "" : `${items.slice(0, -1).join(", ")}, or ${items.at(-1)}`;
5162
+ }
5163
+ var article = (word) => /^[aeiou]/i.test(word) ? "an" : "a";
5164
+ function valueHint(path) {
5165
+ let info;
5166
+ try {
5167
+ info = describeField(path);
5168
+ } catch {
5169
+ info = void 0;
5170
+ }
5171
+ if (!info?.known || !info.variants.length) {
5172
+ return { next: "write a value (text in quotes, a number, true, false, null, an object { } or a list [ ]), or remove the key.", title: 'Add ""', insert: '""', caret: 1 };
5173
+ }
5174
+ const phrases = [];
5175
+ const phrased = /* @__PURE__ */ new Set();
5176
+ const order2 = [info.primary, ...info.variants.map((_, index) => index).filter((index) => index !== info.primary)];
5177
+ for (const index of order2) {
5178
+ const variant = info.variants[index];
5179
+ if (!variant || phrases.length >= HINT_FORMS_LIMIT || phrased.has(variant.type)) continue;
5180
+ phrased.add(variant.type);
5181
+ let phrase;
5182
+ if (variant.enum?.length) phrase = listValues(variant.enum);
5183
+ else if (variant.type === "string" && info.catalogKind) {
5184
+ const example = info.examples.find((item) => typeof item === "string");
5185
+ const name = KIND_NAMES[info.catalogKind] ?? "catalog";
5186
+ phrase = `${article(name)} ${name} id${example ? ` such as ${JSON.stringify(example)}` : ""}`;
5187
+ } else if (variant.type === "string") {
5188
+ const example = info.examples.find((item) => typeof item === "string" && item.length <= 24);
5189
+ phrase = example ? `a string such as ${JSON.stringify(example)}` : "text in double quotes";
5190
+ } else if (variant.type === "boolean") phrase = "true or false";
5191
+ else if (variant.type === "integer" || variant.type === "number") {
5192
+ const example = info.examples.find((item) => typeof item === "number");
5193
+ phrase = `a number${example !== void 0 ? ` such as ${example}` : ""}`;
5194
+ } else if (variant.type === "object") {
5195
+ const keyName2 = variant.requiredChildren[0] ?? info.children.find((child) => !child.advanced)?.key;
5196
+ if (keyName2) {
5197
+ const child = describeField([...path, keyName2]);
5198
+ phrase = `an object such as { ${JSON.stringify(keyName2)}: ${exampleOf(child)} }`;
5199
+ } else phrase = "an object { }";
5200
+ } else if (variant.type === "array") {
5201
+ const item = info.itemInfo;
5202
+ phrase = item?.known ? `a list such as [ ${exampleOf(item)} ]` : "a list [ ]";
5203
+ }
5204
+ if (phrase && !phrases.includes(phrase)) phrases.push(phrase);
5205
+ }
5206
+ const primary = info.variants[info.primary];
5207
+ const next = `${joinAlternatives([...phrases, "remove the key"])}.`;
5208
+ let title = 'Add ""';
5209
+ let insert = '""';
5210
+ let caret = 1;
5211
+ if (primary?.type === "object") {
5212
+ title = "Add { }";
5213
+ insert = "{}";
5214
+ caret = 1;
5215
+ } else if (primary?.type === "array") {
5216
+ title = "Add [ ]";
5217
+ insert = "[]";
5218
+ caret = 1;
5219
+ } else if (primary?.enum?.length) {
5220
+ const value = JSON.stringify(primary.enum[0]?.value);
5221
+ title = `Add ${value}`;
5222
+ insert = value;
5223
+ caret = value.length;
5224
+ } else if (primary?.type === "boolean") {
5225
+ title = "Add false";
5226
+ insert = "false";
5227
+ caret = insert.length;
5228
+ } else if (primary?.type === "integer" || primary?.type === "number") {
5229
+ let value = "0";
5230
+ try {
5231
+ const made = createSchemaValue(schemaAtPath({}, path));
5232
+ if (typeof made === "number") value = String(made);
5233
+ } catch {
5234
+ }
5235
+ title = `Add ${value}`;
5236
+ insert = value;
5237
+ caret = insert.length;
5238
+ }
5239
+ return { next, title, insert, caret };
5240
+ }
5241
+ function analyzeJsonSyntax(text, options = {}) {
5242
+ const limit = Math.max(0, options.limit ?? 5);
5243
+ const analyzer = new Analyzer(String(text ?? ""));
5244
+ try {
5245
+ analyzer.run();
5246
+ } catch {
5247
+ return [];
5248
+ }
5249
+ const seen = /* @__PURE__ */ new Set();
5250
+ return analyzer.problems.sort((a, b) => a.problem.range.offset - b.problem.range.offset || a.order - b.order).map((entry) => entry.problem).filter((problem) => {
5251
+ const id = `${problem.code}:${problem.range.offset}`;
5252
+ if (seen.has(id)) return false;
5253
+ seen.add(id);
5254
+ return true;
5255
+ }).slice(0, limit);
5256
+ }
5257
+
5258
+ // src/check-source.ts
3487
5259
  function parseSource(source) {
3488
5260
  const errors = [], tree = parseTree(
3489
5261
  source.startsWith("\uFEFF") ? " " + source.slice(1) : source,
@@ -3494,35 +5266,22 @@ function parseSource(source) {
3494
5266
  allowEmptyContent: false
3495
5267
  }
3496
5268
  );
3497
- const lineStarts = [0];
3498
- for (let i = 0; i < source.length; i++) {
3499
- if (source[i] === "\r") {
3500
- if (source[i + 1] === "\n") i++;
3501
- lineStarts.push(i + 1);
3502
- } else if (source[i] === "\n") lineStarts.push(i + 1);
3503
- }
3504
- const location = (offset, length) => {
3505
- let lo = 0, hi = lineStarts.length;
3506
- while (lo + 1 < hi) {
3507
- const mid = lo + hi >> 1;
3508
- if ((lineStarts[mid] ?? 0) <= offset) lo = mid;
3509
- else hi = mid;
3510
- }
3511
- return {
3512
- offset,
3513
- length,
3514
- line: lo + 1,
3515
- column: offset - (lineStarts[lo] ?? 0) + 1
3516
- };
3517
- };
5269
+ const location = createLocator(source);
3518
5270
  if (errors.length || !tree) {
3519
- const note = hint(source);
5271
+ const problems = analyzeJsonSyntax(source);
3520
5272
  return {
3521
- syntax: errors.map(
3522
- (error, index) => finding("opf/json-syntax", {
5273
+ syntax: problems.length ? problems.map(
5274
+ (problem) => finding("opf/json-syntax", {
5275
+ path: "",
5276
+ message: problem.message,
5277
+ help: problem.next,
5278
+ location: problem.range
5279
+ })
5280
+ ) : errors.map(
5281
+ (error) => finding("opf/json-syntax", {
3523
5282
  path: "",
3524
5283
  message: printParseErrorCode(error.error),
3525
- help: `Repair the JSON syntax at this source range. No content or whitespace has been rewritten.${index === 0 ? note : ""}`,
5284
+ help: "Repair the JSON syntax at this source range. No content or whitespace has been rewritten.",
3526
5285
  location: location(error.offset, error.length)
3527
5286
  })
3528
5287
  ),
@@ -3611,14 +5370,14 @@ function slideScriptMeasurement(presentation, slideIndex, measurement, options)
3611
5370
  const forLanguage = (lang) => {
3612
5371
  if (typeof lang !== "string" || !lang) return void 0;
3613
5372
  if (!languages.has(lang)) {
3614
- let own2;
5373
+ let own4;
3615
5374
  try {
3616
5375
  const profile = scriptProfile(presentation, slideIndex, options, lang);
3617
- own2 = profile.languageSource === "option" && profile.bcp47?.toLowerCase() !== deckProfile.bcp47?.toLowerCase() ? forScripts.call(measurement, profile) : void 0;
5376
+ own4 = profile.languageSource === "option" && profile.bcp47?.toLowerCase() !== deckProfile.bcp47?.toLowerCase() ? forScripts.call(measurement, profile) : void 0;
3618
5377
  } catch {
3619
- own2 = void 0;
5378
+ own4 = void 0;
3620
5379
  }
3621
- languages.set(lang, own2);
5380
+ languages.set(lang, own4);
3622
5381
  }
3623
5382
  return languages.get(lang);
3624
5383
  };
@@ -3627,8 +5386,8 @@ function slideScriptMeasurement(presentation, slideIndex, measurement, options)
3627
5386
  if (typeof deck.resolveStyle === "function") scripted.resolveStyle = (style) => deck.resolveStyle.call(deck, style);
3628
5387
  if (typeof deck.outlineBounds === "function")
3629
5388
  scripted.outlineBounds = (text, size, style) => {
3630
- const own2 = pick(style);
3631
- return typeof own2.outlineBounds === "function" ? own2.outlineBounds(text, size, style) : deck.outlineBounds.call(deck, text, size, style);
5389
+ const own4 = pick(style);
5390
+ return typeof own4.outlineBounds === "function" ? own4.outlineBounds(text, size, style) : deck.outlineBounds.call(deck, text, size, style);
3632
5391
  };
3633
5392
  return scripted;
3634
5393
  }
@@ -3700,15 +5459,15 @@ var lazy = (compute) => {
3700
5459
  };
3701
5460
  function relax(value) {
3702
5461
  const copy = JSON.parse(JSON.stringify(value));
3703
- const walk = (node) => {
3704
- if (Array.isArray(node)) return node.forEach(walk);
5462
+ const walk2 = (node) => {
5463
+ if (Array.isArray(node)) return node.forEach(walk2);
3705
5464
  if (node && typeof node === "object") {
3706
5465
  const object2 = node;
3707
5466
  if (object2.overflow === "error") object2.overflow = "warn";
3708
- for (const child of Object.values(object2)) walk(child);
5467
+ for (const child of Object.values(object2)) walk2(child);
3709
5468
  }
3710
5469
  };
3711
- walk(copy);
5470
+ walk2(copy);
3712
5471
  return copy;
3713
5472
  }
3714
5473
  function buildSlides(document, options) {
@@ -3776,7 +5535,7 @@ function buildSlides(document, options) {
3776
5535
  var SEVERITIES = ["error", "warning", "info"];
3777
5536
  var OPTION_KEYS = ["only", "ignore", "severity", "ignorePaths", "catalogs", "contracts", "thresholds", "fonts", "chartPalette", "template", "values"];
3778
5537
  var DOCS = "https://github.com/OpenPresentation/opf/blob/main/docs/validate.md";
3779
- var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5538
+ var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3780
5539
  var OPFValidationError = class extends Error {
3781
5540
  /** The full report that failed. */
3782
5541
  report;
@@ -3809,14 +5568,14 @@ function names(list, what) {
3809
5568
  return result;
3810
5569
  }
3811
5570
  function resolveOptions(options) {
3812
- if (!isObject(options)) throw new TypeError("Validate options must be an object.");
5571
+ if (!isObject2(options)) throw new TypeError("Validate options must be an object.");
3813
5572
  for (const key2 of Object.keys(options)) if (!OPTION_KEYS.includes(key2)) throw new TypeError(`Unknown validate option ${JSON.stringify(key2)}. Options: ${OPTION_KEYS.join(", ")}.`);
3814
5573
  checkDocumentOptions(options);
3815
5574
  const only = names(options.only, "only");
3816
5575
  const ignore = names(options.ignore, "ignore");
3817
5576
  const severity = /* @__PURE__ */ new Map();
3818
5577
  if (options.severity !== void 0) {
3819
- if (!isObject(options.severity)) throw new TypeError('Validate severity must map rule ids or category names to a severity or "off".');
5578
+ if (!isObject2(options.severity)) throw new TypeError('Validate severity must map rule ids or category names to a severity or "off".');
3820
5579
  for (const [name, value] of Object.entries(options.severity)) {
3821
5580
  if (typeof value !== "string" || value !== "off" && !SEVERITIES.includes(value)) throw new TypeError(`Validate severity for ${JSON.stringify(name)} must be error, warning, info or off.`);
3822
5581
  const found = ruleFor(name, "severity");
@@ -3827,7 +5586,7 @@ function resolveOptions(options) {
3827
5586
  if (options.ignorePaths !== void 0) {
3828
5587
  if (!Array.isArray(options.ignorePaths)) throw new TypeError("Validate ignorePaths must be an array of {rule, path}.");
3829
5588
  for (const entry of options.ignorePaths) {
3830
- if (!isObject(entry) || typeof entry.rule !== "string" || typeof entry.path !== "string" || entry.path !== "" && !entry.path.startsWith("/"))
5589
+ if (!isObject2(entry) || typeof entry.rule !== "string" || typeof entry.path !== "string" || entry.path !== "" && !entry.path.startsWith("/"))
3831
5590
  throw new TypeError('Each validate ignorePaths entry needs a rule id, category name or "*", and a JSON Pointer path.');
3832
5591
  const found = entry.rule === "*" ? "*" : ruleFor(entry.rule, "ignorePaths");
3833
5592
  ignorePaths.push({
@@ -3838,7 +5597,7 @@ function resolveOptions(options) {
3838
5597
  }
3839
5598
  const thresholds = { ...DEFAULT_VALIDATION_THRESHOLDS };
3840
5599
  if (options.thresholds !== void 0) {
3841
- if (!isObject(options.thresholds)) throw new TypeError("Validate thresholds must be an object.");
5600
+ if (!isObject2(options.thresholds)) throw new TypeError("Validate thresholds must be an object.");
3842
5601
  for (const [key2, value] of Object.entries(options.thresholds)) {
3843
5602
  if (!(key2 in DEFAULT_VALIDATION_THRESHOLDS)) throw new TypeError(`Unknown validate threshold ${JSON.stringify(key2)}. Thresholds: ${Object.keys(DEFAULT_VALIDATION_THRESHOLDS).join(", ")}.`);
3844
5603
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new TypeError(`Validate threshold ${key2} must be a positive number.`);
@@ -3846,14 +5605,14 @@ function resolveOptions(options) {
3846
5605
  }
3847
5606
  }
3848
5607
  if (options.fonts !== void 0) {
3849
- if (!isObject(options.fonts)) throw new TypeError("Validate fonts must be a fonts object with a textMeasurement.");
5608
+ if (!isObject2(options.fonts)) throw new TypeError("Validate fonts must be a fonts object with a textMeasurement.");
3850
5609
  const measurement = options.fonts.textMeasurement;
3851
- if (measurement !== void 0 && typeof measurement !== "function" && !isObject(measurement)) throw new TypeError("Validate fonts.textMeasurement must be a text measurement, or a function of the slide index.");
5610
+ if (measurement !== void 0 && typeof measurement !== "function" && !isObject2(measurement)) throw new TypeError("Validate fonts.textMeasurement must be a text measurement, or a function of the slide index.");
3852
5611
  }
3853
5612
  if (options.chartPalette !== void 0 && (!Array.isArray(options.chartPalette) || !options.chartPalette.every((color) => typeof color === "string" && /^#[0-9a-fA-F]{6}$/.test(color))))
3854
5613
  throw new TypeError("Validate chartPalette must be an array of #RRGGBB colours.");
3855
5614
  if (options.template !== void 0 && typeof options.template !== "boolean") throw new TypeError("Validate template must be true or false.");
3856
- if (options.values !== void 0 && !isObject(options.values)) throw new TypeError("Validate values must be an object keyed by variable id.");
5615
+ if (options.values !== void 0 && !isObject2(options.values)) throw new TypeError("Validate values must be an object keyed by variable id.");
3857
5616
  const level = (info) => severity.get(info.id) ?? severity.get(info.category);
3858
5617
  const off = (info) => level(info) === "off" || !!ignore?.rules.has(info.id) || !!ignore?.categories.has(info.category);
3859
5618
  return {
@@ -3910,7 +5669,7 @@ function syntaxReport(parsed, run) {
3910
5669
  function validateDocument(document, run, parsed) {
3911
5670
  const engine = validateAgainstSchema(document, "presentation", { template: run.options.template, values: run.options.values, ...run.options.catalogs ? { catalogs: run.options.catalogs } : {} });
3912
5671
  const schemaValid = engine.errors.length === 0;
3913
- const slides = isObject(document) && Array.isArray(document.slides) ? document.slides : [];
5672
+ const slides = isObject2(document) && Array.isArray(document.slides) ? document.slides : [];
3914
5673
  const auditWanted = auditInfos.some((info) => run.enabled(info));
3915
5674
  const explainFormat = !schemaValid && auditWanted;
3916
5675
  const enabledRule = (info) => info ? run.enabled(info) || explainFormat && info.category === "format" && !run.off(info) : true;
@@ -3975,7 +5734,7 @@ function validateDocument(document, run, parsed) {
3975
5734
  if (info && run.ignored(info, splitPointer(next.path))) continue;
3976
5735
  const index = slideOf(next.path);
3977
5736
  if (next.slide === void 0 && index !== void 0 && next.scope !== "context") {
3978
- const id = isObject(slides[index]) ? slides[index].id : void 0;
5737
+ const id = isObject2(slides[index]) ? slides[index].id : void 0;
3979
5738
  next.slide = index;
3980
5739
  if (typeof id === "string") next.slideId = id;
3981
5740
  }
@@ -4046,4 +5805,4 @@ function assertValidCatalogRecord(kind, value) {
4046
5805
  if (!report2.valid) throw new OPFValidationError(report2);
4047
5806
  }
4048
5807
 
4049
- export { CATALOG_SCHEMA, DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, catalogDisplayKinds, catalogSchemaNames, findValidationRule, parseSource, resolveSlideContext, resolveSlideScriptMeasurement, validate, validateCatalogRecord, validationCategories, validationRules };
5808
+ export { CATALOG_SCHEMA, COMMON_KEYS, DEFAULT_VALIDATION_THRESHOLDS, OPFPatchError, OPFPatchValidationError, OPFValidationError, UNRANKED_PRIORITY, activeSchema, analyzeJsonSyntax, applyPatch, applyPatchWithInverse, applyTextEdits, assertValid, assertValidCatalogRecord, catalogDisplayKinds, catalogKindAt, catalogSchemaNames, createLocator, createSchemaValue, describeField, escapePointerToken, findValidationRule, formatPointer, getAtPointer, hasPointer, invertPatch, isPromotedRegionKey, jsonEqual, listSchemaFields, normalizePatch, parsePointer, parseSource, pointerFromPath, readPointer, resolveSchema, resolveSlideContext, resolveSlideScriptMeasurement, schemaAtPath, schemaCatalogKinds, schemaLabel, schemaType, schemaVariants, splitOpfPath, splitPointer2 as splitPointer, unescapePointerToken, validate, validateCatalogRecord, validationCategories, validationRules };