@openpresentation/opf 0.26.0 → 0.26.2

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 (58) hide show
  1. package/README.md +4 -0
  2. package/dist/browser.js +14 -15
  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-F7USLFZE.js → chunk-5VJCLUUA.js} +6 -6
  6. package/dist/{chunk-GS37EBPQ.js → chunk-6JAFSZVL.js} +1 -1
  7. package/dist/{chunk-F5ABHDUX.js → chunk-7YZ72FRP.js} +5 -5
  8. package/dist/{chunk-IWFXFWGD.js → chunk-CRCOQ4K2.js} +1 -1
  9. package/dist/{chunk-EQ6S5SEZ.js → chunk-JI3PHXPJ.js} +1 -1
  10. package/dist/{chunk-FITOGMR3.js → chunk-L65YNP4J.js} +1 -1
  11. package/dist/{chunk-X7RANZWP.js → chunk-LN2ASLMF.js} +1 -1
  12. package/dist/{chunk-P7S3ORB4.js → chunk-LUQYSUXS.js} +1790 -69
  13. package/dist/{chunk-F4HFDP6I.js → chunk-N6CNJCIF.js} +18 -0
  14. package/dist/{chunk-3OE6PZIC.js → chunk-NWO6HT4F.js} +4 -4
  15. package/dist/{chunk-LU6XM5JQ.js → chunk-O2ST5ZKL.js} +1 -1
  16. package/dist/{chunk-WW62PPYV.js → chunk-OFWBICDO.js} +1 -2
  17. package/dist/{chunk-MJ57RSYO.js → chunk-Q6KMKTEH.js} +4 -4
  18. package/dist/{chunk-IC6OPLMU.js → chunk-XFK4KPKK.js} +2 -2
  19. package/dist/{composition-Daqvi-GV.d.ts → composition-DIftf6iP.d.ts} +1 -1
  20. package/dist/composition.d.ts +2 -2
  21. package/dist/conversion-BKCKBECG.js +18 -0
  22. package/dist/{conversion-DyR-2OkM.d.ts → conversion-CX_I6Vna.d.ts} +5 -5
  23. package/dist/convert.d.ts +3 -3
  24. package/dist/convert.js +6 -6
  25. package/dist/{deck-report-DHaboWbM.d.ts → deck-report-5vwMGcQz.d.ts} +2 -2
  26. package/dist/deck.d.ts +5 -5
  27. package/dist/deck.js +9 -9
  28. package/dist/diff.js +3 -4
  29. package/dist/docs.js +5 -5
  30. package/dist/format.js +2 -2
  31. package/dist/index.d.ts +13 -12
  32. package/dist/index.js +18 -19
  33. package/dist/markdown.d.ts +3 -3
  34. package/dist/markdown.js +6 -6
  35. package/dist/node-engine.d.ts +7 -7
  36. package/dist/node-engine.js +18 -19
  37. package/dist/{pagination-R8YFwVj7.d.ts → pagination-D_kJe4jH.d.ts} +2 -2
  38. package/dist/pagination.d.ts +3 -3
  39. package/dist/pagination.js +3 -3
  40. package/dist/patch.js +2 -3
  41. package/dist/repo-readme.js +1 -1
  42. package/dist/schema.d.ts +151 -0
  43. package/dist/schema.js +3 -0
  44. package/dist/schemas.js +1 -1
  45. package/dist/source.d.ts +97 -0
  46. package/dist/source.js +90 -0
  47. package/dist/spec/schemas/opf.schema.json +16 -0
  48. package/dist/spec/schemas/theme.schema.json +2 -0
  49. package/dist/types.d.ts +1 -1
  50. package/dist/{validation-types-JW8e06ee.d.ts → validation-types-3ARvTsB0.d.ts} +2 -2
  51. package/dist/validator.d.ts +54 -6
  52. package/dist/validator.js +2 -2
  53. package/dist/yaml.d.ts +3 -3
  54. package/dist/yaml.js +6 -6
  55. package/package.json +13 -5
  56. package/dist/chunk-IY5SCRTV.js +0 -261
  57. package/dist/conversion-ZPBLP2K6.js +0 -19
  58. package/dist/validator-Dym2zYVK.d.ts +0 -67
@@ -1,5 +1,5 @@
1
1
  import { promotedRegionKeys, VARIABLE_KINDS, resolveChartData, chartPaletteForFill, chartSeriesPalette, normalizeHexColor, resolveChartColors, colorContrast, imageBackground, tableGrid, timelineTextColor, LOGO_SHAPES, visitContentPayloads, readingRows, visualReadingOrder, intrinsicImageSize, resolveReference, textWidthMeasurer, resolveColorRef, resolveDesignRecords, resolveFontFamilies, checkCatalogsOption, provenanceOf, unresolvedReference, OPFUnresolvedReferenceError, resolveColorRoles, resolveCanvasDimensions, resolveSlideVariables, normalizeColorVariant, isRecord, hasContentVariables, isTemplate, instantiateForValidation, layoutTemplateIssues, resolveScriptFonts, defaultSlideBackground, catalogReferenceSites, parseReference, catalogGroupDeclared, pointerPath, unusedDatasets, MAX_COMPOSITION_DEPTH, datasetDiagnostics, pathFor, catalogRecords, collectCitations, numberingFindings, walkCitationRuns, resolveTableData, resolveChartOptions, chartOptionTarget, codeHighlightLines, chartNumberFixesByCell, catalogKinds, catalogGroupSource, registeredCatalog, OPFCompositionError, composeSlide } from './chunk-XRMV2GMB.js';
2
- import { schemas, schemaEntries } from './chunk-F4HFDP6I.js';
2
+ import { schemas, schemaEntries } from './chunk-N6CNJCIF.js';
3
3
  import Ajv2020 from 'ajv/dist/2020.js';
4
4
  import addFormats from 'ajv-formats';
5
5
  import { parseTree, printParseErrorCode, getNodePath } from 'jsonc-parser';
