@longsightgroup/qti3-core 0.7.2 → 0.8.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.
Files changed (119) hide show
  1. package/README.md +41 -10
  2. package/dist/content-text.d.ts +14 -0
  3. package/dist/content-text.d.ts.map +1 -0
  4. package/dist/content-text.js +58 -0
  5. package/dist/content-text.js.map +1 -0
  6. package/dist/index.d.ts +7 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +6 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/package-xml.d.ts +12 -0
  11. package/dist/package-xml.d.ts.map +1 -0
  12. package/dist/package-xml.js +17 -0
  13. package/dist/package-xml.js.map +1 -0
  14. package/dist/parser-custom-interactions.d.ts +8 -0
  15. package/dist/parser-custom-interactions.d.ts.map +1 -0
  16. package/dist/parser-custom-interactions.js +178 -0
  17. package/dist/parser-custom-interactions.js.map +1 -0
  18. package/dist/parser-declarations.d.ts +6 -0
  19. package/dist/parser-declarations.d.ts.map +1 -0
  20. package/dist/parser-declarations.js +136 -0
  21. package/dist/parser-declarations.js.map +1 -0
  22. package/dist/parser-item-metadata.d.ts +9 -0
  23. package/dist/parser-item-metadata.d.ts.map +1 -0
  24. package/dist/parser-item-metadata.js +158 -0
  25. package/dist/parser-item-metadata.js.map +1 -0
  26. package/dist/parser-processing.d.ts +5 -0
  27. package/dist/parser-processing.d.ts.map +1 -0
  28. package/dist/parser-processing.js +642 -0
  29. package/dist/parser-processing.js.map +1 -0
  30. package/dist/parser-values.d.ts +8 -0
  31. package/dist/parser-values.d.ts.map +1 -0
  32. package/dist/parser-values.js +49 -0
  33. package/dist/parser-values.js.map +1 -0
  34. package/dist/parser.d.ts.map +1 -1
  35. package/dist/parser.js +32 -1086
  36. package/dist/parser.js.map +1 -1
  37. package/dist/processing-expression-tags.d.ts +56 -0
  38. package/dist/processing-expression-tags.d.ts.map +1 -0
  39. package/dist/processing-expression-tags.js +52 -0
  40. package/dist/processing-expression-tags.js.map +1 -0
  41. package/dist/processing-rules.d.ts +3 -0
  42. package/dist/processing-rules.d.ts.map +1 -0
  43. package/dist/processing-rules.js +10 -0
  44. package/dist/processing-rules.js.map +1 -0
  45. package/dist/serializer-processing-expressions.d.ts +8 -0
  46. package/dist/serializer-processing-expressions.d.ts.map +1 -0
  47. package/dist/serializer-processing-expressions.js +198 -0
  48. package/dist/serializer-processing-expressions.js.map +1 -0
  49. package/dist/serializer-processing-xml.d.ts +15 -0
  50. package/dist/serializer-processing-xml.d.ts.map +1 -0
  51. package/dist/serializer-processing-xml.js +46 -0
  52. package/dist/serializer-processing-xml.js.map +1 -0
  53. package/dist/serializer-processing.d.ts +8 -0
  54. package/dist/serializer-processing.d.ts.map +1 -0
  55. package/dist/serializer-processing.js +106 -0
  56. package/dist/serializer-processing.js.map +1 -0
  57. package/dist/shared-vocabulary-authoring.d.ts +50 -0
  58. package/dist/shared-vocabulary-authoring.d.ts.map +1 -0
  59. package/dist/shared-vocabulary-authoring.js +302 -0
  60. package/dist/shared-vocabulary-authoring.js.map +1 -0
  61. package/dist/shared-vocabulary-interaction-sets.d.ts +6 -0
  62. package/dist/shared-vocabulary-interaction-sets.d.ts.map +1 -0
  63. package/dist/shared-vocabulary-interaction-sets.js +23 -0
  64. package/dist/shared-vocabulary-interaction-sets.js.map +1 -0
  65. package/dist/shared-vocabulary-interaction-validation.d.ts +3 -0
  66. package/dist/shared-vocabulary-interaction-validation.d.ts.map +1 -0
  67. package/dist/shared-vocabulary-interaction-validation.js +99 -0
  68. package/dist/shared-vocabulary-interaction-validation.js.map +1 -0
  69. package/dist/shared-vocabulary-registry-validation.d.ts +5 -0
  70. package/dist/shared-vocabulary-registry-validation.d.ts.map +1 -0
  71. package/dist/shared-vocabulary-registry-validation.js +147 -0
  72. package/dist/shared-vocabulary-registry-validation.js.map +1 -0
  73. package/dist/shared-vocabulary-support.d.ts.map +1 -1
  74. package/dist/shared-vocabulary-support.js +96 -56
  75. package/dist/shared-vocabulary-support.js.map +1 -1
  76. package/dist/shared-vocabulary.d.ts +6 -0
  77. package/dist/shared-vocabulary.d.ts.map +1 -1
  78. package/dist/shared-vocabulary.js +12 -0
  79. package/dist/shared-vocabulary.js.map +1 -1
  80. package/dist/support.d.ts +4 -1
  81. package/dist/support.d.ts.map +1 -1
  82. package/dist/support.js +159 -10
  83. package/dist/support.js.map +1 -1
  84. package/dist/types.d.ts +38 -7
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/validation.d.ts.map +1 -1
  87. package/dist/validation.js +29 -250
  88. package/dist/validation.js.map +1 -1
  89. package/dist/xml.d.ts +10 -0
  90. package/dist/xml.d.ts.map +1 -1
  91. package/dist/xml.js +321 -249
  92. package/dist/xml.js.map +1 -1
  93. package/package.json +1 -4
  94. package/src/content-text.ts +80 -0
  95. package/src/index.ts +67 -0
  96. package/src/interaction-test-fixtures.ts +44 -0
  97. package/src/package-xml.ts +30 -0
  98. package/src/parser-custom-interactions.ts +309 -0
  99. package/src/parser-declarations.ts +174 -0
  100. package/src/parser-item-metadata.ts +194 -0
  101. package/src/parser-processing.ts +701 -0
  102. package/src/parser-values.ts +61 -0
  103. package/src/parser.ts +59 -1230
  104. package/src/processing-expression-tags.ts +65 -0
  105. package/src/processing-rules.ts +11 -0
  106. package/src/serializer-processing-expressions.ts +456 -0
  107. package/src/serializer-processing-xml.ts +67 -0
  108. package/src/serializer-processing.fixtures.ts +469 -0
  109. package/src/serializer-processing.ts +196 -0
  110. package/src/shared-vocabulary-authoring.ts +409 -0
  111. package/src/shared-vocabulary-interaction-sets.ts +28 -0
  112. package/src/shared-vocabulary-interaction-validation.ts +143 -0
  113. package/src/shared-vocabulary-registry-validation.ts +207 -0
  114. package/src/shared-vocabulary-support.ts +127 -101
  115. package/src/shared-vocabulary.ts +18 -0
  116. package/src/support.ts +174 -14
  117. package/src/types.ts +48 -7
  118. package/src/validation.ts +33 -341
  119. package/src/xml.ts +378 -255
