@longsightgroup/qti3-core 0.7.3 → 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 +7 -1
  3. package/dist/content-text.d.ts.map +1 -1
  4. package/dist/content-text.js +14 -0
  5. package/dist/content-text.js.map +1 -1
  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 +15 -1083
  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 +130 -0
  83. package/dist/support.js.map +1 -1
  84. package/dist/types.d.ts +32 -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 -272
  92. package/dist/xml.js.map +1 -1
  93. package/package.json +1 -4
  94. package/src/content-text.ts +20 -1
  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 +36 -1227
  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 +144 -0
  117. package/src/types.ts +42 -7
  118. package/src/validation.ts +33 -341
  119. package/src/xml.ts +376 -276
package/dist/parser.js CHANGED
@@ -1,5 +1,10 @@
1
- import { flatTextFromContent } from "./content-text.js";
2
- import { getInteractionSupport, interactionNameToType, processingSupport } from "./support.js";
1
+ import { appendContentTextNode, flatTextFromContent } from "./content-text.js";
2
+ import { parseOutcomeDeclaration, parseResponseDeclaration, parseTemplateDeclaration, } from "./parser-declarations.js";
3
+ import { firstChildElement, parseCatalogInfo, parseCatalogReferences, parseCompanionMaterialsInfo, parseModalFeedback, parseStylesheet, } from "./parser-item-metadata.js";
4
+ import { parseXmlBoolean } from "./parser-values.js";
5
+ import { parseInteractionCustomPayload } from "./parser-custom-interactions.js";
6
+ import { parseResponseProcessing, parseTemplateProcessing } from "./parser-processing.js";
7
+ import { interactionNameToType, interactionRegistryDiagnostics, interactionRegistryStatus, processingSupport, } from "./support.js";
3
8
  import { validateAssessmentItem } from "./validation.js";
4
9
  import { childElements, descendants, parseXmlTree, textContent } from "./xml.js";
5
10
  const qtiAssessmentItemNamespace = "http://www.imsglobal.org/xsd/imsqtiasi_v3p0";
@@ -80,7 +85,8 @@ function parseAssessmentItem(node, diagnostics) {
80
85
  interactions.push(...descendants(node, isInteractionElement).map((interactionNode) => parseInteraction(interactionNode, diagnostics, responseDeclarationMap)));
81
86
  }
82
87
  const modalFeedback = childElements(node, "qti-modal-feedback").map(parseModalFeedback);
83
- const catalogInfo = parseCatalogInfo(childElements(node, "qti-catalog-info")[0]);
88
+ const catalogInfo = parseCatalogInfo(firstChildElement(node, "qti-catalog-info", diagnostics, "item.child.duplicate"));
89
+ const companionMaterials = parseCompanionMaterialsInfo(firstChildElement(node, "qti-companion-materials-info", diagnostics, "item.child.duplicate"), diagnostics);
84
90
  const catalogReferences = itemBody ? parseCatalogReferences(itemBody) : [];
85
91
  const stylesheets = childElements(node, "qti-stylesheet").map(parseStylesheet);
86
92
  const prompt = itemBody ? childElements(itemBody, "qti-prompt")[0] : undefined;
@@ -101,6 +107,7 @@ function parseAssessmentItem(node, diagnostics) {
101
107
  interactions,
102
108
  modalFeedback,
103
109
  catalogInfo,
110
+ companionMaterials,
104
111
  catalogReferences,
105
112
  stylesheets,
106
113
  body,
@@ -138,45 +145,14 @@ function diagnoseProcessingElements(processingNode, diagnostics) {
138
145
  });
139
146
  }
140
147
  }
141
- function parseStylesheet(node) {
142
- return {
143
- href: node.attributes.href ?? "",
144
- type: node.attributes.type,
145
- media: node.attributes.media,
146
- title: node.attributes.title,
147
- attributes: node.attributes,
148
- source: node.source,
149
- };
150
- }
151
- function parseCatalogReferences(node) {
152
- const references = [
153
- ...(node.attributes["data-catalog-idref"] ? [node] : []),
154
- ...descendants(node, (child) => Boolean(child.attributes["data-catalog-idref"])),
155
- ];
156
- return references.map((reference) => ({
157
- idref: reference.attributes["data-catalog-idref"] ?? "",
158
- source: reference.source,
159
- }));
160
- }
161
148
  function isInteractionElement(node) {
162
149
  return interactionNameToType.has(node.localName) || /^qti-.+-interaction$/.test(node.localName);
163
150
  }