@@ -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,1728 @@ 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-navigation.ts
3759
+ var own3 = (object2, key2) => object2 !== null && typeof object2 === "object" && Object.hasOwn(object2, key2);
3760
+ var documents = new Map(Object.values(schemas).map((schema) => [schema.$id, schema]));
3761
+ var presentationSchema = schemas.presentation;
3762
+ function splitOpfPath(path) {
3763
+ if (Array.isArray(path)) return path.map((segment) => String(segment));
3764
+ if (typeof path !== "string") throw new TypeError("An OPF path must be a string or an array of segments.");
3765
+ if (path === "") return [];
3766
+ if (path.startsWith("/")) return parsePointer(path);
3767
+ return path.split(".").filter(Boolean);
3768
+ }
3769
+ function readSchemaPointer(target, pointer4) {
3770
+ let current = target;
3771
+ for (const token of parsePointer(pointer4)) {
3772
+ if (current === null || typeof current !== "object" || !own3(current, token)) return void 0;
3773
+ current = current[token];
3774
+ }
3775
+ return current;
3776
+ }
3777
+ function absolutize(schema, id) {
3778
+ if (Array.isArray(schema)) return schema.map((item) => absolutize(item, id));
3779
+ if (schema === null || typeof schema !== "object") return schema;
3780
+ const copy = {};
3781
+ for (const [key2, value] of Object.entries(schema)) copy[key2] = key2 === "$ref" && typeof value === "string" && value.startsWith("#") ? `${id}${value}` : absolutize(value, id);
3782
+ return copy;
3783
+ }
3784
+ function mergeAllOf(schema, root, seen) {
3785
+ if (!Array.isArray(schema.allOf)) return schema;
3786
+ const { allOf, ...base } = schema;
3787
+ let merged = base;
3788
+ for (const member of allOf) {
3789
+ const resolved = resolveSchema(member, root, new Set(seen));
3790
+ if (!resolved.properties && !resolved.type && !resolved.oneOf && !resolved.anyOf) continue;
3791
+ merged = {
3792
+ ...resolved,
3793
+ ...merged,
3794
+ ...resolved.properties || merged.properties ? { properties: { ...resolved.properties, ...merged.properties } } : {}
3795
+ };
3796
+ }
3797
+ return merged;
3798
+ }
3799
+ function resolveSchema(schema, root = presentationSchema, seen = /* @__PURE__ */ new Set()) {
3800
+ if (!schema || typeof schema !== "object") return {};
3801
+ if (!schema.$ref) return mergeAllOf(schema, root, seen);
3802
+ const ref = schema.$ref;
3803
+ if (seen.has(ref)) return {};
3804
+ seen.add(ref);
3805
+ const [id = "", fragment = ""] = ref.split("#");
3806
+ const target = id ? documents.get(id) : root;
3807
+ if (!target) throw new Error(`Unresolved OPF schema: ${ref}`);
3808
+ let resolved = fragment ? readSchemaPointer(target, fragment) : target;
3809
+ if (!resolved) throw new Error(`Unresolved OPF schema: ${ref}`);
3810
+ if (id && target !== root) resolved = absolutize(resolved, id);
3811
+ const { $ref: _ref, ...rest } = schema;
3812
+ return mergeAllOf({ ...resolveSchema(resolved, target, seen), ...rest }, target, seen);
3813
+ }
3814
+ function schemaVariants(schema, root = presentationSchema) {
3815
+ const resolved = resolveSchema(schema, root);
3816
+ if (Array.isArray(resolved.type)) return resolved.type.flatMap((type) => schemaVariants({ ...resolved, type }, root));
3817
+ const variants = resolved.oneOf ?? resolved.anyOf;
3818
+ if (!variants) return [resolved];
3819
+ const { $ref: _ref, oneOf: _oneOf, anyOf: _anyOf, ...base } = resolved;
3820
+ return variants.flatMap(
3821
+ (child) => schemaVariants(child, root).map((variant) => {
3822
+ const requiredOnly = !variant.type && !variant.properties && !variant.$ref && Array.isArray(variant.required);
3823
+ return {
3824
+ ...base,
3825
+ ...variant,
3826
+ properties: base.properties || variant.properties ? { ...base.properties, ...variant.properties } : void 0,
3827
+ ...base.required || variant.required ? { required: [.../* @__PURE__ */ new Set([...base.required ?? [], ...variant.required ?? []])] } : {},
3828
+ ...requiredOnly && !variant.title ? { title: variant.required.join(" + ") } : {}
3829
+ };
3830
+ })
3831
+ );
3832
+ }
3833
+ function schemaType(schema, value) {
3834
+ 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);
3835
+ }
3836
+ function score2(schema, value) {
3837
+ const type = schemaType(schema, value);
3838
+ 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;
3839
+ if (own3(schema, "const")) return JSON.stringify(schema.const) === JSON.stringify(value) ? 100 : -100;
3840
+ if (schema.enum && !schema.enum.includes(value)) return -100;
3841
+ let result = 1;
3842
+ const record = value;
3843
+ for (const [key2, child] of Object.entries(schema.properties ?? {})) {
3844
+ if (!record || !own3(record, key2)) continue;
3845
+ if (own3(child, "const")) result += record[key2] === child.const ? 100 : -1e3;
3846
+ else result += 1;
3847
+ }
3848
+ for (const key2 of schema.required ?? []) if (!record || !own3(record, key2)) result -= 2;
3849
+ return result;
3850
+ }
3851
+ function activeSchema(schema, value, root = presentationSchema) {
3852
+ return schemaVariants(schema, root).sort((a, b) => score2(b, value) - score2(a, value))[0] ?? {};
3853
+ }
3854
+ var INDEX = /^(?:0|[1-9][0-9]*|-)$/;
3855
+ function patternChild(schema, key2) {
3856
+ for (const [pattern, child] of Object.entries(schema.patternProperties ?? {})) {
3857
+ try {
3858
+ if (new RegExp(pattern).test(key2)) return child;
3859
+ } catch {
3860
+ }
3861
+ }
3862
+ return void 0;
3863
+ }
3864
+ function childSchema(schema, key2, value) {
3865
+ if (Array.isArray(value) || schema.type === "array") return schema.items ?? void 0;
3866
+ return schema.properties?.[key2] ?? patternChild(schema, key2) ?? (typeof schema.additionalProperties === "object" ? schema.additionalProperties : void 0);
3867
+ }
3868
+ function stepSchema(schema, value, key2, root) {
3869
+ const variants = schemaVariants(schema, root).sort((a, b) => score2(b, value) - score2(a, value));
3870
+ const first = variants[0] ?? {};
3871
+ if (value !== void 0 || variants.length < 2 || childSchema(first, key2, value) !== void 0) return first;
3872
+ return variants.find((variant) => variant.type === "array" ? INDEX.test(key2) && childSchema(variant, key2, value) !== void 0 : childSchema(variant, key2, value) !== void 0) ?? first;
3873
+ }
3874
+ function walk(presentation, parts2, rootSchema) {
3875
+ let current = rootSchema;
3876
+ let root = rootSchema;
3877
+ let value = presentation;
3878
+ let parent;
3879
+ let known = true;
3880
+ for (let index = 0; index < parts2.length; index++) {
3881
+ const key2 = parts2[index];
3882
+ const resolved = stepSchema(current, value, key2, root);
3883
+ parent = resolved;
3884
+ let next;
3885
+ const recordSchema = parts2[0] === "catalogs" && index === 3 ? catalogSchemaNames[parts2[2]] : void 0;
3886
+ if (recordSchema) {
3887
+ next = schemas[recordSchema] ?? {};
3888
+ root = next;
3889
+ } else next = childSchema(resolved, key2, value);
3890
+ if (next === void 0) {
3891
+ known = false;
3892
+ next = {};
3893
+ }
3894
+ current = next;
3895
+ value = value !== null && typeof value === "object" ? value[key2] : void 0;
3896
+ }
3897
+ return { raw: current, schema: resolveSchema(current, root), root, parent, value, known };
3898
+ }
3899
+ function schemaAtPath(presentation, path, root = presentationSchema) {
3900
+ return walk(presentation, splitOpfPath(path), root).schema;
3901
+ }
3902
+ function schemaLabel(schema, index = 0) {
3903
+ const type = schemaType(schema);
3904
+ 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}`;
3905
+ }
3906
+ function createSchemaValue(schema, root = presentationSchema, depth2 = 0) {
3907
+ if (depth2 > 20) throw new Error("Choose a shallower initial value for this recursive structure.");
3908
+ const variants = schemaVariants(schema, root);
3909
+ const s = variants[0] ?? {};
3910
+ if (own3(s, "const")) return structuredClone(s.const);
3911
+ if (own3(s, "default")) return structuredClone(s.default);
3912
+ if (s.enum?.length) return structuredClone(s.enum[0]);
3913
+ const type = schemaType(s);
3914
+ if (type === "object") {
3915
+ const value = {};
3916
+ const properties = s.properties ?? {};
3917
+ 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);
3918
+ return value;
3919
+ }
3920
+ if (type === "array") return Array.from({ length: Math.min(s.minItems ?? 0, 100) }, () => createSchemaValue(s.items ?? {}, root, depth2 + 1));
3921
+ if (type === "boolean") return false;
3922
+ if (type === "null") return null;
3923
+ if (type === "number" || type === "integer") return s.minimum ?? (s.exclusiveMinimum !== void 0 ? s.exclusiveMinimum + 1 : 0);
3924
+ if (s.examples?.length && typeof s.examples[0] === "string" && !s.examples[0].includes("...")) return s.examples[0];
3925
+ if (s.format === "uri") return "https://example.com";
3926
+ if (s.pattern?.includes("a-z")) return "example";
3927
+ return s.minLength ? "Text" : "";
3928
+ }
3929
+ function listSchemaFields() {
3930
+ const result = [];
3931
+ for (const [name, root] of Object.entries(schemas)) {
3932
+ const visit = (schema, path) => {
3933
+ if (!schema || typeof schema !== "object") return;
3934
+ for (const [key2, child] of Object.entries(schema.properties ?? {})) {
3935
+ result.push({
3936
+ schema: name,
3937
+ path: formatPointer([...path, "properties", key2]),
3938
+ name: key2,
3939
+ description: child.description ?? "",
3940
+ reference: child.$ref ?? null,
3941
+ type: child.type ?? null,
3942
+ values: child.enum ?? (own3(child, "const") ? [child.const] : null)
3943
+ });
3944
+ visit(child, [...path, "properties", key2]);
3945
+ }
3946
+ for (const [key2, child] of Object.entries(schema.$defs ?? {})) visit(child, [...path, "$defs", key2]);
3947
+ for (const keyword of ["oneOf", "anyOf", "allOf"]) {
3948
+ const members = schema[keyword] ?? [];
3949
+ for (let index = 0; index < members.length; index++) visit(members[index], [...path, keyword, String(index)]);
3950
+ }
3951
+ if (schema.items) visit(schema.items, [...path, "items"]);
3952
+ if (typeof schema.additionalProperties === "object") visit(schema.additionalProperties, [...path, "additionalProperties"]);
3953
+ for (const [key2, child] of Object.entries(schema.patternProperties ?? {})) visit(child, [...path, "patternProperties", key2]);
3954
+ };
3955
+ visit(root, []);
3956
+ }
3957
+ return result;
3958
+ }
3959
+ var COMMON_KEYS = Object.freeze({
3960
+ /** The root of a document. */
3961
+ deck: Object.freeze(["name", "slides", "design", "language", "audience", "purpose", "tone", "narrative", "organization", "speaker"]),
3962
+ /** A slide (`slides[n]`). */
3963
+ slide: Object.freeze(["title", "subtitle", "layout", "items", "text", "notes", "image", "chart", "table", "quote", "code", "metric", "timeline", "blocks", "design", "id", "section", "tag", "hidden", "beat"]),
3964
+ /** `design`, on the deck and on a slide. */
3965
+ design: Object.freeze(["theme", "colorScheme", "fontScheme", "dimensions", "background", "header", "footer"])
3966
+ });
3967
+ var UNRANKED_PRIORITY = 1e6;
3968
+ var DEFINITION_LISTS = { Slide: COMMON_KEYS.slide, Design: COMMON_KEYS.design, SlideDesign: COMMON_KEYS.design };
3969
+ var ADVANCED_KEYS = /* @__PURE__ */ new Set(["extensions", "$schema"]);
3970
+ var REGION_KEY = /^(?:(?:top|middle|bottom)(?:\+(?:top|middle|bottom))*)?(?::)?(?:(?:left|center|right)(?:\+(?:left|center|right))*)?$/;
3971
+ function isPromotedRegionKey(key2) {
3972
+ return key2 !== "" && key2 !== ":" && REGION_KEY.test(key2);
3973
+ }
3974
+ var uniq = (items) => [...new Set(items)];
3975
+ function variantType(variant) {
3976
+ if (typeof variant.type === "string") return variant.type;
3977
+ if (variant.properties || variant.additionalProperties) return "object";
3978
+ if (variant.items) return "array";
3979
+ if (variant.const !== void 0) return variant.const === null ? "null" : typeof variant.const;
3980
+ if (Array.isArray(variant.enum) && variant.enum.length) return variant.enum[0] === null ? "null" : typeof variant.enum[0];
3981
+ return "any";
3982
+ }
3983
+ var firstParagraph = (text) => typeof text === "string" && text.trim() ? (text.trim().split(/\n[ \t]*\n/)[0] ?? "").trim() : void 0;
3984
+ var definitionOf = (raw) => typeof raw.$ref === "string" ? /#\/\$defs\/([^/]+)$/.exec(raw.$ref)?.[1] : void 0;
3985
+ function enumValues(variants, inherited) {
3986
+ const seen = /* @__PURE__ */ new Map();
3987
+ const add = (value, description) => {
3988
+ if (!(value === null || ["string", "number", "boolean"].includes(typeof value))) return;
3989
+ const id = JSON.stringify(value);
3990
+ if (!seen.has(id)) seen.set(id, { value, ...description ? { description } : {} });
3991
+ };
3992
+ for (const variant of variants) {
3993
+ const own4 = typeof variant.description === "string" && variant.description !== inherited ? firstParagraph(variant.description) : void 0;
3994
+ if (Array.isArray(variant.enum)) for (const value of variant.enum) add(value);
3995
+ else if (variant.const !== void 0) add(variant.const, own4);
3996
+ }
3997
+ return [...seen.values()];
3998
+ }
3999
+ function catalogKindOf(schema, variants = []) {
4000
+ for (const candidate of [schema, ...variants]) {
4001
+ const kind = candidate["x-opf-catalog"];
4002
+ if (typeof kind === "string" && own3(catalogSchemaNames, kind)) return kind;
4003
+ }
4004
+ return void 0;
4005
+ }
4006
+ var formRank = (variant) => {
4007
+ if (Array.isArray(variant.enum) || variant.const !== void 0) return 0;
4008
+ const type = variantType(variant);
4009
+ return type === "string" || type === "boolean" || type === "number" || type === "integer" ? 0 : type === "object" ? 1 : type === "array" ? 2 : 3;
4010
+ };
4011
+ function primaryIndex(variants) {
4012
+ let best = 0;
4013
+ for (let index = 1; index < variants.length; index++) if (formRank(variants[index]) < formRank(variants[best])) best = index;
4014
+ return best;
4015
+ }
4016
+ function typeLabelOf(raw, variants, root) {
4017
+ if (variants.length > 1) return "choice";
4018
+ const variant = variants[0] ?? {};
4019
+ if (Array.isArray(variant.enum) && variant.enum.length > 1) return "enum";
4020
+ const type = variantType(variant);
4021
+ if (type === "array") {
4022
+ const items = variant.items ?? {};
4023
+ const name = definitionOf(items);
4024
+ if (name) return `array of ${name}`;
4025
+ const forms = uniq(schemaVariants(items, root).map(variantType));
4026
+ return `array of ${forms.length === 1 ? forms[0] : forms.length ? "choice" : "any"}`;
4027
+ }
4028
+ if (type === "object") return definitionOf(raw) ?? "object";
4029
+ return type;
4030
+ }
4031
+ function priorityOf(list, key2) {
4032
+ const index = list ? list.indexOf(key2) : -1;
4033
+ return index < 0 ? UNRANKED_PRIORITY : index;
4034
+ }
4035
+ function objectForm(variants, value) {
4036
+ const objects = variants.filter((variant) => variantType(variant) === "object");
4037
+ let used = objects;
4038
+ if (objects.length > 1 && value !== null && typeof value === "object" && !Array.isArray(value)) {
4039
+ const best = [...objects].sort((a, b) => score2(b, value) - score2(a, value))[0];
4040
+ if (best) used = [best];
4041
+ }
4042
+ const properties = /* @__PURE__ */ new Map();
4043
+ for (const variant of used) for (const [key2, child] of Object.entries(variant.properties ?? {})) if (!properties.has(key2)) properties.set(key2, child);
4044
+ const required = used.length ? (used[0]?.required ?? []).filter((key2) => used.every((variant) => (variant.required ?? []).includes(key2))) : [];
4045
+ return { properties, required };
4046
+ }
4047
+ function childrenOf(form, list, isSlide, root) {
4048
+ const children = [];
4049
+ for (const [key2, raw] of form.properties) {
4050
+ const resolved = resolveSchema(raw, root);
4051
+ const variants = schemaVariants(raw, root);
4052
+ const description = firstParagraph(resolved.description);
4053
+ const catalogKind = catalogKindOf(resolved, variants);
4054
+ children.push({
4055
+ key: key2,
4056
+ types: uniq(variants.map(variantType)),
4057
+ typeLabel: typeLabelOf(raw, variants, root),
4058
+ required: form.required.includes(key2),
4059
+ advanced: ADVANCED_KEYS.has(key2) || isSlide && isPromotedRegionKey(key2),
4060
+ priority: priorityOf(list, key2),
4061
+ ...description ? { description } : {},
4062
+ ...catalogKind ? { catalogKind } : {},
4063
+ ...resolved.deprecated === true ? { deprecated: true } : {},
4064
+ hasEnum: variants.some((variant) => Array.isArray(variant.enum) || variant.const !== void 0)
4065
+ });
4066
+ }
4067
+ return children.sort(
4068
+ (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)
4069
+ );
4070
+ }
4071
+ function nodeSegments(path) {
4072
+ const parts2 = splitOpfPath(path);
4073
+ const shown = Array.isArray(path) ? [...path] : parts2.map((part) => /^(?:0|[1-9][0-9]*)$/.test(part) ? Number(part) : part);
4074
+ return { parts: parts2, shown };
4075
+ }
4076
+ var MAX_ITEM_DEPTH = 3;
4077
+ function describeAt(presentation, path, depth2) {
4078
+ let parts2;
4079
+ let shown;
4080
+ try {
4081
+ ({ parts: parts2, shown } = nodeSegments(path));
4082
+ } catch {
4083
+ return unknownField(Array.isArray(path) ? [...path] : [], "");
4084
+ }
4085
+ const pointer4 = formatPointer(parts2);
4086
+ const found = walk(presentation, parts2, presentationSchema);
4087
+ if (!found.known) return unknownField(shown, pointer4);
4088
+ const { raw, schema, root, parent, value } = found;
4089
+ const variants = schemaVariants(raw, root);
4090
+ const inherited = schema.description;
4091
+ const last = parts2.at(-1);
4092
+ const parentIsArray = parent !== void 0 && variantType(parent) === "array";
4093
+ const key2 = last !== void 0 && !parentIsArray ? last : void 0;
4094
+ const definition = definitionOf(raw);
4095
+ const description = firstParagraph(schema.description);
4096
+ const enumList = enumValues(variants, inherited);
4097
+ const primary = primaryIndex(variants);
4098
+ const catalogKind = catalogKindOf(schema, variants);
4099
+ const form = objectForm(variants, value);
4100
+ const isRoot = parts2.length === 0;
4101
+ const list = isRoot ? COMMON_KEYS.deck : definition ? DEFINITION_LISTS[definition] : void 0;
4102
+ const infos2 = variants.map((variant) => {
4103
+ const type = variantType(variant);
4104
+ const items = type === "array" ? variant.items ?? {} : void 0;
4105
+ const variantEnum = enumValues([variant], inherited);
4106
+ return {
4107
+ type,
4108
+ ...typeof variant.title === "string" ? { title: variant.title } : {},
4109
+ ...variantEnum.length ? { enum: variantEnum } : {},
4110
+ ...variant.const !== void 0 ? { const: variant.const } : {},
4111
+ requiredChildren: [...variant.required ?? []],
4112
+ properties: Object.keys(variant.properties ?? {}),
4113
+ ...items ? { itemsType: uniq(schemaVariants(items, root).map(variantType)).join(" | ") || "any" } : {},
4114
+ ...items && definitionOf(items) ? { itemsDefinition: definitionOf(items) } : {}
4115
+ };
4116
+ });
4117
+ const primaryVariant = variants[primary] ?? {};
4118
+ const freeText = variantType(primaryVariant) === "string" && primaryVariant.enum === void 0 && primaryVariant.const === void 0 && primaryVariant.pattern === void 0 && primaryVariant.format === void 0 && catalogKind === void 0;
4119
+ const info = {
4120
+ known: true,
4121
+ path: shown,
4122
+ pointer: pointer4,
4123
+ ...key2 !== void 0 ? { key: key2 } : {},
4124
+ types: uniq(infos2.map((variant) => variant.type)),
4125
+ ...description ? { description } : {},
4126
+ ...typeof schema.description === "string" && schema.description.trim() ? { fullDescription: schema.description.trim() } : {},
4127
+ ...definition ? { definition } : {},
4128
+ required: key2 !== void 0 && Array.isArray(parent?.required) && parent.required.includes(key2),
4129
+ ...schema.deprecated === true ? { deprecated: true } : {},
4130
+ examples: Array.isArray(schema.examples) ? [...schema.examples] : [],
4131
+ ...own3(schema, "default") ? { default: schema.default } : {},
4132
+ ...enumList.length ? { enum: enumList } : {},
4133
+ ...catalogKind ? { catalogKind } : {},
4134
+ variants: infos2,
4135
+ primary,
4136
+ children: childrenOf(form, list, definition === "Slide", root),
4137
+ requiredChildren: form.required,
4138
+ freeText
4139
+ };
4140
+ const arrayVariant = variants.find((variant) => variantType(variant) === "array") ?? void 0;
4141
+ if (arrayVariant && depth2 < MAX_ITEM_DEPTH) {
4142
+ const next = Array.isArray(value) ? value.length : 0;
4143
+ info.itemInfo = describeAt(presentation, [...parts2, String(next)], depth2 + 1);
4144
+ info.itemInfo.path = [...shown, next];
4145
+ }
4146
+ const mapVariant = variants.find((variant) => variantType(variant) === "object" && typeof variant.additionalProperties === "object");
4147
+ if (mapVariant && depth2 < MAX_ITEM_DEPTH) info.entryInfo = describeAt(presentation, [...parts2, "*"], depth2 + 1);
4148
+ if (info.entryInfo) info.entryInfo.path = [...shown, "*"];
4149
+ return info;
4150
+ }
4151
+ function unknownField(path, pointer4) {
4152
+ return { known: false, path, pointer: pointer4, types: [], required: false, examples: [], variants: [], primary: 0, children: [], requiredChildren: [], freeText: false };
4153
+ }
4154
+ function describeField(path, document) {
4155
+ return describeAt(document, path, 0);
4156
+ }
4157
+ function catalogKindAt(path, document) {
4158
+ let parts2;
4159
+ try {
4160
+ parts2 = splitOpfPath(path);
4161
+ } catch {
4162
+ return void 0;
4163
+ }
4164
+ const found = walk(document, parts2, presentationSchema);
4165
+ return found.known ? catalogKindOf(found.schema, schemaVariants(found.raw, found.root)) : void 0;
4166
+ }
4167
+
4168
+ // src/source-text.ts
4169
+ function createLocator(text) {
4170
+ const lineStarts = [0];
4171
+ for (let index = 0; index < text.length; index++) {
4172
+ const code = text.charCodeAt(index);
4173
+ if (code === 13) {
4174
+ if (text.charCodeAt(index + 1) === 10) index++;
4175
+ lineStarts.push(index + 1);
4176
+ } else if (code === 10) lineStarts.push(index + 1);
4177
+ }
4178
+ return (offset, length) => {
4179
+ let low = 0;
4180
+ let high = lineStarts.length;
4181
+ while (low + 1 < high) {
4182
+ const middle = low + high >> 1;
4183
+ if ((lineStarts[middle] ?? 0) <= offset) low = middle;
4184
+ else high = middle;
4185
+ }
4186
+ return { offset, length, line: low + 1, column: offset - (lineStarts[low] ?? 0) + 1 };
4187
+ };
4188
+ }
4189
+ function applyTextEdits(text, edits) {
4190
+ const ordered = edits.map((edit, index) => ({ edit, index })).sort((a, b) => a.edit.offset - b.edit.offset || a.index - b.index);
4191
+ let result = "";
4192
+ let cursor = 0;
4193
+ for (const { edit } of ordered) {
4194
+ if (edit.offset < cursor) throw new RangeError("Text edits overlap.");
4195
+ result += text.slice(cursor, edit.offset) + edit.insert;
4196
+ cursor = edit.offset + edit.length;
4197
+ }
4198
+ return result + text.slice(cursor);
4199
+ }
4200
+
4201
+ // src/json-syntax.ts
4202
+ var WORD = /[\p{L}\p{N}_$.+\-%]/u;
4203
+ var NUMBER = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/;
4204
+ var TYPOGRAPHIC = /[\u2018-\u201F\u00AB\u00BB]/;
4205
+ var HEX = /^[0-9a-fA-F]{4}$/;
4206
+ var isBreak = (char) => char === "\n" || char === "\r";
4207
+ function tokenize(text) {
4208
+ const tokens = [];
4209
+ const lexical = { escapes: /* @__PURE__ */ new Map(), unicode: /* @__PURE__ */ new Map(), controls: /* @__PURE__ */ new Map() };
4210
+ const length = text.length;
4211
+ let index = text.charCodeAt(0) === 65279 ? 1 : 0;
4212
+ const push = (token) => {
4213
+ const full = { ...token, text: text.slice(token.start, token.end) };
4214
+ tokens.push(full);
4215
+ return full;
4216
+ };
4217
+ while (index < length) {
4218
+ const char = text[index];
4219
+ if (char === " " || char === " " || char === "\n" || char === "\r") {
4220
+ index++;
4221
+ continue;
4222
+ }
4223
+ const start = index;
4224
+ if (char === "{" || char === "}" || char === "[" || char === "]" || char === "," || char === ":") {
4225
+ index++;
4226
+ push({ kind: char === "{" ? "lbrace" : char === "}" ? "rbrace" : char === "[" ? "lbracket" : char === "]" ? "rbracket" : char === "," ? "comma" : "colon", start, end: index });
4227
+ continue;
4228
+ }
4229
+ if (char === '"') {
4230
+ index++;
4231
+ const escapes = [];
4232
+ const unicode = [];
4233
+ const controls = [];
4234
+ let terminated = false;
4235
+ while (index < length) {
4236
+ const current = text[index];
4237
+ if (current === '"') {
4238
+ terminated = true;
4239
+ index++;
4240
+ break;
4241
+ }
4242
+ if (isBreak(current)) break;
4243
+ if (current === "\\") {
4244
+ const following = text[index + 1];
4245
+ if (following === void 0 || isBreak(following)) {
4246
+ escapes.push(index);
4247
+ index++;
4248
+ } else if (following === "u") {
4249
+ if (HEX.test(text.slice(index + 2, index + 6))) index += 6;
4250
+ else {
4251
+ unicode.push(index);
4252
+ index += 2;
4253
+ }
4254
+ } else if ('"\\/bfnrt'.includes(following)) index += 2;
4255
+ else {
4256
+ escapes.push(index);
4257
+ index += 1 + ((following.codePointAt(0) ?? 0) > 65535 ? 2 : 1);
4258
+ }
4259
+ continue;
4260
+ }
4261
+ if (current < " ") controls.push(index);
4262
+ index++;
4263
+ }
4264
+ const token = push({ kind: "string", start, end: index, quote: "double", terminated });
4265
+ if (escapes.length) lexical.escapes.set(token, escapes);
4266
+ if (unicode.length) lexical.unicode.set(token, unicode);
4267
+ if (controls.length) lexical.controls.set(token, controls);
4268
+ continue;
4269
+ }
4270
+ if (char === "'") {
4271
+ index++;
4272
+ let terminated = false;
4273
+ while (index < length) {
4274
+ const current = text[index];
4275
+ if (isBreak(current)) break;
4276
+ if (current === "\\" && index + 1 < length && !isBreak(text[index + 1])) {
4277
+ index += 2;
4278
+ continue;
4279
+ }
4280
+ index++;
4281
+ if (current === "'") {
4282
+ terminated = true;
4283
+ break;
4284
+ }
4285
+ }
4286
+ push({ kind: "string", start, end: index, quote: "single", terminated });
4287
+ continue;
4288
+ }
4289
+ if (TYPOGRAPHIC.test(char)) {
4290
+ index++;
4291
+ let terminated = false;
4292
+ while (index < length) {
4293
+ const current = text[index];
4294
+ if (isBreak(current)) break;
4295
+ index++;
4296
+ if (current === '"' || TYPOGRAPHIC.test(current)) {
4297
+ terminated = true;
4298
+ break;
4299
+ }
4300
+ }
4301
+ push({ kind: "string", start, end: index, quote: "typographic", terminated });
4302
+ continue;
4303
+ }
4304
+ if (char === "/" && text[index + 1] === "/") {
4305
+ while (index < length && !isBreak(text[index])) index++;
4306
+ push({ kind: "comment", start, end: index });
4307
+ continue;
4308
+ }
4309
+ if (char === "/" && text[index + 1] === "*") {
4310
+ const close = text.indexOf("*/", index + 2);
4311
+ index = close < 0 ? length : close + 2;
4312
+ push({ kind: "comment", start, end: index, block: true, terminated: close >= 0 });
4313
+ continue;
4314
+ }
4315
+ if (WORD.test(String.fromCodePoint(text.codePointAt(index) ?? 0))) {
4316
+ while (index < length) {
4317
+ const code = text.codePointAt(index) ?? 0;
4318
+ if (!WORD.test(String.fromCodePoint(code))) break;
4319
+ index += code > 65535 ? 2 : 1;
4320
+ }
4321
+ const lexeme = text.slice(start, index);
4322
+ const kind = NUMBER.test(lexeme) ? "number" : lexeme === "true" || lexeme === "false" || lexeme === "null" ? "literal" : /^[+\-.]?[0-9]|^[+-]$/.test(lexeme) ? "badnumber" : "word";
4323
+ push({ kind, start, end: index });
4324
+ continue;
4325
+ }
4326
+ while (index < length) {
4327
+ const code = text.codePointAt(index) ?? 0;
4328
+ const next = String.fromCodePoint(code);
4329
+ if (/[\s{}[\],:"'/]/.test(next) || TYPOGRAPHIC.test(next) || WORD.test(next)) break;
4330
+ index += code > 65535 ? 2 : 1;
4331
+ }
4332
+ if (index === start) index += (text.codePointAt(index) ?? 0) > 65535 ? 2 : 1;
4333
+ push({ kind: "unknown", start, end: index });
4334
+ }
4335
+ push({ kind: "eof", start: length, end: length });
4336
+ return { tokens, lexical };
4337
+ }
4338
+ var KIND_NAMES = { layouts: "layout", themes: "theme", colorSchemes: "color scheme", fontSchemes: "font scheme", narratives: "narrative", audiences: "audience", purposes: "purpose", tones: "tone" };
4339
+ function clip(value, max = 40) {
4340
+ const collapsed = value.replace(/\s+/g, " ").trim();
4341
+ return collapsed.length > max ? `${collapsed.slice(0, max - 1)}\u2026` : collapsed;
4342
+ }
4343
+ function lineBounds(text, offset) {
4344
+ let start = offset;
4345
+ while (start > 0 && !isBreak(text[start - 1])) start--;
4346
+ let end = offset;
4347
+ while (end < text.length && !isBreak(text[end])) end++;
4348
+ return { start, end };
4349
+ }
4350
+ var indentOf = (text, offset) => /^[ \t]*/.exec(text.slice(lineBounds(text, offset).start))?.[0] ?? "";
4351
+ function unquoteSingle(raw) {
4352
+ const inner = raw.endsWith("'") && raw.length > 1 ? raw.slice(1, -1) : raw.slice(1);
4353
+ let result = "";
4354
+ for (let index = 0; index < inner.length; index++) {
4355
+ const char = inner[index];
4356
+ if (char === "\\" && inner[index + 1] === "'") {
4357
+ result += "'";
4358
+ index++;
4359
+ } else if (char === "\\" && index + 1 < inner.length) {
4360
+ result += char + inner[index + 1];
4361
+ index++;
4362
+ } else if (char === '"') result += '\\"';
4363
+ else result += char;
4364
+ }
4365
+ return `"${result}"`;
4366
+ }
3478
4367
  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 "";
4368
+ var MINIMAL_DECK = '{\n "slides": [\n { "title": "" }\n ]\n}\n';
4369
+ var HINT_FORMS_LIMIT = 2;
4370
+ var Analyzer = class {
4371
+ constructor(text) {
4372
+ this.text = text;
4373
+ const lexed = tokenize(text);
4374
+ this.comments = lexed.tokens.filter((token) => token.kind === "comment");
4375
+ this.tokens = lexed.tokens.filter((token) => token.kind !== "comment");
4376
+ this.lexical = lexed.lexical;
4377
+ this.locate = createLocator(text);
4378
+ }
4379
+ text;
4380
+ problems = [];
4381
+ tokens;
4382
+ comments;
4383
+ lexical;
4384
+ locate;
4385
+ index = 0;
4386
+ muteIndex = -1;
4387
+ last;
4388
+ stack = [];
4389
+ order = 0;
4390
+ // Containers left open at the same place close together: the outer one's fix inserts the inner ones' closers first.
4391
+ cascade = { last: void 0, inserts: [] };
4392
+ // -- reporting --
4393
+ add(problem) {
4394
+ this.problems.push({ problem: { severity: "error", ...problem }, order: this.order++ });
4395
+ }
4396
+ /** A structural problem about the token at the read position: dropped when it is the aftershock of the cause just reported. */
4397
+ structural(problem) {
4398
+ if (this.index <= this.muteIndex) return;
4399
+ this.add(problem);
4400
+ }
4401
+ mute() {
4402
+ this.muteIndex = this.index;
4403
+ }
4404
+ range(start, end) {
4405
+ return this.locate(start, Math.max(0, end - start));
4406
+ }
4407
+ line(offset) {
4408
+ return this.locate(offset, 0).line;
4409
+ }
4410
+ // -- tokens --
4411
+ peek(ahead = 0) {
4412
+ return this.tokens[Math.min(this.index + ahead, this.tokens.length - 1)];
4413
+ }
4414
+ take() {
4415
+ const token = this.peek();
4416
+ if (token.kind !== "eof") {
4417
+ this.index++;
4418
+ this.last = token;
4419
+ }
4420
+ return token;
4421
+ }
4422
+ startsEntry(offset = 0) {
4423
+ const token = this.peek(offset);
4424
+ return token.kind === "string" || token.kind === "word" && this.peek(offset + 1).kind === "colon";
4425
+ }
4426
+ // -- the document --
4427
+ run() {
4428
+ const first = this.peek();
4429
+ if (first.kind !== "eof" && first.kind !== "lbrace" && first.kind !== "lbracket" && this.looksLikeOtherFormat(first)) return;
4430
+ this.lexicalProblems();
4431
+ if (first.kind === "eof") {
4432
+ this.emptyDocument();
4433
+ return;
4434
+ }
4435
+ if (first.kind === "word" || first.kind === "unknown" || first.kind === "colon" || first.kind === "comma" || first.kind === "rbrace" || first.kind === "rbracket") {
4436
+ this.add({
4437
+ code: "invalid-symbol",
4438
+ range: this.range(first.start, first.end),
4439
+ message: `Unexpected ${first.kind === "word" || first.kind === "unknown" ? `\`${clip(first.text, 24)}\`` : `\`${first.text}\``} at the start of the document.`,
4440
+ next: 'start with { for a deck, for example { "slides": [ { "title": "\u2026" } ] }.',
4441
+ fixes: []
4442
+ });
4443
+ return;
4444
+ }
4445
+ const root = this.parseValue([], void 0);
4446
+ if (root) {
4447
+ if (root.kind !== "object") this.notAnObject(root, first);
4448
+ const extra = this.peek();
4449
+ if (extra.kind !== "eof") {
4450
+ const end = this.text.trimEnd().length;
4451
+ const closer = root.kind === "object" ? "}" : root.kind === "array" ? "]" : "value";
4452
+ this.add({
4453
+ code: "content-after-root",
4454
+ range: this.range(extra.start, Math.max(end, extra.end)),
4455
+ 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.`,
4456
+ next: "remove the extra text, or put it inside the object.",
4457
+ fixes: [{ id: "remove-extra", title: "Remove the extra text", safe: false, edits: [{ offset: root.end, length: Math.max(end, extra.end) - root.end, insert: "" }] }]
4458
+ });
4459
+ }
4460
+ }
4461
+ }
4462
+ emptyDocument() {
4463
+ const length = this.text.length;
4464
+ this.add({
4465
+ code: "empty-document",
4466
+ range: this.range(0, length),
4467
+ message: "The document is empty.",
4468
+ next: 'start with { "slides": [ { "title": "\u2026" } ] }.',
4469
+ fixes: [{ id: "insert-minimal-deck", title: "Insert minimal deck", safe: false, edits: [{ offset: 0, length, insert: MINIMAL_DECK }], caret: MINIMAL_DECK.indexOf('""') + 1 }]
4470
+ });
4471
+ }
4472
+ looksLikeOtherFormat(first) {
4473
+ const head = this.text.slice(first.start);
4474
+ const lineEnd = lineBounds(this.text, first.start).end;
4475
+ const range = this.range(first.start, Math.max(first.end, this.text.slice(0, lineEnd).trimEnd().length));
4476
+ if (/^---(?:\s|$)/.test(head) || !head.startsWith("#") && YAML_START.test(head) || /^-\s+\S/.test(head)) {
4477
+ this.add({
4478
+ code: "looks-like-yaml",
4479
+ range,
4480
+ message: "This is not JSON. It looks like YAML.",
4481
+ 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.",
4482
+ fixes: [{ id: "switch-yaml", title: "Switch format to YAML", safe: false, edits: [], action: "switch-format", format: "yaml" }]
4483
+ });
4484
+ return true;
4485
+ }
4486
+ if (head.startsWith("#") || head.startsWith("```") || /^>\s/.test(head) || /^[*]\s+\S/.test(head)) {
4487
+ this.add({
4488
+ code: "looks-like-markdown",
4489
+ range,
4490
+ message: "This is not JSON. It looks like Markdown.",
4491
+ next: "switch the format to Markdown (read it with fromMarkdown, then validate the presentation it returns), or write the deck as a JSON object.",
4492
+ fixes: [{ id: "switch-markdown", title: "Switch format to Markdown", safe: false, edits: [], action: "switch-format", format: "md" }]
4493
+ });
4494
+ return true;
4495
+ }
4496
+ return false;
4497
+ }
4498
+ notAnObject(root, first) {
4499
+ const literal = this.text.slice(root.start, root.end);
4500
+ const what = root.kind === "array" ? "an array" : root.kind === "string" ? "a string" : literal === "true" || literal === "false" ? "a boolean" : literal === "null" ? "null" : "a number";
4501
+ const wrap = root.kind === "array" ? [
4502
+ {
4503
+ id: "wrap-slides",
4504
+ title: 'Wrap in { "slides" ... }',
4505
+ safe: false,
4506
+ edits: [
4507
+ { offset: root.start, length: 0, insert: '{ "slides": ' },
4508
+ { offset: root.end, length: 0, insert: " }" }
4509
+ ]
4510
+ }
4511
+ ] : [];
4512
+ this.add({
4513
+ code: "not-an-object",
4514
+ range: this.range(first.start, root.kind === "array" ? first.end : root.end),
4515
+ message: `The top level must be an object { \u2026 }, but this is ${what}.`,
4516
+ next: root.kind === "array" ? 'wrap it as { "slides": [ \u2026 ] }.' : 'write an object such as { "slides": [ { "title": "\u2026" } ] }.',
4517
+ fixes: wrap
4518
+ });
4519
+ }
4520
+ // -- lexical problems (no structure needed) --
4521
+ lexicalProblems() {
4522
+ for (const token of this.comments) {
4523
+ const unclosed = token.block && token.terminated === false;
4524
+ const whole = this.removalOf(token);
4525
+ if (unclosed) {
4526
+ this.add({
4527
+ code: "unclosed-comment",
4528
+ range: this.range(token.start, token.start + 2),
4529
+ message: "This `/*` comment is never closed.",
4530
+ next: "end it with `*/`, or remove it. Everything after `/*` is part of the comment.",
4531
+ fixes: [
4532
+ { id: "close-comment", title: "Close comment", safe: false, edits: [{ offset: this.text.length, length: 0, insert: " */" }] },
4533
+ { id: "remove-comment", title: "Remove comment", safe: false, edits: [whole] }
4534
+ ]
4535
+ });
4536
+ } else {
4537
+ this.add({
4538
+ code: "comment",
4539
+ range: this.range(token.start, token.end),
4540
+ message: "JSON does not allow comments.",
4541
+ next: "remove it, or switch to YAML, where # comments are kept.",
4542
+ fixes: [
4543
+ { id: "remove-comment", title: "Remove comment", safe: true, edits: [whole] },
4544
+ { id: "switch-yaml", title: "Switch format to YAML", safe: false, edits: [], action: "switch-format", format: "yaml" }
4545
+ ]
4546
+ });
4547
+ }
4548
+ }
4549
+ for (const token of this.tokens) {
4550
+ if (token.kind === "string") this.stringProblems(token);
4551
+ else if (token.kind === "badnumber") this.numberProblem(token);
4552
+ else if (token.kind === "unknown") this.unknownProblem(token);
4553
+ }
4554
+ }
4555
+ /** The edit that removes a comment: its whole line when it stands alone on it, else just the comment and the spaces before it. */
4556
+ removalOf(token) {
4557
+ let start = token.start;
4558
+ let end = token.end;
4559
+ while (start > 0 && (this.text[start - 1] === " " || this.text[start - 1] === " ")) start--;
4560
+ const alone = start === 0 || isBreak(this.text[start - 1]);
4561
+ let after = end;
4562
+ while (after < this.text.length && (this.text[after] === " " || this.text[after] === " ")) after++;
4563
+ if (alone && (after >= this.text.length || isBreak(this.text[after]))) {
4564
+ end = after;
4565
+ if (this.text[end] === "\r" && this.text[end + 1] === "\n") end += 2;
4566
+ else if (end < this.text.length) end++;
4567
+ return { offset: start, length: end - start, insert: "" };
4568
+ }
4569
+ return { offset: start, length: end - start, insert: "" };
4570
+ }
4571
+ stringProblems(token) {
4572
+ const { text } = this;
4573
+ if (token.quote === "single") {
4574
+ this.add({
4575
+ code: "single-quotes",
4576
+ range: this.range(token.start, token.end),
4577
+ message: "JSON strings use double quotes, not single quotes.",
4578
+ next: `write ${unquoteSingle(token.text)} instead of ${clip(token.text, 30)}.`,
4579
+ fixes: [{ id: "double-quotes", title: "Use double quotes", safe: true, edits: [{ offset: token.start, length: token.end - token.start, insert: unquoteSingle(token.text) }] }]
4580
+ });
4581
+ return;
4582
+ }
4583
+ if (token.quote === "typographic") {
4584
+ const edits = [{ offset: token.start, length: 1, insert: '"' }];
4585
+ if (token.terminated) edits.push({ offset: token.end - 1, length: 1, insert: '"' });
4586
+ this.add({
4587
+ code: "typographic-quotes",
4588
+ range: this.range(token.start, token.end),
4589
+ message: 'These are typographic quotes; JSON needs straight quotes (").',
4590
+ next: "replace them with straight double quotes. Word processors and chat apps change them as you paste.",
4591
+ fixes: [{ id: "straight-quotes", title: "Replace with straight quotes", safe: token.terminated === true, edits }]
4592
+ });
4593
+ return;
4594
+ }
4595
+ const escapes = this.lexical.escapes.get(token);
4596
+ const unicode = this.lexical.unicode.get(token);
4597
+ const controls = this.lexical.controls.get(token);
4598
+ if (token.terminated === false) {
4599
+ const lineEnd = token.start + clipTrailing(text.slice(token.start, token.end)).length;
4600
+ this.add({
4601
+ code: "unclosed-string",
4602
+ range: this.range(token.start, lineEnd),
4603
+ message: "This string is not closed before the end of the line.",
4604
+ next: "close it with a double quote. JSON strings stay on one line: write a line break as \\n.",
4605
+ fixes: [{ id: "close-string", title: "Close the string", safe: false, edits: [{ offset: lineEnd, length: 0, insert: '"' }] }]
4606
+ });
4607
+ }
4608
+ if (escapes?.length) {
4609
+ const first = escapes[0];
4610
+ const shown = text.slice(first, first + 2);
4611
+ this.add({
4612
+ code: "invalid-escape",
4613
+ range: this.range(first, Math.min(token.end, first + 2)),
4614
+ message: `\`${shown.trim() || "\\"}\` is not a JSON escape.`,
4615
+ next: 'write a backslash as \\\\ (the valid escapes are \\" \\\\ \\/ \\b \\f \\n \\r \\t and \\uXXXX).',
4616
+ fixes: [{ id: "double-backslash", title: escapes.length > 1 ? "Double the backslashes" : "Double the backslash", safe: false, edits: escapes.map((offset) => ({ offset, length: 0, insert: "\\" })) }]
4617
+ });
4618
+ }
4619
+ if (unicode?.length) {
4620
+ const first = unicode[0];
4621
+ this.add({
4622
+ code: "invalid-unicode",
4623
+ range: this.range(first, Math.min(token.end, first + 6)),
4624
+ message: "`\\u` must be followed by four hex digits.",
4625
+ next: "write the character as \\u00e9 (four digits 0 to 9 and a to f), or as the character itself.",
4626
+ fixes: []
4627
+ });
4628
+ }
4629
+ if (controls?.length) {
4630
+ const first = controls[0];
4631
+ this.add({
4632
+ code: "control-character",
4633
+ range: this.range(first, first + 1),
4634
+ message: "A tab or control character sits inside this string.",
4635
+ next: "write it as \\t, or as \\n for a line break.",
4636
+ fixes: [
4637
+ {
4638
+ id: "escape-controls",
4639
+ title: "Replace with escape",
4640
+ safe: true,
4641
+ edits: controls.map((offset) => {
4642
+ const code = text.charCodeAt(offset);
4643
+ const named = { 8: "\\b", 9: "\\t", 12: "\\f" };
4644
+ return { offset, length: 1, insert: named[code] ?? `\\u${code.toString(16).padStart(4, "0")}` };
4645
+ })
4646
+ }
4647
+ ]
4648
+ });
4649
+ }
4650
+ }
4651
+ numberProblem(token) {
4652
+ const word = token.text;
4653
+ const fixes = [];
4654
+ let message = `\`${word}\` is not a JSON number.`;
4655
+ let next = "Numbers have no leading zero, plus sign or trailing dot; put text such as 12% in quotes.";
4656
+ const numeric = (id, title, value) => {
4657
+ fixes.push({ id, title, safe: false, edits: [{ offset: token.start, length: token.end - token.start, insert: value }] });
4658
+ };
4659
+ if (/^0[0-9]+$/.test(word) || /^-0[0-9]+$/.test(word)) {
4660
+ message = `\`${word}\` has a leading zero, which JSON numbers do not allow.`;
4661
+ next = 'remove the leading zero, or put it in quotes if it is an identifier such as "007".';
4662
+ numeric("remove-leading-zero", "Remove leading zeros", word.replace(/^(-?)0+(?=[0-9])/, "$1"));
4663
+ } else if (/^\+[0-9]/.test(word) && NUMBER.test(word.slice(1))) {
4664
+ message = `\`${word}\` has a plus sign, which JSON numbers do not allow.`;
4665
+ next = "remove the plus sign.";
4666
+ numeric("remove-plus", "Remove plus sign", word.slice(1));
4667
+ } else if (/^-?\.[0-9]+(?:[eE][+-]?[0-9]+)?$/.test(word)) {
4668
+ message = `\`${word}\` is missing the digit before the dot.`;
4669
+ next = `write ${word.startsWith("-") ? "-0" : "0"}${word.replace("-", "")}.`;
4670
+ numeric("add-leading-zero", "Add leading zero", `${word.startsWith("-") ? "-0" : "0"}${word.replace("-", "")}`);
4671
+ } else if (/^-?[0-9]+\.(?:[eE][+-]?[0-9]+)?$/.test(word)) {
4672
+ message = `\`${word}\` ends with a dot, which JSON numbers do not allow.`;
4673
+ next = "remove the dot or add digits after it.";
4674
+ numeric("remove-trailing-dot", "Remove trailing dot", word.replace(".", ""));
4675
+ }
4676
+ fixes.push({ id: "quote-value", title: "Quote the value", safe: false, edits: [{ offset: token.start, length: token.end - token.start, insert: JSON.stringify(word) }] });
4677
+ this.add({ code: "invalid-number", range: this.range(token.start, token.end), message, next, fixes });
4678
+ }
4679
+ unknownProblem(token) {
4680
+ if (token === this.tokens[0]) return;
4681
+ const shown = clip(token.text, 12);
4682
+ const invisible = /^[\s\u200B-\u200D\u2060\uFEFF]+$/u.test(token.text);
4683
+ this.add({
4684
+ code: "invalid-symbol",
4685
+ range: this.range(token.start, token.end),
4686
+ message: invisible ? "An invisible or non-standard space sits here." : `Unexpected character \`${shown}\`.`,
4687
+ 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.",
4688
+ 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 ? " " : "" }] }]
4689
+ });
4690
+ }
4691
+ // -- values --
4692
+ parseValue(path, key2) {
4693
+ const token = this.peek();
4694
+ switch (token.kind) {
4695
+ case "lbrace":
4696
+ return this.parseObject(path);
4697
+ case "lbracket":
4698
+ return this.parseArray(path);
4699
+ case "string": {
4700
+ this.take();
4701
+ if (token.terminated === false) this.mute();
4702
+ return { start: token.start, end: token.end, kind: "string" };
4703
+ }
4704
+ case "number":
4705
+ case "literal":
4706
+ case "badnumber":
4707
+ this.take();
4708
+ return { start: token.start, end: token.end, kind: "scalar" };
4709
+ case "word":
4710
+ this.take();
4711
+ this.bareWord(token, path, key2);
4712
+ return { start: token.start, end: token.end, kind: "scalar" };
4713
+ case "unknown":
4714
+ this.take();
4715
+ return { start: token.start, end: token.end, kind: "scalar" };
4716
+ default:
4717
+ return void 0;
4718
+ }
4719
+ }
4720
+ bareWord(token, path, key2) {
4721
+ const word = token.text;
4722
+ const lower = word.toLowerCase();
4723
+ const edit = (insert) => [{ offset: token.start, length: token.end - token.start, insert }];
4724
+ let message;
4725
+ let next;
4726
+ const fixes = [];
4727
+ if (lower === "true" || lower === "false") {
4728
+ message = `\`${word}\` is not JSON. Write ${lower} in lowercase.`;
4729
+ next = `write ${lower}, or "${word}" in quotes if it is text.`;
4730
+ fixes.push({ id: `use-${lower}`, title: `Use ${lower}`, safe: true, edits: edit(lower) });
4731
+ } else if (["none", "nil", "null", "undefined", "nan", "infinity", "-infinity", "+infinity", "inf"].includes(lower)) {
4732
+ message = lower === "null" ? "`" + word + "` is not JSON. Write null in lowercase." : `\`${word}\` is not JSON. Use null for no value.`;
4733
+ next = "write null, or put it in quotes if it is text. JSON has no NaN, Infinity or undefined.";
4734
+ fixes.push({ id: "use-null", title: "Use null", safe: lower === "null", edits: edit("null") });
4735
+ } else {
4736
+ message = key2 !== void 0 ? `\`${key2}\` needs a quoted string: ${JSON.stringify(word)}.` : `\`${clip(word, 24)}\` needs quotes to be text: ${JSON.stringify(word)}.`;
4737
+ next = `put it in double quotes: ${JSON.stringify(word)}.`;
4738
+ }
4739
+ fixes.push({ id: "quote-value", title: "Quote the value", safe: false, edits: edit(JSON.stringify(word)) });
4740
+ this.add({ code: "bare-word", range: this.range(token.start, token.end), message, next, path: [...path], fixes });
4741
+ }
4742
+ parseObject(path) {
4743
+ const open = this.take();
4744
+ const container = { type: "{", open, path };
4745
+ this.stack.push(container);
4746
+ let expectKey = true;
4747
+ let comma;
4748
+ let lastEntry;
4749
+ let valueEnd = open.end;
4750
+ for (; ; ) {
4751
+ const token = this.peek();
4752
+ if (token.kind === "rbrace") {
4753
+ if (comma && expectKey) this.trailingComma(comma, container, lastEntry);
4754
+ this.take();
4755
+ this.stack.pop();
4756
+ return { start: open.start, end: token.end, kind: "object" };
4757
+ }
4758
+ if (token.kind === "eof") {
4759
+ this.unclosed(container);
4760
+ this.stack.pop();
4761
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "object" };
4762
+ }
4763
+ if (token.kind === "rbracket") {
4764
+ if (this.closeMismatched(container, token)) return { start: open.start, end: token.end, kind: "object" };
4765
+ this.unclosed(container);
4766
+ this.stack.pop();
4767
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "object" };
4768
+ }
4769
+ if (token.kind === "comma") {
4770
+ if (expectKey) this.doubleComma(token, container, comma !== void 0 || lastEntry !== void 0);
4771
+ else comma = token;
4772
+ this.take();
4773
+ expectKey = true;
4774
+ continue;
4775
+ }
4776
+ if (!expectKey) {
4777
+ if (this.startsEntry()) {
4778
+ this.structural({
4779
+ code: "missing-comma",
4780
+ range: this.range(valueEnd - 1, valueEnd),
4781
+ path: [...path],
4782
+ ...this.missingComma(valueEnd, lastEntry)
4783
+ });
4784
+ } else if (token.kind === "lbrace" || token.kind === "lbracket") {
4785
+ this.unclosed(container);
4786
+ this.stack.pop();
4787
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "object" };
4788
+ } else {
4789
+ this.take();
4790
+ this.unexpected(token, "a comma or }");
4791
+ valueEnd = token.end;
4792
+ continue;
4793
+ }
4794
+ }
4795
+ const entryStart = token.start;
4796
+ if (token.kind === "string" || token.kind === "word" && this.peek(1).kind === "colon") {
4797
+ const keyToken = this.take();
4798
+ if (token.kind === "word") this.unquotedKey(keyToken, path);
4799
+ if (keyToken.terminated === false) this.mute();
4800
+ const entryKey = keyToken.kind === "string" ? keyName(keyToken) : keyToken.text;
4801
+ const entryPath = [...path, entryKey];
4802
+ let hasColon = false;
4803
+ let colon;
4804
+ if (this.peek().kind === "colon") {
4805
+ colon = this.take();
4806
+ hasColon = true;
4807
+ } else {
4808
+ const following = this.peek();
4809
+ 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";
4810
+ this.missingColon(keyToken, entryKey, entryPath, valueFollows, container);
4811
+ if (!valueFollows) {
4812
+ this.mute();
4813
+ expectKey = false;
4814
+ comma = void 0;
4815
+ valueEnd = keyToken.end;
4816
+ lastEntry = { start: entryStart, text: clip(this.text.slice(entryStart, keyToken.end)) };
4817
+ continue;
4818
+ }
4819
+ }
4820
+ let value;
4821
+ const after = this.peek();
4822
+ const keyIsNext = (after.kind === "string" || after.kind === "word") && this.peek(1).kind === "colon";
4823
+ if (hasColon && (after.kind === "rbrace" || after.kind === "rbracket" || after.kind === "comma" || after.kind === "eof" || keyIsNext)) {
4824
+ this.missingValue(keyToken, colon, entryKey, entryPath);
4825
+ this.mute();
4826
+ valueEnd = colon.end;
4827
+ } else {
4828
+ value = this.parseValue(entryPath, entryKey);
4829
+ valueEnd = value?.end ?? keyToken.end;
4830
+ }
4831
+ const entryEnd = value?.end ?? valueEnd;
4832
+ lastEntry = { start: entryStart, text: entryText(this.text, entryStart, entryEnd, value?.kind) };
4833
+ expectKey = false;
4834
+ comma = void 0;
4835
+ continue;
4836
+ }
4837
+ if (token.kind === "lbrace" || token.kind === "lbracket") {
4838
+ this.unexpected(token, "a property name");
4839
+ valueEnd = this.parseValue([...path], void 0)?.end ?? valueEnd;
4840
+ this.mute();
4841
+ } else {
4842
+ this.take();
4843
+ this.unexpected(token, "a property name (a quoted string)");
4844
+ this.mute();
4845
+ valueEnd = token.end;
4846
+ }
4847
+ expectKey = false;
4848
+ comma = void 0;
4849
+ }
4850
+ }
4851
+ parseArray(path) {
4852
+ const open = this.take();
4853
+ const container = { type: "[", open, path };
4854
+ this.stack.push(container);
4855
+ let expectItem = true;
4856
+ let comma;
4857
+ let count = 0;
4858
+ let valueEnd = open.end;
4859
+ let lastItem;
4860
+ for (; ; ) {
4861
+ const token = this.peek();
4862
+ if (token.kind === "rbracket") {
4863
+ if (comma && expectItem) this.trailingComma(comma, container, lastItem);
4864
+ this.take();
4865
+ this.stack.pop();
4866
+ return { start: open.start, end: token.end, kind: "array" };
4867
+ }
4868
+ if (token.kind === "eof") {
4869
+ this.unclosed(container);
4870
+ this.stack.pop();
4871
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "array" };
4872
+ }
4873
+ if (token.kind === "rbrace") {
4874
+ if (this.closeMismatched(container, token)) return { start: open.start, end: token.end, kind: "array" };
4875
+ this.unclosed(container);
4876
+ this.stack.pop();
4877
+ return { start: open.start, end: this.last?.end ?? open.end, kind: "array" };
4878
+ }
4879
+ if (token.kind === "comma") {
4880
+ if (expectItem) this.doubleComma(token, container, comma !== void 0 || lastItem !== void 0);
4881
+ else comma = token;
4882
+ this.take();
4883
+ expectItem = true;
4884
+ continue;
4885
+ }
4886
+ const startsValue = token.kind === "string" || token.kind === "number" || token.kind === "literal" || token.kind === "badnumber" || token.kind === "word" || token.kind === "lbrace" || token.kind === "lbracket";
4887
+ if (!startsValue) {
4888
+ this.take();
4889
+ this.unexpected(token, "a value");
4890
+ this.mute();
4891
+ valueEnd = token.end;
4892
+ continue;
4893
+ }
4894
+ if (!expectItem) {
4895
+ this.structural({
4896
+ code: "missing-comma",
4897
+ range: this.range(valueEnd - 1, valueEnd),
4898
+ path: [...path],
4899
+ ...this.missingComma(valueEnd, lastItem)
4900
+ });
4901
+ }
4902
+ const itemStart = token.start;
4903
+ const value = this.parseValue([...path, count], void 0);
4904
+ count++;
4905
+ valueEnd = value?.end ?? token.end;
4906
+ lastItem = { start: itemStart, text: entryText(this.text, itemStart, valueEnd, value?.kind) };
4907
+ expectItem = false;
4908
+ comma = void 0;
4909
+ }
4910
+ }
4911
+ // -- structural problems --
4912
+ missingComma(valueEnd, previous) {
4913
+ const line = this.line(Math.max(valueEnd - 1, 0));
4914
+ return {
4915
+ message: `Missing comma after ${previous ? `\`${previous.text}\`` : "the previous value"} (line ${line}).`,
4916
+ next: `add \`,\` right after it, on line ${line}.`,
4917
+ fixes: [{ id: "insert-comma", title: "Insert comma", safe: true, edits: [{ offset: valueEnd, length: 0, insert: "," }], caret: valueEnd + 1 }]
4918
+ };
4919
+ }
4920
+ trailingComma(comma, container, previous) {
4921
+ const what = container.type === "{" ? "property" : "item";
4922
+ this.add({
4923
+ code: "trailing-comma",
4924
+ range: this.range(comma.start, comma.end),
4925
+ path: [...container.path],
4926
+ 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.",
4927
+ next: "remove the comma.",
4928
+ fixes: [{ id: "remove-comma", title: "Remove comma", safe: true, edits: [{ offset: comma.start, length: comma.end - comma.start, insert: "" }] }]
4929
+ });
4930
+ }
4931
+ doubleComma(comma, container, afterSomething) {
4932
+ this.add({
4933
+ code: "double-comma",
4934
+ range: this.range(comma.start, comma.end),
4935
+ path: [...container.path],
4936
+ message: afterSomething ? "Two commas in a row." : `This comma has nothing before it in the ${container.type === "{" ? "object" : "list"}.`,
4937
+ next: "remove the extra comma.",
4938
+ fixes: [{ id: "remove-comma", title: "Remove comma", safe: true, edits: [{ offset: comma.start, length: comma.end - comma.start, insert: "" }] }]
4939
+ });
4940
+ }
4941
+ unquotedKey(token, path) {
4942
+ this.add({
4943
+ code: "unquoted-key",
4944
+ range: this.range(token.start, token.end),
4945
+ path: [...path, token.text],
4946
+ message: `Property names need double quotes: ${JSON.stringify(token.text)}.`,
4947
+ next: `write ${JSON.stringify(token.text)} with quotes.`,
4948
+ 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) }] }]
4949
+ });
4950
+ }
4951
+ unexpected(token, expected) {
4952
+ this.add({
4953
+ code: "invalid-symbol",
4954
+ range: this.range(token.start, token.end),
4955
+ message: `Unexpected \`${clip(token.text, 20)}\`. Expected ${expected} here.`,
4956
+ next: "remove it, or add the missing punctuation before it.",
4957
+ fixes: [{ id: "remove-token", title: `Remove ${clip(token.text, 12)}`, safe: false, edits: [{ offset: token.start, length: token.end - token.start, insert: "" }] }]
4958
+ });
4959
+ }
4960
+ missingColon(keyToken, key2, path, valueFollows, container) {
4961
+ const keyEnd = keyToken.end;
4962
+ const shown = clip(key2, 30);
4963
+ const removal = this.removeEntry(keyToken.start, keyEnd);
4964
+ this.structural({
4965
+ code: "missing-colon",
4966
+ range: this.range(keyToken.start, keyToken.end),
4967
+ path,
4968
+ message: valueFollows ? `\`${shown}\` is missing a colon.` : `\`${shown}\` has no colon and value.`,
4969
+ next: valueFollows ? "add `:` after the property name." : `write ${JSON.stringify(key2)}: and a value, or remove the property.`,
4970
+ fixes: [
4971
+ { id: "insert-colon", title: "Add colon", safe: valueFollows, edits: [{ offset: keyEnd, length: 0, insert: valueFollows ? ":" : ": " }], caret: keyEnd + (valueFollows ? 1 : 2) },
4972
+ ...valueFollows ? [] : [{ id: "remove-key", title: `Remove ${JSON.stringify(key2)}`, safe: false, edits: removal }]
4973
+ ]
4974
+ });
4975
+ }
4976
+ missingValue(keyToken, colon, key2, path) {
4977
+ const needsComma = (() => {
4978
+ const next = this.peek();
4979
+ return (next.kind === "string" || next.kind === "word") && this.peek(1).kind === "colon";
4980
+ })();
4981
+ const hint = valueHint(path);
4982
+ const gap = this.text[colon.end] === " " || this.text[colon.end] === " " ? 0 : 1;
4983
+ const at2 = colon.end + (gap === 0 ? 1 : 0);
4984
+ const edits = [{ offset: at2, length: 0, insert: `${gap ? " " : ""}${hint.insert}` }];
4985
+ if (needsComma) edits.push({ offset: at2, length: 0, insert: "," });
4986
+ const fixes = [{ id: "add-value", title: hint.title, safe: false, edits, caret: at2 + (gap ? 1 : 0) + hint.caret }];
4987
+ fixes.push({ id: "remove-key", title: `Remove ${JSON.stringify(key2)}`, safe: false, edits: this.removeEntry(keyToken.start, colon.end) });
4988
+ this.structural({
4989
+ code: "missing-value",
4990
+ range: this.range(keyToken.start, colon.end),
4991
+ path,
4992
+ message: `\`${key2}\` needs a value.`,
4993
+ next: `${hint.next} Press Ctrl+Space after the colon for options.`,
4994
+ fixes
4995
+ });
4996
+ }
4997
+ /** The edits that remove an entry whose value is missing (key through colon), and the comma that belonged to it. */
4998
+ removeEntry(start, end) {
4999
+ const { text } = this;
5000
+ const firstFrom = (offset) => {
5001
+ let low = 0;
5002
+ let high = this.tokens.length;
5003
+ while (low < high) {
5004
+ const middle = low + high >> 1;
5005
+ if (this.tokens[middle].start < offset) low = middle + 1;
5006
+ else high = middle;
5007
+ }
5008
+ return low;
5009
+ };
5010
+ const startIndex = firstFrom(start);
5011
+ const before = this.tokens[startIndex - 1];
5012
+ const firstIndex = firstFrom(end);
5013
+ const first = this.tokens[firstIndex];
5014
+ const second = first?.kind === "comma" ? this.tokens[firstIndex + 1] : first;
5015
+ const entryFollows = (token) => token !== void 0 && (token.kind === "string" || token.kind === "word" && this.tokens[firstFrom(token.start) + 1]?.kind === "colon");
5016
+ let from = start;
5017
+ let to = end;
5018
+ if (first?.kind === "comma") to = first.end;
5019
+ else if (!entryFollows(second) && before?.kind === "comma") from = before.start;
5020
+ 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));
5021
+ if (alone) {
5022
+ const bounds = lineBounds(text, start);
5023
+ let lineEnd = lineBounds(text, Math.max(to - 1, 0)).end;
5024
+ if (text[lineEnd] === "\r" && text[lineEnd + 1] === "\n") lineEnd += 2;
5025
+ else if (lineEnd < text.length) lineEnd++;
5026
+ return [{ offset: bounds.start, length: lineEnd - bounds.start, insert: "" }];
5027
+ }
5028
+ return [{ offset: from, length: to - from, insert: "" }];
5029
+ }
5030
+ unclosed(container) {
5031
+ const { text } = this;
5032
+ const closer = container.type === "{" ? "}" : "]";
5033
+ const open = container.open;
5034
+ const last = this.last && this.last.start >= open.start ? this.last : open;
5035
+ if (last.kind === "string" && last.terminated === false) {
5036
+ this.mute();
5037
+ return;
5038
+ }
5039
+ const dangling = last.kind === "comma" && last !== open;
5040
+ const insertAt = dangling ? last.start : last.end;
5041
+ const removed = dangling ? last.end - last.start : 0;
5042
+ const sameLine = this.line(open.start) === this.line(Math.max(last.end - 1, 0));
5043
+ const own4 = sameLine ? closer : `
5044
+ ${indentOf(text, open.start)}${closer}`;
5045
+ const inner = this.cascade.last === this.last && removed === 0 ? this.cascade.inserts : [];
5046
+ const insert = inner.join("") + own4;
5047
+ this.cascade = { last: this.last, inserts: [...inner, own4] };
5048
+ const closers = insert.replace(/\s/g, "");
5049
+ const key2 = container.path.at(-1);
5050
+ const entry = typeof key2 === "string" ? `"${key2}": ${open.text}` : open.text;
5051
+ const context = container.type === "{" ? firstEntryOf(text, open) : void 0;
5052
+ const what = container.type === "{" ? "property" : "item";
5053
+ this.add({
5054
+ code: container.type === "{" ? "unclosed-object" : "unclosed-array",
5055
+ range: this.range(open.start, open.end),
5056
+ path: [...container.path],
5057
+ 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.`,
5058
+ next: `add \`${closer}\` after its last ${what}${sameLine ? "" : ", on its own line"}.`,
5059
+ fixes: [{ id: "insert-closer", title: `Insert ${closers}`, safe: false, edits: [{ offset: insertAt, length: removed, insert }], caret: insertAt + insert.length }]
5060
+ });
5061
+ this.mute();
5062
+ }
5063
+ /** 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. */
5064
+ closeMismatched(container, token) {
5065
+ const wanted = container.type === "{" ? "[" : "{";
5066
+ if (this.stack.some((other) => other !== container && other.type === wanted)) return false;
5067
+ const closer = container.type === "{" ? "}" : "]";
5068
+ const open = container.open;
5069
+ this.take();
5070
+ this.stack.pop();
5071
+ this.add({
5072
+ code: container.type === "{" ? "unclosed-object" : "unclosed-array",
5073
+ range: this.range(open.start, open.end),
5074
+ path: [...container.path],
5075
+ message: `The \`${open.text}\` on line ${this.line(open.start)} is closed with \`${token.text}\`, which does not match.`,
5076
+ next: `end it with \`${closer}\`.`,
5077
+ fixes: [{ id: "replace-closer", title: `Replace ${token.text} with ${closer}`, safe: false, edits: [{ offset: token.start, length: token.end - token.start, insert: closer }] }]
5078
+ });
5079
+ return true;
5080
+ }
5081
+ };
5082
+ function keyName(token) {
5083
+ if (token.quote === "double" && token.terminated !== false) {
5084
+ try {
5085
+ return JSON.parse(token.text);
5086
+ } catch {
5087
+ }
5088
+ }
5089
+ const inner = token.text.slice(1, token.terminated === false ? void 0 : -1);
5090
+ return inner;
5091
+ }
5092
+ function clipTrailing(value) {
5093
+ return value.replace(/[ \t]+$/, "");
5094
+ }
5095
+ function entryText(text, start, end, kind) {
5096
+ const raw = text.slice(start, end);
5097
+ if (kind === "object" || kind === "array") {
5098
+ const head = raw.slice(0, raw.indexOf(kind === "object" ? "{" : "["));
5099
+ return clip(`${head}${kind === "object" ? "{\u2026}" : "[\u2026]"}`, 40);
5100
+ }
5101
+ return clip(raw, 40);
5102
+ }
5103
+ function firstEntryOf(text, open) {
5104
+ const rest = text.slice(open.end, open.end + 200);
5105
+ const match = /^\s*("(?:[^"\\\n]|\\.)*"\s*:\s*(?:"(?:[^"\\\n]|\\.)*"|[^\s,{}[\]]+))/.exec(rest);
5106
+ return match?.[1] ? clip(match[1], 36) : void 0;
5107
+ }
5108
+ function jsonShort(value) {
5109
+ const text = JSON.stringify(value);
5110
+ return text !== void 0 && text.length <= 32 ? text : void 0;
5111
+ }
5112
+ function exampleOf(info) {
5113
+ const example = info.examples.map(jsonShort).find((item) => item !== void 0) ?? (info.enum?.[0] ? jsonShort(info.enum[0].value) : void 0);
5114
+ if (example) return example;
5115
+ const type = info.types[0];
5116
+ return type === "boolean" ? "true" : type === "number" || type === "integer" ? "1" : type === "object" ? "{ \u2026 }" : type === "array" ? "[ \u2026 ]" : '"\u2026"';
5117
+ }
5118
+ function listValues(values) {
5119
+ const shown = values.slice(0, 5).map((entry) => String(entry.value));
5120
+ return values.length > 5 ? `one of ${values.length} values (${shown.slice(0, 3).join(", ")}, \u2026)` : `one of ${shown.join(", ")}`;
5121
+ }
5122
+ function joinAlternatives(items) {
5123
+ return items.length <= 1 ? items[0] ?? "" : `${items.slice(0, -1).join(", ")}, or ${items.at(-1)}`;
5124
+ }
5125
+ var article = (word) => /^[aeiou]/i.test(word) ? "an" : "a";
5126
+ function valueHint(path) {
5127
+ let info;
5128
+ try {
5129
+ info = describeField(path);
5130
+ } catch {
5131
+ info = void 0;
5132
+ }
5133
+ if (!info?.known || !info.variants.length) {
5134
+ 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 };
5135
+ }
5136
+ const phrases = [];
5137
+ const phrased = /* @__PURE__ */ new Set();
5138
+ const order2 = [info.primary, ...info.variants.map((_, index) => index).filter((index) => index !== info.primary)];
5139
+ for (const index of order2) {
5140
+ const variant = info.variants[index];
5141
+ if (!variant || phrases.length >= HINT_FORMS_LIMIT || phrased.has(variant.type)) continue;
5142
+ phrased.add(variant.type);
5143
+ let phrase;
5144
+ if (variant.enum?.length) phrase = listValues(variant.enum);
5145
+ else if (variant.type === "string" && info.catalogKind) {
5146
+ const example = info.examples.find((item) => typeof item === "string");
5147
+ const name = KIND_NAMES[info.catalogKind] ?? "catalog";
5148
+ phrase = `${article(name)} ${name} id${example ? ` such as ${JSON.stringify(example)}` : ""}`;
5149
+ } else if (variant.type === "string") {
5150
+ const example = info.examples.find((item) => typeof item === "string" && item.length <= 24);
5151
+ phrase = example ? `a string such as ${JSON.stringify(example)}` : "text in double quotes";
5152
+ } else if (variant.type === "boolean") phrase = "true or false";
5153
+ else if (variant.type === "integer" || variant.type === "number") {
5154
+ const example = info.examples.find((item) => typeof item === "number");
5155
+ phrase = `a number${example !== void 0 ? ` such as ${example}` : ""}`;
5156
+ } else if (variant.type === "object") {
5157
+ const keyName2 = variant.requiredChildren[0] ?? info.children.find((child) => !child.advanced)?.key;
5158
+ if (keyName2) {
5159
+ const child = describeField([...path, keyName2]);
5160
+ phrase = `an object such as { ${JSON.stringify(keyName2)}: ${exampleOf(child)} }`;
5161
+ } else phrase = "an object { }";
5162
+ } else if (variant.type === "array") {
5163
+ const item = info.itemInfo;
5164
+ phrase = item?.known ? `a list such as [ ${exampleOf(item)} ]` : "a list [ ]";
5165
+ }
5166
+ if (phrase && !phrases.includes(phrase)) phrases.push(phrase);
5167
+ }
5168
+ const primary = info.variants[info.primary];
5169
+ const next = `${joinAlternatives([...phrases, "remove the key"])}.`;
5170
+ let title = 'Add ""';
5171
+ let insert = '""';
5172
+ let caret = 1;
5173
+ if (primary?.type === "object") {
5174
+ title = "Add { }";
5175
+ insert = "{}";
5176
+ caret = 1;
5177
+ } else if (primary?.type === "array") {
5178
+ title = "Add [ ]";
5179
+ insert = "[]";
5180
+ caret = 1;
5181
+ } else if (primary?.enum?.length) {
5182
+ const value = JSON.stringify(primary.enum[0]?.value);
5183
+ title = `Add ${value}`;
5184
+ insert = value;
5185
+ caret = value.length;
5186
+ } else if (primary?.type === "boolean") {
5187
+ title = "Add false";
5188
+ insert = "false";
5189
+ caret = insert.length;
5190
+ } else if (primary?.type === "integer" || primary?.type === "number") {
5191
+ let value = "0";
5192
+ try {
5193
+ const made = createSchemaValue(schemaAtPath({}, path));
5194
+ if (typeof made === "number") value = String(made);
5195
+ } catch {
5196
+ }
5197
+ title = `Add ${value}`;
5198
+ insert = value;
5199
+ caret = insert.length;
5200
+ }
5201
+ return { next, title, insert, caret };
3486
5202
  }