package/src/support.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import type {
2
+ QtiDiagnostic,
2
3
  QtiElementSupport,
3
4
  QtiInteractionElementSupport,
5
+ QtiInteractionRegistryStatus,
4
6
  QtiInteractionType,
7
+ QtiItemMetadataElementSupport,
5
8
  QtiProcessingElementSupport,
9
+ QtiSourceLocation,
6
10
  } from "./types.js";
7
11
 
8
12
  export const interactionSupport: QtiInteractionElementSupport[] = [
@@ -10,9 +14,7 @@ export const interactionSupport: QtiInteractionElementSupport[] = [
10
14
  entry("qti-choice-interaction", "choice"),
11
15
  entry("qti-drawing-interaction", "drawing"),
12
16
  entry("qti-end-attempt-interaction", "endAttempt"),
13
- textControlInteractionEntry("qti-extended-text-interaction", "extendedText", [
14
- "packages/fixtures/packages/sv-matrix/items/extended-text-pattern-mask.xml",
15
- ]),
17
+ extendedTextInteractionEntry(),
16
18
  entry("qti-gap-match-interaction", "gapMatch"),
17
19
  entry("qti-graphic-associate-interaction", "graphicAssociate"),
18
20
  entry("qti-graphic-gap-match-interaction", "graphicGapMatch"),
@@ -27,9 +29,7 @@ export const interactionSupport: QtiInteractionElementSupport[] = [
27
29
  pciEntry(),
28
30
  entry("qti-select-point-interaction", "selectPoint"),
29
31
  entry("qti-slider-interaction", "slider"),
30
- textControlInteractionEntry("qti-text-entry-interaction", "textEntry", [
31
- "packages/fixtures/packages/sv-matrix/items/text-entry-pattern-mask-inline.xml",
32
- ]),
32
+ textEntryInteractionEntry(),
33
33
  entry("qti-upload-interaction", "upload"),
34
34
  ];
35
35
 
@@ -192,10 +192,114 @@ export const processingSupport: QtiProcessingElementSupport[] = [
192
192
  processingEntry("qti-custom-operator", "packages/core/src/core.test.ts"),
193
193
  ];
194
194
 
195
+ export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
196
+ {
197
+ qtiName: "qti-catalog-info",
198
+ category: "itemMetadata",
199
+ support: "parsed",
200
+ specReference: "QTI 3.0.1 ASI",
201
+ parse: true,
202
+ validate: true,
203
+ render: false,
204
+ process: false,
205
+ fixtures: [
206
+ "packages/fixtures/packages/basic-item-player/valid-item-only/items/tolerance-extra-features.xml",
207
+ ],
208
+ tests: ["packages/core/src/core.test.ts", "packages/core/src/parser-item-metadata.test.ts"],
209
+ notes:
210
+ "Parsed by parser-item-metadata.ts and validated by validateCatalogInfo. Duplicate containers emit item.child.duplicate.",
211
+ },
212
+ {
213
+ qtiName: "qti-stylesheet",
214
+ category: "itemMetadata",
215
+ support: "parsed",
216
+ specReference: "QTI 3.0.1 ASI",
217
+ parse: true,
218
+ validate: true,
219
+ render: false,
220
+ process: false,
221
+ fixtures: [
222
+ "packages/fixtures/packages/basic-item-player/valid-item-only/items/tolerance-extra-features.xml",
223
+ ],
224
+ tests: ["packages/core/src/core.test.ts", "packages/core/src/parser-item-metadata.test.ts"],
225
+ notes: "Parsed by parser-item-metadata.ts and validated by validateStylesheets.",
226
+ },
227
+ {
228
+ qtiName: "qti-modal-feedback",
229
+ category: "itemMetadata",
230
+ support: "parsed",
231
+ specReference: "QTI 3.0.1 ASI",
232
+ parse: true,
233
+ validate: true,
234
+ render: false,
235
+ process: false,
236
+ fixtures: ["packages/fixtures/src/index.ts"],
237
+ tests: ["packages/core/src/core.test.ts", "packages/core/src/parser-item-metadata.test.ts"],
238
+ notes:
239
+ "Parsed by parser-item-metadata.ts and validated by validateModalFeedback. Player rendering remains out of scope for this metadata slice.",
240
+ },
241
+ {
242
+ qtiName: "qti-companion-materials-info",
243
+ category: "itemMetadata",
244
+ support: "parsed",
245
+ specReference: "QTI 3.0.1 ASI",
246
+ parse: true,
247
+ validate: true,
248
+ render: false,
249
+ process: false,
250
+ fixtures: [
251
+ "packages/fixtures/packages/basic-item-player/valid-item-only/items/tolerance-extra-features.xml",
252
+ ],
253
+ tests: [
254
+ "packages/core/src/parser-companion-materials.test.ts",
255
+ "packages/core/src/parser-item-metadata.test.ts",
256
+ ],
257
+ notes:
258
+ "Parses qti-physical-material text only. qti-digital-material is preserved in unparsedChildren and emits companionMaterials.child.unsupported at parse time.",
259
+ },
260
+ {
261
+ qtiName: "qti-physical-material",
262
+ category: "itemMetadata",
263
+ support: "parsed",
264
+ specReference: "QTI 3.0.1 ASI",
265
+ parse: true,
266
+ validate: true,
267
+ render: false,
268
+ process: false,
269
+ fixtures: ["packages/core/src/parser-companion-materials.test.ts"],
270
+ tests: [
271
+ "packages/core/src/parser-companion-materials.test.ts",
272
+ "packages/core/src/parser-item-metadata.test.ts",
273
+ ],
274
+ notes:
275
+ "Child of qti-companion-materials-info. Empty XML text emits companionMaterials.physicalMaterial.empty at parse time; invalid model entries emit companionMaterials.physicalMaterial.empty.model at validation time.",
276
+ },
277
+ {
278
+ qtiName: "qti-digital-material",
279
+ category: "itemMetadata",
280
+ support: "unsupported",
281
+ specReference: "QTI 3.0.1 ASI",
282
+ parse: false,
283
+ validate: false,
284
+ render: false,
285
+ process: false,
286
+ fixtures: [
287
+ "packages/fixtures/packages/basic-item-player/valid-item-only/items/tolerance-extra-features.xml",
288
+ ],
289
+ tests: [
290
+ "packages/core/src/parser-companion-materials.test.ts",
291
+ "packages/core/src/parser-item-metadata.test.ts",
292
+ ],
293
+ notes:
294
+ "Recognized as an unparsed child of qti-companion-materials-info. Full digital material parsing is not implemented yet.",
295
+ },
296
+ ];
297
+
195
298
  export const elementSupport: QtiElementSupport[] = [
196
299
  ...interactionSupport,
197
300
  ...deprecatedInteractionSupport,
198
301
  ...processingSupport,
302
+ ...itemMetadataSupport,
199
303
  ];
200
304
 
201
305
  const allInteractionSupport = [...interactionSupport, ...deprecatedInteractionSupport];
@@ -208,6 +312,42 @@ export function getInteractionSupport(qtiName: string): QtiElementSupport | unde
208
312
  return allInteractionSupport.find((item) => item.qtiName === qtiName);
209
313
  }