164
- function parseModalFeedback(node) {
165
- const showHide = node.attributes["show-hide"] === "hide" ? "hide" : "show";
166
- return {
167
- identifier: node.attributes.identifier ?? "",
168
- outcomeIdentifier: node.attributes["outcome-identifier"] ?? "",
169
- showHide,
170
- text: textContent(node),
171
- source: node.source,
172
- };
173
- }
174
151
  function parseContentChildren(node, diagnostics, responseDeclarationMap, interactions) {
175
152
  const content = [];
176
153
  for (const entry of node.content) {
177
154
  if (typeof entry === "string") {
178
- if (entry.length > 0)
179
- content.push({ kind: "text", text: entry, source: node.source });
155
+ appendContentTextNode(content, entry, node.source);
180
156
  continue;
181
157
  }
182
158
  const parsed = parseContentNode(entry, diagnostics, responseDeclarationMap, interactions);
@@ -226,116 +202,6 @@ function parseContentNode(node, diagnostics, responseDeclarationMap, interaction
226
202
  source: node.source,
227
203
  };
228
204
  }
229
- function parseCatalogInfo(node) {
230
- if (!node)
231
- return undefined;
232
- return {
233
- catalogs: childElements(node, "qti-catalog").map((catalog) => ({
234
- id: catalog.attributes.id ?? "",
235
- attributes: catalog.attributes,
236
- cards: childElements(catalog, "qti-card").map(parseCatalogCard),
237
- source: catalog.source,
238
- })),
239
- source: node.source,
240
- };
241
- }
242
- function parseCatalogCard(node) {
243
- return {
244
- support: node.attributes.support ?? "",
245
- htmlContent: parseCatalogHtmlContent(childElements(node, "qti-html-content")[0]),
246
- fileHrefs: childElements(node, "qti-file-href").map(parseCatalogFileHref),
247
- entries: childElements(node, "qti-card-entry").map(parseCatalogCardEntry),
248
- attributes: node.attributes,
249
- source: node.source,
250
- };
251
- }
252
- function parseCatalogCardEntry(node) {
253
- return {
254
- language: node.attributes["xml:lang"] ?? node.attributes.lang,
255
- default: node.attributes.default === "true",
256
- htmlContent: parseCatalogHtmlContent(childElements(node, "qti-html-content")[0]),
257
- fileHrefs: childElements(node, "qti-file-href").map(parseCatalogFileHref),
258
- attributes: node.attributes,
259
- source: node.source,
260
- };
261
- }
262
- function parseCatalogHtmlContent(node) {
263
- if (!node)
264
- return undefined;
265
- return {
266
- text: textContent(node),
267
- children: parseCatalogHtmlChildren(node),
268
- attributes: node.attributes,
269
- source: node.source,
270
- };
271
- }
272
- function parseCatalogHtmlChildren(node) {
273
- const content = [];
274
- for (const entry of node.content) {
275
- if (typeof entry === "string") {
276
- if (entry.length > 0)
277
- content.push({ kind: "text", text: entry, source: node.source });
278
- continue;
279
- }
280
- content.push({
281
- kind: "element",
282
- qtiName: entry.localName,
283
- attributes: entry.attributes,
284
- children: parseCatalogHtmlChildren(entry),
285
- source: entry.source,
286
- });
287
- }
288
- return content;
289
- }
290
- function parseCatalogFileHref(node) {
291
- return {
292
- href: textContent(node).trim(),
293
- mimeType: node.attributes["mime-type"],
294
- attributes: node.attributes,
295
- source: node.source,
296
- };
297
- }
298
- function parseResponseDeclaration(node) {
299
- const cardinality = parseCardinality(node.attributes.cardinality);
300
- const baseType = node.attributes["base-type"];
301
- return {
302
- kind: "response",
303
- identifier: node.attributes.identifier ?? "",
304
- cardinality,
305
- baseType,
306
- defaultValue: parseVariableValue(childElements(node, "qti-default-value")[0], baseType),
307
- correctResponse: normalizeValueForCardinality(parseVariableValue(childElements(node, "qti-correct-response")[0], baseType), cardinality),
308
- mapping: parseMapping(childElements(node, "qti-mapping")[0]),
309
- areaMapping: parseAreaMapping(childElements(node, "qti-area-mapping")[0]),
310
- attributes: node.attributes,
311
- source: node.source,
312
- };
313
- }
314
- function parseOutcomeDeclaration(node) {
315
- const baseType = node.attributes["base-type"];
316
- return {
317
- kind: "outcome",
318
- identifier: node.attributes.identifier ?? "",
319
- cardinality: parseCardinality(node.attributes.cardinality),
320
- baseType,
321
- defaultValue: parseVariableValue(childElements(node, "qti-default-value")[0], baseType),
322
- lookupTable: parseLookupTable(node, baseType),
323
- attributes: node.attributes,
324
- source: node.source,
325
- };
326
- }
327
- function parseTemplateDeclaration(node) {
328
- const baseType = node.attributes["base-type"];
329
- return {
330
- kind: "template",
331
- identifier: node.attributes.identifier ?? "",
332
- cardinality: parseCardinality(node.attributes.cardinality),
333
- baseType,
334
- defaultValue: parseVariableValue(childElements(node, "qti-default-value")[0], baseType),
335
- attributes: node.attributes,
336
- source: node.source,
337
- };
338
- }
339
205
  function parseInteraction(node, diagnostics, responseDeclarationMap) {
340
206
  const interactionType = interactionNameToType.get(node.localName);
341
207
  const responseIdentifier = node.attributes["response-identifier"];
@@ -343,25 +209,8 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
343
209
  ? responseDeclarationMap.get(responseIdentifier)
344
210
  : undefined;
345
211
  const prompt = childElements(node, "qti-prompt")[0];
346
- if (!interactionType) {
347
- diagnostics.push({
348
- code: "interaction.unsupported",
349
- severity: "warning",
350
- message: `${node.localName} is not currently in the support registry.`,
351
- path: node.source.path,
352
- source: node.source,
353
- });
354
- }
355
- const support = getInteractionSupport(node.localName);
356
- if (support?.support === "deprecated") {
357
- diagnostics.push({
358
- code: "interaction.deprecated",
359
- severity: "warning",
360
- message: `${node.localName} is deprecated. ${support.notes ?? ""}`.trim(),
361
- path: node.source.path,
362
- source: node.source,
363
- });
364
- }
212
+ const registryStatus = interactionRegistryStatus(node.localName);
213
+ diagnostics.push(...interactionRegistryDiagnostics(node.localName, node.source));
365
214
  const objectNode = interactionType === "positionObject"
366
215
  ? positionObjectInteractionObject(node)
367
216
  : interactionType === "media"
@@ -374,6 +223,7 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
374
223
  : undefined;
375
224
  return {
376
225
  type: interactionType ?? "custom",
226
+ registryStatus,
377
227
  qtiName: node.localName,
378
228
  responseIdentifier,
379
229
  responseCardinality: responseDeclaration?.cardinality,
@@ -391,9 +241,7 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
391
241
  positionObjectStage: interactionType === "positionObject"
392
242
  ? parseObjectAsset(positionObjectStageObject(node))
393
243
  : undefined,
394
- portableCustom: interactionType === "portableCustom"
395
- ? parsePortableCustomDefinition(node, diagnostics)
396
- : undefined,
244
+ ...parseInteractionCustomPayload(node, interactionType, diagnostics, isInteractionElement),
397
245
  choices: parseChoices(node, diagnostics, responseDeclarationMap),
398
246
  hottextSegments: interactionType === "hottext" ? parseHottextSegments(node) : undefined,
399
247
  gapMatchSegments: interactionType === "gapMatch" || interactionType === "graphicGapMatch"
@@ -408,135 +256,6 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
408
256
  source: node.source,
409
257
  };
410
258
  }
411
- function parsePortableCustomDefinition(node, diagnostics) {
412
- const markup = firstChildElement(node, "qti-interaction-markup", diagnostics);
413
- const modules = firstChildElement(node, "qti-interaction-modules", diagnostics);
414
- return {
415
- responseIdentifier: node.attributes["response-identifier"],
416
- customInteractionTypeIdentifier: node.attributes["custom-interaction-type-identifier"],
417
- module: node.attributes.module,
418
- interactionModules: modules ? parsePortableCustomInteractionModules(modules) : undefined,
419
- interactionMarkup: markup ? parsePortableCustomMarkupChildren(markup, diagnostics) : [],
420
- interactionMarkupRaw: markup ? serializeXmlContent(markup) : undefined,
421
- templateVariables: childElements(node, "qti-template-variable").map((variable) => parsePortableCustomVariableBinding(variable, "template")),
422
- contextVariables: childElements(node, "qti-context-variable").map((variable) => parsePortableCustomVariableBinding(variable, "context")),
423
- stylesheets: childElements(node, "qti-stylesheet").map(parseStylesheet),
424
- catalogInfo: parseCatalogInfo(childElements(node, "qti-catalog-info")[0]),
425
- dataAttributes: Object.fromEntries(Object.entries(node.attributes).filter(([name]) => name.startsWith("data-"))),
426
- attributes: node.attributes,
427
- source: node.source,
428
- };
429
- }
430
- function firstChildElement(node, name, diagnostics) {
431
- const matches = childElements(node, name);
432
- if (matches.length > 1) {
433
- diagnostics.push({
434
- code: "interaction.portableCustom.child.duplicate",
435
- severity: "error",
436
- message: `${node.localName} allows at most one ${name} child.`,
437
- path: matches[1]?.source?.path ?? node.source?.path,
438
- source: matches[1]?.source ?? node.source,
439
- });
440
- }
441
- return matches[0];
442
- }
443
- function parsePortableCustomMarkupChildren(node, diagnostics) {
444
- const content = [];
445
- for (const entry of node.content) {
446
- if (typeof entry === "string") {
447
- if (entry.length > 0)
448
- content.push({ kind: "text", text: entry, source: node.source });
449
- continue;
450
- }
451
- content.push(parsePortableCustomMarkupNode(entry, diagnostics));
452
- }
453
- return content;
454
- }
455
- function parsePortableCustomMarkupNode(node, diagnostics) {
456
- if (isInteractionElement(node)) {
457
- diagnostics.push({
458
- code: "interaction.portableCustom.markupInteraction",
459
- severity: "error",
460
- message: `qti-interaction-markup must not contain nested QTI interaction ${node.localName}.`,
461
- path: node.source?.path,
462
- source: node.source,
463
- });
464
- }
465
- if (node.localName === "qti-printed-variable") {
466
- return {
467
- kind: "printedVariable",
468
- identifier: node.attributes.identifier ?? "",
469
- format: node.attributes.format,
470
- attributes: node.attributes,
471
- source: node.source,
472
- };
473
- }
474
- if (node.localName === "qti-feedback-block" || node.localName === "qti-feedback-inline") {
475
- return {
476
- kind: "feedback",
477
- feedbackType: node.localName === "qti-feedback-block" ? "block" : "inline",
478
- identifier: node.attributes.identifier ?? "",
479
- outcomeIdentifier: node.attributes["outcome-identifier"] ?? "",
480
- showHide: node.attributes["show-hide"] === "hide" ? "hide" : "show",
481
- attributes: node.attributes,
482
- children: parsePortableCustomMarkupChildren(node, diagnostics),
483
- source: node.source,
484
- };
485
- }
486
- return {
487
- kind: "element",
488
- qtiName: node.localName,
489
- attributes: node.attributes,
490
- children: parsePortableCustomMarkupChildren(node, diagnostics),
491
- source: node.source,
492
- };
493
- }
494
- function parsePortableCustomInteractionModules(node) {
495
- return {
496
- primaryConfiguration: node.attributes["primary-configuration"],
497
- secondaryConfiguration: node.attributes["secondary-configuration"] ?? node.attributes["fallback-configuration"],
498
- modules: childElements(node, "qti-interaction-module").map(parsePortableCustomInteractionModule),
499
- attributes: node.attributes,
500
- source: node.source,
501
- };
502
- }
503
- function parsePortableCustomInteractionModule(node) {
504
- return {
505
- id: node.attributes.id,
506
- primaryPath: node.attributes["primary-path"],
507
- fallbackPath: node.attributes["fallback-path"],
508
- attributes: node.attributes,
509
- source: node.source,
510
- };
511
- }
512
- function parsePortableCustomVariableBinding(node, kind) {
513
- return {
514
- kind,
515
- identifier: node.attributes.identifier ?? node.attributes["template-identifier"],
516
- variableIdentifier: node.attributes["variable-identifier"],
517
- attributes: node.attributes,
518
- source: node.source,
519
- };
520
- }
521
- function serializeXmlContent(node) {
522
- return node.content
523
- .map((entry) => (typeof entry === "string" ? escapeXmlText(entry) : serializeXmlNode(entry)))
524
- .join("");
525
- }
526
- function serializeXmlNode(node) {
527
- const attributes = Object.entries(node.attributes)
528
- .map(([name, value]) => ` ${name}="${escapeXmlAttribute(value)}"`)
529
- .join("");
530
- if (node.content.length === 0)
531
- return `<${node.name}${attributes}/>`;
532
- return `<${node.name}${attributes}>${serializeXmlContent(node)}</${node.name}>`;
533
- }
534
- function escapeXmlText(value) {
535
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
536
- }
537
- function escapeXmlAttribute(value) {
538
- return escapeXmlText(value).replaceAll('"', "&quot;");
539
- }
540
259
  function positionObjectInteractionObject(node) {
541
260
  return childElements(node).find((child) => child.localName === "object" || child.localName === "img");
542
261
  }
@@ -883,791 +602,4 @@ function matchSetIndex(node) {
883
602
  const siblings = node.parent?.children.filter((sibling) => sibling.localName === "qti-simple-match-set") ?? [];
884
603
  return siblings.indexOf(node);
885
604
  }
886
- function parseVariableValue(node, baseType) {
887
- if (!node)
888
- return null;
889
- const valueNodes = childElements(node, "qti-value");
890
- const entries = valueNodes.map((valueNode) => ({
891
- fieldIdentifier: valueNode.attributes["field-identifier"],
892
- value: coerceValue(textContent(valueNode), valueNode.attributes["base-type"] ?? baseType),
893
- }));
894
- const recordEntries = entries.filter((entry) => Boolean(entry.fieldIdentifier));
895
- if (recordEntries.length > 0) {
896
- return Object.fromEntries(recordEntries.map((entry) => [entry.fieldIdentifier, entry.value]));
897
- }
898
- if (entries.length === 0) {
899
- const text = textContent(node);
900
- return text.length > 0 ? coerceValue(text, baseType) : null;
901
- }
902
- if (entries.length === 1)
903
- return entries[0]?.value ?? null;
904
- return entries.map((entry) => entry.value);
905
- }
906
- function parseMapping(node) {
907
- if (!node)
908
- return undefined;
909
- return {
910
- defaultValue: Number(node.attributes["default-value"] ?? 0),
911
- attributes: node.attributes,
912
- source: node.source,
913
- entries: childElements(node, "qti-map-entry").map((entry) => ({
914
- mapKey: entry.attributes["map-key"],
915
- mappedValue: Number(entry.attributes["mapped-value"] ?? 0),
916
- attributes: entry.attributes,
917
- source: entry.source,
918
- })),
919
- };
920
- }
921
- function parseAreaMapping(node) {
922
- if (!node)
923
- return undefined;
924
- return {
925
- defaultValue: Number(node.attributes["default-value"] ?? 0),
926
- attributes: node.attributes,
927
- source: node.source,
928
- entries: childElements(node, "qti-area-map-entry").map((entry) => ({
929
- shape: parseShape(entry.attributes.shape),
930
- coords: parseCoords(entry.attributes.coords),
931
- mappedValue: Number(entry.attributes["mapped-value"] ?? 0),
932
- attributes: entry.attributes,
933
- source: entry.source,
934
- })),
935
- };
936
- }
937
- function parseLookupTable(node, baseType) {
938
- const matchTable = childElements(node, "qti-match-table")[0];
939
- if (matchTable)
940
- return parseMatchTable(matchTable, baseType);
941
- const interpolationTable = childElements(node, "qti-interpolation-table")[0];
942
- if (interpolationTable)
943
- return parseInterpolationTable(interpolationTable, baseType);
944
- return undefined;
945
- }
946
- function parseMatchTable(node, baseType) {
947
- return {
948
- type: "match",
949
- defaultValue: parseLookupValue(node.attributes["default-value"], baseType),
950
- attributes: node.attributes,
951
- source: node.source,
952
- entries: childElements(node, "qti-match-table-entry").map((entry) => ({
953
- sourceValue: Number(entry.attributes["source-value"]),
954
- targetValue: parseLookupValue(entry.attributes["target-value"], baseType),
955
- attributes: entry.attributes,
956
- source: entry.source,
957
- })),
958
- };
959
- }
960
- function parseInterpolationTable(node, baseType) {
961
- return {
962
- type: "interpolation",
963
- defaultValue: parseLookupValue(node.attributes["default-value"], baseType),
964
- attributes: node.attributes,
965
- source: node.source,
966
- entries: childElements(node, "qti-interpolation-table-entry").map((entry) => ({
967
- sourceValue: Number(entry.attributes["source-value"]),
968
- targetValue: parseLookupValue(entry.attributes["target-value"], baseType),
969
- includeBoundary: entry.attributes["include-boundary"] !== "false",
970
- attributes: entry.attributes,
971
- source: entry.source,
972
- })),
973
- };
974
- }
975
- function parseLookupValue(value, baseType) {
976
- return value === undefined ? null : coerceValue(value, baseType);
977
- }
978
- function parseShape(shape) {
979
- if (shape === "circle" || shape === "rect" || shape === "poly")
980
- return shape;
981
- return "default";
982
- }
983
- function parseCoords(value) {
984
- return (value ?? "")
985
- .split(",")
986
- .map((part) => Number(part.trim()))
987
- .filter((part) => Number.isFinite(part));
988
- }
989
- function parseResponseProcessing(node) {
990
- if (!node)
991
- return undefined;
992
- return {
993
- template: node.attributes.template,
994
- rules: parseResponseRules(node),
995
- conditions: responseConditionsFromChildren(node),
996
- };
997
- }
998
- function parseTemplateProcessing(node) {
999
- if (!node)
1000
- return undefined;
1001
- return {
1002
- rules: parseTemplateRules(node),
1003
- };
1004
- }
1005
- function parseTemplateRules(node) {
1006
- return childElements(node)
1007
- .map(parseTemplateRule)
1008
- .filter((rule) => rule !== undefined);
1009
- }
1010
- function parseTemplateRule(node) {
1011
- if (node.localName === "qti-set-template-value") {
1012
- return {
1013
- type: "setTemplateValue",
1014
- identifier: node.attributes.identifier ?? "",
1015
- expression: parseFirstExpression(node) ?? {
1016
- type: "baseValue",
1017
- value: null,
1018
- source: node.source,
1019
- },
1020
- source: node.source,
1021
- };
1022
- }
1023
- if (node.localName === "qti-set-default-value") {
1024
- return {
1025
- type: "setDefaultValue",
1026
- identifier: node.attributes.identifier ?? "",
1027
- expression: parseFirstExpression(node) ?? {
1028
- type: "baseValue",
1029
- value: null,
1030
- source: node.source,
1031
- },
1032
- source: node.source,
1033
- };
1034
- }
1035
- if (node.localName === "qti-set-correct-response") {
1036
- return {
1037
- type: "setCorrectResponse",
1038
- identifier: node.attributes.identifier ?? "",
1039
- expression: parseFirstExpression(node) ?? {
1040
- type: "baseValue",
1041
- value: null,
1042
- source: node.source,
1043
- },
1044
- source: node.source,
1045
- };
1046
- }
1047
- if (node.localName === "qti-template-condition") {
1048
- const templateIf = childElements(node, "qti-template-if")[0];
1049
- const templateElse = childElements(node, "qti-template-else")[0];
1050
- return {
1051
- type: "templateCondition",
1052
- ifExpression: templateIf ? parseFirstExpression(templateIf) : undefined,
1053
- thenRules: templateIf ? parseTemplateRules(templateIf) : [],
1054
- elseIfs: childElements(node, "qti-template-else-if").map((branch) => ({
1055
- expression: parseFirstExpression(branch),
1056
- rules: parseTemplateRules(branch),
1057
- })),
1058
- elseRules: templateElse ? parseTemplateRules(templateElse) : [],
1059
- source: node.source,
1060
- };
1061
- }
1062
- if (node.localName === "qti-exit-template") {
1063
- return {
1064
- type: "exitTemplate",
1065
- source: node.source,
1066
- };
1067
- }
1068
- if (node.localName === "qti-template-constraint") {
1069
- return {
1070
- type: "templateConstraint",
1071
- expression: parseFirstExpression(node) ?? {
1072
- type: "baseValue",
1073
- value: null,
1074
- source: node.source,
1075
- },
1076
- source: node.source,
1077
- };
1078
- }
1079
- return undefined;
1080
- }
1081
- function responseConditionsFromChildren(node) {
1082
- return childElements(node).flatMap((child) => {
1083
- if (child.localName === "qti-response-condition")
1084
- return [parseResponseCondition(child)];
1085
- if (child.localName === "qti-response-processing-fragment") {
1086
- return responseConditionsFromChildren(child);
1087
- }
1088
- return [];
1089
- });
1090
- }
1091
- function parseResponseCondition(node) {
1092
- const responseIf = childElements(node, "qti-response-if")[0];
1093
- const responseElse = childElements(node, "qti-response-else")[0];
1094
- return {
1095
- ifExpression: responseIf ? parseFirstExpression(responseIf) : undefined,
1096
- thenRules: responseIf ? parseResponseRules(responseIf) : [],
1097
- elseIfs: childElements(node, "qti-response-else-if").map((branch) => ({
1098
- expression: parseFirstExpression(branch),
1099
- rules: parseResponseRules(branch),
1100
- })),
1101
- elseRules: responseElse ? parseResponseRules(responseElse) : [],
1102
- };
1103
- }
1104
- function parseResponseRules(node) {
1105
- return childElements(node)
1106
- .map(parseResponseRule)
1107
- .filter((rule) => rule !== undefined);
1108
- }
1109
- function parseResponseRule(node) {
1110
- if (node.localName === "qti-response-condition") {
1111
- return {
1112
- type: "responseCondition",
1113
- condition: parseResponseCondition(node),
1114
- source: node.source,
1115
- };
1116
- }
1117
- if (node.localName === "qti-set-outcome-value")
1118
- return parseSetOutcomeValue(node);
1119
- if (node.localName === "qti-lookup-outcome-value")
1120
- return parseLookupOutcomeValue(node);
1121
- if (node.localName === "qti-exit-response") {
1122
- return { type: "exitResponse", source: node.source };
1123
- }
1124
- if (node.localName === "qti-response-processing-fragment") {
1125
- return {
1126
- type: "responseProcessingFragment",
1127
- rules: parseResponseRules(node),
1128
- source: node.source,
1129
- };
1130
- }
1131
- return undefined;
1132
- }
1133
- function parseLookupOutcomeValue(node) {
1134
- return {
1135
- type: "lookupOutcomeValue",
1136
- identifier: node.attributes.identifier ?? "",
1137
- expression: parseFirstExpression(node) ?? {
1138
- type: "baseValue",
1139
- value: null,
1140
- source: node.source,
1141
- },
1142
- source: node.source,
1143
- };
1144
- }
1145
- function parseSetOutcomeValue(setNode) {
1146
- return {
1147
- type: "setOutcomeValue",
1148
- identifier: setNode.attributes.identifier ?? "",
1149
- expression: parseFirstExpression(setNode) ?? {
1150
- type: "baseValue",
1151
- value: null,
1152
- source: setNode.source,
1153
- },
1154
- source: setNode.source,
1155
- };
1156
- }
1157
- function parseFirstExpression(node) {
1158
- for (const child of node.children) {
1159
- const expression = parseExpression(child);
1160
- if (expression)
1161
- return expression;
1162
- }
1163
- return undefined;
1164
- }
1165
- function parseExpression(node) {
1166
- if (node.localName === "qti-base-value") {
1167
- const rawValue = textContent(node);
1168
- return {
1169
- type: "baseValue",
1170
- value: coerceValue(rawValue, node.attributes["base-type"]),
1171
- rawValue,
1172
- baseType: node.attributes["base-type"],
1173
- source: node.source,
1174
- };
1175
- }
1176
- if (node.localName === "qti-null") {
1177
- return { type: "null", source: node.source };
1178
- }
1179
- if (node.localName === "qti-is-null") {
1180
- const variable = childElements(node, "qti-variable")[0];
1181
- return {
1182
- type: "isNull",
1183
- identifier: variable?.attributes.identifier ?? "",
1184
- source: node.source,
1185
- };
1186
- }
1187
- if (node.localName === "qti-map-response") {
1188
- return {
1189
- type: "mapResponse",
1190
- identifier: node.attributes.identifier ?? "",
1191
- source: node.source,
1192
- };
1193
- }
1194
- if (node.localName === "qti-map-response-point") {
1195
- return {
1196
- type: "mapResponsePoint",
1197
- identifier: node.attributes.identifier ?? "",
1198
- source: node.source,
1199
- };
1200
- }
1201
- if (node.localName === "qti-correct") {
1202
- return {
1203
- type: "correct",
1204
- identifier: node.attributes.identifier ?? "",
1205
- source: node.source,
1206
- };
1207
- }
1208
- if (node.localName === "qti-default") {
1209
- return {
1210
- type: "default",
1211
- identifier: node.attributes.identifier ?? "",
1212
- source: node.source,
1213
- };
1214
- }
1215
- if (node.localName === "qti-variable") {
1216
- return { type: "variable", identifier: node.attributes.identifier ?? "", source: node.source };
1217
- }
1218
- if (node.localName === "qti-random-integer") {
1219
- return {
1220
- type: "randomInteger",
1221
- min: Number(node.attributes.min ?? 0),
1222
- max: Number(node.attributes.max ?? 0),
1223
- step: Number(node.attributes.step ?? 1),
1224
- attributes: node.attributes,
1225
- source: node.source,
1226
- };
1227
- }
1228
- if (node.localName === "qti-random-float") {
1229
- return {
1230
- type: "randomFloat",
1231
- min: Number(node.attributes.min ?? 0),
1232
- max: Number(node.attributes.max ?? 0),
1233
- attributes: node.attributes,
1234
- source: node.source,
1235
- };
1236
- }
1237
- if (node.localName === "qti-random") {
1238
- const multiple = childElements(node, "qti-multiple")[0];
1239
- return {
1240
- type: "random",
1241
- values: childElements(multiple ?? node)
1242
- .map(parseExpression)
1243
- .filter((expression) => expression !== undefined),
1244
- source: node.source,
1245
- };
1246
- }
1247
- if (node.localName === "qti-multiple") {
1248
- return {
1249
- type: "multiple",
1250
- expressions: childElements(node)
1251
- .map(parseExpression)
1252
- .filter((expression) => expression !== undefined),
1253
- source: node.source,
1254
- };
1255
- }
1256
- if (node.localName === "qti-ordered") {
1257
- return {
1258
- type: "ordered",
1259
- expressions: childElements(node)
1260
- .map(parseExpression)
1261
- .filter((expression) => expression !== undefined),
1262
- source: node.source,
1263
- };
1264
- }
1265
- if (node.localName === "qti-index") {
1266
- const expression = parseFirstExpression(node);
1267
- if (expression) {
1268
- return { type: "index", expression, n: node.attributes.n ?? "", source: node.source };
1269
- }
1270
- }
1271
- if (node.localName === "qti-container-size") {
1272
- const expression = parseFirstExpression(node);
1273
- if (expression)
1274
- return { type: "containerSize", expression, source: node.source };
1275
- }
1276
- if (node.localName === "qti-sum") {
1277
- return {
1278
- type: "sum",
1279
- expressions: childElements(node)
1280
- .map(parseExpression)
1281
- .filter((expression) => expression !== undefined),
1282
- source: node.source,
1283
- };
1284
- }
1285
- if (node.localName === "qti-product") {
1286
- return {
1287
- type: "product",
1288
- expressions: childElements(node)
1289
- .map(parseExpression)
1290
- .filter((expression) => expression !== undefined),
1291
- source: node.source,
1292
- };
1293
- }
1294
- if (node.localName === "qti-min") {
1295
- return {
1296
- type: "min",
1297
- expressions: childElements(node)
1298
- .map(parseExpression)
1299
- .filter((expression) => expression !== undefined),
1300
- source: node.source,
1301
- };
1302
- }
1303
- if (node.localName === "qti-max") {
1304
- return {
1305
- type: "max",
1306
- expressions: childElements(node)
1307
- .map(parseExpression)
1308
- .filter((expression) => expression !== undefined),
1309
- source: node.source,
1310
- };
1311
- }
1312
- if (node.localName === "qti-subtract") {
1313
- const expressions = childElements(node)
1314
- .map(parseExpression)
1315
- .filter((expression) => expression !== undefined);
1316
- const [left, right] = expressions;
1317
- if (left && right)
1318
- return { type: "subtract", left, right, source: node.source };
1319
- }
1320
- if (node.localName === "qti-divide") {
1321
- const expressions = childElements(node)
1322
- .map(parseExpression)
1323
- .filter((expression) => expression !== undefined);
1324
- const [left, right] = expressions;
1325
- if (left && right)
1326
- return { type: "divide", left, right, source: node.source };
1327
- }
1328
- if (node.localName === "qti-power") {
1329
- const expressions = childElements(node)
1330
- .map(parseExpression)
1331
- .filter((expression) => expression !== undefined);
1332
- const [left, right] = expressions;
1333
- if (left && right)
1334
- return { type: "power", left, right, source: node.source };
1335
- }
1336
- if (node.localName === "qti-integer-divide") {
1337
- const expressions = childElements(node)
1338
- .map(parseExpression)
1339
- .filter((expression) => expression !== undefined);
1340
- const [left, right] = expressions;
1341
- if (left && right)
1342
- return { type: "integerDivide", left, right, source: node.source };
1343
- }
1344
- if (node.localName === "qti-integer-modulus") {
1345
- const expressions = childElements(node)
1346
- .map(parseExpression)
1347
- .filter((expression) => expression !== undefined);
1348
- const [left, right] = expressions;
1349
- if (left && right)
1350
- return { type: "integerModulus", left, right, source: node.source };
1351
- }
1352
- if (node.localName === "qti-round") {
1353
- const expression = parseFirstExpression(node);
1354
- if (expression)
1355
- return { type: "round", expression, source: node.source };
1356
- }
1357
- if (node.localName === "qti-round-to") {
1358
- const expression = parseFirstExpression(node);
1359
- const roundingMode = node.attributes["rounding-mode"];
1360
- const figures = Number(node.attributes.figures ?? 0);
1361
- if (expression &&
1362
- (roundingMode === "decimalPlaces" || roundingMode === "significantFigures") &&
1363
- Number.isInteger(figures) &&
1364
- (roundingMode === "decimalPlaces" ? figures >= 0 : figures > 0)) {
1365
- return { type: "roundTo", expression, roundingMode, figures, source: node.source };
1366
- }
1367
- }
1368
- if (node.localName === "qti-truncate") {
1369
- const expression = parseFirstExpression(node);
1370
- if (expression)
1371
- return { type: "truncate", expression, source: node.source };
1372
- }
1373
- if (node.localName === "qti-integer-to-float") {
1374
- const expression = parseFirstExpression(node);
1375
- if (expression)
1376
- return { type: "integerToFloat", expression, source: node.source };
1377
- }
1378
- if (node.localName === "qti-and") {
1379
- return {
1380
- type: "and",
1381
- expressions: childElements(node)
1382
- .map(parseExpression)
1383
- .filter((expression) => expression !== undefined),
1384
- source: node.source,
1385
- };
1386
- }
1387
- if (node.localName === "qti-any-n") {
1388
- return {
1389
- type: "anyN",
1390
- min: node.attributes.min ?? "",
1391
- max: node.attributes.max ?? "",
1392
- expressions: childElements(node)
1393
- .map(parseExpression)
1394
- .filter((expression) => expression !== undefined),
1395
- source: node.source,
1396
- };
1397
- }
1398
- if (node.localName === "qti-or") {
1399
- return {
1400
- type: "or",
1401
- expressions: childElements(node)
1402
- .map(parseExpression)
1403
- .filter((expression) => expression !== undefined),
1404
- source: node.source,
1405
- };
1406
- }
1407
- if (node.localName === "qti-not") {
1408
- const expression = parseFirstExpression(node);
1409
- if (expression)
1410
- return { type: "not", expression, source: node.source };
1411
- }
1412
- if (node.localName === "qti-equal") {
1413
- const [left, right] = childElements(node)
1414
- .map(parseExpression)
1415
- .filter((expression) => expression !== undefined);
1416
- if (left && right)
1417
- return { type: "equal", left, right, source: node.source };
1418
- }
1419
- if (node.localName === "qti-equal-rounded") {
1420
- const [left, right] = childElements(node)
1421
- .map(parseExpression)
1422
- .filter((expression) => expression !== undefined);
1423
- const roundingMode = node.attributes["rounding-mode"] ?? "";
1424
- const figures = Number(node.attributes.figures ?? 0);
1425
- if (left && right) {
1426
- return { type: "equalRounded", left, right, roundingMode, figures, source: node.source };
1427
- }
1428
- }
1429
- const numericCompareOperator = numericCompareOperatorFor(node.localName);
1430
- if (numericCompareOperator) {
1431
- const [left, right] = childElements(node)
1432
- .map(parseExpression)
1433
- .filter((expression) => expression !== undefined);
1434
- if (left && right) {
1435
- return {
1436
- type: "numericCompare",
1437
- operator: numericCompareOperator,
1438
- left,
1439
- right,
1440
- source: node.source,
1441
- };
1442
- }
1443
- }
1444
- const durationCompareOperator = durationCompareOperatorFor(node.localName);
1445
- if (durationCompareOperator) {
1446
- const [left, right] = childElements(node)
1447
- .map(parseExpression)
1448
- .filter((expression) => expression !== undefined);
1449
- if (left && right) {
1450
- return {
1451
- type: "durationCompare",
1452
- operator: durationCompareOperator,
1453
- left,
1454
- right,
1455
- source: node.source,
1456
- };
1457
- }
1458
- }
1459
- if (node.localName === "qti-string-match") {
1460
- const [left, right] = childElements(node)
1461
- .map(parseExpression)
1462
- .filter((expression) => expression !== undefined);
1463
- if (left && right) {
1464
- return {
1465
- type: "stringMatch",
1466
- left,
1467
- right,
1468
- caseSensitive: node.attributes["case-sensitive"] !== "false",
1469
- substring: node.attributes.substring === "true",
1470
- source: node.source,
1471
- };
1472
- }
1473
- }
1474
- if (node.localName === "qti-substring") {
1475
- const [left, right] = childElements(node)
1476
- .map(parseExpression)
1477
- .filter((expression) => expression !== undefined);
1478
- if (left && right) {
1479
- return {
1480
- type: "substring",
1481
- left,
1482
- right,
1483
- caseSensitive: node.attributes["case-sensitive"] !== "false",
1484
- source: node.source,
1485
- };
1486
- }
1487
- }
1488
- if (node.localName === "qti-pattern-match") {
1489
- const expression = parseFirstExpression(node);
1490
- if (expression) {
1491
- return {
1492
- type: "patternMatch",
1493
- expression,
1494
- pattern: node.attributes.pattern ?? "",
1495
- source: node.source,
1496
- };
1497
- }
1498
- }
1499
- if (node.localName === "qti-field-value") {
1500
- const expression = parseFirstExpression(node);
1501
- if (expression) {
1502
- return {
1503
- type: "fieldValue",
1504
- fieldIdentifier: node.attributes["field-identifier"] ?? "",
1505
- expression,
1506
- source: node.source,
1507
- };
1508
- }
1509
- }
1510
- if (node.localName === "qti-member") {
1511
- const [value, collection] = childElements(node)
1512
- .map(parseExpression)
1513
- .filter((expression) => expression !== undefined);
1514
- if (value && collection)
1515
- return { type: "member", value, collection, source: node.source };
1516
- }
1517
- if (node.localName === "qti-delete") {
1518
- const [value, collection] = childElements(node)
1519
- .map(parseExpression)
1520
- .filter((expression) => expression !== undefined);
1521
- if (value && collection)
1522
- return { type: "delete", value, collection, source: node.source };
1523
- }
1524
- if (node.localName === "qti-contains") {
1525
- const [collection, values] = childElements(node)
1526
- .map(parseExpression)
1527
- .filter((expression) => expression !== undefined);
1528
- if (collection && values)
1529
- return { type: "contains", collection, values, source: node.source };
1530
- }
1531
- if (node.localName === "qti-gcd" || node.localName === "qti-lcm") {
1532
- return {
1533
- type: node.localName === "qti-gcd" ? "gcd" : "lcm",
1534
- expressions: childElements(node)
1535
- .map(parseExpression)
1536
- .filter((expression) => expression !== undefined),
1537
- source: node.source,
1538
- };
1539
- }
1540
- if (node.localName === "qti-inside") {
1541
- const expression = parseFirstExpression(node);
1542
- if (expression) {
1543
- return {
1544
- type: "inside",
1545
- expression,
1546
- shape: parseShape(node.attributes.shape),
1547
- coords: parseCoords(node.attributes.coords),
1548
- attributes: node.attributes,
1549
- source: node.source,
1550
- };
1551
- }
1552
- }
1553
- if (node.localName === "qti-math-constant") {
1554
- return { type: "mathConstant", name: node.attributes.name ?? "", source: node.source };
1555
- }
1556
- if (node.localName === "qti-math-operator") {
1557
- return {
1558
- type: "mathOperator",
1559
- name: node.attributes.name ?? "",
1560
- expressions: childElements(node)
1561
- .map(parseExpression)
1562
- .filter((expression) => expression !== undefined),
1563
- source: node.source,
1564
- };
1565
- }
1566
- if (node.localName === "qti-repeat") {
1567
- return {
1568
- type: "repeat",
1569
- numberRepeats: node.attributes["number-repeats"] ?? "",
1570
- expressions: childElements(node)
1571
- .map(parseExpression)
1572
- .filter((expression) => expression !== undefined),
1573
- source: node.source,
1574
- };
1575
- }
1576
- if (node.localName === "qti-stats-operator") {
1577
- const expression = parseFirstExpression(node);
1578
- if (expression) {
1579
- return {
1580
- type: "statsOperator",
1581
- name: node.attributes.name ?? "",
1582
- expression,
1583
- source: node.source,
1584
- };
1585
- }
1586
- }
1587
- if (node.localName === "qti-custom-operator") {
1588
- return {
1589
- type: "customOperator",
1590
- definition: node.attributes.definition,
1591
- className: node.attributes.class,
1592
- attributes: node.attributes,
1593
- expressions: childElements(node)
1594
- .map(parseExpression)
1595
- .filter((expression) => expression !== undefined),
1596
- source: node.source,
1597
- };
1598
- }
1599
- if (node.localName === "qti-match") {
1600
- const variable = childElements(node, "qti-variable")[0];
1601
- const correct = childElements(node, "qti-correct")[0];
1602
- if (variable && correct) {
1603
- return {
1604
- type: "matchCorrect",
1605
- identifier: variable?.attributes.identifier ?? "",
1606
- correctIdentifier: correct?.attributes.identifier ?? "",
1607
- source: node.source,
1608
- };
1609
- }
1610
- const [left, right] = childElements(node)
1611
- .map(parseExpression)
1612
- .filter((expression) => expression !== undefined);
1613
- if (left && right)
1614
- return { type: "match", left, right, source: node.source };
1615
- }
1616
- return undefined;
1617
- }
1618
- function numericCompareOperatorFor(localName) {
1619
- if (localName === "qti-lt")
1620
- return "lt";
1621
- if (localName === "qti-lte")
1622
- return "lte";
1623
- if (localName === "qti-gt")
1624
- return "gt";
1625
- if (localName === "qti-gte")
1626
- return "gte";
1627
- return undefined;
1628
- }
1629
- function durationCompareOperatorFor(localName) {
1630
- if (localName === "qti-duration-lt")
1631
- return "lt";
1632
- if (localName === "qti-duration-gte")
1633
- return "gte";
1634
- return undefined;
1635
- }
1636
- function coerceValue(value, baseType) {
1637
- if (baseType === "integer")
1638
- return Number.parseInt(value, 10);
1639
- if (baseType === "float")
1640
- return Number.parseFloat(value);
1641
- if (baseType === "boolean") {
1642
- if (value === "true")
1643
- return true;
1644
- if (value === "false")
1645
- return false;
1646
- }
1647
- return value;
1648
- }
1649
- function parseCardinality(value) {
1650
- if (value === "multiple" || value === "ordered" || value === "record")
1651
- return value;
1652
- return "single";
1653
- }
1654
- function parseXmlBoolean(value) {
1655
- if (value === "true" || value === "1")
1656
- return true;
1657
- if (value === "false" || value === "0")
1658
- return false;
1659
- return undefined;
1660
- }
1661
- function normalizeValueForCardinality(value, cardinality) {
1662
- if ((cardinality === "multiple" || cardinality === "ordered") &&
1663
- value !== null &&
1664
- !Array.isArray(value) &&
1665
- !isRecordValue(value)) {
1666
- return [value];
1667
- }
1668
- return value;
1669
- }
1670
- function isRecordValue(value) {
1671
- return typeof value === "object" && value !== null && !Array.isArray(value);
1672
- }
1673
605
  //# sourceMappingURL=parser.js.map