@openpresentation/opf 0.20.5 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +16 -16
- package/dist/{chunk-2M7U4WL3.js → chunk-23MOKE4B.js} +292 -104
- package/dist/{chunk-TCBEF5OY.js → chunk-2SENTA2R.js} +1 -1
- package/dist/{chunk-7PQZDC2H.js → chunk-3T7MNRBY.js} +1 -1
- package/dist/{chunk-VFMHM2IL.js → chunk-3UAVON3O.js} +28 -2
- package/dist/{chunk-IDMQWS6O.js → chunk-7XNAMCGT.js} +6 -6
- package/dist/{chunk-URUNMZFO.js → chunk-BULGAKBY.js} +2 -2
- package/dist/{chunk-WD4W7KKK.js → chunk-DY7H4RG6.js} +54 -11
- package/dist/{chunk-V66KJA3H.js → chunk-KUNXFN7B.js} +1 -1
- package/dist/{chunk-52GKVP3I.js → chunk-MAJHCGOW.js} +22 -12
- package/dist/{chunk-OIGCC6VA.js → chunk-MG34DCDB.js} +2 -2
- package/dist/{chunk-5SQ6CIWR.js → chunk-MLMTXXM4.js} +5 -5
- package/dist/{chunk-Z75YB54S.js → chunk-SBWIZSPB.js} +5 -5
- package/dist/{chunk-TZ5IZETE.js → chunk-SPCONMIY.js} +1 -1
- package/dist/{chunk-XAYMPBNE.js → chunk-VKNFZEMG.js} +403 -189
- package/dist/{chunk-S4FR7ATF.js → chunk-X3PQMMAY.js} +1 -1
- package/dist/{chunk-YOLDSF7U.js → chunk-YSJRBRHB.js} +1 -1
- package/dist/{chunk-DYGYMBBW.js → chunk-YUIEWE5B.js} +2 -2
- package/dist/{composition-QtS1rnhv.d.ts → composition-CkHn6JrM.d.ts} +139 -5
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +1 -1
- package/dist/conversion-3TTBY7OY.js +19 -0
- package/dist/{conversion-Cqwu0Ds9.d.ts → conversion-DS4OiVls.d.ts} +6 -5
- package/dist/convert.d.ts +3 -3
- package/dist/convert.js +7 -7
- package/dist/data.d.ts +1 -1
- package/dist/data.js +2 -2
- package/dist/{deck-report-BdG5H3Qj.d.ts → deck-report-vnWmXBjq.d.ts} +2 -2
- package/dist/deck.d.ts +6 -6
- package/dist/deck.js +10 -10
- package/dist/diff.d.ts +1 -1
- package/dist/diff.js +5 -5
- package/dist/docs.js +18 -12
- package/dist/examples.d.ts +1 -1
- package/dist/{finding-DqgwzUmr.d.ts → finding-l9dlQ2NF.d.ts} +4 -0
- package/dist/format.js +2 -2
- package/dist/index.d.ts +21 -15
- package/dist/index.js +20 -20
- package/dist/markdown.d.ts +4 -4
- package/dist/markdown.js +7 -7
- package/dist/node-engine.d.ts +30 -9
- package/dist/node-engine.js +20 -20
- package/dist/{pagination-CAX6mHG6.d.ts → pagination-C4jzAtFy.d.ts} +2 -2
- package/dist/pagination.d.ts +2 -2
- package/dist/pagination.js +4 -4
- package/dist/patch.d.ts +1 -1
- package/dist/patch.js +4 -4
- package/dist/{presentation-BG4QCcdB.d.ts → presentation-fN6tORPZ.d.ts} +58 -8
- package/dist/repo-readme.js +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/schemas/finding.schema.json +6 -1
- package/dist/spec/schemas/opf.schema.json +14 -2
- package/dist/types.d.ts +2 -2
- package/dist/{validation-types-DRaNnieo.d.ts → validation-types-B21fZfb2.d.ts} +3 -3
- package/dist/{validator-BJtXHJKh.d.ts → validator-Cflv4F9e.d.ts} +4 -10
- package/dist/validator.d.ts +5 -5
- package/dist/validator.js +3 -3
- package/dist/yaml.d.ts +4 -4
- package/dist/yaml.js +7 -7
- package/package.json +1 -1
- package/dist/conversion-DRCJIMWS.js +0 -19
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { promotedRegionKeys, VARIABLE_KINDS, resolveChartData, chartPaletteForFill, imageBackground, tableGrid, timelineTextColor, LOGO_SHAPES, visitContentPayloads, readingRows, visualReadingOrder, intrinsicImageSize, resolveReference, textWidthMeasurer,
|
|
2
|
-
import {
|
|
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, defaultSlideBackground, isRecord, hasContentVariables, isTemplate, instantiateForValidation, layoutTemplateIssues, resolveScriptFonts, 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-VKNFZEMG.js';
|
|
2
|
+
import { schemas, schemaEntries } from './chunk-3UAVON3O.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';
|
|
@@ -87,6 +87,125 @@ function unusedReferenceWarnings(value) {
|
|
|
87
87
|
return value.references.flatMap((reference, index) => isRecord(reference) && typeof reference.id === "string" && unused.includes(reference.id) ? [issue(`/references/${index}`, `reference '${reference.id}' is never cited; cite it with a run's cite field or remove it`, { code: "unused-reference", id: reference.id })] : []);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// src/union-errors.ts
|
|
91
|
+
var compiled = /* @__PURE__ */ new WeakMap();
|
|
92
|
+
var origin = /* @__PURE__ */ new WeakMap();
|
|
93
|
+
var isUnion = (error) => (error.keyword === "oneOf" || error.keyword === "anyOf") && Array.isArray(error.schema);
|
|
94
|
+
var key = (error) => `${error.instancePath}\0${error.keyword}\0${error.message}\0${JSON.stringify(error.params)}`;
|
|
95
|
+
var depth = (pointer4) => pointer4 ? pointer4.split("/").length - 1 : 0;
|
|
96
|
+
var original = (node) => {
|
|
97
|
+
let current = node;
|
|
98
|
+
while (current && typeof current === "object" && origin.has(current)) current = origin.get(current);
|
|
99
|
+
return current;
|
|
100
|
+
};
|
|
101
|
+
function qualify(node, rootId) {
|
|
102
|
+
if (Array.isArray(node)) {
|
|
103
|
+
const copy = node.map((item) => qualify(item, rootId));
|
|
104
|
+
origin.set(copy, node);
|
|
105
|
+
return copy;
|
|
106
|
+
}
|
|
107
|
+
if (node && typeof node === "object") {
|
|
108
|
+
const copy = {};
|
|
109
|
+
for (const [name, value] of Object.entries(node)) copy[name] = name === "$ref" && typeof value === "string" && value.startsWith("#") ? rootId + value : qualify(value, rootId);
|
|
110
|
+
origin.set(copy, node);
|
|
111
|
+
return copy;
|
|
112
|
+
}
|
|
113
|
+
return node;
|
|
114
|
+
}
|
|
115
|
+
function branchValidator(ajv2, rootId, branch) {
|
|
116
|
+
let validate2 = compiled.get(branch);
|
|
117
|
+
if (!validate2) {
|
|
118
|
+
validate2 = ajv2.compile(qualify(branch, rootId));
|
|
119
|
+
compiled.set(branch, validate2);
|
|
120
|
+
}
|
|
121
|
+
return validate2;
|
|
122
|
+
}
|
|
123
|
+
function branchLabel(branch, index) {
|
|
124
|
+
if (typeof branch.$ref === "string") return branch.$ref.slice(branch.$ref.lastIndexOf("/") + 1);
|
|
125
|
+
if (typeof branch.type === "string") return branch.type;
|
|
126
|
+
if (Array.isArray(branch.type)) return branch.type.join("|");
|
|
127
|
+
if ("const" in branch) return JSON.stringify(branch.const);
|
|
128
|
+
return `option ${index + 1}`;
|
|
129
|
+
}
|
|
130
|
+
function describe(errors, base) {
|
|
131
|
+
const types = [...new Set(errors.filter((error) => depth(error.instancePath) === base && error.keyword === "type").flatMap((error) => [error.params.type].flat()))];
|
|
132
|
+
if (types.length) return `must be ${types.join(" or ")}`;
|
|
133
|
+
const shallow = [...errors].filter((error) => !isUnion(error) || errors.every(isUnion)).sort((a, b) => depth(a.instancePath) - depth(b.instancePath))[0];
|
|
134
|
+
if (!shallow) return "does not match";
|
|
135
|
+
const rest = shallow.instancePath.split("/").slice(base + 1).join("/");
|
|
136
|
+
return depth(shallow.instancePath) === base || !rest ? shallow.message ?? "does not match" : `${shallow.message} at /${rest}`;
|
|
137
|
+
}
|
|
138
|
+
function bestMatchErrors(ajv2, rootId, errors) {
|
|
139
|
+
const unions = errors.filter(isUnion);
|
|
140
|
+
if (!unions.length) return errors;
|
|
141
|
+
const analyses = [];
|
|
142
|
+
for (const union of unions) {
|
|
143
|
+
const analysis = analyze(ajv2, rootId, union);
|
|
144
|
+
if (analysis) analyses.push(analysis);
|
|
145
|
+
}
|
|
146
|
+
if (!analyses.length) return errors;
|
|
147
|
+
const within = (inner, other) => other !== inner && [...inner.span].every((entry) => other.span.has(entry)) && (other.span.size > inner.span.size || errors.indexOf(other.union) > errors.indexOf(inner.union));
|
|
148
|
+
const outer = analyses.filter((analysis) => !analyses.some((other) => within(analysis, other)));
|
|
149
|
+
const replaced = new Map(outer.map((analysis) => [analysis.union, analysis]));
|
|
150
|
+
const drop = new Set(outer.flatMap((analysis) => [...analysis.span]));
|
|
151
|
+
const result = [];
|
|
152
|
+
for (const error of errors) {
|
|
153
|
+
const analysis = replaced.get(error);
|
|
154
|
+
if (analysis) result.push(...analysis.replacement);
|
|
155
|
+
else if (!drop.has(key(error))) result.push(error);
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
function analyze(ajv2, rootId, union) {
|
|
160
|
+
const branches = union.schema;
|
|
161
|
+
const base = depth(union.instancePath);
|
|
162
|
+
const results = [];
|
|
163
|
+
for (const [index, branch] of branches.entries()) {
|
|
164
|
+
if (!branch || typeof branch !== "object") return void 0;
|
|
165
|
+
const validate2 = branchValidator(ajv2, rootId, branch);
|
|
166
|
+
if (validate2(union.data) === true) return void 0;
|
|
167
|
+
const raw = (validate2.errors ?? []).map((error) => ({ ...error, instancePath: union.instancePath + error.instancePath }));
|
|
168
|
+
results.push({ label: branchLabel(branch, index), errors: raw });
|
|
169
|
+
}
|
|
170
|
+
const span = /* @__PURE__ */ new Set([key(union), ...results.flatMap((result) => result.errors.map(key))]);
|
|
171
|
+
const reduced = results.map((result) => ({ label: result.label, errors: bestMatchErrors(ajv2, rootId, result.errors) }));
|
|
172
|
+
const mismatch = (error) => {
|
|
173
|
+
const level = depth(error.instancePath) - base;
|
|
174
|
+
return level === 0 && (error.keyword === "type" || isUnion(error)) || level === 1 && error.keyword === "const";
|
|
175
|
+
};
|
|
176
|
+
const matched = reduced.filter((result) => result.errors.length && !result.errors.some(mismatch));
|
|
177
|
+
const reach = (errors) => Math.min(...errors.map((error) => depth(error.instancePath)));
|
|
178
|
+
const ranked = [...matched].sort((a, b) => reach(b.errors) - reach(a.errors));
|
|
179
|
+
const summary = (list) => list.map((result) => `${result.label}: ${describe(result.errors, base)}`);
|
|
180
|
+
const named = (list) => list.length > 1 ? `${list.slice(0, -1).join(", ")} or ${list.at(-1)}` : list[0] ?? "";
|
|
181
|
+
const lead = ranked[0];
|
|
182
|
+
if (!lead) {
|
|
183
|
+
const rootTypes = reduced.map((result) => result.errors.filter((error) => depth(error.instancePath) === base && error.keyword === "type"));
|
|
184
|
+
const plain = rootTypes.every((list, index) => list.length === reduced[index]?.errors.length && list.length > 0);
|
|
185
|
+
const types = [...new Set(rootTypes.flatMap((list) => list.flatMap((error) => [error.params.type].flat())))].map(String);
|
|
186
|
+
return { union, span, replacement: [{ ...union, message: plain ? `must be ${named(types)}` : union.message, alternatives: summary(reduced) }] };
|
|
187
|
+
}
|
|
188
|
+
const top = ranked.filter((result) => reach(result.errors) === reach(lead.errors));
|
|
189
|
+
const others = reduced.filter((result) => !top.includes(result));
|
|
190
|
+
const single = (result) => {
|
|
191
|
+
const only = result.errors[0];
|
|
192
|
+
return result.errors.length === 1 && only && depth(only.instancePath) === base ? only : void 0;
|
|
193
|
+
};
|
|
194
|
+
const singles = top.map(single);
|
|
195
|
+
const keyword = singles[0]?.keyword;
|
|
196
|
+
if (top.length > 1 && (keyword === "format" || keyword === "required") && singles.every((error) => error?.keyword === keyword)) {
|
|
197
|
+
const values = singles.map((error) => JSON.stringify(keyword === "format" ? error?.params.format : error?.params.missingProperty).replace(/^"|"$/g, keyword === "format" ? '"' : "'"));
|
|
198
|
+
const message = keyword === "format" ? `must match format ${named(values)}` : `must have required property ${named(values)}`;
|
|
199
|
+
return { union, span, replacement: [{ ...singles[0], message, alternatives: summary(top) }] };
|
|
200
|
+
}
|
|
201
|
+
const chosen = top.flatMap((result) => result.errors).map((error, index) => {
|
|
202
|
+
const mapped = { ...error, parentSchema: original(error.parentSchema), schema: original(error.schema) };
|
|
203
|
+
if (index === 0 && others.length) mapped.alternatives = summary(others);
|
|
204
|
+
return mapped;
|
|
205
|
+
});
|
|
206
|
+
return { union, span, replacement: chosen };
|
|
207
|
+
}
|
|
208
|
+
|
|
90
209
|
// src/validation-definitions.ts
|
|
91
210
|
var schemaLocations;
|
|
92
211
|
var issueLocations = /* @__PURE__ */ new WeakMap();
|
|
@@ -107,8 +226,8 @@ function rememberValidationDefinition(issue2, parentSchema, keyword) {
|
|
|
107
226
|
pointer: current.pointer,
|
|
108
227
|
uri: `${entry.schema.$id}#${current.pointer}`
|
|
109
228
|
});
|
|
110
|
-
for (const [
|
|
111
|
-
const segment =
|
|
229
|
+
for (const [key2, value] of Object.entries(current.value)) {
|
|
230
|
+
const segment = key2.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
112
231
|
stack.push({ value, pointer: `${current.pointer}/${segment}` });
|
|
113
232
|
}
|
|
114
233
|
}
|
|
@@ -254,7 +373,8 @@ function toIssue(error) {
|
|
|
254
373
|
message: slideDimensions ? "a slide's design cannot set 'dimensions': a PPTX has one slide size, so set design.dimensions on the deck (or its theme)" : error.message ?? "failed validation",
|
|
255
374
|
keyword: error.keyword,
|
|
256
375
|
schemaPath: error.schemaPath,
|
|
257
|
-
params: error.params
|
|
376
|
+
params: error.params,
|
|
377
|
+
...error.alternatives?.length ? { alternatives: error.alternatives } : {}
|
|
258
378
|
};
|
|
259
379
|
rememberValidationDefinition(issue2, error.parentSchema, error.keyword);
|
|
260
380
|
return issue2;
|
|
@@ -268,8 +388,8 @@ function semanticIssue(path, message, params = {}) {
|
|
|
268
388
|
params
|
|
269
389
|
};
|
|
270
390
|
}
|
|
271
|
-
function hasOwn(value,
|
|
272
|
-
return Object.hasOwn(value,
|
|
391
|
+
function hasOwn(value, key2) {
|
|
392
|
+
return Object.hasOwn(value, key2);
|
|
273
393
|
}
|
|
274
394
|
function isEnUkTag(value) {
|
|
275
395
|
return typeof value === "string" && value.toLowerCase() === "en-uk";
|
|
@@ -391,20 +511,20 @@ function validateContentPayload(value, path, options = {}) {
|
|
|
391
511
|
}
|
|
392
512
|
return issues;
|
|
393
513
|
}
|
|
394
|
-
function slideRegion(
|
|
395
|
-
if (
|
|
396
|
-
const [rowPart, columnPart] =
|
|
514
|
+
function slideRegion(key2) {
|
|
515
|
+
if (key2.includes(":")) {
|
|
516
|
+
const [rowPart, columnPart] = key2.split(":");
|
|
397
517
|
if (!rowPart || !columnPart) return void 0;
|
|
398
518
|
const rows = rowSpans[rowPart];
|
|
399
519
|
const columns = columnSpans[columnPart];
|
|
400
520
|
return rows && columns ? { rows, columns } : void 0;
|
|
401
521
|
}
|
|
402
|
-
if (
|
|
403
|
-
const columns = columnSpans[
|
|
522
|
+
if (key2 in columnSpans) {
|
|
523
|
+
const columns = columnSpans[key2];
|
|
404
524
|
return columns ? { rows: [0, 1, 2], columns } : void 0;
|
|
405
525
|
}
|
|
406
|
-
if (
|
|
407
|
-
const rows = rowSpans[
|
|
526
|
+
if (key2 in rowSpans) {
|
|
527
|
+
const rows = rowSpans[key2];
|
|
408
528
|
return rows ? { rows, columns: [0, 1, 2] } : void 0;
|
|
409
529
|
}
|
|
410
530
|
return void 0;
|
|
@@ -417,7 +537,7 @@ function regionsOverlap(left, right) {
|
|
|
417
537
|
}
|
|
418
538
|
function validateSlideRegions(slide, slidePath) {
|
|
419
539
|
const issues = [];
|
|
420
|
-
const regionKeys = Object.keys(slide).filter((
|
|
540
|
+
const regionKeys = Object.keys(slide).filter((key2) => promotedRegionKeySet.has(key2));
|
|
421
541
|
const rootFields = presentFields(slide, rootPayloadFields);
|
|
422
542
|
if (regionKeys.length > 0 && rootFields.length > 0) {
|
|
423
543
|
issues.push(semanticIssue(slidePath, "root content payload fields cannot be mixed with promoted region keys", {
|
|
@@ -428,10 +548,10 @@ function validateSlideRegions(slide, slidePath) {
|
|
|
428
548
|
if (regionKeys.length === 0 && rootFields.length > 0) {
|
|
429
549
|
issues.push(...validateContentPayload(slide, slidePath, { slideRoot: true }));
|
|
430
550
|
}
|
|
431
|
-
for (const
|
|
432
|
-
const value = slide[
|
|
551
|
+
for (const key2 of regionKeys) {
|
|
552
|
+
const value = slide[key2];
|
|
433
553
|
if (isRecord(value)) {
|
|
434
|
-
issues.push(...validateContentPayload(value, pathFor(slidePath,
|
|
554
|
+
issues.push(...validateContentPayload(value, pathFor(slidePath, key2)));
|
|
435
555
|
}
|
|
436
556
|
}
|
|
437
557
|
for (let leftIndex = 0; leftIndex < regionKeys.length; leftIndex += 1) {
|
|
@@ -455,19 +575,19 @@ function validateSlideRegions(slide, slidePath) {
|
|
|
455
575
|
}
|
|
456
576
|
function deckMetadataIssues(value) {
|
|
457
577
|
const issues = [];
|
|
458
|
-
const entries = (
|
|
459
|
-
const field = value[
|
|
460
|
-
if (Array.isArray(field)) return field.flatMap((entry, index) => isRecord(entry) ? [{ entry, path: `/${
|
|
461
|
-
return isRecord(field) ? [{ entry: field, path: `/${
|
|
578
|
+
const entries = (key2) => {
|
|
579
|
+
const field = value[key2];
|
|
580
|
+
if (Array.isArray(field)) return field.flatMap((entry, index) => isRecord(entry) ? [{ entry, path: `/${key2}/${index}` }] : []);
|
|
581
|
+
return isRecord(field) ? [{ entry: field, path: `/${key2}` }] : [];
|
|
462
582
|
};
|
|
463
583
|
const organizationIds = /* @__PURE__ */ new Set();
|
|
464
|
-
for (const
|
|
584
|
+
for (const key2 of ["organization", "speaker"]) {
|
|
465
585
|
const seen = /* @__PURE__ */ new Set();
|
|
466
|
-
for (const { entry, path } of entries(
|
|
586
|
+
for (const { entry, path } of entries(key2)) {
|
|
467
587
|
if (typeof entry.id !== "string") continue;
|
|
468
|
-
if (seen.has(entry.id)) issues.push(semanticIssue(`${path}/id`, `${
|
|
588
|
+
if (seen.has(entry.id)) issues.push(semanticIssue(`${path}/id`, `${key2} ids must be unique within a presentation`, { id: entry.id }));
|
|
469
589
|
seen.add(entry.id);
|
|
470
|
-
if (
|
|
590
|
+
if (key2 === "organization") organizationIds.add(entry.id);
|
|
471
591
|
}
|
|
472
592
|
}
|
|
473
593
|
for (const { entry, path } of entries("speaker")) {
|
|
@@ -621,9 +741,9 @@ function dataIssues(value) {
|
|
|
621
741
|
const seen = /* @__PURE__ */ new Set();
|
|
622
742
|
const add = (diagnostics) => {
|
|
623
743
|
for (const diagnostic of diagnostics) {
|
|
624
|
-
const
|
|
625
|
-
if (seen.has(
|
|
626
|
-
seen.add(
|
|
744
|
+
const key2 = `${diagnostic.code}|${diagnostic.path}`;
|
|
745
|
+
if (seen.has(key2)) continue;
|
|
746
|
+
seen.add(key2);
|
|
627
747
|
(diagnostic.severity === "error" ? out.errors : out.warnings).push(semanticIssue(diagnostic.path, diagnostic.message, { code: diagnostic.code }));
|
|
628
748
|
}
|
|
629
749
|
};
|
|
@@ -808,9 +928,9 @@ function presentationReferenceWarnings(value, catalogs) {
|
|
|
808
928
|
};
|
|
809
929
|
highlightWarnings(slide, slidePath);
|
|
810
930
|
visitContentPayloads(slide, slidePath, highlightWarnings);
|
|
811
|
-
for (const
|
|
812
|
-
if (promotedRegionKeySet.has(
|
|
813
|
-
issues.push(...chartTypeWarnings(slide[
|
|
931
|
+
for (const key2 of Object.keys(slide)) {
|
|
932
|
+
if (promotedRegionKeySet.has(key2)) {
|
|
933
|
+
issues.push(...chartTypeWarnings(slide[key2], pathFor(slidePath, key2)));
|
|
814
934
|
}
|
|
815
935
|
}
|
|
816
936
|
});
|
|
@@ -835,7 +955,7 @@ function contentDepthIssues(value) {
|
|
|
835
955
|
if (Array.isArray(slide.blocks)) slide.blocks.forEach((block, i) => {
|
|
836
956
|
stack.push({ value: block, path: `/slides/${index}/blocks/${i}`, depth: 0, ancestors: [] });
|
|
837
957
|
});
|
|
838
|
-
for (const
|
|
958
|
+
for (const key2 of promotedRegionKeys) if (hasOwn(slide, key2)) stack.push({ value: slide[key2], path: `/slides/${index}/${key2}`, depth: 0, ancestors: [] });
|
|
839
959
|
});
|
|
840
960
|
while (stack.length) {
|
|
841
961
|
const entry = stack.pop();
|
|
@@ -890,9 +1010,9 @@ function typedVariableErrors(errors, value) {
|
|
|
890
1010
|
function pointerValue(root, pointer4) {
|
|
891
1011
|
let current = root;
|
|
892
1012
|
for (const token of pointer4.split("/").slice(1)) {
|
|
893
|
-
const
|
|
894
|
-
if (Array.isArray(current)) current = current[Number(
|
|
895
|
-
else if (isRecord(current) && hasOwn(current,
|
|
1013
|
+
const key2 = token.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
1014
|
+
if (Array.isArray(current)) current = current[Number(key2)];
|
|
1015
|
+
else if (isRecord(current) && hasOwn(current, key2)) current = current[key2];
|
|
896
1016
|
else return void 0;
|
|
897
1017
|
}
|
|
898
1018
|
return current;
|
|
@@ -955,7 +1075,8 @@ function validateAgainstSchema(value, schemaOrKind = "presentation", options = {
|
|
|
955
1075
|
}
|
|
956
1076
|
const valid = resolved.validate(subject) === true;
|
|
957
1077
|
const ajvErrors = valid ? [] : typedVariableErrors(resolved.validate.errors ?? [], subject);
|
|
958
|
-
const
|
|
1078
|
+
const specific = resolved.schemaName === "presentation" ? dataUnionErrors(ajvErrors, subject) : ajvErrors;
|
|
1079
|
+
const mapped = bestMatchErrors(getAjv(), schemas[resolved.schemaName].$id, specific).map(toIssue);
|
|
959
1080
|
const errors = mapped;
|
|
960
1081
|
if (resolved.schemaName === "layout" && valid) for (const issue2 of layoutTemplateIssues(subject)) errors.push(semanticIssue(issue2.path, issue2.message, { code: issue2.code }));
|
|
961
1082
|
const warnings = [];
|
|
@@ -1283,20 +1404,6 @@ function readableColor(candidates, sample, minimum) {
|
|
|
1283
1404
|
if (worstContrast(candidate, sample).ratio >= minimum) return candidate;
|
|
1284
1405
|
return void 0;
|
|
1285
1406
|
}
|
|
1286
|
-
var DEFAULT_CHART_PALETTE = [
|
|
1287
|
-
"#2874A6",
|
|
1288
|
-
"#1B4F72",
|
|
1289
|
-
"#5499C7",
|
|
1290
|
-
"#7BDBB2",
|
|
1291
|
-
"#3AC67A",
|
|
1292
|
-
"#24A89E",
|
|
1293
|
-
"#F59E0B",
|
|
1294
|
-
"#EF4444",
|
|
1295
|
-
"#8B5CF6",
|
|
1296
|
-
"#14B8A6",
|
|
1297
|
-
"#0F172A",
|
|
1298
|
-
"#64748B"
|
|
1299
|
-
];
|
|
1300
1407
|
|
|
1301
1408
|
// src/rule-content.ts
|
|
1302
1409
|
var pointer2 = (...parts2) => parts2.length ? "/" + parts2.map((part) => String(part).replaceAll("~", "~0").replaceAll("/", "~1")).join("/") : "";
|
|
@@ -1356,9 +1463,9 @@ function textValues(slide, slidePath) {
|
|
|
1356
1463
|
const metric = node.metric;
|
|
1357
1464
|
if (typeof metric === "string") push(at2("metric"), metric, "metric", "metric");
|
|
1358
1465
|
else if (metric && typeof metric === "object")
|
|
1359
|
-
for (const
|
|
1360
|
-
const value = metric[
|
|
1361
|
-
if (typeof value === "string") push(at2("metric",
|
|
1466
|
+
for (const key2 of ["value", "label", "unit", "delta"]) {
|
|
1467
|
+
const value = metric[key2];
|
|
1468
|
+
if (typeof value === "string") push(at2("metric", key2), value, "metric", "metric");
|
|
1362
1469
|
}
|
|
1363
1470
|
const timeline = node.timeline, events = Array.isArray(timeline) ? timeline : Array.isArray(rec(timeline).events) ? rec(timeline).events : [];
|
|
1364
1471
|
if (typeof rec(timeline).name === "string") push(at2("timeline", "name"), rec(timeline).name, "timeline", "timeline");
|
|
@@ -1595,8 +1702,8 @@ var contrastRules = [
|
|
|
1595
1702
|
}
|
|
1596
1703
|
continue;
|
|
1597
1704
|
}
|
|
1598
|
-
const
|
|
1599
|
-
const known = aggregated.get(
|
|
1705
|
+
const key2 = `${sample.source === "default" ? "default" : sample.path}|${sample.color}|${worst.against}|${large}`;
|
|
1706
|
+
const known = aggregated.get(key2);
|
|
1600
1707
|
if (known && sample.source === "default") {
|
|
1601
1708
|
known.extra++;
|
|
1602
1709
|
continue;
|
|
@@ -1617,7 +1724,7 @@ var contrastRules = [
|
|
|
1617
1724
|
measured: { ratio: round(worst.ratio), needed, large, sizePt: round(sample.sizePx * PX_TO_PT), textColor: sample.color, backgroundColor: worst.against },
|
|
1618
1725
|
fixes
|
|
1619
1726
|
});
|
|
1620
|
-
if (sample.source === "default") aggregated.set(
|
|
1727
|
+
if (sample.source === "default") aggregated.set(key2, { diagnostic, extra: 0 });
|
|
1621
1728
|
}
|
|
1622
1729
|
for (const { diagnostic, extra } of aggregated.values())
|
|
1623
1730
|
if (extra) {
|
|
@@ -1893,7 +2000,7 @@ var chartColorRule = rule(
|
|
|
1893
2000
|
{
|
|
1894
2001
|
standard: "WCAG 2.2 SC 1.4.1 Use of Color, level A",
|
|
1895
2002
|
thresholds: ["minSeriesColorDifference"],
|
|
1896
|
-
approximations: "Uses the engine's series palette (ValidateOptions.chartPalette; default
|
|
2003
|
+
approximations: "Uses the engine's series palette (ValidateOptions.chartPalette; default chartSeriesPalette of each slide colour scheme (accent1 to accent6, then their tints), adjusted for the card surface like the preview does) in series order: series i takes colour i, pie/doughnut/treemap/funnel slices take colours per category. Pairs are compared by CIE76 distance after simulating protanopia, deuteranopia, tritanopia (Machado 2009, severity 1) and greyscale. Single-series charts and chart types without a series legend are skipped."
|
|
1897
2004
|
}
|
|
1898
2005
|
);
|
|
1899
2006
|
var chartAltRule = rule(
|
|
@@ -1904,6 +2011,21 @@ var chartAltRule = rule(
|
|
|
1904
2011
|
"A chart conveys a message; people who cannot see it need the message and ideally the numbers in text. The chart's alt field is that text alternative (the preview exposes it as the chart's accessible name and the PowerPoint export writes it as the frame's alternative text); a sentence or table beside the chart also serves. An empty alt marks a chart decorative, which is reported as info so the choice is reviewed: a chart rarely carries no message.",
|
|
1905
2012
|
{ standard: "WCAG 2.2 SC 1.1.1 Non-text Content, level A", approximations: `A chart passes when chart.alt has text. Without alt, it passes when the slide has any other text, list, table, quote or metric content besides title and tag, or a subtitle. It does not judge whether alt or that text states the chart's point (see opf/poor-alt-text for generic alt text). alt: "" is reported as a decorative chart, whatever else is on the slide.` }
|
|
1906
2013
|
);
|
|
2014
|
+
var chartColorsRule = rule(
|
|
2015
|
+
"chart-colors-adjusted",
|
|
2016
|
+
"accessibility",
|
|
2017
|
+
"warning",
|
|
2018
|
+
"A chart colour cannot be used as authored.",
|
|
2019
|
+
"`chart.colors` sets the colour of series 1, 2, 3 and so on. A colour that cannot be resolved (a `var:` colour variable the deck does not define, or a name that is not a colour) is ignored and the deck palette colour of that position is used. A colour with under 3:1 contrast against the chart panel is kept for its hue but drawn lighter or darker, so the chart does not show the colour that was written.",
|
|
2020
|
+
{
|
|
2021
|
+
standard: "WCAG 2.2 SC 1.4.11 Non-text Contrast, level AA",
|
|
2022
|
+
approximations: "Resolves each entry like any colour reference (hex, colour-scheme slot or role, `var:` variable) against the slide's scheme and compares it with the chart panel (the slide surface colour) by WCAG contrast; 3:1 is the threshold the engines enforce. Translucent colours and unresolved panels are not measured. Colours that look alike to colour-vision deficiency are reported by opf/chart-color-only, which judges the authored colours."
|
|
2023
|
+
}
|
|
2024
|
+
);
|
|
2025
|
+
var chartColorContext = (design) => ({
|
|
2026
|
+
roles: { primary: design.colors.primary, secondary: design.colors.secondary, accent: design.colors.accent, background: design.colors.background, surface: design.colors.surface, text: design.colors.text, textSecondary: design.colors.mutedText },
|
|
2027
|
+
variables: design.variables
|
|
2028
|
+
});
|
|
1907
2029
|
var chartSkip = /(histogram|box|pareto|waterfall|world|map)/;
|
|
1908
2030
|
var perCategory = /(pie|doughnut|treemap|funnel)/;
|
|
1909
2031
|
var chartRules = [
|
|
@@ -1922,7 +2044,7 @@ var chartRules = [
|
|
|
1922
2044
|
const count = slices ? data.rows.length : data.columns.length - 1;
|
|
1923
2045
|
if (count < 2) continue;
|
|
1924
2046
|
const surface = slide.design.colors.surface;
|
|
1925
|
-
const palette = chartPaletteForFill(surface, context.chartPalette);
|
|
2047
|
+
const palette = chartPaletteForFill(surface, context.chartPalette ?? chartSeriesPalette(slide.design.colorScheme, chart.colors, chartColorContext(slide.design)));
|
|
1926
2048
|
if (!palette.length) continue;
|
|
1927
2049
|
const used = Array.from({ length: Math.min(count, 24) }, (_, i) => palette[i % palette.length]);
|
|
1928
2050
|
const pairs = [];
|
|
@@ -1939,17 +2061,51 @@ var chartRules = [
|
|
|
1939
2061
|
if (!pairs.length && !cycles) continue;
|
|
1940
2062
|
pairs.sort((x, y) => x.difference - y.difference);
|
|
1941
2063
|
const names2 = slices ? data.rows.map((row) => String(row?.[0] ?? "")) : data.columns.slice(1).map(String);
|
|
1942
|
-
const
|
|
2064
|
+
const describe2 = (p) => `${JSON.stringify(names2[p.a] ?? `#${p.a + 1}`)} and ${JSON.stringify(names2[p.b] ?? `#${p.b + 1}`)} (${p.model}, difference ${p.difference.toFixed(1)})`;
|
|
1943
2065
|
context.report(chartColorRule, {
|
|
1944
2066
|
path: `${payload.path === slide.path ? slide.path : payload.path}/chart`,
|
|
1945
2067
|
slide,
|
|
1946
|
-
message: `${slices ? "Slices" : "Series"} may be hard to tell apart without colour: ${pairs.slice(0, 3).map(
|
|
2068
|
+
message: `${slices ? "Slices" : "Series"} may be hard to tell apart without colour: ${pairs.slice(0, 3).map(describe2).join("; ")}${cycles ? `${pairs.length ? "; " : ""}${count} ${slices ? "slices" : "series"} exceed the ${palette.length}-colour palette, so colours repeat` : ""}.`,
|
|
1947
2069
|
help: "Show fewer series per chart (split into two charts), state the key values in the slide text or a table next to the chart, or choose a chart type whose categories sit on an axis instead of in a legend.",
|
|
1948
2070
|
measured: { series: count, confusablePairs: pairs.length, minDifference: pairs[0] ? round(pairs[0].difference) : null }
|
|
1949
2071
|
});
|
|
1950
2072
|
}
|
|
1951
2073
|
}
|
|
1952
2074
|
},
|
|
2075
|
+
{
|
|
2076
|
+
info: chartColorsRule,
|
|
2077
|
+
run(context) {
|
|
2078
|
+
for (const slide of context.slides)
|
|
2079
|
+
for (const payload of slidePayloads(slide.slide, slide.path)) {
|
|
2080
|
+
const chart = rec(payload.node.chart);
|
|
2081
|
+
if (!Array.isArray(chart.colors)) continue;
|
|
2082
|
+
const surface = normalizeHexColor(slide.design.colors.surface);
|
|
2083
|
+
const resolved = resolveChartColors(chart.colors, slide.design.colorScheme, chartColorContext(slide.design));
|
|
2084
|
+
resolved.forEach((color, index) => {
|
|
2085
|
+
const path = `${payload.path === slide.path ? slide.path : payload.path}/chart/colors/${index}`;
|
|
2086
|
+
const entry = chart.colors[index];
|
|
2087
|
+
if (color === void 0) {
|
|
2088
|
+
context.report(chartColorsRule, {
|
|
2089
|
+
path,
|
|
2090
|
+
slide,
|
|
2091
|
+
message: `Chart colour ${index + 1} (${JSON.stringify(entry)}) cannot be resolved, so the palette colour of that position is used.`,
|
|
2092
|
+
help: 'Use a hex colour, a colour-scheme name such as "accent2", or the id of a colour variable the deck defines (var:<id>).'
|
|
2093
|
+
});
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
const contrast = surface ? colorContrast(color, surface) : void 0;
|
|
2097
|
+
if (contrast === void 0 || contrast >= 3) return;
|
|
2098
|
+
context.report(chartColorsRule, {
|
|
2099
|
+
path,
|
|
2100
|
+
slide,
|
|
2101
|
+
message: `Chart colour ${index + 1} (${color}) has ${contrast.toFixed(1)}:1 contrast against the chart panel (${surface}); it is drawn lighter or darker to reach 3:1.`,
|
|
2102
|
+
help: "Choose a colour that already has at least 3:1 contrast against the slide surface, so the chart shows the colour you wrote.",
|
|
2103
|
+
measured: { contrast: round(contrast), panel: surface }
|
|
2104
|
+
});
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
},
|
|
1953
2109
|
{
|
|
1954
2110
|
info: chartAltRule,
|
|
1955
2111
|
run(context) {
|
|
@@ -2196,6 +2352,18 @@ var unplacedRule = rule(
|
|
|
2196
2352
|
{ cost: "composition" }
|
|
2197
2353
|
);
|
|
2198
2354
|
var REGION_RULES = { "region-unknown": regionUnknownRule, "region-kind": regionKindRule, "region-full": regionFullRule, "layout-unplaced": unplacedRule };
|
|
2355
|
+
var MAX_FURNITURE_LINES = 2;
|
|
2356
|
+
var furnitureWrapsRule = rule(
|
|
2357
|
+
"furniture-wraps",
|
|
2358
|
+
"layout",
|
|
2359
|
+
"warning",
|
|
2360
|
+
`A header or footer zone wraps onto more than ${MAX_FURNITURE_LINES} lines.`,
|
|
2361
|
+
"Header and footer text repeats on every slide in a band at the slide edge. A zone that wraps onto three or more lines grows into the body, and long source lines read poorly at footer size. Footers carry short repeated text (a title, a date, a page number); sources belong in a caption or the speaker notes.",
|
|
2362
|
+
{
|
|
2363
|
+
cost: "composition",
|
|
2364
|
+
approximations: "Uses the lines core composed for the zone at the deck's slide size; without a host-supplied text measurement (ValidateOptions.fonts.textMeasurement) widths are core's portable estimates. A deck-level header or footer is reported once."
|
|
2365
|
+
}
|
|
2366
|
+
);
|
|
2199
2367
|
var layoutRules = [
|
|
2200
2368
|
{
|
|
2201
2369
|
info: overflowRule,
|
|
@@ -2208,9 +2376,9 @@ var layoutRules = [
|
|
|
2208
2376
|
for (const { diagnostic } of slide.layoutDiagnostics) {
|
|
2209
2377
|
const path = pointerOfDotted(diagnostic.path);
|
|
2210
2378
|
if (!path.startsWith("/slides/")) {
|
|
2211
|
-
const
|
|
2212
|
-
if (deckLevel.has(
|
|
2213
|
-
deckLevel.add(
|
|
2379
|
+
const key2 = `${diagnostic.code}|${path}|${diagnostic.message}`;
|
|
2380
|
+
if (deckLevel.has(key2)) continue;
|
|
2381
|
+
deckLevel.add(key2);
|
|
2214
2382
|
}
|
|
2215
2383
|
if (diagnostic.code === "text-overflow")
|
|
2216
2384
|
context.report(overflowRule, { path, slide, message: diagnostic.message, help: "Shorten the text, give it more space (fewer blocks, a different composition) or split the slide (paginate); raising the readable minimum makes it worse." });
|
|
@@ -2254,6 +2422,26 @@ var layoutRules = [
|
|
|
2254
2422
|
}
|
|
2255
2423
|
}
|
|
2256
2424
|
}
|
|
2425
|
+
},
|
|
2426
|
+
{
|
|
2427
|
+
info: furnitureWrapsRule,
|
|
2428
|
+
run(context) {
|
|
2429
|
+
const reported = /* @__PURE__ */ new Set();
|
|
2430
|
+
for (const slide of context.slides)
|
|
2431
|
+
for (const part of slide.composition?.furniture?.parts ?? []) {
|
|
2432
|
+
if (part.type !== "text" || part.fit.lines.length <= MAX_FURNITURE_LINES) continue;
|
|
2433
|
+
const path = pointerOfDotted(part.sourcePath ?? part.path);
|
|
2434
|
+
if (reported.has(path)) continue;
|
|
2435
|
+
reported.add(path);
|
|
2436
|
+
context.report(furnitureWrapsRule, {
|
|
2437
|
+
path,
|
|
2438
|
+
slide,
|
|
2439
|
+
message: `The ${part.kind} ${part.zone} text wraps onto ${part.fit.lines.length} lines (at most ${MAX_FURNITURE_LINES}).`,
|
|
2440
|
+
help: "Shorten the text, or move sources and notes to a caption or the speaker notes; keep the footer for short repeated text.",
|
|
2441
|
+
measured: { lines: part.fit.lines.length, maximumLines: MAX_FURNITURE_LINES }
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2257
2445
|
}
|
|
2258
2446
|
];
|
|
2259
2447
|
var outsideRule = rule(
|
|
@@ -2648,7 +2836,7 @@ function ruleInfo(id) {
|
|
|
2648
2836
|
}
|
|
2649
2837
|
|
|
2650
2838
|
// src/check-document.ts
|
|
2651
|
-
var own = (value,
|
|
2839
|
+
var own = (value, key2) => Object.hasOwn(value, key2);
|
|
2652
2840
|
var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2653
2841
|
var primitive = (value) => value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
2654
2842
|
var pointer3 = (parts2) => parts2.length ? "/" + parts2.map((part) => String(part).replaceAll("~", "~0").replaceAll("/", "~1")).join("/") : "";
|
|
@@ -2660,7 +2848,7 @@ function parts(path) {
|
|
|
2660
2848
|
}
|
|
2661
2849
|
function at(value, path) {
|
|
2662
2850
|
return parts(path).reduce(
|
|
2663
|
-
(current,
|
|
2851
|
+
(current, key2) => object(current) || Array.isArray(current) ? own(current, key2) ? current[key2] : void 0 : void 0,
|
|
2664
2852
|
value
|
|
2665
2853
|
);
|
|
2666
2854
|
}
|
|
@@ -2670,13 +2858,13 @@ function checkDocumentOptions(options) {
|
|
|
2670
2858
|
throw new TypeError("Validate contracts must be an array.");
|
|
2671
2859
|
for (const contract of options.contracts ?? []) {
|
|
2672
2860
|
if (!object(contract) || Object.keys(contract).some(
|
|
2673
|
-
(
|
|
2861
|
+
(key2) => ![
|
|
2674
2862
|
"path",
|
|
2675
2863
|
"allowedValues",
|
|
2676
2864
|
"severity",
|
|
2677
2865
|
"message",
|
|
2678
2866
|
"documentation"
|
|
2679
|
-
].includes(
|
|
2867
|
+
].includes(key2)
|
|
2680
2868
|
) || typeof contract.path !== "string" || !Array.isArray(contract.allowedValues) || !contract.allowedValues.every(primitive) || contract.severity !== void 0 && (typeof contract.severity !== "string" || !["error", "warning", "info"].includes(contract.severity)) || contract.message !== void 0 && typeof contract.message !== "string" || contract.documentation !== void 0 && typeof contract.documentation !== "string")
|
|
2681
2869
|
throw new TypeError(
|
|
2682
2870
|
"Invalid contract: provide a path and primitive allowedValues, with optional severity, message and documentation."
|
|
@@ -2754,13 +2942,13 @@ function score(schema, value) {
|
|
|
2754
2942
|
if (own(schema, "const")) return schema.const === value ? 100 : -1e4;
|
|
2755
2943
|
let result = 1;
|
|
2756
2944
|
if (object(value) && object(schema.properties)) {
|
|
2757
|
-
for (const [
|
|
2758
|
-
if (own(value,
|
|
2759
|
-
result += own(child, "const") ? child.const === value[
|
|
2945
|
+
for (const [key2, child] of Object.entries(schema.properties))
|
|
2946
|
+
if (own(value, key2) && object(child))
|
|
2947
|
+
result += own(child, "const") ? child.const === value[key2] ? 100 : -1e3 : 1;
|
|
2760
2948
|
}
|
|
2761
2949
|
if (Array.isArray(schema.required)) {
|
|
2762
|
-
for (const
|
|
2763
|
-
if (typeof
|
|
2950
|
+
for (const key2 of schema.required)
|
|
2951
|
+
if (typeof key2 === "string" && (!object(value) || !own(value, key2)))
|
|
2764
2952
|
result -= 2;
|
|
2765
2953
|
}
|
|
2766
2954
|
return result;
|
|
@@ -2782,7 +2970,7 @@ function active(cursor, value) {
|
|
|
2782
2970
|
const selected = candidates.sort((a, b) => score(b.schema, value) - score(a.schema, value))[0];
|
|
2783
2971
|
return selected ? { ...selected, baseSchemas: [...selected.baseSchemas ?? [], resolved] } : resolved;
|
|
2784
2972
|
}
|
|
2785
|
-
function childCursor(cursors,
|
|
2973
|
+
function childCursor(cursors, key2) {
|
|
2786
2974
|
for (const keyword of [
|
|
2787
2975
|
"properties",
|
|
2788
2976
|
"patternProperties",
|
|
@@ -2791,8 +2979,8 @@ function childCursor(cursors, key) {
|
|
|
2791
2979
|
for (const cursor of cursors) {
|
|
2792
2980
|
const declarations = cursor.schema[keyword];
|
|
2793
2981
|
if (!object(declarations)) continue;
|
|
2794
|
-
const name = keyword === "properties" ?
|
|
2795
|
-
(pattern) => new RegExp(pattern).test(
|
|
2982
|
+
const name = keyword === "properties" ? key2 : keyword === "patternProperties" ? Object.keys(declarations).find(
|
|
2983
|
+
(pattern) => new RegExp(pattern).test(key2)
|
|
2796
2984
|
) : void 0;
|
|
2797
2985
|
const schema = keyword === "additionalProperties" ? declarations : name === void 0 ? void 0 : declarations[name];
|
|
2798
2986
|
if (object(schema))
|
|
@@ -2851,13 +3039,13 @@ function fields(document) {
|
|
|
2851
3039
|
});
|
|
2852
3040
|
});
|
|
2853
3041
|
else if (object(entry.value))
|
|
2854
|
-
for (const [
|
|
2855
|
-
if (entry.path.length === 0 &&
|
|
2856
|
-
const cursor = childCursor(cursors,
|
|
3042
|
+
for (const [key2, value] of Object.entries(entry.value)) {
|
|
3043
|
+
if (entry.path.length === 0 && key2 === "catalogs") continue;
|
|
3044
|
+
const cursor = childCursor(cursors, key2);
|
|
2857
3045
|
if (!cursor) continue;
|
|
2858
3046
|
stack.push({
|
|
2859
3047
|
value,
|
|
2860
|
-
path: [...entry.path,
|
|
3048
|
+
path: [...entry.path, key2],
|
|
2861
3049
|
cursor,
|
|
2862
3050
|
ancestors
|
|
2863
3051
|
});
|
|
@@ -3003,8 +3191,8 @@ function variableFindings(document, engine, schemaValid) {
|
|
|
3003
3191
|
})
|
|
3004
3192
|
);
|
|
3005
3193
|
if (engine.template !== void 0 || !schemaValid) return findings;
|
|
3006
|
-
const walk = (value, path,
|
|
3007
|
-
if (
|
|
3194
|
+
const walk = (value, path, depth2) => {
|
|
3195
|
+
if (depth2 > 64) return;
|
|
3008
3196
|
if (typeof value === "string") {
|
|
3009
3197
|
if (value.startsWith("data:") || !value.includes("{{")) return;
|
|
3010
3198
|
for (const match of value.matchAll(VARIABLE_TOKEN))
|
|
@@ -3021,14 +3209,14 @@ function variableFindings(document, engine, schemaValid) {
|
|
|
3021
3209
|
}
|
|
3022
3210
|
if (Array.isArray(value)) {
|
|
3023
3211
|
value.forEach((entry, index) => {
|
|
3024
|
-
walk(entry, [...path, String(index)],
|
|
3212
|
+
walk(entry, [...path, String(index)], depth2 + 1);
|
|
3025
3213
|
});
|
|
3026
3214
|
return;
|
|
3027
3215
|
}
|
|
3028
3216
|
if (value && typeof value === "object")
|
|
3029
|
-
for (const [
|
|
3030
|
-
if (path.length === 0 && ["variables", "extensions", "catalogs", "assets"].includes(
|
|
3031
|
-
walk(entry, [...path,
|
|
3217
|
+
for (const [key2, entry] of Object.entries(value)) {
|
|
3218
|
+
if (path.length === 0 && ["variables", "extensions", "catalogs", "assets"].includes(key2)) continue;
|
|
3219
|
+
walk(entry, [...path, key2], depth2 + 1);
|
|
3032
3220
|
}
|
|
3033
3221
|
};
|
|
3034
3222
|
walk(document, [], 0);
|
|
@@ -3223,7 +3411,7 @@ function contractFindings(document, contracts) {
|
|
|
3223
3411
|
};
|
|
3224
3412
|
const message = (contract.message ?? "This value is outside the allowed values for {{path}}.").replace(
|
|
3225
3413
|
/\{\{(path|value|allowed|file)\}\}/g,
|
|
3226
|
-
(_,
|
|
3414
|
+
(_, key2) => values[key2] ?? ""
|
|
3227
3415
|
);
|
|
3228
3416
|
findings.push(
|
|
3229
3417
|
finding("opf/contract", {
|
|
@@ -3241,9 +3429,9 @@ function contractFindings(document, contracts) {
|
|
|
3241
3429
|
})
|
|
3242
3430
|
);
|
|
3243
3431
|
} else if (object(entry.value) || Array.isArray(entry.value)) {
|
|
3244
|
-
const
|
|
3432
|
+
const key2 = pattern[entry.depth];
|
|
3245
3433
|
for (const [name, value] of Object.entries(entry.value))
|
|
3246
|
-
if (
|
|
3434
|
+
if (key2 === "*" || key2 === name)
|
|
3247
3435
|
stack.push({
|
|
3248
3436
|
value,
|
|
3249
3437
|
path: [...entry.path, name],
|
|
@@ -3316,18 +3504,18 @@ function parseSource(source) {
|
|
|
3316
3504
|
if (node.type === "object") {
|
|
3317
3505
|
const keys = /* @__PURE__ */ new Set();
|
|
3318
3506
|
for (const property of node.children ?? []) {
|
|
3319
|
-
const
|
|
3320
|
-
if (!
|
|
3321
|
-
if (keys.has(
|
|
3507
|
+
const key2 = property.children?.[0];
|
|
3508
|
+
if (!key2) continue;
|
|
3509
|
+
if (keys.has(key2.value))
|
|
3322
3510
|
duplicates.push(
|
|
3323
3511
|
finding("opf/duplicate-key", {
|
|
3324
|
-
path: pointer3(getNodePath(
|
|
3325
|
-
message: `Duplicate JSON property ${JSON.stringify(
|
|
3512
|
+
path: pointer3(getNodePath(key2)),
|
|
3513
|
+
message: `Duplicate JSON property ${JSON.stringify(key2.value)}.`,
|
|
3326
3514
|
help: "Resolve the duplicate explicitly, preserving the intended content from both occurrences. JSON.parse would otherwise hide an earlier value.",
|
|
3327
|
-
location: location(
|
|
3515
|
+
location: location(key2.offset, key2.length)
|
|
3328
3516
|
})
|
|
3329
3517
|
);
|
|
3330
|
-
keys.add(
|
|
3518
|
+
keys.add(key2.value);
|
|
3331
3519
|
}
|
|
3332
3520
|
}
|
|
3333
3521
|
stack.push(...node.children ?? []);
|
|
@@ -3368,7 +3556,7 @@ function scriptProfile(presentation, slideIndex, options, language) {
|
|
|
3368
3556
|
}
|
|
3369
3557
|
const slots = (role) => {
|
|
3370
3558
|
const latin = options.fontFamilies[role];
|
|
3371
|
-
const slot = (
|
|
3559
|
+
const slot = (key2) => !resolved || resolved.sources[key2] === "latin" || resolved.sources[key2] === "schemeFamily" ? latin : resolved[role][key2] ?? latin;
|
|
3372
3560
|
return { latin, eastAsian: slot("eastAsian"), complexScript: slot("complexScript") };
|
|
3373
3561
|
};
|
|
3374
3562
|
return {
|
|
@@ -3588,7 +3776,7 @@ function names(list, what) {
|
|
|
3588
3776
|
}
|
|
3589
3777
|
function resolveOptions(options) {
|
|
3590
3778
|
if (!isObject(options)) throw new TypeError("Validate options must be an object.");
|
|
3591
|
-
for (const
|
|
3779
|
+
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(", ")}.`);
|
|
3592
3780
|
checkDocumentOptions(options);
|
|
3593
3781
|
const only = names(options.only, "only");
|
|
3594
3782
|
const ignore = names(options.ignore, "ignore");
|
|
@@ -3617,10 +3805,10 @@ function resolveOptions(options) {
|
|
|
3617
3805
|
const thresholds = { ...DEFAULT_VALIDATION_THRESHOLDS };
|
|
3618
3806
|
if (options.thresholds !== void 0) {
|
|
3619
3807
|
if (!isObject(options.thresholds)) throw new TypeError("Validate thresholds must be an object.");
|
|
3620
|
-
for (const [
|
|
3621
|
-
if (!(
|
|
3622
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new TypeError(`Validate threshold ${
|
|
3623
|
-
thresholds[
|
|
3808
|
+
for (const [key2, value] of Object.entries(options.thresholds)) {
|
|
3809
|
+
if (!(key2 in DEFAULT_VALIDATION_THRESHOLDS)) throw new TypeError(`Unknown validate threshold ${JSON.stringify(key2)}. Thresholds: ${Object.keys(DEFAULT_VALIDATION_THRESHOLDS).join(", ")}.`);
|
|
3810
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new TypeError(`Validate threshold ${key2} must be a positive number.`);
|
|
3811
|
+
thresholds[key2] = value;
|
|
3624
3812
|
}
|
|
3625
3813
|
}
|
|
3626
3814
|
if (options.fonts !== void 0) {
|
|
@@ -3644,7 +3832,7 @@ function resolveOptions(options) {
|
|
|
3644
3832
|
},
|
|
3645
3833
|
ignored: (info, path) => ignorePaths.some((entry) => entry.match(info) && entry.path.every((part, index) => path[index] === part)),
|
|
3646
3834
|
thresholds,
|
|
3647
|
-
chartPalette: options.chartPalette
|
|
3835
|
+
chartPalette: options.chartPalette
|
|
3648
3836
|
};
|
|
3649
3837
|
}
|
|
3650
3838
|
var order = new Map(validationRules.map((info, index) => [info.id, index]));
|
|
@@ -3824,4 +4012,4 @@ function assertValidCatalogRecord(kind, value) {
|
|
|
3824
4012
|
if (!report2.valid) throw new OPFValidationError(report2);
|
|
3825
4013
|
}
|
|
3826
4014
|
|
|
3827
|
-
export { CATALOG_SCHEMA,
|
|
4015
|
+
export { CATALOG_SCHEMA, DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, catalogDisplayKinds, catalogSchemaNames, findValidationRule, parseSource, resolveSlideContext, resolveSlideScriptMeasurement, validate, validateCatalogRecord, validationCategories, validationRules };
|