210
314
 
315
+ export function interactionRegistryStatus(qtiName: string): QtiInteractionRegistryStatus {
316
+ const support = getInteractionSupport(qtiName);
317
+ if (!support) return "unsupported";
318
+ return support.support === "deprecated" ? "deprecated" : "supported";
319
+ }
320
+
321
+ export function interactionRegistryDiagnostics(
322
+ qtiName: string,
323
+ source: QtiSourceLocation,
324
+ ): QtiDiagnostic[] {
325
+ const support = getInteractionSupport(qtiName);
326
+ if (!support) {
327
+ return [
328
+ {
329
+ code: "interaction.unsupported",
330
+ severity: "warning",
331
+ message: `${qtiName} is not currently in the support registry.`,
332
+ path: source.path,
333
+ source,
334
+ },
335
+ ];
336
+ }
337
+ if (support.support === "deprecated") {
338
+ return [
339
+ {
340
+ code: "interaction.deprecated",
341
+ severity: "warning",
342
+ message: `${qtiName} is deprecated. ${support.notes ?? ""}`.trim(),
343
+ path: source.path,
344
+ source,
345
+ },
346
+ ];
347
+ }
348
+ return [];
349
+ }
350
+
211
351
  function entry(qtiName: string, interactionType: QtiInteractionType): QtiInteractionElementSupport {
212
352
  return {
213
353
  qtiName,
@@ -229,14 +369,34 @@ function entry(qtiName: string, interactionType: QtiInteractionType): QtiInterac
229
369
  };
230
370
  }