5203
+ function analyzeJsonSyntax(text, options = {}) {
5204
+ const limit = Math.max(0, options.limit ?? 5);
5205
+ const analyzer = new Analyzer(String(text ?? ""));
5206
+ try {
5207
+ analyzer.run();
5208
+ } catch {
5209
+ return [];
5210
+ }
5211
+ const seen = /* @__PURE__ */ new Set();
5212
+ return analyzer.problems.sort((a, b) => a.problem.range.offset - b.problem.range.offset || a.order - b.order).map((entry) => entry.problem).filter((problem) => {
5213
+ const id = `${problem.code}:${problem.range.offset}`;
5214
+ if (seen.has(id)) return false;
5215
+ seen.add(id);
5216
+ return true;
5217
+ }).slice(0, limit);
5218
+ }
5219
+
5220
+ // src/check-source.ts
3487
5221
  function parseSource(source) {
3488
5222
  const errors = [], tree = parseTree(
3489
5223
  source.startsWith("\uFEFF") ? " " + source.slice(1) : source,
@@ -3494,35 +5228,22 @@ function parseSource(source) {
3494
5228
  allowEmptyContent: false
3495
5229
  }
3496
5230
  );
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
- };
5231
+ const location = createLocator(source);
3518
5232
  if (errors.length || !tree) {
3519
- const note = hint(source);
5233
+ const problems = analyzeJsonSyntax(source);
3520
5234
  return {
3521
- syntax: errors.map(
3522
- (error, index) => finding("opf/json-syntax", {
5235
+ syntax: problems.length ? problems.map(
5236
+ (problem) => finding("opf/json-syntax", {
5237
+ path: "",
5238
+ message: problem.message,
5239
+ help: problem.next,
5240
+ location: problem.range
5241
+ })
5242
+ ) : errors.map(
5243
+ (error) => finding("opf/json-syntax", {
3523
5244
  path: "",
3524
5245
  message: printParseErrorCode(error.error),
3525
- help: `Repair the JSON syntax at this source range. No content or whitespace has been rewritten.${index === 0 ? note : ""}`,
5246
+ help: "Repair the JSON syntax at this source range. No content or whitespace has been rewritten.",
3526
5247
  location: location(error.offset, error.length)
3527
5248
  })
3528
5249
  ),
@@ -3611,14 +5332,14 @@ function slideScriptMeasurement(presentation, slideIndex, measurement, options)
3611
5332
  const forLanguage = (lang) => {
3612
5333
  if (typeof lang !== "string" || !lang) return void 0;
3613
5334
  if (!languages.has(lang)) {
3614
- let own2;
5335
+ let own4;
3615
5336
  try {
3616
5337
  const profile = scriptProfile(presentation, slideIndex, options, lang);
3617
- own2 = profile.languageSource === "option" && profile.bcp47?.toLowerCase() !== deckProfile.bcp47?.toLowerCase() ? forScripts.call(measurement, profile) : void 0;
5338
+ own4 = profile.languageSource === "option" && profile.bcp47?.toLowerCase() !== deckProfile.bcp47?.toLowerCase() ? forScripts.call(measurement, profile) : void 0;
3618
5339
  } catch {
3619
- own2 = void 0;
5340
+ own4 = void 0;
3620
5341
  }
3621
- languages.set(lang, own2);
5342
+ languages.set(lang, own4);
3622
5343
  }
3623
5344
  return languages.get(lang);
3624
5345
  };
@@ -3627,8 +5348,8 @@ function slideScriptMeasurement(presentation, slideIndex, measurement, options)
3627
5348
  if (typeof deck.resolveStyle === "function") scripted.resolveStyle = (style) => deck.resolveStyle.call(deck, style);
3628
5349
  if (typeof deck.outlineBounds === "function")
3629
5350
  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);
5351
+ const own4 = pick(style);
5352
+ return typeof own4.outlineBounds === "function" ? own4.outlineBounds(text, size, style) : deck.outlineBounds.call(deck, text, size, style);
3632
5353
  };
3633
5354
  return scripted;
3634
5355
  }
@@ -3700,15 +5421,15 @@ var lazy = (compute) => {
3700
5421
  };
3701
5422
  function relax(value) {
3702
5423
  const copy = JSON.parse(JSON.stringify(value));
3703
- const walk = (node) => {
3704
- if (Array.isArray(node)) return node.forEach(walk);
5424
+ const walk2 = (node) => {
5425
+ if (Array.isArray(node)) return node.forEach(walk2);
3705
5426
  if (node && typeof node === "object") {
3706
5427
  const object2 = node;
3707
5428
  if (object2.overflow === "error") object2.overflow = "warn";
3708
- for (const child of Object.values(object2)) walk(child);
5429
+ for (const child of Object.values(object2)) walk2(child);
3709
5430
  }
3710
5431
  };
3711
- walk(copy);
5432
+ walk2(copy);
3712
5433
  return copy;
3713
5434
  }
3714
5435
  function buildSlides(document, options) {
@@ -3776,7 +5497,7 @@ function buildSlides(document, options) {
3776
5497
  var SEVERITIES = ["error", "warning", "info"];
3777
5498
  var OPTION_KEYS = ["only", "ignore", "severity", "ignorePaths", "catalogs", "contracts", "thresholds", "fonts", "chartPalette", "template", "values"];
3778
5499
  var DOCS = "https://github.com/OpenPresentation/opf/blob/main/docs/validate.md";
3779
- var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5500
+ var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3780
5501
  var OPFValidationError = class extends Error {
3781
5502
  /** The full report that failed. */
3782
5503
  report;
@@ -3809,14 +5530,14 @@ function names(list, what) {
3809
5530
  return result;
3810
5531
  }
3811
5532
  function resolveOptions(options) {
3812
- if (!isObject(options)) throw new TypeError("Validate options must be an object.");
5533
+ if (!isObject2(options)) throw new TypeError("Validate options must be an object.");
3813
5534
  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
5535
  checkDocumentOptions(options);
3815
5536
  const only = names(options.only, "only");
3816
5537
  const ignore = names(options.ignore, "ignore");
3817
5538
  const severity = /* @__PURE__ */ new Map();
3818
5539
  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".');
5540
+ if (!isObject2(options.severity)) throw new TypeError('Validate severity must map rule ids or category names to a severity or "off".');
3820
5541
  for (const [name, value] of Object.entries(options.severity)) {
3821
5542
  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
5543
  const found = ruleFor(name, "severity");
@@ -3827,7 +5548,7 @@ function resolveOptions(options) {
3827
5548
  if (options.ignorePaths !== void 0) {
3828
5549
  if (!Array.isArray(options.ignorePaths)) throw new TypeError("Validate ignorePaths must be an array of {rule, path}.");
3829
5550
  for (const entry of options.ignorePaths) {
3830
- if (!isObject(entry) || typeof entry.rule !== "string" || typeof entry.path !== "string" || entry.path !== "" && !entry.path.startsWith("/"))
5551
+ if (!isObject2(entry) || typeof entry.rule !== "string" || typeof entry.path !== "string" || entry.path !== "" && !entry.path.startsWith("/"))
3831
5552
  throw new TypeError('Each validate ignorePaths entry needs a rule id, category name or "*", and a JSON Pointer path.');
3832
5553
  const found = entry.rule === "*" ? "*" : ruleFor(entry.rule, "ignorePaths");
3833
5554
  ignorePaths.push({
@@ -3838,7 +5559,7 @@ function resolveOptions(options) {
3838
5559
  }
3839
5560
  const thresholds = { ...DEFAULT_VALIDATION_THRESHOLDS };
3840
5561
  if (options.thresholds !== void 0) {
3841
- if (!isObject(options.thresholds)) throw new TypeError("Validate thresholds must be an object.");
5562
+ if (!isObject2(options.thresholds)) throw new TypeError("Validate thresholds must be an object.");
3842
5563
  for (const [key2, value] of Object.entries(options.thresholds)) {
3843
5564
  if (!(key2 in DEFAULT_VALIDATION_THRESHOLDS)) throw new TypeError(`Unknown validate threshold ${JSON.stringify(key2)}. Thresholds: ${Object.keys(DEFAULT_VALIDATION_THRESHOLDS).join(", ")}.`);
3844
5565
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new TypeError(`Validate threshold ${key2} must be a positive number.`);
@@ -3846,14 +5567,14 @@ function resolveOptions(options) {
3846
5567
  }
3847
5568
  }
3848
5569
  if (options.fonts !== void 0) {
3849
- if (!isObject(options.fonts)) throw new TypeError("Validate fonts must be a fonts object with a textMeasurement.");
5570
+ if (!isObject2(options.fonts)) throw new TypeError("Validate fonts must be a fonts object with a textMeasurement.");
3850
5571
  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.");
5572
+ 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
5573
  }
3853
5574
  if (options.chartPalette !== void 0 && (!Array.isArray(options.chartPalette) || !options.chartPalette.every((color) => typeof color === "string" && /^#[0-9a-fA-F]{6}$/.test(color))))
3854
5575
  throw new TypeError("Validate chartPalette must be an array of #RRGGBB colours.");
3855
5576
  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.");
5577
+ if (options.values !== void 0 && !isObject2(options.values)) throw new TypeError("Validate values must be an object keyed by variable id.");
3857
5578
  const level = (info) => severity.get(info.id) ?? severity.get(info.category);
3858
5579
  const off = (info) => level(info) === "off" || !!ignore?.rules.has(info.id) || !!ignore?.categories.has(info.category);
3859
5580
  return {
@@ -3910,7 +5631,7 @@ function syntaxReport(parsed, run) {
3910
5631
  function validateDocument(document, run, parsed) {
3911
5632
  const engine = validateAgainstSchema(document, "presentation", { template: run.options.template, values: run.options.values, ...run.options.catalogs ? { catalogs: run.options.catalogs } : {} });
3912
5633
  const schemaValid = engine.errors.length === 0;
3913
- const slides = isObject(document) && Array.isArray(document.slides) ? document.slides : [];
5634
+ const slides = isObject2(document) && Array.isArray(document.slides) ? document.slides : [];
3914
5635
  const auditWanted = auditInfos.some((info) => run.enabled(info));
3915
5636
  const explainFormat = !schemaValid && auditWanted;
3916
5637
  const enabledRule = (info) => info ? run.enabled(info) || explainFormat && info.category === "format" && !run.off(info) : true;
@@ -3975,7 +5696,7 @@ function validateDocument(document, run, parsed) {
3975
5696
  if (info && run.ignored(info, splitPointer(next.path))) continue;
3976
5697
  const index = slideOf(next.path);
3977
5698
  if (next.slide === void 0 && index !== void 0 && next.scope !== "context") {
3978
- const id = isObject(slides[index]) ? slides[index].id : void 0;
5699
+ const id = isObject2(slides[index]) ? slides[index].id : void 0;
3979
5700
  next.slide = index;
3980
5701
  if (typeof id === "string") next.slideId = id;
3981
5702
  }
@@ -4046,4 +5767,4 @@ function assertValidCatalogRecord(kind, value) {
4046
5767
  if (!report2.valid) throw new OPFValidationError(report2);
4047
5768
  }
4048
5769
 
4049
- export { CATALOG_SCHEMA, DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, catalogDisplayKinds, catalogSchemaNames, findValidationRule, parseSource, resolveSlideContext, resolveSlideScriptMeasurement, validate, validateCatalogRecord, validationCategories, validationRules };
5770
+ 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, schemaLabel, schemaType, schemaVariants, splitOpfPath, splitPointer2 as splitPointer, unescapePointerToken, validate, validateCatalogRecord, validationCategories, validationRules };