@openpresentation/opf 0.12.1 → 0.13.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/README.md +1 -1
- package/dist/audit.d.ts +9 -130
- package/dist/audit.js +5 -5
- package/dist/{catalogs-BnI7JKcn.d.ts → catalogs-ywCamd8C.d.ts} +1 -1
- package/dist/catalogs.d.ts +2 -2
- package/dist/chart-data-Cdr6rRVV.d.ts +177 -0
- package/dist/{chunk-6LZK2IZB.js → chunk-7N4VMAT4.js} +11 -7
- package/dist/{chunk-4LAM3N6S.js → chunk-7QBCWNWL.js} +424 -18
- package/dist/{chunk-SOSOBKQ2.js → chunk-ATGDDJPX.js} +4 -5
- package/dist/{chunk-V2537ENL.js → chunk-II5EYKZU.js} +9 -7
- package/dist/{chunk-MWPMJE6P.js → chunk-INU7ZNSB.js} +50 -2
- package/dist/{chunk-RUC7L5DJ.js → chunk-IS2CPNVJ.js} +1 -1
- package/dist/{chunk-JKVKYVWL.js → chunk-IYCJUKWD.js} +90 -471
- package/dist/{chunk-HOBSL7AJ.js → chunk-MPM7NRG7.js} +2396 -1408
- package/dist/{chunk-AUM5MXBR.js → chunk-RAYB46AB.js} +8 -6
- package/dist/{composition-DWbCiMwF.d.ts → composition-D7we69Tr.d.ts} +7 -0
- package/dist/composition.d.ts +2 -1
- package/dist/composition.js +1 -1
- package/dist/convert.d.ts +4 -1
- package/dist/convert.js +33 -16
- package/dist/data.d.ts +2 -1
- package/dist/data.js +3 -1
- package/dist/diff.d.ts +1 -1
- package/dist/diff.js +8 -7
- package/dist/docs.js +32 -26
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +102 -57
- package/dist/format.js +1 -1
- package/dist/index.d.ts +13 -10
- package/dist/index.js +8 -8
- package/dist/lint-B7azdiuE.d.ts +197 -0
- package/dist/lint.d.ts +7 -70
- package/dist/lint.js +4 -4
- package/dist/markdown.d.ts +7 -5
- package/dist/markdown.js +5 -5
- package/dist/pagination.d.ts +2 -1
- package/dist/pagination.js +4 -4
- package/dist/patch.js +4 -4
- package/dist/{presentation-DcF-MZcz.d.ts → presentation-mG-IYErQ.d.ts} +187 -14
- package/dist/repo-readme.js +1 -1
- package/dist/{schemas-BSjlG8-R.d.ts → schemas-BbPonsTz.d.ts} +18 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/catalogs/manifest.json +3 -3
- package/dist/spec/schemas/opf.schema.json +238 -20
- package/dist/types.d.ts +4 -4
- package/dist/{validator-DfG9yxba.d.ts → validator-CIylxoIm.d.ts} +3 -3
- package/dist/validator.d.ts +4 -4
- package/dist/validator.js +3 -3
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { promotedRegionKeys, isRecord, collectCitations,
|
|
1
|
+
import { promotedRegionKeys, VARIABLE_KINDS, isRecord, collectCitations, hasContentVariables, isTemplate, instantiateForValidation, MAX_COMPOSITION_DEPTH, visitContentPayloads, datasetDiagnostics, pathFor, tableGrid, numberingFindings, walkCitationRuns, resolveChartData, resolveTableData, bareIdPattern, catalogIds, deprecatedCatalogIds, resolveChartOptions, chartOptionTarget } from './chunk-MPM7NRG7.js';
|
|
2
2
|
import { catalogSchemaNames } from './chunk-E34DJ5LL.js';
|
|
3
|
-
import { schemas, schemaEntries } from './chunk-
|
|
3
|
+
import { schemas, schemaEntries } from './chunk-7QBCWNWL.js';
|
|
4
4
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
5
5
|
import addFormats from 'ajv-formats';
|
|
6
6
|
|
|
@@ -110,453 +110,6 @@ function validationDefinition(issue2) {
|
|
|
110
110
|
return issueLocations.get(issue2);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
// src/variables.ts
|
|
114
|
-
var VARIABLE_KINDS = ["color", "text", "number", "date", "image", "url", "list"];
|
|
115
|
-
var DEFAULT_VARIABLE_DATE_FORMAT = "MMMM d, yyyy";
|
|
116
|
-
var OPFVariableError = class extends Error {
|
|
117
|
-
diagnostics;
|
|
118
|
-
constructor(diagnostics) {
|
|
119
|
-
const first = diagnostics.find((entry) => entry.severity === "error") ?? diagnostics[0];
|
|
120
|
-
super(first ? `OPF variables: ${first.message}` : "OPF variables failed");
|
|
121
|
-
this.name = "OPFVariableError";
|
|
122
|
-
this.diagnostics = diagnostics;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
var idPattern = /^[a-z][a-z0-9-]*$/;
|
|
126
|
-
var referencePattern = /^var:([a-z][a-z0-9-]*)$/;
|
|
127
|
-
var hexPattern = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
128
|
-
var urlPattern = /^(?:https?:\/\/|mailto:|tel:)\S+$/;
|
|
129
|
-
var datePrefixPattern = /^(\d{4}-\d{2}-\d{2})(?:[T ].*)?$/;
|
|
130
|
-
var decimalPattern = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
131
|
-
var tokenPattern = /\\\{\{|\{\{\s*([a-z][a-z0-9-]*)\s*(?:\|([^{}]*))?\}\}/g;
|
|
132
|
-
var SAMPLES = {
|
|
133
|
-
color: "#000000",
|
|
134
|
-
text: "Sample text",
|
|
135
|
-
number: 0,
|
|
136
|
-
date: "2000-01-01",
|
|
137
|
-
image: "https://example.invalid/sample.png",
|
|
138
|
-
url: "https://example.invalid/",
|
|
139
|
-
list: ["Sample item"]
|
|
140
|
-
};
|
|
141
|
-
var hasOwn = (value, key) => Object.hasOwn(value, key);
|
|
142
|
-
var clone = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
|
|
143
|
-
function isTemplate(presentation) {
|
|
144
|
-
return isRecord(presentation) && presentation.template === true;
|
|
145
|
-
}
|
|
146
|
-
function isKind(value) {
|
|
147
|
-
return typeof value === "string" && VARIABLE_KINDS.includes(value);
|
|
148
|
-
}
|
|
149
|
-
function coerceVariableValue(kind, raw) {
|
|
150
|
-
const fail = (message) => ({ ok: false, message });
|
|
151
|
-
switch (kind) {
|
|
152
|
-
case "color":
|
|
153
|
-
return typeof raw === "string" && hexPattern.test(raw.trim()) ? { ok: true, value: raw.trim() } : fail("expected a hex color such as #0F4C81");
|
|
154
|
-
case "text": {
|
|
155
|
-
if (typeof raw === "string") return { ok: true, value: raw };
|
|
156
|
-
if (typeof raw === "number" && Number.isFinite(raw)) return { ok: true, value: String(raw) };
|
|
157
|
-
if (typeof raw === "boolean") return { ok: true, value: String(raw) };
|
|
158
|
-
if (Array.isArray(raw) && raw.every((run) => typeof run === "string" || isRecord(run) && typeof run.text === "string")) return { ok: true, value: clone(raw) };
|
|
159
|
-
return fail("expected text: a string or an array of text runs");
|
|
160
|
-
}
|
|
161
|
-
case "number": {
|
|
162
|
-
if (typeof raw === "number" && Number.isFinite(raw)) return { ok: true, value: raw };
|
|
163
|
-
if (typeof raw === "string" && decimalPattern.test(raw.trim())) {
|
|
164
|
-
const value = Number(raw.trim());
|
|
165
|
-
if (Number.isFinite(value)) return { ok: true, value };
|
|
166
|
-
}
|
|
167
|
-
return fail("expected a finite number");
|
|
168
|
-
}
|
|
169
|
-
case "date": {
|
|
170
|
-
if (typeof raw === "string") {
|
|
171
|
-
const match = datePrefixPattern.exec(raw.trim());
|
|
172
|
-
if (match?.[1] && parseIsoDate(match[1])) return { ok: true, value: match[1] };
|
|
173
|
-
}
|
|
174
|
-
return fail("expected an ISO calendar date (YYYY-MM-DD)");
|
|
175
|
-
}
|
|
176
|
-
case "image": {
|
|
177
|
-
if (typeof raw === "string" && raw.trim()) return { ok: true, value: raw.trim() };
|
|
178
|
-
if (isRecord(raw) && typeof raw.src === "string" && raw.src.trim()) return { ok: true, value: clone(raw) };
|
|
179
|
-
return fail("expected an image source: a string or an object with src");
|
|
180
|
-
}
|
|
181
|
-
case "url":
|
|
182
|
-
return typeof raw === "string" && urlPattern.test(raw.trim()) ? { ok: true, value: raw.trim() } : fail("expected an http, https, mailto or tel link");
|
|
183
|
-
case "list": {
|
|
184
|
-
if (typeof raw === "string") {
|
|
185
|
-
const entries = raw.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
|
|
186
|
-
return { ok: true, value: entries };
|
|
187
|
-
}
|
|
188
|
-
if (Array.isArray(raw) && raw.every((entry) => typeof entry === "string" || typeof entry === "number" && Number.isFinite(entry))) {
|
|
189
|
-
return { ok: true, value: raw.map(String) };
|
|
190
|
-
}
|
|
191
|
-
return fail("expected a list of strings");
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function isProvided(kind, raw) {
|
|
196
|
-
if (raw === void 0 || raw === null) return false;
|
|
197
|
-
if (typeof raw === "string" && raw.trim() === "" && kind !== "text") return false;
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
function variableDeclarations(presentation) {
|
|
201
|
-
if (!isRecord(presentation) || !isRecord(presentation.variables)) return [];
|
|
202
|
-
const out = [];
|
|
203
|
-
for (const [id, entry] of Object.entries(presentation.variables)) {
|
|
204
|
-
if (!idPattern.test(id)) continue;
|
|
205
|
-
const path = pathFor("/variables", id);
|
|
206
|
-
if (typeof entry === "string") {
|
|
207
|
-
if (hexPattern.test(entry)) out.push({ id, kind: "color", path, required: true, value: entry });
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
if (!isRecord(entry) || !isKind(entry.type)) continue;
|
|
211
|
-
const kind = entry.type;
|
|
212
|
-
const declaration = { id, kind, path, required: entry.required !== false };
|
|
213
|
-
const value = entry.value !== void 0 ? coerceVariableValue(kind, entry.value) : void 0;
|
|
214
|
-
if (value?.ok) declaration.value = value.value;
|
|
215
|
-
const example = entry.example !== void 0 ? coerceVariableValue(kind, entry.example) : void 0;
|
|
216
|
-
if (example?.ok) declaration.example = example.value;
|
|
217
|
-
if (typeof entry.format === "string") declaration.format = entry.format;
|
|
218
|
-
if (typeof entry.label === "string") declaration.label = entry.label;
|
|
219
|
-
if (typeof entry.description === "string") declaration.description = entry.description;
|
|
220
|
-
out.push(declaration);
|
|
221
|
-
}
|
|
222
|
-
return out;
|
|
223
|
-
}
|
|
224
|
-
function hasContentVariables(presentation) {
|
|
225
|
-
return isTemplate(presentation) || variableDeclarations(presentation).some((declaration) => declaration.kind !== "color");
|
|
226
|
-
}
|
|
227
|
-
var POSITIVE_ZERO = "0";
|
|
228
|
-
function formatVariableNumber(value, pattern) {
|
|
229
|
-
if (!Number.isFinite(value)) return { error: `'${String(value)}' is not a finite number.` };
|
|
230
|
-
if (pattern === void 0 || pattern === "") return { text: String(value) };
|
|
231
|
-
const match = /^([^#0,.]*)((?:[#0,]*)(?:\.[#0]+)?)([\s\S]*)$/.exec(pattern);
|
|
232
|
-
const numeric = match?.[2] ?? "";
|
|
233
|
-
if (!match || !/[#0]/.test(numeric)) return { error: `Number format '${pattern}' needs a numeric part of '#' and '0' characters.` };
|
|
234
|
-
const prefix = match[1] ?? "";
|
|
235
|
-
const suffix = match[3] ?? "";
|
|
236
|
-
const [integerPattern = "", decimalPattern2 = ""] = numeric.split(".");
|
|
237
|
-
const grouping = integerPattern.includes(",");
|
|
238
|
-
const minInteger = [...integerPattern].filter((char) => char === "0").length;
|
|
239
|
-
const minDecimals = [...decimalPattern2].filter((char) => char === "0").length;
|
|
240
|
-
const maxDecimals = decimalPattern2.length;
|
|
241
|
-
const scaled = prefix.includes("%") || suffix.includes("%") ? value * 100 : value;
|
|
242
|
-
const absolute = Math.abs(scaled);
|
|
243
|
-
let fixed;
|
|
244
|
-
const shifted = Number(`${absolute}e${maxDecimals}`);
|
|
245
|
-
if (absolute >= 1e21) fixed = `${BigInt(absolute).toString()}${maxDecimals ? `.${"0".repeat(maxDecimals)}` : ""}`;
|
|
246
|
-
else if (/e/i.test(String(absolute)) || !Number.isSafeInteger(Math.round(shifted))) fixed = absolute.toFixed(maxDecimals);
|
|
247
|
-
else {
|
|
248
|
-
const digits = String(Math.round(shifted)).padStart(maxDecimals + 1, "0");
|
|
249
|
-
fixed = maxDecimals ? `${digits.slice(0, -maxDecimals)}.${digits.slice(-maxDecimals)}` : digits;
|
|
250
|
-
}
|
|
251
|
-
let [integer = POSITIVE_ZERO, decimals = ""] = fixed.split(".");
|
|
252
|
-
while (decimals.length > minDecimals && decimals.endsWith("0")) decimals = decimals.slice(0, -1);
|
|
253
|
-
integer = integer.replace(/^0+(?=\d)/, "");
|
|
254
|
-
if (integer === POSITIVE_ZERO && minInteger === 0 && decimals) integer = "";
|
|
255
|
-
integer = integer.padStart(minInteger, "0");
|
|
256
|
-
if (grouping) integer = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
257
|
-
const body = `${integer}${decimals ? `.${decimals}` : ""}` || POSITIVE_ZERO;
|
|
258
|
-
const negative = scaled < 0 && /[1-9]/.test(body);
|
|
259
|
-
return { text: `${negative ? "-" : ""}${prefix}${body}${suffix}` };
|
|
260
|
-
}
|
|
261
|
-
function diag(context, entry, dedupeKey) {
|
|
262
|
-
if (dedupeKey) {
|
|
263
|
-
if (context.seen.has(dedupeKey)) return;
|
|
264
|
-
context.seen.add(dedupeKey);
|
|
265
|
-
}
|
|
266
|
-
context.diagnostics.push(entry);
|
|
267
|
-
}
|
|
268
|
-
function sampleValue(declaration) {
|
|
269
|
-
return declaration.example !== void 0 ? declaration.example : declaration.value !== void 0 ? declaration.value : SAMPLES[declaration.kind];
|
|
270
|
-
}
|
|
271
|
-
function plainText(value) {
|
|
272
|
-
if (typeof value === "string") return value;
|
|
273
|
-
if (Array.isArray(value)) return value.map((run) => typeof run === "string" ? run : isRecord(run) && typeof run.text === "string" ? run.text : "").join("");
|
|
274
|
-
return "";
|
|
275
|
-
}
|
|
276
|
-
function inlineText(effective, format, path, context) {
|
|
277
|
-
const { declaration } = effective;
|
|
278
|
-
const value = effective.status === "unfilled" ? sampleValue(declaration) : effective.value;
|
|
279
|
-
switch (declaration.kind) {
|
|
280
|
-
case "color":
|
|
281
|
-
case "url":
|
|
282
|
-
return String(value);
|
|
283
|
-
case "text":
|
|
284
|
-
if (Array.isArray(value)) {
|
|
285
|
-
diag(context, { code: "variable-rich-flattened", severity: "info", path, id: declaration.id, message: `Rich text variable '${declaration.id}' is inserted as plain text inside a string; reference it whole as 'var:${declaration.id}' to keep its formatting.` }, `flat:${declaration.id}:${path}`);
|
|
286
|
-
}
|
|
287
|
-
return plainText(value);
|
|
288
|
-
case "number": {
|
|
289
|
-
const pattern = (format ?? declaration.format)?.trim();
|
|
290
|
-
const formatted = formatVariableNumber(value, pattern === "" ? void 0 : pattern);
|
|
291
|
-
if ("error" in formatted) {
|
|
292
|
-
diag(context, { code: "variable-format", severity: "error", path, id: declaration.id, message: formatted.error }, `fmt:${declaration.id}:${format ?? ""}`);
|
|
293
|
-
return String(value);
|
|
294
|
-
}
|
|
295
|
-
return formatted.text;
|
|
296
|
-
}
|
|
297
|
-
case "date": {
|
|
298
|
-
const pattern = (format ?? declaration.format)?.trim() || DEFAULT_VARIABLE_DATE_FORMAT;
|
|
299
|
-
const formatted = formatFurnitureDate(String(value), pattern);
|
|
300
|
-
if ("error" in formatted) {
|
|
301
|
-
diag(context, { code: "variable-format", severity: "error", path, id: declaration.id, message: formatted.error }, `fmt:${declaration.id}:${format ?? ""}`);
|
|
302
|
-
return String(value);
|
|
303
|
-
}
|
|
304
|
-
return formatted.text;
|
|
305
|
-
}
|
|
306
|
-
case "image":
|
|
307
|
-
return isRecord(value) ? String(value.src) : String(value);
|
|
308
|
-
case "list":
|
|
309
|
-
return value.join(format ?? ", ");
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
function interpolate(text, path, context) {
|
|
313
|
-
let changed = false;
|
|
314
|
-
const value = text.replace(tokenPattern, (match, id, format) => {
|
|
315
|
-
if (id === void 0) {
|
|
316
|
-
if (context.unescape) {
|
|
317
|
-
changed = true;
|
|
318
|
-
return "{{";
|
|
319
|
-
}
|
|
320
|
-
return match;
|
|
321
|
-
}
|
|
322
|
-
context.uses.push({ id, path, form: "token" });
|
|
323
|
-
const effective = context.effective.get(id);
|
|
324
|
-
if (!effective) {
|
|
325
|
-
diag(context, { code: "variable-unknown", severity: "warning", path, id, message: `'{{${id}}}' names no declared variable; declare '${id}' in the top-level variables map, or write '\\{{' for literal braces.` }, `unk:${id}:${path}`);
|
|
326
|
-
return match;
|
|
327
|
-
}
|
|
328
|
-
if (effective.status === "unfilled" && !context.shape) {
|
|
329
|
-
if (!effective.declaration.required) {
|
|
330
|
-
changed = true;
|
|
331
|
-
return "";
|
|
332
|
-
}
|
|
333
|
-
return match;
|
|
334
|
-
}
|
|
335
|
-
const result = inlineText(effective, effective.declaration.kind === "list" ? format : format?.trim(), path, context);
|
|
336
|
-
if (result === void 0) return match;
|
|
337
|
-
changed = true;
|
|
338
|
-
return result;
|
|
339
|
-
});
|
|
340
|
-
return { value, changed };
|
|
341
|
-
}
|
|
342
|
-
var OMIT = /* @__PURE__ */ Symbol("omit");
|
|
343
|
-
var Splice = class {
|
|
344
|
-
constructor(values) {
|
|
345
|
-
this.values = values;
|
|
346
|
-
}
|
|
347
|
-
values;
|
|
348
|
-
};
|
|
349
|
-
function reduceString(text, path, parentIsArray, context) {
|
|
350
|
-
const reference = referencePattern.exec(text);
|
|
351
|
-
if (reference) {
|
|
352
|
-
const id = reference[1];
|
|
353
|
-
const effective = context.effective.get(id);
|
|
354
|
-
if (effective && effective.declaration.kind !== "color") {
|
|
355
|
-
context.uses.push({ id, path, form: "reference" });
|
|
356
|
-
if (effective.status === "unfilled" && !context.shape) {
|
|
357
|
-
return effective.declaration.required ? { kind: "keep" } : { kind: "omit" };
|
|
358
|
-
}
|
|
359
|
-
const value = effective.status === "unfilled" ? sampleValue(effective.declaration) : effective.value;
|
|
360
|
-
if (effective.declaration.kind === "list" && parentIsArray) {
|
|
361
|
-
return context.shape ? { kind: "replace", value: value[0] ?? "" } : { kind: "splice", values: clone(value) };
|
|
362
|
-
}
|
|
363
|
-
return { kind: "replace", value: clone(value) };
|
|
364
|
-
}
|
|
365
|
-
if (effective) {
|
|
366
|
-
context.uses.push({ id, path, form: "reference" });
|
|
367
|
-
return { kind: "keep" };
|
|
368
|
-
}
|
|
369
|
-
return { kind: "keep" };
|
|
370
|
-
}
|
|
371
|
-
if (!text.includes("{{")) return { kind: "keep" };
|
|
372
|
-
const result = interpolate(text, path, context);
|
|
373
|
-
return result.changed ? { kind: "replace", value: result.value } : { kind: "keep" };
|
|
374
|
-
}
|
|
375
|
-
var skippedKeys = /* @__PURE__ */ new Set(["extensions"]);
|
|
376
|
-
var skippedRootKeys = /* @__PURE__ */ new Set(["variables", "catalogs", "$schema", "template"]);
|
|
377
|
-
function walk(value, path, parentIsArray, context) {
|
|
378
|
-
if (typeof value === "string") {
|
|
379
|
-
const reduced = reduceString(value, path, parentIsArray, context);
|
|
380
|
-
if (reduced.kind === "keep") return value;
|
|
381
|
-
if (reduced.kind === "omit") return OMIT;
|
|
382
|
-
if (reduced.kind === "splice") return new Splice(reduced.values);
|
|
383
|
-
return reduced.value;
|
|
384
|
-
}
|
|
385
|
-
if (Array.isArray(value)) {
|
|
386
|
-
let changed = false;
|
|
387
|
-
const out = [];
|
|
388
|
-
value.forEach((entry, index) => {
|
|
389
|
-
const next = walk(entry, `${path}/${index}`, true, context);
|
|
390
|
-
if (next === OMIT) {
|
|
391
|
-
changed = true;
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (next instanceof Splice) {
|
|
395
|
-
changed = true;
|
|
396
|
-
out.push(...next.values);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
if (next !== entry) changed = true;
|
|
400
|
-
out.push(next);
|
|
401
|
-
});
|
|
402
|
-
return changed ? out : value;
|
|
403
|
-
}
|
|
404
|
-
if (isRecord(value)) {
|
|
405
|
-
let changed = false;
|
|
406
|
-
const entries = [];
|
|
407
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
408
|
-
if (skippedKeys.has(key) || path === "" && skippedRootKeys.has(key)) {
|
|
409
|
-
entries.push([key, entry]);
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
const next = walk(entry, pathFor(path || "/", key), false, context);
|
|
413
|
-
if (next === OMIT) {
|
|
414
|
-
changed = true;
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
if (next !== entry) changed = true;
|
|
418
|
-
entries.push([key, next]);
|
|
419
|
-
}
|
|
420
|
-
return changed ? Object.fromEntries(entries) : value;
|
|
421
|
-
}
|
|
422
|
-
return value;
|
|
423
|
-
}
|
|
424
|
-
function plan(presentation, values, options) {
|
|
425
|
-
const declarations = variableDeclarations(presentation);
|
|
426
|
-
const template = options.template ?? isTemplate(presentation);
|
|
427
|
-
const lenient = template || options.partial === true;
|
|
428
|
-
const context = {
|
|
429
|
-
effective: /* @__PURE__ */ new Map(),
|
|
430
|
-
diagnostics: [],
|
|
431
|
-
uses: [],
|
|
432
|
-
unescape: false,
|
|
433
|
-
shape: options.shape === true,
|
|
434
|
-
content: hasContentVariables(presentation) || template,
|
|
435
|
-
seen: /* @__PURE__ */ new Set()
|
|
436
|
-
};
|
|
437
|
-
const unfilled = [];
|
|
438
|
-
const examplesUsed = [];
|
|
439
|
-
const providedColors = /* @__PURE__ */ new Map();
|
|
440
|
-
const declared = new Set(declarations.map((declaration) => declaration.id));
|
|
441
|
-
for (const key of Object.keys(values)) {
|
|
442
|
-
if (!declared.has(key)) {
|
|
443
|
-
context.diagnostics.push({ code: "variable-unknown-value", severity: "warning", path: "/variables", id: key, message: `A value was supplied for '${key}', which no variable declares.` });
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
for (const declaration of declarations) {
|
|
447
|
-
const raw = hasOwn(values, declaration.id) ? values[declaration.id] : void 0;
|
|
448
|
-
let effective;
|
|
449
|
-
if (isProvided(declaration.kind, raw)) {
|
|
450
|
-
const coerced = coerceVariableValue(declaration.kind, raw);
|
|
451
|
-
if (coerced.ok) {
|
|
452
|
-
effective = { declaration, status: "filled", value: coerced.value, provided: true };
|
|
453
|
-
if (declaration.kind === "color") providedColors.set(declaration.id, coerced.value);
|
|
454
|
-
} else {
|
|
455
|
-
context.diagnostics.push({ code: "variable-invalid-value", severity: "error", path: declaration.path, id: declaration.id, message: `Value for '${declaration.id}' (${declaration.kind}): ${coerced.message}.` });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
if (!effective && declaration.value !== void 0) effective = { declaration, status: "filled", value: declaration.value, provided: false };
|
|
459
|
-
if (!effective && options.examples && declaration.example !== void 0) {
|
|
460
|
-
effective = { declaration, status: "example", value: declaration.example, provided: false };
|
|
461
|
-
examplesUsed.push(declaration.id);
|
|
462
|
-
context.diagnostics.push({ code: "variable-example-used", severity: "info", path: declaration.path, id: declaration.id, message: `'${declaration.id}' is unfilled; its example is used.` });
|
|
463
|
-
if (declaration.kind === "color") providedColors.set(declaration.id, declaration.example);
|
|
464
|
-
}
|
|
465
|
-
if (!effective) {
|
|
466
|
-
effective = { declaration, status: "unfilled", provided: false };
|
|
467
|
-
if (declaration.required) {
|
|
468
|
-
unfilled.push(declaration.id);
|
|
469
|
-
context.diagnostics.push({
|
|
470
|
-
code: "variable-unfilled",
|
|
471
|
-
severity: lenient ? "info" : "error",
|
|
472
|
-
path: declaration.path,
|
|
473
|
-
id: declaration.id,
|
|
474
|
-
message: lenient ? `'${declaration.id}' (${declaration.kind}) is not filled yet.` : `Required variable '${declaration.id}' (${declaration.kind}) has no value. Provide one, or mark the document as a template ("template": true).`
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
context.effective.set(declaration.id, effective);
|
|
479
|
-
}
|
|
480
|
-
return { context, declarations, unfilled, examplesUsed, providedColors };
|
|
481
|
-
}
|
|
482
|
-
function rebuildVariables(presentation, built, keepAll) {
|
|
483
|
-
const source = presentation.variables;
|
|
484
|
-
if (!isRecord(source)) return void 0;
|
|
485
|
-
const out = [];
|
|
486
|
-
for (const [id, entry] of Object.entries(source)) {
|
|
487
|
-
const effective = built.context.effective.get(id);
|
|
488
|
-
if (!effective) {
|
|
489
|
-
out.push([id, entry]);
|
|
490
|
-
continue;
|
|
491
|
-
}
|
|
492
|
-
const { declaration } = effective;
|
|
493
|
-
if (declaration.kind !== "color") {
|
|
494
|
-
if (keepAll || effective.status === "unfilled" && declaration.required) out.push([id, entry]);
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
const color = built.providedColors.get(id);
|
|
498
|
-
if (color === void 0) {
|
|
499
|
-
out.push([id, entry]);
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
502
|
-
const base = isRecord(entry) ? entry : {};
|
|
503
|
-
const { example: _example, required: _required, label: _label, ...rest } = base;
|
|
504
|
-
out.push([id, { ...rest, type: "color", value: color }]);
|
|
505
|
-
}
|
|
506
|
-
return out.length ? Object.fromEntries(out) : void 0;
|
|
507
|
-
}
|
|
508
|
-
function finish(presentation, built, options) {
|
|
509
|
-
const { context } = built;
|
|
510
|
-
const complete = built.unfilled.length === 0;
|
|
511
|
-
context.unescape = complete && context.content && !context.shape;
|
|
512
|
-
const walked = context.content ? walk(presentation, "", false, context) : presentation;
|
|
513
|
-
const result = { ...walked };
|
|
514
|
-
const variables = rebuildVariables(presentation, built, context.shape);
|
|
515
|
-
if (variables) result.variables = variables;
|
|
516
|
-
else delete result.variables;
|
|
517
|
-
if (!context.shape && complete && hasOwn(result, "template")) delete result.template;
|
|
518
|
-
if (context.content) {
|
|
519
|
-
const used = new Set(context.uses.map((use) => use.id));
|
|
520
|
-
for (const declaration of built.declarations) {
|
|
521
|
-
if (declaration.kind !== "color" && !used.has(declaration.id)) {
|
|
522
|
-
context.diagnostics.push({ code: "variable-unused", severity: "warning", path: declaration.path, id: declaration.id, message: `Variable '${declaration.id}' is declared but never used; reference it as '{{${declaration.id}}}' or 'var:${declaration.id}'.` });
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
const diagnostics = context.diagnostics;
|
|
527
|
-
if (options.strict && diagnostics.some((entry) => entry.severity === "error")) throw new OPFVariableError(diagnostics);
|
|
528
|
-
return { presentation: result, diagnostics, unfilled: built.unfilled, examplesUsed: built.examplesUsed, complete };
|
|
529
|
-
}
|
|
530
|
-
function resolveVariables(presentation, values = {}, options = {}) {
|
|
531
|
-
if (!isRecord(presentation)) throw new TypeError("resolveVariables expects a presentation object.");
|
|
532
|
-
if (!isRecord(values)) throw new TypeError("resolveVariables values must be an object keyed by variable id.");
|
|
533
|
-
const built = plan(presentation, values, options);
|
|
534
|
-
if (!built.context.content && built.providedColors.size === 0 && built.context.diagnostics.length === 0) {
|
|
535
|
-
return { presentation, diagnostics: [], unfilled: built.unfilled, examplesUsed: [], complete: built.unfilled.length === 0 };
|
|
536
|
-
}
|
|
537
|
-
return finish(presentation, built, options);
|
|
538
|
-
}
|
|
539
|
-
function instantiateForValidation(presentation, values = {}, template) {
|
|
540
|
-
const options = { template, shape: true, partial: true };
|
|
541
|
-
return finish(presentation, plan(presentation, values, options), options);
|
|
542
|
-
}
|
|
543
|
-
function listVariables(presentation, values = {}) {
|
|
544
|
-
if (!isRecord(presentation)) return [];
|
|
545
|
-
const built = plan(presentation, isRecord(values) ? values : {}, { examples: false, partial: true });
|
|
546
|
-
built.context.shape = true;
|
|
547
|
-
built.context.content = true;
|
|
548
|
-
walk(presentation, "", false, built.context);
|
|
549
|
-
return built.declarations.map((declaration) => {
|
|
550
|
-
const effective = built.context.effective.get(declaration.id);
|
|
551
|
-
return {
|
|
552
|
-
...declaration,
|
|
553
|
-
...effective?.status === "filled" && effective.provided ? { value: effective.value } : {},
|
|
554
|
-
filled: effective?.status === "filled",
|
|
555
|
-
uses: built.context.uses.filter((use) => use.id === declaration.id)
|
|
556
|
-
};
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
|
|
560
113
|
// src/validator.ts
|
|
561
114
|
var OPFValidationError = class extends Error {
|
|
562
115
|
issues;
|
|
@@ -712,14 +265,14 @@ function semanticIssue(path, message, params = {}) {
|
|
|
712
265
|
params
|
|
713
266
|
};
|
|
714
267
|
}
|
|
715
|
-
function
|
|
268
|
+
function hasOwn(value, key) {
|
|
716
269
|
return Object.hasOwn(value, key);
|
|
717
270
|
}
|
|
718
271
|
function isEnUkTag(value) {
|
|
719
272
|
return typeof value === "string" && value.toLowerCase() === "en-uk";
|
|
720
273
|
}
|
|
721
274
|
function presentFields(value, fields) {
|
|
722
|
-
return fields.filter((field) =>
|
|
275
|
+
return fields.filter((field) => hasOwn(value, field));
|
|
723
276
|
}
|
|
724
277
|
function isContentKind(value) {
|
|
725
278
|
return typeof value === "string" && value in contentKindSpecs;
|
|
@@ -727,19 +280,19 @@ function isContentKind(value) {
|
|
|
727
280
|
function inferredKinds(value) {
|
|
728
281
|
const kinds = [];
|
|
729
282
|
if (presentFields(value, contentKindSpecs.text.fields).length > 0) kinds.push("text");
|
|
730
|
-
if (
|
|
731
|
-
if (
|
|
732
|
-
if (
|
|
733
|
-
if (
|
|
734
|
-
if (
|
|
735
|
-
if (
|
|
736
|
-
if (
|
|
737
|
-
if (
|
|
738
|
-
if (
|
|
283
|
+
if (hasOwn(value, "items")) kinds.push("list");
|
|
284
|
+
if (hasOwn(value, "image")) kinds.push("image");
|
|
285
|
+
if (hasOwn(value, "video")) kinds.push("video");
|
|
286
|
+
if (hasOwn(value, "chart")) kinds.push("chart");
|
|
287
|
+
if (hasOwn(value, "table")) kinds.push("table");
|
|
288
|
+
if (hasOwn(value, "code")) kinds.push("code");
|
|
289
|
+
if (hasOwn(value, "metric")) kinds.push("metric");
|
|
290
|
+
if (hasOwn(value, "quote")) kinds.push("quote");
|
|
291
|
+
if (hasOwn(value, "timeline")) kinds.push("timeline");
|
|
739
292
|
return kinds;
|
|
740
293
|
}
|
|
741
294
|
function isImplicitBlocksComposition(value, inferred, options) {
|
|
742
|
-
if (options.slideRoot !== true ||
|
|
295
|
+
if (options.slideRoot !== true || hasOwn(value, "blocks")) {
|
|
743
296
|
return false;
|
|
744
297
|
}
|
|
745
298
|
return inferred.length > 1;
|
|
@@ -748,10 +301,10 @@ function validateContentPayload(value, path, options = {}) {
|
|
|
748
301
|
const issues = [];
|
|
749
302
|
const explicitType = value.type;
|
|
750
303
|
const payloadFields = presentFields(value, rootPayloadFields);
|
|
751
|
-
const hasBlocks =
|
|
304
|
+
const hasBlocks = hasOwn(value, "blocks");
|
|
752
305
|
const captionIssue = (reason, params = {}) => semanticIssue(pathFor(path, "caption"), `caption is only valid on an image, chart, table or video payload; ${reason}`, { code: "caption-unsupported-payload", ...params });
|
|
753
306
|
if (hasBlocks || explicitType === "group") {
|
|
754
|
-
if (
|
|
307
|
+
if (hasOwn(value, "caption")) issues.push(captionIssue("a group cannot carry one"));
|
|
755
308
|
if (explicitType !== void 0 && explicitType !== "group") issues.push(semanticIssue(path, "a group must use type 'group' or omit type"));
|
|
756
309
|
const incompatible2 = payloadFields.filter((field) => field !== "blocks" && field !== "type");
|
|
757
310
|
if (incompatible2.length) issues.push(semanticIssue(path, "blocks cannot be mixed with leaf payload fields", { fields: incompatible2 }));
|
|
@@ -761,7 +314,7 @@ function validateContentPayload(value, path, options = {}) {
|
|
|
761
314
|
});
|
|
762
315
|
return issues;
|
|
763
316
|
}
|
|
764
|
-
if (
|
|
317
|
+
if (hasOwn(value, "composition") && !options.slideRoot) issues.push(semanticIssue(pathFor(path, "composition"), "composition is only valid on a group containing blocks"));
|
|
765
318
|
if (explicitType !== void 0 && !isContentKind(explicitType)) {
|
|
766
319
|
return issues;
|
|
767
320
|
}
|
|
@@ -777,7 +330,7 @@ function validateContentPayload(value, path, options = {}) {
|
|
|
777
330
|
}
|
|
778
331
|
if (!kind && inferred.length > 1) {
|
|
779
332
|
if (isImplicitBlocksComposition(value, inferred, options)) {
|
|
780
|
-
if (
|
|
333
|
+
if (hasOwn(value, "caption")) issues.push(captionIssue("a slide root with several payloads cannot carry one; put the caption on a block", { inferredTypes: inferred }));
|
|
781
334
|
return issues;
|
|
782
335
|
}
|
|
783
336
|
issues.push(semanticIssue(path, "content payload mixes fields from incompatible content types", {
|
|
@@ -790,13 +343,13 @@ function validateContentPayload(value, path, options = {}) {
|
|
|
790
343
|
return issues;
|
|
791
344
|
}
|
|
792
345
|
const spec = contentKindSpecs[resolvedKind];
|
|
793
|
-
if (
|
|
346
|
+
if (hasOwn(value, "caption") && !captionableKinds.has(resolvedKind)) issues.push(captionIssue(`this payload is '${resolvedKind}'`, { type: resolvedKind }));
|
|
794
347
|
const allowedFields = /* @__PURE__ */ new Set([
|
|
795
348
|
"type",
|
|
796
349
|
...spec.fields
|
|
797
350
|
]);
|
|
798
351
|
for (const required of spec.required) {
|
|
799
|
-
if (!
|
|
352
|
+
if (!hasOwn(value, required)) {
|
|
800
353
|
issues.push(semanticIssue(path, `content payload type '${resolvedKind}' requires '${required}'`, {
|
|
801
354
|
type: resolvedKind,
|
|
802
355
|
required
|
|
@@ -1007,6 +560,31 @@ function chartTypeWarnings(payload, path, context) {
|
|
|
1007
560
|
}
|
|
1008
561
|
return issues;
|
|
1009
562
|
}
|
|
563
|
+
function dataIssues(value) {
|
|
564
|
+
const out = { errors: [], warnings: [] };
|
|
565
|
+
if (!isRecord(value) || !Array.isArray(value.slides)) return out;
|
|
566
|
+
const seen = /* @__PURE__ */ new Set();
|
|
567
|
+
const add = (diagnostics) => {
|
|
568
|
+
for (const diagnostic of diagnostics) {
|
|
569
|
+
const key = `${diagnostic.code}|${diagnostic.path}`;
|
|
570
|
+
if (seen.has(key)) continue;
|
|
571
|
+
seen.add(key);
|
|
572
|
+
(diagnostic.severity === "error" ? out.errors : out.warnings).push(semanticIssue(diagnostic.path, diagnostic.message, { code: diagnostic.code }));
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
add(datasetDiagnostics(value));
|
|
576
|
+
const payload = (node, path) => {
|
|
577
|
+
if (isRecord(node.chart)) add(resolveChartData(node.chart, value, { path: pathFor(path, "chart") }).diagnostics);
|
|
578
|
+
if (isRecord(node.table)) add(resolveTableData(node.table, value, { path: pathFor(path, "table") }).diagnostics);
|
|
579
|
+
};
|
|
580
|
+
value.slides.forEach((slide, index) => {
|
|
581
|
+
if (!isRecord(slide)) return;
|
|
582
|
+
const slidePath = `/slides/${index}`;
|
|
583
|
+
payload(slide, slidePath);
|
|
584
|
+
visitContentPayloads(slide, slidePath, payload);
|
|
585
|
+
});
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
1010
588
|
var cellBorderEdges = ["top", "right", "bottom", "left"];
|
|
1011
589
|
var variableIdPattern = /^[a-z][a-z0-9-]*$/;
|
|
1012
590
|
var hexColorPattern = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
@@ -1040,7 +618,7 @@ function variableReferenceWarnings(value) {
|
|
|
1040
618
|
const colorRef = (entry, path) => {
|
|
1041
619
|
if (typeof entry !== "string" || !entry.startsWith("var:")) return;
|
|
1042
620
|
const id = entry.slice("var:".length);
|
|
1043
|
-
if (!variableIdPattern.test(id) ||
|
|
621
|
+
if (!variableIdPattern.test(id) || hasOwn(variables, id)) return;
|
|
1044
622
|
issues.push(semanticIssue(path, `unknown variable '${id}'; declare it in the top-level variables map`, { id }));
|
|
1045
623
|
};
|
|
1046
624
|
const runColor = (entry, path) => {
|
|
@@ -1206,7 +784,7 @@ function contentDepthIssues(value) {
|
|
|
1206
784
|
if (Array.isArray(slide.blocks)) slide.blocks.forEach((block, i) => {
|
|
1207
785
|
stack.push({ value: block, path: `/slides/${index}/blocks/${i}`, depth: 0, ancestors: [] });
|
|
1208
786
|
});
|
|
1209
|
-
for (const key of promotedRegionKeys) if (
|
|
787
|
+
for (const key of promotedRegionKeys) if (hasOwn(slide, key)) stack.push({ value: slide[key], path: `/slides/${index}/${key}`, depth: 0, ancestors: [] });
|
|
1210
788
|
});
|
|
1211
789
|
while (stack.length) {
|
|
1212
790
|
const entry = stack.pop();
|
|
@@ -1258,6 +836,43 @@ function typedVariableErrors(errors, value) {
|
|
|
1258
836
|
}
|
|
1259
837
|
return kept;
|
|
1260
838
|
}
|
|
839
|
+
function pointerValue(root, pointer2) {
|
|
840
|
+
let current = root;
|
|
841
|
+
for (const token of pointer2.split("/").slice(1)) {
|
|
842
|
+
const key = token.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
843
|
+
if (Array.isArray(current)) current = current[Number(key)];
|
|
844
|
+
else if (isRecord(current) && hasOwn(current, key)) current = current[key];
|
|
845
|
+
else return void 0;
|
|
846
|
+
}
|
|
847
|
+
return current;
|
|
848
|
+
}
|
|
849
|
+
var datasetTableMessage = (field) => `'${field}' is not allowed on a dataset-backed table: it takes its headers, rows and column formats from the dataset`;
|
|
850
|
+
function dataUnionErrors(errors, value) {
|
|
851
|
+
let kept = errors;
|
|
852
|
+
const schemaId = schemas.presentation.$id;
|
|
853
|
+
for (const union of errors) {
|
|
854
|
+
if (union.keyword !== "oneOf") continue;
|
|
855
|
+
const chartData = /\/chart\/data$/.test(union.instancePath) && /\/properties\/data\/oneOf$/.test(union.schemaPath);
|
|
856
|
+
const header = /\/table\/columns\/\d+$/.test(union.instancePath) && /\/properties\/columns\/items\/oneOf$/.test(union.schemaPath);
|
|
857
|
+
if (!chartData && !header) continue;
|
|
858
|
+
const target = pointerValue(value, union.instancePath);
|
|
859
|
+
if (!isRecord(target)) continue;
|
|
860
|
+
const branch = chartData ? hasOwn(target, "dataset") ? "DatasetRef" : hasOwn(target, "src") ? "ChartDataSource" : hasOwn(target, "rows") ? "ChartData" : void 0 : hasOwn(target, "value") ? "StyledTableCell" : hasOwn(target, "name") ? "DataColumn" : void 0;
|
|
861
|
+
const check = branch ? getAjv().getSchema(`${schemaId}#/$defs/${branch}`) : void 0;
|
|
862
|
+
if (!check || check(target) === true) continue;
|
|
863
|
+
const branchErrors = (check.errors ?? []).map((error) => ({ ...error, instancePath: `${union.instancePath}${error.instancePath}`, schemaPath: `#/$defs/${branch}${error.schemaPath.replace(/^#/, "")}` }));
|
|
864
|
+
const prefix = `${union.instancePath}/`;
|
|
865
|
+
kept = [...kept.filter((error) => error.instancePath !== union.instancePath && !error.instancePath.startsWith(prefix)), ...branchErrors];
|
|
866
|
+
}
|
|
867
|
+
const exclusions = kept.filter((error) => error.keyword === "false schema" && /\/table\/(rows|columns|fields)$/.test(error.instancePath) && /\/(then|else)\/properties\/(rows|columns|fields)\//.test(`${error.schemaPath}/`));
|
|
868
|
+
if (!exclusions.length) return kept;
|
|
869
|
+
const tables = new Set(exclusions.map((error) => error.instancePath.replace(/\/[^/]+$/, "")));
|
|
870
|
+
return kept.filter((error) => !(error.keyword === "if" && tables.has(error.instancePath))).map((error) => {
|
|
871
|
+
if (!exclusions.includes(error)) return error;
|
|
872
|
+
const field = error.instancePath.slice(error.instancePath.lastIndexOf("/") + 1);
|
|
873
|
+
return { ...error, message: field === "fields" ? "'fields' applies only to a dataset-backed table; add 'dataset' or remove 'fields'" : datasetTableMessage(field) };
|
|
874
|
+
});
|
|
875
|
+
}
|
|
1261
876
|
function validate(value, schemaOrKind = "presentation", options = {}) {
|
|
1262
877
|
const resolved = resolveValidator(schemaOrKind);
|
|
1263
878
|
if (resolved.schemaName === "presentation") {
|
|
@@ -1287,12 +902,16 @@ function validate(value, schemaOrKind = "presentation", options = {}) {
|
|
|
1287
902
|
}
|
|
1288
903
|
}
|
|
1289
904
|
const valid = resolved.validate(subject) === true;
|
|
1290
|
-
const
|
|
905
|
+
const ajvErrors = valid ? [] : typedVariableErrors(resolved.validate.errors ?? [], subject);
|
|
906
|
+
const errors = (resolved.schemaName === "presentation" ? dataUnionErrors(ajvErrors, subject) : ajvErrors).map(toIssue);
|
|
1291
907
|
const warnings = [];
|
|
1292
908
|
if (resolved.schemaName === "presentation") {
|
|
1293
909
|
errors.push(...validatePresentationSemantics(subject));
|
|
1294
910
|
errors.push(...variableIssues.errors);
|
|
911
|
+
const data = dataIssues(subject);
|
|
912
|
+
errors.push(...data.errors);
|
|
1295
913
|
warnings.push(...presentationReferenceWarnings(subject));
|
|
914
|
+
warnings.push(...data.warnings);
|
|
1296
915
|
warnings.push(...variableIssues.warnings);
|
|
1297
916
|
} else if (resolved.schemaName === "language") {
|
|
1298
917
|
errors.push(...validateLanguageSemantics(value));
|
|
@@ -1329,4 +948,4 @@ function assertValidCatalogRecord(kind, value) {
|
|
|
1329
948
|
assertValid(value, kind);
|
|
1330
949
|
}
|
|
1331
950
|
|
|
1332
|
-
export {
|
|
951
|
+
export { OPFValidationError, assertValid, assertValidCatalogRecord, assertValidPresentation, unusedReferenceWarnings, validate, validateCatalogRecord, validatePresentation, validationDefinition };
|