@openpresentation/opf 0.26.1 → 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.
- package/README.md +4 -0
- package/dist/browser.js +14 -15
- package/dist/{catalog-refs-CSHevLn1.d.ts → catalog-refs-BSfGELpt.d.ts} +1 -1
- package/dist/catalog-schemas-DoxA2BgY.d.ts +19 -0
- package/dist/{chunk-JIHHLV47.js → chunk-5VJCLUUA.js} +6 -6
- package/dist/{chunk-GS37EBPQ.js → chunk-6JAFSZVL.js} +1 -1
- package/dist/{chunk-F5ABHDUX.js → chunk-7YZ72FRP.js} +5 -5
- package/dist/{chunk-IWFXFWGD.js → chunk-CRCOQ4K2.js} +1 -1
- package/dist/{chunk-EQ6S5SEZ.js → chunk-JI3PHXPJ.js} +1 -1
- package/dist/{chunk-FITOGMR3.js → chunk-L65YNP4J.js} +1 -1
- package/dist/{chunk-X7RANZWP.js → chunk-LN2ASLMF.js} +1 -1
- package/dist/{chunk-P7S3ORB4.js → chunk-LUQYSUXS.js} +1790 -69
- package/dist/{chunk-F4HFDP6I.js → chunk-N6CNJCIF.js} +18 -0
- package/dist/{chunk-3OE6PZIC.js → chunk-NWO6HT4F.js} +4 -4
- package/dist/{chunk-LU6XM5JQ.js → chunk-O2ST5ZKL.js} +1 -1
- package/dist/{chunk-WW62PPYV.js → chunk-OFWBICDO.js} +1 -2
- package/dist/{chunk-MJ57RSYO.js → chunk-Q6KMKTEH.js} +4 -4
- package/dist/{chunk-IC6OPLMU.js → chunk-XFK4KPKK.js} +2 -2
- package/dist/{composition-Daqvi-GV.d.ts → composition-DIftf6iP.d.ts} +1 -1
- package/dist/composition.d.ts +2 -2
- package/dist/conversion-BKCKBECG.js +18 -0
- package/dist/{conversion-BFr5USlh.d.ts → conversion-CX_I6Vna.d.ts} +5 -5
- package/dist/convert.d.ts +3 -3
- package/dist/convert.js +6 -6
- package/dist/{deck-report-DHaboWbM.d.ts → deck-report-5vwMGcQz.d.ts} +2 -2
- package/dist/deck.d.ts +5 -5
- package/dist/deck.js +9 -9
- package/dist/diff.js +3 -4
- package/dist/docs.js +5 -5
- package/dist/format.js +2 -2
- package/dist/index.d.ts +13 -12
- package/dist/index.js +18 -19
- package/dist/markdown.d.ts +3 -3
- package/dist/markdown.js +6 -6
- package/dist/node-engine.d.ts +7 -7
- package/dist/node-engine.js +18 -19
- package/dist/{pagination-R8YFwVj7.d.ts → pagination-D_kJe4jH.d.ts} +2 -2
- package/dist/pagination.d.ts +3 -3
- package/dist/pagination.js +3 -3
- package/dist/patch.js +2 -3
- package/dist/repo-readme.js +1 -1
- package/dist/schema.d.ts +151 -0
- package/dist/schema.js +3 -0
- package/dist/schemas.js +1 -1
- package/dist/source.d.ts +97 -0
- package/dist/source.js +90 -0
- package/dist/spec/schemas/opf.schema.json +16 -0
- package/dist/spec/schemas/theme.schema.json +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/{validation-types-JW8e06ee.d.ts → validation-types-3ARvTsB0.d.ts} +2 -2
- package/dist/validator.d.ts +54 -6
- package/dist/validator.js +2 -2
- package/dist/yaml.d.ts +3 -3
- package/dist/yaml.js +6 -6
- package/package.json +13 -5
- package/dist/chunk-IY5SCRTV.js +0 -261
- package/dist/conversion-ETAHLWAH.js +0 -19
- 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-
|
|
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
|
|
1315
|
-
return { variant, ...fill ? { background: fill } : {}, drawn: variantOnly ? picture : { definition:
|
|
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),
|
|
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([
|
|
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 :
|
|
1343
|
-
const picture = imageBackground(choice.drawn ? choice.drawn.definition :
|
|
1344
|
-
const backgroundImage = picture &&
|
|
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
|
|
1529
|
-
if (typeof
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3273
|
+
walk2(entry, [...path, key2], depth2 + 1);
|
|
3253
3274
|
}
|
|
3254
3275
|
};
|
|
3255
|
-
|
|
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
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
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
|
|
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
|
|
5233
|
+
const problems = analyzeJsonSyntax(source);
|
|
3520
5234
|
return {
|
|
3521
|
-
syntax:
|
|
3522
|
-
(
|
|
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:
|
|
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
|
|
5335
|
+
let own4;
|
|
3615
5336
|
try {
|
|
3616
5337
|
const profile = scriptProfile(presentation, slideIndex, options, lang);
|
|
3617
|
-
|
|
5338
|
+
own4 = profile.languageSource === "option" && profile.bcp47?.toLowerCase() !== deckProfile.bcp47?.toLowerCase() ? forScripts.call(measurement, profile) : void 0;
|
|
3618
5339
|
} catch {
|
|
3619
|
-
|
|
5340
|
+
own4 = void 0;
|
|
3620
5341
|
}
|
|
3621
|
-
languages.set(lang,
|
|
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
|
|
3631
|
-
return typeof
|
|
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
|
|
3704
|
-
if (Array.isArray(node)) return node.forEach(
|
|
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))
|
|
5429
|
+
for (const child of Object.values(object2)) walk2(child);
|
|
3709
5430
|
}
|
|
3710
5431
|
};
|
|
3711
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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" && !
|
|
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 && !
|
|
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 =
|
|
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 =
|
|
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 };
|