231
371
 
232
- function textControlInteractionEntry(
233
- qtiName: string,
234
- interactionType: "extendedText" | "textEntry",
235
- extraFixtures: string[],
236
- ): QtiInteractionElementSupport {
372
+ function extendedTextInteractionEntry(): QtiInteractionElementSupport {
373
+ return {
374
+ ...entry("qti-extended-text-interaction", "extendedText"),
375
+ fixtures: [
376
+ "packages/fixtures/xml/extendedText-reference.xml",
377
+ "packages/fixtures/packages/sv-matrix/items/extended-text-pattern-mask.xml",
378
+ "packages/fixtures/packages/sv-matrix/items/extended-text-xhtml.xml",
379
+ ],
380
+ tests: [
381
+ "packages/fixtures/src/fixtures.test.ts",
382
+ "packages/conformance/src/conformance.test.ts",
383
+ "packages/a11y/src/a11y.test.ts",
384
+ "packages/core/src/pattern-mask.test.ts",
385
+ "tests/browser/player.spec.ts",
386
+ "tests/browser/player-dom-behavior.spec.ts",
387
+ "tests/browser/player-extended-text-xhtml.spec.ts",
388
+ ],
389
+ notes: "Supports plain and format=xhtml extended text.",
390
+ };
391
+ }
392
+
393
+ function textEntryInteractionEntry(): QtiInteractionElementSupport {
237
394
  return {
238
- ...entry(qtiName, interactionType),
239
- fixtures: [`packages/fixtures/xml/${interactionType}-reference.xml`, ...extraFixtures],
395
+ ...entry("qti-text-entry-interaction", "textEntry"),
396
+ fixtures: [
397
+ "packages/fixtures/xml/textEntry-reference.xml",
398
+ "packages/fixtures/packages/sv-matrix/items/text-entry-pattern-mask-inline.xml",
399
+ ],
240
400
  tests: [
241
401
  "packages/fixtures/src/fixtures.test.ts",
242
402
  "packages/conformance/src/conformance.test.ts",
@@ -245,7 +405,7 @@ function textControlInteractionEntry(
245
405
  "tests/browser/player.spec.ts",
246
406
  "tests/browser/player-dom-behavior.spec.ts",
247
407
  ],
248
- notes: "Supports placeholder-text and pattern-mask on block and inline text-entry controls.",
408
+ notes: "Supports placeholder-text and pattern-mask on text-entry controls.",
249
409
  };
250
410
  }
251
411
 
package/src/types.ts CHANGED
@@ -138,7 +138,10 @@ export interface QtiTemplateDeclaration extends QtiVariableDeclaration {
138
138
 
139
139
  export interface QtiChoice {
140
140
  identifier: string;
141
+ /** Plain-text fallback used for speech, TTS, diagnostics, and native controls. */
141
142
  text: string;
143
+ /** Parsed visual content for renderers that can display rich choice markup such as MathML. */
144
+ content?: QtiContentNode[] | undefined;
142
145
  asset?: QtiObjectAsset | undefined;
143
146
  role: QtiChoiceRole;
144
147
  qtiName: string;
@@ -186,18 +189,27 @@ export type QtiInteractionType =
186
189
  | "textEntry"
187
190
  | "upload";
188
191
 
192
+ /** Parse-time status from the core interaction support registry. */
193
+ export type QtiInteractionRegistryStatus = "supported" | "deprecated" | "unsupported";
194
+
189
195
  export interface QtiInteraction {
190
196
  type: QtiInteractionType;
197
+ /** Parse-time status from the core interaction support registry. */
198
+ registryStatus: QtiInteractionRegistryStatus;
191
199
  qtiName: string;
192
200
  responseIdentifier?: string | undefined;
193
201
  responseCardinality?: QtiCardinality | undefined;
194
202
  responseBaseType?: QtiBaseType | undefined;
203
+ /** Flat accessibility/fallback label derived from prompt content with annotations stripped. */
195
204
  prompt?: string | undefined;
205
+ /** Structured prompt content for visual rendering. When present, block headings render this instead of prompt. */
206
+ promptContent?: QtiContentNode[] | undefined;
196
207
  promptAttributes?: Record<string, string> | undefined;
197
208
  promptSource?: QtiSourceLocation | undefined;
198
209
  contextText?: string | undefined;
199
210
  object?: QtiObjectAsset | undefined;
200
211
  positionObjectStage?: QtiObjectAsset | undefined;
212
+ customInteraction?: QtiCustomInteractionDefinition | undefined;
201
213
  portableCustom?: QtiPortableCustomDefinition | undefined;
202
214
  choices: QtiChoice[];
203
215
  hottextSegments?: QtiHottextSegment[] | undefined;
@@ -266,20 +278,25 @@ export interface QtiMediaTrack {
266
278
  source?: QtiSourceLocation | undefined;
267
279
  }
268
280
 
269
- export interface QtiPortableCustomDefinition {
281
+ export interface QtiInteractionMarkupDefinition {
270
282
  responseIdentifier?: string | undefined;
283
+ interactionMarkup: QtiContentNode[];
284
+ interactionMarkupRaw?: string | undefined;
285
+ dataAttributes: Record<string, string>;
286
+ attributes: Record<string, string>;
287
+ source?: QtiSourceLocation | undefined;
288
+ }
289
+
290
+ export type QtiCustomInteractionDefinition = QtiInteractionMarkupDefinition;
291
+
292
+ export interface QtiPortableCustomDefinition extends QtiInteractionMarkupDefinition {
271
293
  customInteractionTypeIdentifier?: string | undefined;
272
294
  module?: string | undefined;
273
295
  interactionModules?: QtiPortableCustomInteractionModules | undefined;
274
- interactionMarkup: QtiContentNode[];
275
- interactionMarkupRaw?: string | undefined;
276
296
  templateVariables: QtiPortableCustomVariableBinding[];
277
297
  contextVariables: QtiPortableCustomVariableBinding[];
278
298
  stylesheets: QtiStylesheet[];
279
299
  catalogInfo?: QtiCatalogInfo | undefined;
280
- dataAttributes: Record<string, string>;
281
- attributes: Record<string, string>;
282
- source?: QtiSourceLocation | undefined;
283
300
  }
284
301
 
285
302
  export interface QtiPortableCustomInteractionModules {
@@ -429,6 +446,7 @@ export interface QtiAssessmentItem {
429
446
  interactions: QtiInteraction[];
430
447
  modalFeedback: QtiModalFeedback[];
431
448
  catalogInfo?: QtiCatalogInfo | undefined;
449
+ companionMaterials?: QtiCompanionMaterialsInfo | undefined;
432
450
  catalogReferences: QtiCatalogReference[];
433
451
  stylesheets: QtiStylesheet[];
434
452
  body: QtiContentNode[];
@@ -436,6 +454,22 @@ export interface QtiAssessmentItem {
436
454
  source?: QtiSourceLocation | undefined;
437
455
  }
438
456
 
457
+ export interface QtiCompanionMaterialsInfo {
458
+ physicalMaterials: QtiPhysicalMaterial[];
459
+ unparsedChildren: QtiCompanionMaterialsUnparsedChild[];
460
+ source?: QtiSourceLocation | undefined;
461
+ }
462
+
463
+ export interface QtiCompanionMaterialsUnparsedChild {
464
+ qtiName: string;
465
+ source?: QtiSourceLocation | undefined;
466
+ }
467
+
468
+ export interface QtiPhysicalMaterial {
469
+ text: string;
470
+ source?: QtiSourceLocation | undefined;
471
+ }
472
+
439
473
  export interface QtiModalFeedback {
440
474
  identifier: string;
441
475
  outcomeIdentifier: string;
@@ -682,7 +716,10 @@ export interface QtiScoreResult {
682
716
  state: QtiAttemptStateV1;
683
717
  }
684
718
 
685
- export type QtiElementSupport = QtiInteractionElementSupport | QtiProcessingElementSupport;
719
+ export type QtiElementSupport =
720
+ | QtiInteractionElementSupport
721
+ | QtiProcessingElementSupport
722
+ | QtiItemMetadataElementSupport;
686
723
 
687
724
  interface QtiElementSupportBase {
688
725
  qtiName: string;
@@ -706,6 +743,10 @@ export interface QtiProcessingElementSupport extends QtiElementSupportBase {
706
743
  category: "processing";
707
744
  }
708
745
 
746
+ export interface QtiItemMetadataElementSupport extends QtiElementSupportBase {
747
+ category: "itemMetadata";
748
+ }
749
+
709
750
  export interface SharedVocabularyClassSupport {
710
751
  className: string;
711
752
  scope: "interaction" | "content" | "gap";