@memizy/oqse 0.1.5 → 1.0.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +90 -75
  3. package/dist/answers.cjs +1 -0
  4. package/dist/answers.js +78 -0
  5. package/dist/assets.cjs +1 -0
  6. package/dist/assets.js +7 -0
  7. package/dist/checkAnswer.cjs +1 -0
  8. package/dist/checkAnswer.js +166 -0
  9. package/dist/compatibility.cjs +1 -0
  10. package/dist/compatibility.js +32 -0
  11. package/dist/features.cjs +1 -0
  12. package/dist/features.js +58 -0
  13. package/dist/fileValidation.cjs +2 -0
  14. package/dist/fileValidation.js +230 -0
  15. package/dist/headerValidation.cjs +1 -0
  16. package/dist/headerValidation.js +26 -0
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.ts +10 -0
  19. package/dist/index.js +19 -1029
  20. package/dist/manifest.cjs +1 -0
  21. package/dist/manifest.js +75 -0
  22. package/dist/manifestValidation.cjs +1 -0
  23. package/dist/manifestValidation.js +69 -0
  24. package/dist/markdownSet.cjs +11 -0
  25. package/dist/markdownSet.js +232 -0
  26. package/dist/markdownUtils.cjs +5 -0
  27. package/dist/markdownUtils.js +47 -0
  28. package/dist/oqse.cjs +1 -0
  29. package/dist/oqse.js +108 -0
  30. package/dist/oqseValidation.cjs +1 -0
  31. package/dist/oqseValidation.js +586 -0
  32. package/dist/plainText.cjs +7 -0
  33. package/dist/plainText.js +9 -0
  34. package/dist/progressValidation.cjs +1 -0
  35. package/dist/progressValidation.js +52 -0
  36. package/dist/richTextFields.cjs +1 -0
  37. package/dist/richTextFields.js +101 -0
  38. package/dist/richTextProcessor.cjs +1 -0
  39. package/dist/richTextProcessor.js +51 -0
  40. package/dist/src/answers.d.ts +54 -0
  41. package/dist/src/answers.test.d.ts +1 -0
  42. package/dist/src/assets.d.ts +6 -0
  43. package/dist/src/checkAnswer.d.ts +2 -0
  44. package/dist/src/checkAnswer.test.d.ts +1 -0
  45. package/dist/src/compatibility.d.ts +40 -0
  46. package/dist/src/compatibility.test.d.ts +1 -0
  47. package/dist/src/features.d.ts +12 -0
  48. package/dist/src/features.test.d.ts +1 -0
  49. package/dist/src/fileValidation.d.ts +63 -0
  50. package/dist/src/fileValidation.test.d.ts +1 -0
  51. package/dist/src/index.d.ts +10 -0
  52. package/dist/src/manifest.d.ts +23 -14
  53. package/dist/src/manifestValidation.d.ts +13 -13
  54. package/dist/src/markdownSet.d.ts +47 -0
  55. package/dist/src/markdownSet.test.d.ts +1 -0
  56. package/dist/src/markdownUtils.d.ts +21 -0
  57. package/dist/src/oqse.d.ts +167 -146
  58. package/dist/src/oqseValidation.d.ts +3027 -710
  59. package/dist/src/plainText.d.ts +14 -0
  60. package/dist/src/plainText.test.d.ts +1 -0
  61. package/dist/src/progress.d.ts +2 -2
  62. package/dist/src/richTextFields.d.ts +8 -0
  63. package/dist/src/richTextProcessor.d.ts +33 -11
  64. package/dist/src/specExamples.test.d.ts +5 -0
  65. package/dist/src/utils.d.ts +4 -0
  66. package/dist/utils.cjs +1 -0
  67. package/dist/utils.js +29 -0
  68. package/oqse-header.md +56 -0
  69. package/oqse-manifest.md +249 -0
  70. package/oqse-progress.md +172 -0
  71. package/oqse.md +2232 -0
  72. package/package.json +20 -5
  73. package/schemas/{oqse-header-v0.1.json → oqse-header-v1.0.json} +11 -11
  74. package/schemas/{oqse-manifest-v0.1.json → oqse-manifest-v1.0.json} +37 -18
  75. package/schemas/{oqse-progress-v0.1.json → oqse-progress-v1.0.json} +1 -1
  76. package/schemas/oqse-v1.0.json +8268 -0
  77. package/schemas/oqse-v0.1.json +0 -7626
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 memizy
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 memizy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,75 +1,90 @@
1
- # OQSE SDK
2
-
3
- Official TypeScript SDK for the Open Quiz & Study Exchange ecosystem, published as @memizy/oqse.
4
-
5
- 🚀 **[OQSE Web Validator](https://memizy.github.io/oqse-specification/)** - Online tool for easy testing and debugging of OQSE JSON structures in the browser.
6
-
7
- This repository contains:
8
- - The human-readable specifications split into four standalone documents:
9
- - [OQSE (Study Sets)](./oqse.md)
10
- - [OQSEM (Application Manifests)](./oqse-manifest.md)
11
- - [OQSEP (User Progress)](./oqse-progress.md)
12
- - [OQSEH (Set Headers)](./oqse-header.md)
13
- - Runtime validators built with Zod.
14
- - Manually authored TypeScript types for developer ergonomics.
15
- - Auto-generated JSON Schemas distributed with the package.
16
-
17
- ## Package
18
-
19
- - Name: @memizy/oqse
20
- - Purpose: validate, parse, and type OQSE, OQSEM (Application Manifest), and OQSEP (User Progress) data.
21
-
22
- ## Schemas
23
-
24
- The auto-generated JSON Schemas are published with this repository and are available at the following URLs:
25
-
26
- - Core OQSE Schema: https://cdn.jsdelivr.net/gh/memizy/oqse-specification@main/schemas/oqse-v0.1.json
27
- - Manifest Schema: https://cdn.jsdelivr.net/gh/memizy/oqse-specification@main/schemas/oqse-manifest-v0.1.json
28
- - Progress Schema: https://cdn.jsdelivr.net/gh/memizy/oqse-specification@main/schemas/oqse-progress-v0.1.json
29
- - Header Schema: https://cdn.jsdelivr.net/gh/memizy/oqse-specification@main/schemas/oqse-header-v0.1.json
30
-
31
- ## Architecture & Single Source of Truth
32
-
33
- The human-readable Markdown specifications are the **absolute single source of truth** for the OQSE ecosystem.
34
-
35
- 1. **The Specifications**
36
- The four Markdown files ([oqse.md](./oqse.md), [oqse-manifest.md](./oqse-manifest.md), [oqse-progress.md](./oqse-progress.md), and [oqse-header.md](./oqse-header.md)) constitute the normative documentation for concepts, rules, and semantics.
37
-
38
- 2. **Zod Schemas**
39
- Runtime validators (`*Validation.ts`) are implementations. They **MUST match** the rules defined in the Markdown specs. They are not the source of truth; if they disagree with the text spec, the code is considered a bug.
40
-
41
- 3. **TypeScript Types**
42
- TypeScript types (`oqse.ts`, `manifest.ts`, `progress.ts`) manually mirror the Zod schemas for better Developer Experience. They must also strictly adhere to the Markdown specifications.
43
-
44
- 4. JSON schemas are strictly auto-generated using npm run generate:schemas and contributors MUST NOT edit them manually.
45
- If validation behavior changes, update the Zod schemas first, then regenerate.
46
-
47
- ## Development
48
-
49
- Install dependencies:
50
-
51
- ```bash
52
- npm install
53
- ```
54
-
55
- Build and regenerate schemas:
56
-
57
- ```bash
58
- npm run build
59
- ```
60
-
61
- Generate only JSON schemas:
62
-
63
- ```bash
64
- npm run generate:schemas
65
- ```
66
-
67
- Run tests:
68
-
69
- ```bash
70
- npm test
71
- ```
72
-
73
- ## Contributing Rule
74
-
75
- Never edit files in schemas/ by hand. Always regenerate via npm run generate:schemas.
1
+ # OQSE SDK
2
+
3
+ Official TypeScript SDK for the Open Quiz & Study Exchange ecosystem, published as @memizy/oqse.
4
+
5
+ 🚀 **[OQSE Web Validator](https://memizy.github.io/memizy/)** - Online tool for easy testing and debugging of OQSE JSON structures in the browser (source: [`docs/`](./docs/), published by GitHub Pages).
6
+
7
+ This repository contains:
8
+ - The human-readable specifications split into four standalone documents:
9
+ - [OQSE (Study Sets)](./oqse.md)
10
+ - [OQSEM (Application Manifests)](./oqse-manifest.md)
11
+ - [OQSEP (User Progress)](./oqse-progress.md)
12
+ - [OQSEH (Set Headers)](./oqse-header.md)
13
+ - Runtime validators built with Zod.
14
+ - Manually authored TypeScript types for developer ergonomics.
15
+ - Auto-generated JSON Schemas distributed with the package.
16
+
17
+ ## Package
18
+
19
+ - Name: @memizy/oqse
20
+ - Purpose: validate, parse, and type OQSE, OQSEM (Application Manifest), and OQSEP (User Progress) data.
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ import {
26
+ loadOQSEFile, // tolerant import: skips invalid items, returns errors + warnings
27
+ validateOQSEFile, // strict save/export validation (throws OQSEValidationError)
28
+ resolveAsset, // <asset:key /> lookup: item.assets first, then meta.assets
29
+ checkAnswer, // is a learner's answer correct? (Answer Formats in the specification)
30
+ parseMarkdownSet, // OQSE Markdown (.oqse.md) -> OQSE file
31
+ serializeMarkdownSet, // OQSE file of notes -> OQSE Markdown
32
+ } from '@memizy/oqse';
33
+
34
+ const { success, data, errors, warnings } = loadOQSEFile(JSON.parse(text));
35
+ ```
36
+
37
+ ## Schemas
38
+
39
+ The auto-generated JSON Schemas are published with this repository and are available at the following URLs:
40
+
41
+ - Core OQSE Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-v1.0.json
42
+ - Manifest Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-manifest-v1.0.json
43
+ - Progress Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-progress-v1.0.json
44
+ - Header Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-header-v1.0.json
45
+
46
+ ## Architecture & Single Source of Truth
47
+
48
+ The human-readable Markdown specifications are the **absolute single source of truth** for the OQSE ecosystem.
49
+
50
+ 1. **The Specifications**
51
+ The four Markdown files ([oqse.md](./oqse.md), [oqse-manifest.md](./oqse-manifest.md), [oqse-progress.md](./oqse-progress.md), and [oqse-header.md](./oqse-header.md)) constitute the normative documentation for concepts, rules, and semantics.
52
+
53
+ 2. **Zod Schemas**
54
+ Runtime validators (`*Validation.ts`) are implementations. They **MUST match** the rules defined in the Markdown specs. They are not the source of truth; if they disagree with the text spec, the code is considered a bug.
55
+
56
+ 3. **TypeScript Types**
57
+ TypeScript types (`oqse.ts`, `manifest.ts`, `progress.ts`) manually mirror the Zod schemas for better Developer Experience. They must also strictly adhere to the Markdown specifications.
58
+
59
+ 4. JSON schemas are strictly auto-generated using bun run generate:schemas and contributors MUST NOT edit them manually.
60
+ If validation behavior changes, update the Zod schemas first, then regenerate.
61
+
62
+ ## Development
63
+
64
+ Install dependencies (from the monorepo root):
65
+
66
+ ```bash
67
+ bun install
68
+ ```
69
+
70
+ Build and regenerate schemas:
71
+
72
+ ```bash
73
+ bun run build
74
+ ```
75
+
76
+ Generate only JSON schemas:
77
+
78
+ ```bash
79
+ bun run generate:schemas
80
+ ```
81
+
82
+ Run tests:
83
+
84
+ ```bash
85
+ bun run test
86
+ ```
87
+
88
+ ## Contributing Rule
89
+
90
+ Never edit files in schemas/ by hand. Always regenerate via bun run generate:schemas.
@@ -0,0 +1 @@
1
+ function e(e,t={}){let n=e.normalize(`NFC`).replace(/\s+/gu,` `).trim();return t.caseSensitive||(n=n.toLowerCase()),(t.ignoreDiacritics??!0)&&(n=n.normalize(`NFD`).replace(/\p{M}/gu,``).normalize(`NFC`)),n}function t(e){let t=[...e].length;return t<=3?0:t<=7?1:2}function n(n,i,a={}){let o=e(n,a),s=e(i,a);if(o===s)return!0;if(!a.acceptPartial)return!1;let c=t(s);return c>0&&r(o,s,c)<=c}function r(e,t,n=1/0){let r=[...e],i=[...t];if(Math.abs(r.length-i.length)>n)return n+1;let a=[],o=Array.from({length:i.length+1},(e,t)=>t);for(let e=1;e<=r.length;e++){let t=[e],s=e;for(let n=1;n<=i.length;n++){let c=r[e-1]===i[n-1]?0:1,l=Math.min(o[n]+1,t[n-1]+1,o[n-1]+c);e>1&&n>1&&r[e-1]===i[n-2]&&r[e-2]===i[n-1]&&(l=Math.min(l,a[n-2]+1)),t[n]=l,s=Math.min(s,l)}if(s>n)return n+1;a=o,o=t}return o[i.length]}var i=/^(-?)(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})?)?$/,a={year:0,month:1,day:2,datetime:3};function o(e){let t=i.exec(e);if(!t)return null;let n=(t[1]?-1:1)*Number(t[2]);if(n===0)return null;let r=Number(t[3]),a=s(n<0?n+1:n,r,Number(t[4])),o=t[5]!==void 0,c=0;if(o){c=((Number(t[5])*60+Number(t[6]))*60+Number(t[7]))*1e3+Number((t[8]??`0`).padEnd(3,`0`));let e=t[9];if(e&&e!==`Z`){let t=e[0]===`-`?-1:1;c-=t*(Number(e.slice(1,3))*60+Number(e.slice(4,6)))*6e4}}return{year:n,month:r,days:a,instant:a*864e5+c,hasTime:o}}function s(e,t,n){let r=t<=2?e-1:e,i=Math.floor(r/400),a=r-i*400,o=Math.floor((153*(t+(t>2?-3:9))+2)/5)+n-1,s=a*365+Math.floor(a/4)-Math.floor(a/100)+o;return i*146097+s-719468}function c(e){return o(e)!==null}function l(e,t){let n=o(e.date),r=o(t.date);if(!n||!r)throw Error(`Invalid timeline date: ${n?t.date:e.date}`);let i=(e,t)=>a[t??(e.hasTime?`datetime`:`day`)],s=Math.min(i(n,e.precision),i(r,t.precision));return s===a.year?Math.sign(n.year-r.year):s===a.month?Math.sign(n.year-r.year||n.month-r.month):s===a.day?Math.sign(n.days-r.days):Math.sign(n.instant-r.instant)}function u(e){for(let t=0;t<e.length;t++)for(let n=t+1;n<e.length;n++)if(l(e[t],e[n])>0)return!1;return!0}exports.allowedTypos=t,exports.compareTimelineEvents=l,exports.editDistance=r,exports.isTimelineDate=c,exports.isTimelineOrderCorrect=u,exports.normalizeAnswerText=e,exports.textAnswerMatches=n;
@@ -0,0 +1,78 @@
1
+ //#region src/answers.ts
2
+ function e(e, t = {}) {
3
+ let n = e.normalize("NFC").replace(/\s+/gu, " ").trim();
4
+ return t.caseSensitive || (n = n.toLowerCase()), (t.ignoreDiacritics ?? !0) && (n = n.normalize("NFD").replace(/\p{M}/gu, "").normalize("NFC")), n;
5
+ }
6
+ function t(e) {
7
+ let t = [...e].length;
8
+ return t <= 3 ? 0 : t <= 7 ? 1 : 2;
9
+ }
10
+ function n(n, i, a = {}) {
11
+ let o = e(n, a), s = e(i, a);
12
+ if (o === s) return !0;
13
+ if (!a.acceptPartial) return !1;
14
+ let c = t(s);
15
+ return c > 0 && r(o, s, c) <= c;
16
+ }
17
+ function r(e, t, n = Infinity) {
18
+ let r = [...e], i = [...t];
19
+ if (Math.abs(r.length - i.length) > n) return n + 1;
20
+ let a = [], o = Array.from({ length: i.length + 1 }, (e, t) => t);
21
+ for (let e = 1; e <= r.length; e++) {
22
+ let t = [e], s = e;
23
+ for (let n = 1; n <= i.length; n++) {
24
+ let c = r[e - 1] === i[n - 1] ? 0 : 1, l = Math.min(o[n] + 1, t[n - 1] + 1, o[n - 1] + c);
25
+ e > 1 && n > 1 && r[e - 1] === i[n - 2] && r[e - 2] === i[n - 1] && (l = Math.min(l, a[n - 2] + 1)), t[n] = l, s = Math.min(s, l);
26
+ }
27
+ if (s > n) return n + 1;
28
+ a = o, o = t;
29
+ }
30
+ return o[i.length];
31
+ }
32
+ var i = /^(-?)(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})?)?$/, a = {
33
+ year: 0,
34
+ month: 1,
35
+ day: 2,
36
+ datetime: 3
37
+ };
38
+ function o(e) {
39
+ let t = i.exec(e);
40
+ if (!t) return null;
41
+ let n = (t[1] ? -1 : 1) * Number(t[2]);
42
+ if (n === 0) return null;
43
+ let r = Number(t[3]), a = s(n < 0 ? n + 1 : n, r, Number(t[4])), o = t[5] !== void 0, c = 0;
44
+ if (o) {
45
+ c = ((Number(t[5]) * 60 + Number(t[6])) * 60 + Number(t[7])) * 1e3 + Number((t[8] ?? "0").padEnd(3, "0"));
46
+ let e = t[9];
47
+ if (e && e !== "Z") {
48
+ let t = e[0] === "-" ? -1 : 1;
49
+ c -= t * (Number(e.slice(1, 3)) * 60 + Number(e.slice(4, 6))) * 6e4;
50
+ }
51
+ }
52
+ return {
53
+ year: n,
54
+ month: r,
55
+ days: a,
56
+ instant: a * 864e5 + c,
57
+ hasTime: o
58
+ };
59
+ }
60
+ function s(e, t, n) {
61
+ let r = t <= 2 ? e - 1 : e, i = Math.floor(r / 400), a = r - i * 400, o = Math.floor((153 * (t + (t > 2 ? -3 : 9)) + 2) / 5) + n - 1, s = a * 365 + Math.floor(a / 4) - Math.floor(a / 100) + o;
62
+ return i * 146097 + s - 719468;
63
+ }
64
+ function c(e) {
65
+ return o(e) !== null;
66
+ }
67
+ function l(e, t) {
68
+ let n = o(e.date), r = o(t.date);
69
+ if (!n || !r) throw Error(`Invalid timeline date: ${n ? t.date : e.date}`);
70
+ let i = (e, t) => a[t ?? (e.hasTime ? "datetime" : "day")], s = Math.min(i(n, e.precision), i(r, t.precision));
71
+ return s === a.year ? Math.sign(n.year - r.year) : s === a.month ? Math.sign(n.year - r.year || n.month - r.month) : s === a.day ? Math.sign(n.days - r.days) : Math.sign(n.instant - r.instant);
72
+ }
73
+ function u(e) {
74
+ for (let t = 0; t < e.length; t++) for (let n = t + 1; n < e.length; n++) if (l(e[t], e[n]) > 0) return !1;
75
+ return !0;
76
+ }
77
+ //#endregion
78
+ export { t as allowedTypos, l as compareTimelineEvents, r as editDistance, c as isTimelineDate, u as isTimelineOrderCorrect, e as normalizeAnswerText, n as textAnswerMatches };
@@ -0,0 +1 @@
1
+ function e(e,t,n){let r=e.toLowerCase();return t?.assets?.[r]??n?.assets?.[r]}exports.resolveAsset=e;
package/dist/assets.js ADDED
@@ -0,0 +1,7 @@
1
+ //#region src/assets.ts
2
+ function e(e, t, n) {
3
+ let r = e.toLowerCase();
4
+ return t?.assets?.[r] ?? n?.assets?.[r];
5
+ }
6
+ //#endregion
7
+ export { e as resolveAsset };
@@ -0,0 +1 @@
1
+ const e=require("./answers.cjs");function t(t,p){if(!t||typeof t!=`object`)throw Error(`checkAnswer: missing item`);switch(t.type){case`mcq-single`:return p===t.correctId;case`mcq-multi`:{let e=i(p);return e!==null&&c(e,t.correctIds)}case`true-false`:return r(p)===t.correctAnswer;case`short-answer`:{let n=p;return typeof n==`string`&&t.correctAnswers.some(r=>e.textAnswerMatches(n,r,t))}case`numeric-input`:{let e=n(p);return e!==null&&Math.abs(e-t.correctAnswer)<=(t.tolerance??0)+1e-9}case`slider`:{let e=n(p);return e!==null&&Math.abs(e-t.correctAnswer)<=(t.tolerance??0)+1e-9}case`math-input`:{if(typeof p!=`string`)return!1;let e=l(p);return[t.correctAnswer,...t.alternativeAnswers??[]].some(t=>l(t)===e)}case`sort-items`:{let e=i(p);return e!==null&&e.length===t.correctOrder.length&&e.every((e,n)=>e===t.correctOrder[n])}case`match-pairs`:{let e=s(p);return!!e&&t.prompts.every(n=>e[n.id]===t.pairs[n.id])}case`match-complex`:{let e=a(p);if(!e)return!1;let n=new Set(t.connections.map(([e,t])=>o(e,t))),r=new Set(e.map(([e,t])=>o(e,t))),i=[...r].filter(e=>n.has(e)).length;return i===r.size&&i>=(t.minCorrect??n.size)}case`matrix`:{let e=a(p);return e?c(e.map(([e,t])=>o(e,t)),t.correctCells.map(([e,t])=>o(e,t))):!1}case`fill-in-blanks`:{let n=s(p);return n?Object.entries(t.blanks).every(([r,i])=>{let a=n[r];return typeof a==`string`&&i.some(n=>e.textAnswerMatches(a,n,t))}):!1}case`fill-in-select`:{let e=s(p);return!!e&&Object.entries(t.blanks).every(([t,n])=>e[t]===n.correctId)}case`categorize`:{let e=s(p);return!!e&&t.items.every(t=>e[t.id]===t.correctCategoryId)}case`timeline`:{let n=i(p);if(!n||n.length!==t.events.length||new Set(n).size!==n.length)return!1;let r=n.map(e=>t.events.find(t=>t.id===e));return r.every(e=>!!e)&&e.isTimelineOrderCorrect(r)}case`pin-on-image`:{let e=(Array.isArray(p)?p:[p]).map(d);if(e.length===0||e.some(e=>e===null))return!1;if(!t.multipleCorrect)return e.length===1&&t.hotspots.some(t=>f(e[0],t));let n=new Set;for(let r of e){let e=t.hotspots.findIndex(e=>f(r,e));if(e===-1)return!1;n.add(e)}return n.size>=(t.minCorrect??t.hotspots.length)}case`diagram-label`:{let n=s(p);return n?t.zones.every(r=>{let i=n[r.id];if(t.requireTyping){let n=t.labels.find(e=>e.id===r.correctLabelId);return!!n&&typeof i==`string`&&e.textAnswerMatches(i,n.text,t)}return i===r.correctLabelId}):!1}case`pin-on-model`:{let e=(Array.isArray(p)?p:[p]).filter(e=>typeof e==`string`);if(e.length===0)return!1;let n=e=>t.hotspots.findIndex(t=>e.toLowerCase().includes(t.targetName.toLowerCase()));if(!t.multipleCorrect)return e.length===1&&n(e[0])!==-1;let r=new Set;for(let t of e){let e=n(t);if(e===-1)return!1;r.add(e)}return r.size>=(t.minCorrect??t.hotspots.length)}case`chess-puzzle`:{if(!Array.isArray(p)||p.some(e=>typeof e!=`string`))return!1;let e=p.map(u);return t.correctAnswers.some(t=>t.length===e.length&&t.every((t,n)=>u(t)===e[n]))}case`flashcard`:{let e=n(p);return e===3||e===4}case`note`:case`open-ended`:throw Error(`checkAnswer: "${t.type}" items cannot be checked automatically.`);default:throw Error(`checkAnswer: unsupported item type "${t.type}".`)}}function n(e){if(typeof e==`number`)return Number.isFinite(e)?e:null;if(typeof e!=`string`)return null;let t=e.trim().replace(/\s+/g,``).replace(`,`,`.`);return/^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(t)?Number(t):null}function r(e){return typeof e==`boolean`?e:e===`true`||e!==`false`&&null}function i(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)?e:null}function a(e){return Array.isArray(e)&&e.every(e=>Array.isArray(e)&&e.length===2&&typeof e[0]==`string`&&typeof e[1]==`string`)?e:null}var o=(e,t)=>`${e}\u0000${t}`;function s(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:null}function c(e,t){let n=new Set(e),r=new Set(t);return n.size===e.length&&n.size===r.size&&[...n].every(e=>r.has(e))}function l(e){return e.trim().replace(/^\$\$?|\$\$?$/g,``).replace(/\s+/g,``)}function u(e){return e.trim().replace(/[+#!?]+$/g,``).replace(/^0-0(-0)?$/,e=>e.replace(/0/g,`O`)).replace(/^([a-h](?:x[a-h])?[18])([QRBN])$/,`$1=$2`)}function d(e){let t=s(e);return t&&typeof t.x==`number`&&typeof t.y==`number`?{x:t.x,y:t.y}:null}function f(e,t){switch(t.type){case`rect`:return e.x>=t.x&&e.x<=t.x+t.width&&e.y>=t.y&&e.y<=t.y+t.height;case`circle`:return(e.x-t.x)**2+(e.y-t.y)**2<=t.radius**2;case`polygon`:{let n=!1,r=t.points;for(let t=0,i=r.length-1;t<r.length;i=t++)r[t].y>e.y!=r[i].y>e.y&&e.x<(r[i].x-r[t].x)*(e.y-r[t].y)/(r[i].y-r[t].y)+r[t].x&&(n=!n);return n}}}exports.checkAnswer=t;
@@ -0,0 +1,166 @@
1
+ import { isTimelineOrderCorrect as e, textAnswerMatches as t } from "./answers.js";
2
+ //#region src/checkAnswer.ts
3
+ function n(n, m) {
4
+ if (!n || typeof n != "object") throw Error("checkAnswer: missing item");
5
+ switch (n.type) {
6
+ case "mcq-single": return m === n.correctId;
7
+ case "mcq-multi": {
8
+ let e = a(m);
9
+ return e !== null && l(e, n.correctIds);
10
+ }
11
+ case "true-false": return i(m) === n.correctAnswer;
12
+ case "short-answer": {
13
+ let e = m;
14
+ return typeof e == "string" && n.correctAnswers.some((r) => t(e, r, n));
15
+ }
16
+ case "numeric-input": {
17
+ let e = r(m);
18
+ return e !== null && Math.abs(e - n.correctAnswer) <= (n.tolerance ?? 0) + 1e-9;
19
+ }
20
+ case "slider": {
21
+ let e = r(m);
22
+ return e !== null && Math.abs(e - n.correctAnswer) <= (n.tolerance ?? 0) + 1e-9;
23
+ }
24
+ case "math-input": {
25
+ if (typeof m != "string") return !1;
26
+ let e = u(m);
27
+ return [n.correctAnswer, ...n.alternativeAnswers ?? []].some((t) => u(t) === e);
28
+ }
29
+ case "sort-items": {
30
+ let e = a(m);
31
+ return e !== null && e.length === n.correctOrder.length && e.every((e, t) => e === n.correctOrder[t]);
32
+ }
33
+ case "match-pairs": {
34
+ let e = c(m);
35
+ return !!e && n.prompts.every((t) => e[t.id] === n.pairs[t.id]);
36
+ }
37
+ case "match-complex": {
38
+ let e = o(m);
39
+ if (!e) return !1;
40
+ let t = new Set(n.connections.map(([e, t]) => s(e, t))), r = new Set(e.map(([e, t]) => s(e, t))), i = [...r].filter((e) => t.has(e)).length;
41
+ return i === r.size && i >= (n.minCorrect ?? t.size);
42
+ }
43
+ case "matrix": {
44
+ let e = o(m);
45
+ return e ? l(e.map(([e, t]) => s(e, t)), n.correctCells.map(([e, t]) => s(e, t))) : !1;
46
+ }
47
+ case "fill-in-blanks": {
48
+ let e = c(m);
49
+ return e ? Object.entries(n.blanks).every(([r, i]) => {
50
+ let a = e[r];
51
+ return typeof a == "string" && i.some((e) => t(a, e, n));
52
+ }) : !1;
53
+ }
54
+ case "fill-in-select": {
55
+ let e = c(m);
56
+ return !!e && Object.entries(n.blanks).every(([t, n]) => e[t] === n.correctId);
57
+ }
58
+ case "categorize": {
59
+ let e = c(m);
60
+ return !!e && n.items.every((t) => e[t.id] === t.correctCategoryId);
61
+ }
62
+ case "timeline": {
63
+ let t = a(m);
64
+ if (!t || t.length !== n.events.length || new Set(t).size !== t.length) return !1;
65
+ let r = t.map((e) => n.events.find((t) => t.id === e));
66
+ return r.every((e) => !!e) && e(r);
67
+ }
68
+ case "pin-on-image": {
69
+ let e = (Array.isArray(m) ? m : [m]).map(f);
70
+ if (e.length === 0 || e.some((e) => e === null)) return !1;
71
+ if (!n.multipleCorrect) return e.length === 1 && n.hotspots.some((t) => p(e[0], t));
72
+ let t = /* @__PURE__ */ new Set();
73
+ for (let r of e) {
74
+ let e = n.hotspots.findIndex((e) => p(r, e));
75
+ if (e === -1) return !1;
76
+ t.add(e);
77
+ }
78
+ return t.size >= (n.minCorrect ?? n.hotspots.length);
79
+ }
80
+ case "diagram-label": {
81
+ let e = c(m);
82
+ return e ? n.zones.every((r) => {
83
+ let i = e[r.id];
84
+ if (n.requireTyping) {
85
+ let e = n.labels.find((e) => e.id === r.correctLabelId);
86
+ return !!e && typeof i == "string" && t(i, e.text, n);
87
+ }
88
+ return i === r.correctLabelId;
89
+ }) : !1;
90
+ }
91
+ case "pin-on-model": {
92
+ let e = (Array.isArray(m) ? m : [m]).filter((e) => typeof e == "string");
93
+ if (e.length === 0) return !1;
94
+ let t = (e) => n.hotspots.findIndex((t) => e.toLowerCase().includes(t.targetName.toLowerCase()));
95
+ if (!n.multipleCorrect) return e.length === 1 && t(e[0]) !== -1;
96
+ let r = /* @__PURE__ */ new Set();
97
+ for (let n of e) {
98
+ let e = t(n);
99
+ if (e === -1) return !1;
100
+ r.add(e);
101
+ }
102
+ return r.size >= (n.minCorrect ?? n.hotspots.length);
103
+ }
104
+ case "chess-puzzle": {
105
+ if (!Array.isArray(m) || m.some((e) => typeof e != "string")) return !1;
106
+ let e = m.map(d);
107
+ return n.correctAnswers.some((t) => t.length === e.length && t.every((t, n) => d(t) === e[n]));
108
+ }
109
+ case "flashcard": {
110
+ let e = r(m);
111
+ return e === 3 || e === 4;
112
+ }
113
+ case "note":
114
+ case "open-ended": throw Error(`checkAnswer: "${n.type}" items cannot be checked automatically.`);
115
+ default: throw Error(`checkAnswer: unsupported item type "${n.type}".`);
116
+ }
117
+ }
118
+ function r(e) {
119
+ if (typeof e == "number") return Number.isFinite(e) ? e : null;
120
+ if (typeof e != "string") return null;
121
+ let t = e.trim().replace(/\s+/g, "").replace(",", ".");
122
+ return /^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(t) ? Number(t) : null;
123
+ }
124
+ function i(e) {
125
+ return typeof e == "boolean" ? e : e === "true" || e !== "false" && null;
126
+ }
127
+ function a(e) {
128
+ return Array.isArray(e) && e.every((e) => typeof e == "string") ? e : null;
129
+ }
130
+ function o(e) {
131
+ return Array.isArray(e) && e.every((e) => Array.isArray(e) && e.length === 2 && typeof e[0] == "string" && typeof e[1] == "string") ? e : null;
132
+ }
133
+ var s = (e, t) => `${e}\u0000${t}`;
134
+ function c(e) {
135
+ return e && typeof e == "object" && !Array.isArray(e) ? e : null;
136
+ }
137
+ function l(e, t) {
138
+ let n = new Set(e), r = new Set(t);
139
+ return n.size === e.length && n.size === r.size && [...n].every((e) => r.has(e));
140
+ }
141
+ function u(e) {
142
+ return e.trim().replace(/^\$\$?|\$\$?$/g, "").replace(/\s+/g, "");
143
+ }
144
+ function d(e) {
145
+ return e.trim().replace(/[+#!?]+$/g, "").replace(/^0-0(-0)?$/, (e) => e.replace(/0/g, "O")).replace(/^([a-h](?:x[a-h])?[18])([QRBN])$/, "$1=$2");
146
+ }
147
+ function f(e) {
148
+ let t = c(e);
149
+ return t && typeof t.x == "number" && typeof t.y == "number" ? {
150
+ x: t.x,
151
+ y: t.y
152
+ } : null;
153
+ }
154
+ function p(e, t) {
155
+ switch (t.type) {
156
+ case "rect": return e.x >= t.x && e.x <= t.x + t.width && e.y >= t.y && e.y <= t.y + t.height;
157
+ case "circle": return (e.x - t.x) ** 2 + (e.y - t.y) ** 2 <= t.radius ** 2;
158
+ case "polygon": {
159
+ let n = !1, r = t.points;
160
+ for (let t = 0, i = r.length - 1; t < r.length; i = t++) r[t].y > e.y != r[i].y > e.y && e.x < (r[i].x - r[t].x) * (e.y - r[t].y) / (r[i].y - r[t].y) + r[t].x && (n = !n);
161
+ return n;
162
+ }
163
+ }
164
+ }
165
+ //#endregion
166
+ export { n as checkAnswer };
@@ -0,0 +1 @@
1
+ const e=require("./manifest.cjs"),t=require("./features.cjs");function n(n,r){let i=e.withManifestDefaults(r).capabilities,a=(e,t)=>!!e&&e.length>0&&(e.includes(`*`)||e.includes(t)),o=[...new Set(n.items.map(e=>e.type))].filter(e=>!a(i.types,e)),s=[],c=[],l=(e,t)=>{for(let[n,r]of Object.entries(e??{})){let e=i.assets?.[r.type],a={path:`${t}.${n}`,type:r.type,mimeType:r.mimeType};if(!e||e.length===0)s.push(a);else if(e.includes(`*`))continue;else r.mimeType?e.includes(r.mimeType)||s.push(a):c.push(a)}};l(n.meta.assets,`meta.assets`),n.items.forEach((e,t)=>l(e.assets,`items[${t}].assets`));let u=n.meta.requirements,d=(e,t)=>(e??[]).filter(e=>!(t??[]).includes(e)),f=d(t.effectiveFeatures(n),i.features),p=d(u?.latexPackages,i.latexPackages),m=[...d(u?.itemProperties,i.itemProperties),...d(u?.metaProperties,i.metaProperties)],h=e.isVersionCompatible(n.version,r),g=h&&o.length===0&&s.length===0;return{level:g?f.length>0||p.length>0?`simplified`:`full`:`incompatible`,compatible:g,versionCompatible:h,unsupportedTypes:o,unsupportedAssets:s,unverifiedAssets:c,missingFeatures:f,missingLatexPackages:p,missingProperties:m}}exports.checkCompatibility=n;
@@ -0,0 +1,32 @@
1
+ import { isVersionCompatible as e, withManifestDefaults as t } from "./manifest.js";
2
+ import { effectiveFeatures as n } from "./features.js";
3
+ //#region src/compatibility.ts
4
+ function r(r, i) {
5
+ let a = t(i).capabilities, o = (e, t) => !!e && e.length > 0 && (e.includes("*") || e.includes(t)), s = [...new Set(r.items.map((e) => e.type))].filter((e) => !o(a.types, e)), c = [], l = [], u = (e, t) => {
6
+ for (let [n, r] of Object.entries(e ?? {})) {
7
+ let e = a.assets?.[r.type], i = {
8
+ path: `${t}.${n}`,
9
+ type: r.type,
10
+ mimeType: r.mimeType
11
+ };
12
+ if (!e || e.length === 0) c.push(i);
13
+ else if (e.includes("*")) continue;
14
+ else r.mimeType ? e.includes(r.mimeType) || c.push(i) : l.push(i);
15
+ }
16
+ };
17
+ u(r.meta.assets, "meta.assets"), r.items.forEach((e, t) => u(e.assets, `items[${t}].assets`));
18
+ let d = r.meta.requirements, f = (e, t) => (e ?? []).filter((e) => !(t ?? []).includes(e)), p = f(n(r), a.features), m = f(d?.latexPackages, a.latexPackages), h = [...f(d?.itemProperties, a.itemProperties), ...f(d?.metaProperties, a.metaProperties)], g = e(r.version, i), _ = g && s.length === 0 && c.length === 0;
19
+ return {
20
+ level: _ ? p.length > 0 || m.length > 0 ? "simplified" : "full" : "incompatible",
21
+ compatible: _,
22
+ versionCompatible: g,
23
+ unsupportedTypes: s,
24
+ unsupportedAssets: c,
25
+ unverifiedAssets: l,
26
+ missingFeatures: p,
27
+ missingLatexPackages: m,
28
+ missingProperties: h
29
+ };
30
+ }
31
+ //#endregion
32
+ export { r as checkCompatibility };
@@ -0,0 +1 @@
1
+ const e=require("./manifest.cjs"),t=require("./richTextProcessor.cjs"),n=require("./richTextFields.cjs");var r=[`ar`,`he`,`fa`,`ur`,`yi`,`ps`,`sd`,`dv`,`ug`],i={mermaid:`mermaid`,smiles:`smiles`,abc:`abc-notation`},a=/^ {0,3}(?:`{3,}|~{3,})[ \t]*([^\s`~]*)/gm,o=/^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^ {0,3}\1[`~]*[ \t]*$|(?![\s\S]))/gm,s=/\$\$[\s\S]*?\$\$|\$[^$\n]+\$/g,c=[/^ {0,3}#{1,6}[ \t]+\S/m,/(\*\*|__)(?=\S)[^\n]*?\S\1/,/(^|[^\w*])\*(?=[^\s*])[^*\n]*?[^\s*]\*(?![\w*])/m,/(^|[^\w])_(?=[^\s_])[^_\n]*?[^\s_]_(?!\w)/m,/~~(?=\S)[^\n]*?\S~~/,/^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+\S/m,/^ {0,3}>/m,/!?\[[^\]\n]*\]\([^)\n]*\)/,/`[^`\n]+`/,/^[ \t]*\|?[ \t]*:?-{3,}:?[ \t]*\|/m,/^ {0,3}([-*_])([ \t]*\1){2,}[ \t]*$/m],l=/\$\$[^$]*[\\^_{}][^$]*\$\$|\$[^$\n]*[\\^_{}][^$\n]*\$/;function u(e){return e?.split(/[-_]/)[0]?.toLowerCase()}function d(e,n,r){for(let t of e.matchAll(a)){r.add(`markdown`);let e=t[1].toLowerCase();e&&r.add(i[e]??`syntax-highlighting`)}t.findRawHtml(e,{latex:n})&&r.add(`html`);let l=e.replace(o,``);n&&(l=l.replace(s,``)),c.some(e=>e.test(l))&&r.add(`markdown`)}function f(t){let i=t.meta.requirements?.features?.includes(`latex`)??!1,a=new Set;for(let[,e]of n.metaRichText(t.meta))d(e,i,a);for(let e of t.items)for(let[,t]of n.itemRichText(e))d(t,i,a);return[t.meta.language,...t.items.map(e=>e.lang)].some(e=>r.includes(u(e)??``))&&a.add(`rtl`),e.OFFICIAL_FEATURE_KEYS.filter(e=>a.has(e))}function p(e){return[...new Set([...e.meta.requirements?.features??[],...f(e)])]}function m(e){return l.test(e.replace(o,``).replace(/`[^`\n]+`/g,``))}exports.RTL_LANGUAGES=r,exports.detectFeatures=f,exports.effectiveFeatures=p,exports.looksLikeLatex=m;
@@ -0,0 +1,58 @@
1
+ import { OFFICIAL_FEATURE_KEYS as e } from "./manifest.js";
2
+ import { findRawHtml as t } from "./richTextProcessor.js";
3
+ import { itemRichText as n, metaRichText as r } from "./richTextFields.js";
4
+ //#region src/features.ts
5
+ var i = [
6
+ "ar",
7
+ "he",
8
+ "fa",
9
+ "ur",
10
+ "yi",
11
+ "ps",
12
+ "sd",
13
+ "dv",
14
+ "ug"
15
+ ], a = {
16
+ mermaid: "mermaid",
17
+ smiles: "smiles",
18
+ abc: "abc-notation"
19
+ }, o = /^ {0,3}(?:`{3,}|~{3,})[ \t]*([^\s`~]*)/gm, s = /^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^ {0,3}\1[`~]*[ \t]*$|(?![\s\S]))/gm, c = /\$\$[\s\S]*?\$\$|\$[^$\n]+\$/g, l = [
20
+ /^ {0,3}#{1,6}[ \t]+\S/m,
21
+ /(\*\*|__)(?=\S)[^\n]*?\S\1/,
22
+ /(^|[^\w*])\*(?=[^\s*])[^*\n]*?[^\s*]\*(?![\w*])/m,
23
+ /(^|[^\w])_(?=[^\s_])[^_\n]*?[^\s_]_(?!\w)/m,
24
+ /~~(?=\S)[^\n]*?\S~~/,
25
+ /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+\S/m,
26
+ /^ {0,3}>/m,
27
+ /!?\[[^\]\n]*\]\([^)\n]*\)/,
28
+ /`[^`\n]+`/,
29
+ /^[ \t]*\|?[ \t]*:?-{3,}:?[ \t]*\|/m,
30
+ /^ {0,3}([-*_])([ \t]*\1){2,}[ \t]*$/m
31
+ ], u = /\$\$[^$]*[\\^_{}][^$]*\$\$|\$[^$\n]*[\\^_{}][^$\n]*\$/;
32
+ function d(e) {
33
+ return e?.split(/[-_]/)[0]?.toLowerCase();
34
+ }
35
+ function f(e, n, r) {
36
+ for (let t of e.matchAll(o)) {
37
+ r.add("markdown");
38
+ let e = t[1].toLowerCase();
39
+ e && r.add(a[e] ?? "syntax-highlighting");
40
+ }
41
+ t(e, { latex: n }) && r.add("html");
42
+ let i = e.replace(s, "");
43
+ n && (i = i.replace(c, "")), l.some((e) => e.test(i)) && r.add("markdown");
44
+ }
45
+ function p(t) {
46
+ let a = t.meta.requirements?.features?.includes("latex") ?? !1, o = /* @__PURE__ */ new Set();
47
+ for (let [, e] of r(t.meta)) f(e, a, o);
48
+ for (let e of t.items) for (let [, t] of n(e)) f(t, a, o);
49
+ return [t.meta.language, ...t.items.map((e) => e.lang)].some((e) => i.includes(d(e) ?? "")) && o.add("rtl"), e.filter((e) => o.has(e));
50
+ }
51
+ function m(e) {
52
+ return [.../* @__PURE__ */ new Set([...e.meta.requirements?.features ?? [], ...p(e)])];
53
+ }
54
+ function h(e) {
55
+ return u.test(e.replace(s, "").replace(/`[^`\n]+`/g, ""));
56
+ }
57
+ //#endregion
58
+ export { i as RTL_LANGUAGES, p as detectFeatures, m as effectiveFeatures, h as looksLikeLatex };
@@ -0,0 +1,2 @@
1
+ const e=require("./oqse.cjs"),t=require("./utils.cjs"),n=require("./oqseValidation.cjs"),r=require("./manifest.cjs"),i=require("./markdownUtils.cjs"),a=require("./richTextProcessor.cjs"),o=require("./assets.cjs"),s=require("./richTextFields.cjs"),c=require("./features.cjs");var l=class extends Error{errors;warnings;constructor(e,t){super(`Invalid OQSE file:\n${u(e).join(`
2
+ `)}`),this.name=`OQSEValidationError`,this.errors=e,this.warnings=t}};function u(e){return e.map(e=>e.path?`${e.path}: ${e.message}`:e.message)}var d=1,f=`1.0`;function p(e){return v(e,`load`)}function m(e){let t=v(e,`strict`),n=t.success&&t.errors.length===0;return{success:n,data:n?t.data:void 0,errors:t.errors,warnings:t.warnings}}function h(e){let t=m(e);if(!t.success)throw new l(t.errors,t.warnings);return t.data}var g=new Set(e.OFFICIAL_ITEM_TYPES),_=class{errors=[];warnings=[];add(e,n,r,i,a){let o={severity:e,code:n,message:r,path:t.formatPath(i)};a&&(o.itemId=a),(e===`error`?this.errors:this.warnings).push(o)}addZod(e,t,n,r){for(let i of e.issues)this.add(`error`,t,i.message,[...n,...i.path],r)}};function v(e,i){let o=new _,l=()=>({success:!1,errors:o.errors,warnings:o.warnings}),u=i===`strict`?`error`:`warning`;try{t.validateJsonDepth(e,10)}catch(e){return o.add(`error`,`SECURITY_LIMIT`,e.message,[]),l()}if(!w(e)||!w(e.meta)||!Array.isArray(e.items))return o.add(`error`,`INVALID_ROOT`,`An OQSE file must be an object with "version", "meta" and "items" (array).`,[]),l();let d=structuredClone(e);if(d.$schema!==void 0&&typeof d.$schema!=`string`)return o.add(`error`,`INVALID_ROOT`,`"$schema" must be a string URL.`,[`$schema`]),l();if(typeof d.version!=`string`||!/^\d+\.\d+$/.test(d.version))return o.add(`error`,`INVALID_VERSION`,`Version must be in MAJOR.MINOR format (e.g. "1.0").`,[`version`]),l();let f=Number(d.version.split(`.`)[0]);if(f===0)return o.add(`error`,`UNSUPPORTED_VERSION`,`OQSE ${d.version} is a pre-release version; this library reads OQSE 1.x (convert the set to 1.0).`,[`version`]),l();if(f!==1&&o.add(`warning`,`UNSUPPORTED_VERSION`,`Unsupported MAJOR version ${d.version}; loading in best-effort mode.`,[`version`]),d.items.length>1e4)return o.add(`error`,`TOO_MANY_ITEMS`,`Maximum 10000 items per set.`,[`items`]),l();if(i===`load`&&!C(d.meta,[`meta`,`assets`],o))return l();let p=n.OQSEMetaSchema.safeParse(d.meta);if(!p.success)return o.addZod(p.error,`INVALID_META`,[`meta`]),l();let m=p.data,h={features:r.OFFICIAL_FEATURE_KEYS,itemProperties:r.OFFICIAL_ITEM_PROPERTIES,metaProperties:r.OFFICIAL_META_PROPERTIES};for(let[e,t]of Object.entries(h))for(let n of m.requirements?.[e]??[])!n.startsWith(`x-`)&&!t.includes(n)&&o.add(`warning`,`UNKNOWN_FEATURE`,`Unknown ${e===`features`?`feature`:`property`} "${n}" in requirements.${e} (ignored).`,[`meta`,`requirements`,e]);let v=new Set(m.requirements?.features??[]),x=v.has(`html`),S=v.has(`latex`);b(m.assets,[`meta`,`assets`],o,i),m.thumbnail&&!m.assets?.[m.thumbnail]&&(o.add(u,`MISSING_THUMBNAIL_ASSET`,`Thumbnail references non-existent asset "${m.thumbnail}".`,[`meta`,`thumbnail`]),i===`load`&&delete m.thumbnail);for(let[e,t]of s.metaRichText(m)){let n=x?null:a.findRawHtml(t,{latex:S});n&&(o.add(u,`RAW_HTML_NOT_ALLOWED`,T(n),e),i===`load`&&E(m,e.slice(1)))}let D=new Set([m.id]),O=[];d.items.forEach((e,t)=>{let r=[`items`,t],a=w(e)&&typeof e.id==`string`?e.id:void 0,s=w(e)?e.type:void 0;if(typeof s==`string`&&!s.startsWith(`x-`)&&!g.has(s)){o.add(u,`UNKNOWN_ITEM_TYPE`,`Unknown item type "${s}" (custom types must use the "x-" prefix).`,[...r,`type`],a);return}if(i===`load`&&w(e)&&!C(e,[...r,`assets`],o,a))return;let c=n.getItemSchema(e).safeParse(e);if(!c.success){o.addZod(c.error,`INVALID_ITEM`,r,a);return}let l=c.data;if(D.has(l.id)){o.add(`error`,`DUPLICATE_ID`,`Duplicate ID "${l.id}" (IDs must be unique within the set, including meta.id).`,[...r,`id`],l.id);return}D.add(l.id),b(l.assets,[...r,`assets`],o,i,l.id),y(l,r,m,x,S,o,u)&&O.push({item:l,index:t})});let k=O.map(e=>e.item),A=new Set(k.map(e=>e.id)),j=new Set((m.sourceMaterials??[]).map(e=>e.id));for(let{item:e,index:t}of O){let n=[`items`,t];for(let t of[`relatedItems`,`dependencyItems`]){let r=e[t];if(!r)continue;let a=r.filter(t=>t!==e.id&&A.has(t));a.length!==r.length&&(o.add(u,`DANGLING_REFERENCE`,`${t} contains IDs that do not exist in the set or reference the item itself.`,[...n,t],e.id),i===`load`&&(e[t]=a))}if(e.sources){let t=e.sources.filter(e=>j.has(e.id));t.length!==e.sources.length&&(o.add(u,`DANGLING_REFERENCE`,`sources reference source materials that do not exist in meta.sourceMaterials.`,[...n,`sources`],e.id),i===`load`&&(e.sources=t))}}for(let e of c.detectFeatures({meta:m,items:k}))e!==`html`&&!v.has(e)&&o.add(`warning`,`UNDECLARED_FEATURE`,`The content uses "${e}", but meta.requirements.features does not declare it.`,[`meta`,`requirements`,`features`]);if(!S)for(let{item:e,index:t}of O){let n=s.itemRichText(e).find(([,e])=>c.looksLikeLatex(e));n&&o.add(`warning`,`LOOKS_LIKE_LATEX`,`The text looks like a LaTeX formula; if it is one, declare the "latex" feature (otherwise $ is an ordinary character).`,[`items`,t,...n[0]],e.id)}return{success:!0,data:{...d,meta:m,items:k},errors:o.errors,warnings:o.warnings}}function y(e,t,n,r,c,l,u){let d=!0;if(!r)for(let[n,r]of s.itemRichText(e)){let i=a.findRawHtml(r,{latex:c});i&&(l.add(`error`,`RAW_HTML_NOT_ALLOWED`,T(i),[...t,...n],e.id),d=!1)}if(`targetAsset`in e&&typeof e.targetAsset==`string`&&!e.type.startsWith(`x-`)){let r=o.resolveAsset(e.targetAsset,e,n),i=e.type===`pin-on-model`?`model`:`image`;r?r.type!==i&&(l.add(`error`,`INVALID_TARGET_ASSET`,`targetAsset "${e.targetAsset}" must be of type "${i}".`,[...t,`targetAsset`],e.id),d=!1):(l.add(`error`,`MISSING_TARGET_ASSET`,`targetAsset "${e.targetAsset}" does not exist in item.assets or meta.assets.`,[...t,`targetAsset`],e.id),d=!1)}let f=e.type===`timeline`?e.events:e.type===`categorize`?e.items:void 0;if(f){let n=f.map(e=>e.id);new Set(n).size!==n.length&&(l.add(`error`,`DUPLICATE_ID`,`Internal IDs must be unique within the item.`,[...t,e.type===`timeline`?`events`:`items`],e.id),d=!1)}if(e.type===`chess-puzzle`&&e.correctAnswers.some(e=>e.length%2==0)&&l.add(`warning`,`CHESS_SEQUENCE_LENGTH`,`Each move sequence should end with the learner's move (an odd number of moves; the application plays the replies).`,[...t,`correctAnswers`],e.id),e.type===`note`)for(let n of[`content`,`hiddenContent`]){let r=e[n];r&&i.findHeadings(i.splitLines(r)).some(e=>e.level===1)&&l.add(`warning`,`NOTE_HEADING_LEVEL`,`Headings inside a note should start at level 2 (##); the note title is level 1.`,[...t,n],e.id)}let p=e.type===`fill-in-blanks`||e.type===`fill-in-select`;for(let[n,r]of s.itemRichText(e)){for(let i of x(r)){if(/^https:\/\//i.test(i))continue;let r=/^http:\/\//i.test(i);l.add(r?`warning`:u,`INVALID_IMAGE_URL`,r?`Image "${i}" uses http:// (prefer https://).`:`Image "${i}" needs an absolute https:// URL (use <asset:key /> for other media).`,[...t,...n],e.id)}!(p&&n.length===1&&n[0]===`text`)&&/<blank:[a-zA-Z0-9_-]+\s*\/>/i.test(r)&&l.add(`warning`,`BLANK_OUTSIDE_TEXT`,`Blank tags belong only in the text of fill-in-blanks and fill-in-select (shown here as an empty line).`,[...t,...n],e.id)}for(let[r,i]of s.itemRichText(e))for(let s of a.findAssetKeys(i))o.resolveAsset(s,e,n)||l.add(`warning`,`MISSING_ASSET`,`<asset:${s} /> does not exist in item.assets or meta.assets.`,[...t,...r],e.id);for(let r of Object.keys(e.assets??{}))n.assets?.[r]&&l.add(`warning`,`ASSET_SHADOWED`,`Item asset "${r}" shadows a global asset with the same key; use unique keys.`,[...t,`assets`,r],e.id);return d}function b(e,t,n,r,i){for(let[a,o]of Object.entries(e??{}))o.type!==`image`||o.altText?.trim()||(n.add(r===`strict`?`error`:`warning`,`MISSING_ALT_TEXT`,`Image "${a}" has no altText (alternative text for screen readers).`,[...t,a,`altText`],i),r===`load`&&(o.altText=S(o.value)||a))}function x(e){return[...e.replace(/^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^ {0,3}\1[`~]*[ \t]*$|(?![\s\S]))/gm,``).replace(/`[^`\n]+`/g,``).matchAll(/!\[[^\]\n]*\]\(\s*<?([^)\s>]+)/g)].map(e=>e[1])}function S(e){return(e.split(/[?#]/)[0].split(`/`).pop()??``).replace(/\.[a-z0-9]+$/i,``).replace(/[-_]+/g,` `).trim()}function C(e,t,n,r){let i=e.assets;if(!w(i))return!0;let a={};for(let[e,o]of Object.entries(i)){let i=e.toLowerCase();if(i in a)return n.add(`error`,`ASSET_KEY_COLLISION`,`Asset keys collide after lowercasing ("${e}").`,[...t,e],r),!1;i!==e&&n.add(`warning`,`ASSET_KEY_NORMALIZED`,`Asset key "${e}" was converted to "${i}".`,[...t,e],r),a[i]=o}e.assets=a;for(let t of[`thumbnail`,`targetAsset`])typeof e[t]==`string`&&(e[t]=e[t].toLowerCase());return!0}function w(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function T(e){return`Raw HTML ${e} is not allowed unless the set declares the "html" feature (use Markdown instead).`}function E(e,t){let n=e;for(let e of t.slice(0,-1))n=n[e];delete n[t[t.length-1]]}exports.OQSEValidationError=l,exports.OQSE_VERSION=f,exports.SUPPORTED_OQSE_MAJOR_VERSION=d,exports.formatOQSEIssues=u,exports.loadOQSEFile=p,exports.safeValidateOQSEFile=m,exports.validateOQSEFile=h;