@longsightgroup/qti3-core 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -11
- package/dist/adaptive-turn-materializer.d.ts +2 -0
- package/dist/adaptive-turn-materializer.d.ts.map +1 -1
- package/dist/adaptive-turn-materializer.js +25 -19
- package/dist/adaptive-turn-materializer.js.map +1 -1
- package/dist/adaptive-turn.d.ts +8 -1
- package/dist/adaptive-turn.d.ts.map +1 -1
- package/dist/adaptive-turn.js +15 -5
- package/dist/adaptive-turn.js.map +1 -1
- package/dist/assert-never.d.ts +3 -0
- package/dist/assert-never.d.ts.map +1 -0
- package/dist/assert-never.js +5 -0
- package/dist/assert-never.js.map +1 -0
- package/dist/attempt-state-constants.d.ts +6 -0
- package/dist/attempt-state-constants.d.ts.map +1 -0
- package/dist/attempt-state-constants.js +6 -0
- package/dist/attempt-state-constants.js.map +1 -0
- package/dist/attempt-state.d.ts +5 -0
- package/dist/attempt-state.d.ts.map +1 -0
- package/dist/attempt-state.js +190 -0
- package/dist/attempt-state.js.map +1 -0
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/content-text.d.ts.map +1 -1
- package/dist/content-text.js +3 -0
- package/dist/content-text.js.map +1 -1
- package/dist/custom-operators.d.ts +13 -0
- package/dist/custom-operators.d.ts.map +1 -0
- package/dist/custom-operators.js +2 -0
- package/dist/custom-operators.js.map +1 -0
- package/dist/delivery-preparation.d.ts +20 -0
- package/dist/delivery-preparation.d.ts.map +1 -0
- package/dist/delivery-preparation.js +71 -0
- package/dist/delivery-preparation.js.map +1 -0
- package/dist/delivery-redaction.d.ts +5 -0
- package/dist/delivery-redaction.d.ts.map +1 -1
- package/dist/delivery-redaction.js +27 -6
- package/dist/delivery-redaction.js.map +1 -1
- package/dist/delivery-security.js +2 -2
- package/dist/delivery-security.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/item-scoring-expectation.d.ts +4 -0
- package/dist/item-scoring-expectation.d.ts.map +1 -0
- package/dist/item-scoring-expectation.js +6 -0
- package/dist/item-scoring-expectation.js.map +1 -0
- package/dist/item-submission-materialization.d.ts +25 -0
- package/dist/item-submission-materialization.d.ts.map +1 -0
- package/dist/item-submission-materialization.js +68 -0
- package/dist/item-submission-materialization.js.map +1 -0
- package/dist/parser-custom-interactions.js +1 -1
- package/dist/parser-custom-interactions.js.map +1 -1
- package/dist/parser-declarations.d.ts.map +1 -1
- package/dist/parser-declarations.js +4 -4
- package/dist/parser-declarations.js.map +1 -1
- package/dist/parser-item-metadata.d.ts.map +1 -1
- package/dist/parser-item-metadata.js +8 -7
- package/dist/parser-item-metadata.js.map +1 -1
- package/dist/parser-processing.js +2 -2
- package/dist/parser-processing.js.map +1 -1
- package/dist/parser-values.d.ts +5 -1
- package/dist/parser-values.d.ts.map +1 -1
- package/dist/parser-values.js +33 -0
- package/dist/parser-values.js.map +1 -1
- package/dist/processing-evaluator-boolean.d.ts +8 -0
- package/dist/processing-evaluator-boolean.d.ts.map +1 -0
- package/dist/processing-evaluator-boolean.js +47 -0
- package/dist/processing-evaluator-boolean.js.map +1 -0
- package/dist/processing-evaluator-collection.d.ts +12 -0
- package/dist/processing-evaluator-collection.d.ts.map +1 -0
- package/dist/processing-evaluator-collection.js +35 -0
- package/dist/processing-evaluator-collection.js.map +1 -0
- package/dist/processing-evaluator-comparison.d.ts +8 -0
- package/dist/processing-evaluator-comparison.d.ts.map +1 -0
- package/dist/processing-evaluator-comparison.js +88 -0
- package/dist/processing-evaluator-comparison.js.map +1 -0
- package/dist/processing-evaluator-custom.d.ts +8 -0
- package/dist/processing-evaluator-custom.d.ts.map +1 -0
- package/dist/processing-evaluator-custom.js +14 -0
- package/dist/processing-evaluator-custom.js.map +1 -0
- package/dist/processing-evaluator-geometry.d.ts +8 -0
- package/dist/processing-evaluator-geometry.d.ts.map +1 -0
- package/dist/processing-evaluator-geometry.js +21 -0
- package/dist/processing-evaluator-geometry.js.map +1 -0
- package/dist/processing-evaluator-numeric.d.ts +8 -0
- package/dist/processing-evaluator-numeric.d.ts.map +1 -0
- package/dist/processing-evaluator-numeric.js +112 -0
- package/dist/processing-evaluator-numeric.js.map +1 -0
- package/dist/processing-evaluator-string.d.ts +8 -0
- package/dist/processing-evaluator-string.d.ts.map +1 -0
- package/dist/processing-evaluator-string.js +33 -0
- package/dist/processing-evaluator-string.js.map +1 -0
- package/dist/processing-evaluator-variable.d.ts +8 -0
- package/dist/processing-evaluator-variable.d.ts.map +1 -0
- package/dist/processing-evaluator-variable.js +39 -0
- package/dist/processing-evaluator-variable.js.map +1 -0
- package/dist/processing-evaluator.d.ts +22 -0
- package/dist/processing-evaluator.d.ts.map +1 -0
- package/dist/processing-evaluator.js +137 -0
- package/dist/processing-evaluator.js.map +1 -0
- package/dist/processing-expression-children.d.ts +3 -0
- package/dist/processing-expression-children.d.ts.map +1 -0
- package/dist/processing-expression-children.js +68 -0
- package/dist/processing-expression-children.js.map +1 -0
- package/dist/processing-mapping.d.ts +13 -0
- package/dist/processing-mapping.d.ts.map +1 -0
- package/dist/processing-mapping.js +120 -0
- package/dist/processing-mapping.js.map +1 -0
- package/dist/processing-operators.d.ts +11 -0
- package/dist/processing-operators.d.ts.map +1 -0
- package/dist/processing-operators.js +178 -0
- package/dist/processing-operators.js.map +1 -0
- package/dist/processing-random.d.ts +2 -0
- package/dist/processing-random.d.ts.map +1 -0
- package/dist/processing-random.js +16 -0
- package/dist/processing-random.js.map +1 -0
- package/dist/processing-state.d.ts +8 -0
- package/dist/processing-state.d.ts.map +1 -0
- package/dist/processing-state.js +44 -0
- package/dist/processing-state.js.map +1 -0
- package/dist/processing-templates.d.ts +3 -0
- package/dist/processing-templates.d.ts.map +1 -0
- package/dist/processing-templates.js +12 -0
- package/dist/processing-templates.js.map +1 -0
- package/dist/processing-values.d.ts +15 -0
- package/dist/processing-values.d.ts.map +1 -0
- package/dist/processing-values.js +150 -0
- package/dist/processing-values.js.map +1 -0
- package/dist/processing-variables.d.ts +7 -0
- package/dist/processing-variables.d.ts.map +1 -0
- package/dist/processing-variables.js +40 -0
- package/dist/processing-variables.js.map +1 -0
- package/dist/qti-package-assets.d.ts +8 -0
- package/dist/qti-package-assets.d.ts.map +1 -0
- package/dist/qti-package-assets.js +173 -0
- package/dist/qti-package-assets.js.map +1 -0
- package/dist/qti-package-items.d.ts +14 -0
- package/dist/qti-package-items.d.ts.map +1 -0
- package/dist/qti-package-items.js +138 -0
- package/dist/qti-package-items.js.map +1 -0
- package/dist/qti-package-manifest.d.ts +13 -0
- package/dist/qti-package-manifest.d.ts.map +1 -0
- package/dist/qti-package-manifest.js +109 -0
- package/dist/qti-package-manifest.js.map +1 -0
- package/dist/qti-package-metadata.d.ts +8 -0
- package/dist/qti-package-metadata.d.ts.map +1 -0
- package/dist/qti-package-metadata.js +115 -0
- package/dist/qti-package-metadata.js.map +1 -0
- package/dist/qti-package-paths.d.ts +6 -0
- package/dist/qti-package-paths.d.ts.map +1 -0
- package/dist/qti-package-paths.js +32 -0
- package/dist/qti-package-paths.js.map +1 -0
- package/dist/qti-package-types.d.ts +98 -0
- package/dist/qti-package-types.d.ts.map +1 -0
- package/dist/qti-package-types.js +2 -0
- package/dist/qti-package-types.js.map +1 -0
- package/dist/qti-package-xml.d.ts +21 -0
- package/dist/qti-package-xml.d.ts.map +1 -0
- package/dist/qti-package-xml.js +68 -0
- package/dist/qti-package-xml.js.map +1 -0
- package/dist/qti-package-zip.d.ts +24 -0
- package/dist/qti-package-zip.d.ts.map +1 -0
- package/dist/qti-package-zip.js +185 -0
- package/dist/qti-package-zip.js.map +1 -0
- package/dist/qti-package.d.ts +7 -0
- package/dist/qti-package.d.ts.map +1 -0
- package/dist/qti-package.js +76 -0
- package/dist/qti-package.js.map +1 -0
- package/dist/response-input.d.ts +9 -0
- package/dist/response-input.d.ts.map +1 -0
- package/dist/response-input.js +9 -0
- package/dist/response-input.js.map +1 -0
- package/dist/response-validation-policy.d.ts +31 -0
- package/dist/response-validation-policy.d.ts.map +1 -0
- package/dist/response-validation-policy.js +158 -0
- package/dist/response-validation-policy.js.map +1 -0
- package/dist/response-validation.d.ts +23 -0
- package/dist/response-validation.d.ts.map +1 -0
- package/dist/response-validation.js +157 -0
- package/dist/response-validation.js.map +1 -0
- package/dist/scoring-disposition-policy.d.ts +8 -0
- package/dist/scoring-disposition-policy.d.ts.map +1 -0
- package/dist/scoring-disposition-policy.js +24 -0
- package/dist/scoring-disposition-policy.js.map +1 -0
- package/dist/serializer-processing-expressions.d.ts.map +1 -1
- package/dist/serializer-processing-expressions.js +2 -8
- package/dist/serializer-processing-expressions.js.map +1 -1
- package/dist/serializer-processing-xml.js +2 -2
- package/dist/serializer-processing-xml.js.map +1 -1
- package/dist/serializer-processing.d.ts.map +1 -1
- package/dist/serializer-processing.js +3 -0
- package/dist/serializer-processing.js.map +1 -1
- package/dist/session.d.ts +5 -14
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +76 -1158
- package/dist/session.js.map +1 -1
- package/dist/shared-vocabulary-coverage-policy.js +1 -1
- package/dist/shared-vocabulary-coverage-policy.js.map +1 -1
- package/dist/shared-vocabulary-generated-families.js +1 -1
- package/dist/shared-vocabulary-generated-families.js.map +1 -1
- package/dist/shared-vocabulary-support.js +3 -3
- package/dist/shared-vocabulary-support.js.map +1 -1
- package/dist/shared-vocabulary.d.ts.map +1 -1
- package/dist/shared-vocabulary.js +4 -3
- package/dist/shared-vocabulary.js.map +1 -1
- package/dist/support-evidence.d.ts +22 -0
- package/dist/support-evidence.d.ts.map +1 -0
- package/dist/support-evidence.js +93 -0
- package/dist/support-evidence.js.map +1 -0
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +30 -43
- package/dist/support.js.map +1 -1
- package/dist/template-presentation-materializer.d.ts +14 -0
- package/dist/template-presentation-materializer.d.ts.map +1 -0
- package/dist/template-presentation-materializer.js +143 -0
- package/dist/template-presentation-materializer.js.map +1 -0
- package/dist/trusted-item-session.d.ts +25 -8
- package/dist/trusted-item-session.d.ts.map +1 -1
- package/dist/trusted-item-session.js +57 -51
- package/dist/trusted-item-session.js.map +1 -1
- package/dist/trusted-session-materialization.d.ts +33 -0
- package/dist/trusted-session-materialization.d.ts.map +1 -0
- package/dist/trusted-session-materialization.js +26 -0
- package/dist/trusted-session-materialization.js.map +1 -0
- package/dist/tts.d.ts.map +1 -1
- package/dist/tts.js +16 -2
- package/dist/tts.js.map +1 -1
- package/dist/validation-geometry.d.ts +8 -0
- package/dist/validation-geometry.d.ts.map +1 -0
- package/dist/validation-geometry.js +187 -0
- package/dist/validation-geometry.js.map +1 -0
- package/dist/validation-interactions.d.ts +3 -0
- package/dist/validation-interactions.d.ts.map +1 -0
- package/dist/validation-interactions.js +644 -0
- package/dist/validation-interactions.js.map +1 -0
- package/dist/validation-primitives.d.ts +12 -0
- package/dist/validation-primitives.d.ts.map +1 -0
- package/dist/validation-primitives.js +53 -0
- package/dist/validation-primitives.js.map +1 -0
- package/dist/validation-processing.d.ts +4 -0
- package/dist/validation-processing.d.ts.map +1 -0
- package/dist/validation-processing.js +569 -0
- package/dist/validation-processing.js.map +1 -0
- package/dist/validation-shared-vocabulary-layout.d.ts +10 -0
- package/dist/validation-shared-vocabulary-layout.d.ts.map +1 -0
- package/dist/validation-shared-vocabulary-layout.js +102 -0
- package/dist/validation-shared-vocabulary-layout.js.map +1 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +27 -1641
- package/dist/validation.js.map +1 -1
- package/dist/variable-snapshot.d.ts +9 -0
- package/dist/variable-snapshot.d.ts.map +1 -0
- package/dist/variable-snapshot.js +9 -0
- package/dist/variable-snapshot.js.map +1 -0
- package/dist/xml.js +1 -1
- package/dist/xml.js.map +1 -1
- package/package.json +1 -1
- package/src/adaptive-turn-materializer.ts +29 -16
- package/src/adaptive-turn.ts +29 -6
- package/src/assert-never.ts +4 -0
- package/src/attempt-state-constants.ts +5 -0
- package/src/attempt-state.ts +245 -0
- package/src/catalog.ts +1 -1
- package/src/content-text.ts +3 -0
- package/src/custom-operators.ts +12 -0
- package/src/delivery-preparation.ts +121 -0
- package/src/delivery-redaction.ts +37 -6
- package/src/delivery-security.ts +2 -2
- package/src/index.ts +67 -0
- package/src/item-scoring-expectation.ts +9 -0
- package/src/item-submission-materialization.ts +120 -0
- package/src/parser-custom-interactions.ts +1 -1
- package/src/parser-declarations.ts +4 -3
- package/src/parser-item-metadata.ts +8 -7
- package/src/parser-processing.ts +2 -2
- package/src/parser-values.ts +36 -0
- package/src/processing-evaluator-boolean.ts +48 -0
- package/src/processing-evaluator-collection.ts +50 -0
- package/src/processing-evaluator-comparison.ts +106 -0
- package/src/processing-evaluator-custom.ts +20 -0
- package/src/processing-evaluator-geometry.ts +26 -0
- package/src/processing-evaluator-numeric.ts +155 -0
- package/src/processing-evaluator-string.ts +60 -0
- package/src/processing-evaluator-variable.ts +81 -0
- package/src/processing-evaluator.ts +173 -0
- package/src/processing-expression-children.ts +69 -0
- package/src/processing-mapping.ts +153 -0
- package/src/processing-operators.ts +184 -0
- package/src/processing-random.ts +16 -0
- package/src/processing-state.ts +69 -0
- package/src/processing-templates.ts +12 -0
- package/src/processing-values.ts +162 -0
- package/src/processing-variables.ts +83 -0
- package/src/qti-package-assets.ts +256 -0
- package/src/qti-package-items.ts +240 -0
- package/src/qti-package-manifest.ts +210 -0
- package/src/qti-package-metadata.ts +143 -0
- package/src/qti-package-paths.ts +56 -0
- package/src/qti-package-types.ts +117 -0
- package/src/qti-package-xml.ts +97 -0
- package/src/qti-package-zip.ts +325 -0
- package/src/qti-package.ts +176 -0
- package/src/response-input.ts +19 -0
- package/src/response-validation-policy.ts +218 -0
- package/src/response-validation.ts +282 -0
- package/src/scoring-disposition-policy.ts +37 -0
- package/src/serializer-processing-expressions.ts +2 -12
- package/src/serializer-processing-xml.ts +2 -2
- package/src/serializer-processing.ts +3 -0
- package/src/session.ts +128 -2006
- package/src/shared-vocabulary-coverage-policy.ts +1 -1
- package/src/shared-vocabulary-generated-families.ts +1 -1
- package/src/shared-vocabulary-support.ts +3 -3
- package/src/shared-vocabulary.ts +3 -2
- package/src/support-evidence.ts +107 -0
- package/src/support.ts +59 -50
- package/src/template-presentation-materializer.ts +219 -0
- package/src/trusted-item-session.ts +86 -78
- package/src/trusted-session-materialization.ts +67 -0
- package/src/tts.ts +29 -15
- package/src/validation-geometry.ts +204 -0
- package/src/validation-interactions.ts +831 -0
- package/src/validation-primitives.ts +68 -0
- package/src/validation-processing.ts +785 -0
- package/src/validation-shared-vocabulary-layout.ts +117 -0
- package/src/validation.ts +44 -2077
- package/src/variable-snapshot.ts +20 -0
- package/src/xml.ts +1 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
QtiAttemptStateV1,
|
|
3
|
+
QtiAttemptStatus,
|
|
4
|
+
QtiDiagnostic,
|
|
5
|
+
QtiDocument,
|
|
6
|
+
QtiPortableCustomStateValue,
|
|
7
|
+
QtiScalarValue,
|
|
8
|
+
QtiValue,
|
|
9
|
+
QtiVariableDeclaration,
|
|
10
|
+
} from "./types.js";
|
|
11
|
+
import {
|
|
12
|
+
ATTEMPT_STATE_SCHEMA,
|
|
13
|
+
COMPLETION_COMPLETED,
|
|
14
|
+
COMPLETION_NOT_ATTEMPTED,
|
|
15
|
+
COMPLETION_STATUS,
|
|
16
|
+
COMPLETION_UNKNOWN,
|
|
17
|
+
} from "./attempt-state-constants.js";
|
|
18
|
+
import { isQtiPortableCustomStateValue, isQtiValue, qtiValueToString } from "./value-format.js";
|
|
19
|
+
import { isRecordValue } from "./processing-values.js";
|
|
20
|
+
import { isPair, isPoint } from "./validation-primitives.js";
|
|
21
|
+
|
|
22
|
+
export function isQtiAttemptStateV1(value: unknown): value is QtiAttemptStateV1 {
|
|
23
|
+
return attemptStateErrors(value).length === 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function assertQtiAttemptStateV1(value: unknown): asserts value is QtiAttemptStateV1 {
|
|
27
|
+
const [firstError] = attemptStateErrors(value);
|
|
28
|
+
if (firstError) throw new Error(firstError);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function assertCompatiblePriorState(
|
|
32
|
+
document: QtiDocument,
|
|
33
|
+
priorState: QtiAttemptStateV1 | undefined,
|
|
34
|
+
): void {
|
|
35
|
+
if (!priorState) return;
|
|
36
|
+
assertQtiAttemptStateV1(priorState);
|
|
37
|
+
if (priorState.itemIdentifier !== document.item.identifier) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Cannot restore state for ${priorState.itemIdentifier} into ${document.item.identifier}.`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const responseIdentifiers = new Set(
|
|
43
|
+
document.item.responseDeclarations.map((declaration) => declaration.identifier),
|
|
44
|
+
);
|
|
45
|
+
const outcomeIdentifiers = new Set([
|
|
46
|
+
...document.item.outcomeDeclarations.map((declaration) => declaration.identifier),
|
|
47
|
+
COMPLETION_STATUS,
|
|
48
|
+
]);
|
|
49
|
+
const templateIdentifiers = new Set(
|
|
50
|
+
document.item.templateDeclarations.map((declaration) => declaration.identifier),
|
|
51
|
+
);
|
|
52
|
+
const interactionStateIdentifiers = new Set(
|
|
53
|
+
document.item.interactions
|
|
54
|
+
.filter((interaction) => interaction.type === "portableCustom")
|
|
55
|
+
.map((interaction) => interaction.responseIdentifier)
|
|
56
|
+
.filter((identifier): identifier is string => Boolean(identifier)),
|
|
57
|
+
);
|
|
58
|
+
assertKnownStateIdentifiers("response", priorState.responses, responseIdentifiers);
|
|
59
|
+
assertKnownStateIdentifiers("outcome", priorState.outcomes, outcomeIdentifiers);
|
|
60
|
+
assertKnownStateIdentifiers("template", priorState.templateValues ?? {}, templateIdentifiers);
|
|
61
|
+
assertKnownPortableCustomStateIdentifiers(
|
|
62
|
+
priorState.interactionStates ?? {},
|
|
63
|
+
interactionStateIdentifiers,
|
|
64
|
+
);
|
|
65
|
+
for (const message of priorState.validationMessages) {
|
|
66
|
+
if (message.path && !responseIdentifiers.has(message.path)) {
|
|
67
|
+
throw new Error(`Cannot restore validation message for unknown response ${message.path}.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const declaration of document.item.responseDeclarations) {
|
|
71
|
+
assertRestoredValueMatchesDeclaration("response", declaration, priorState.responses);
|
|
72
|
+
}
|
|
73
|
+
for (const declaration of document.item.outcomeDeclarations) {
|
|
74
|
+
assertRestoredValueMatchesDeclaration("outcome", declaration, priorState.outcomes);
|
|
75
|
+
}
|
|
76
|
+
for (const declaration of document.item.templateDeclarations) {
|
|
77
|
+
assertRestoredValueMatchesDeclaration("template", declaration, priorState.templateValues ?? {});
|
|
78
|
+
}
|
|
79
|
+
const completionStatus = priorState.outcomes[COMPLETION_STATUS];
|
|
80
|
+
if (
|
|
81
|
+
completionStatus !== undefined &&
|
|
82
|
+
completionStatus !== COMPLETION_NOT_ATTEMPTED &&
|
|
83
|
+
completionStatus !== COMPLETION_UNKNOWN &&
|
|
84
|
+
completionStatus !== COMPLETION_COMPLETED &&
|
|
85
|
+
completionStatus !== "incomplete"
|
|
86
|
+
) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Cannot restore unsupported completionStatus ${qtiValueToString(completionStatus)}.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function assertKnownStateIdentifiers(
|
|
94
|
+
kind: string,
|
|
95
|
+
record: Record<string, QtiValue>,
|
|
96
|
+
allowed: Set<string>,
|
|
97
|
+
): void {
|
|
98
|
+
const unknown = Object.keys(record).find((identifier) => !allowed.has(identifier));
|
|
99
|
+
if (unknown) throw new Error(`Cannot restore unknown ${kind} identifier ${unknown}.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function assertKnownPortableCustomStateIdentifiers(
|
|
103
|
+
record: Record<string, QtiPortableCustomStateValue>,
|
|
104
|
+
allowed: Set<string>,
|
|
105
|
+
): void {
|
|
106
|
+
const unknown = Object.keys(record).find((identifier) => !allowed.has(identifier));
|
|
107
|
+
if (unknown) throw new Error(`Cannot restore unknown interaction state identifier ${unknown}.`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function assertRestoredValueMatchesDeclaration(
|
|
111
|
+
kind: string,
|
|
112
|
+
declaration: QtiVariableDeclaration,
|
|
113
|
+
record: Record<string, QtiValue>,
|
|
114
|
+
): void {
|
|
115
|
+
if (!(declaration.identifier in record)) return;
|
|
116
|
+
const value = record[declaration.identifier] ?? null;
|
|
117
|
+
const error = restoredValueError(declaration, value);
|
|
118
|
+
if (error) throw new Error(`Cannot restore ${kind} ${declaration.identifier}: ${error}.`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function restoredValueError(
|
|
122
|
+
declaration: QtiVariableDeclaration,
|
|
123
|
+
value: QtiValue,
|
|
124
|
+
): string | undefined {
|
|
125
|
+
if (value === null) return undefined;
|
|
126
|
+
if (declaration.cardinality === "record") {
|
|
127
|
+
return isRecordValue(value) ? undefined : "expected record value";
|
|
128
|
+
}
|
|
129
|
+
if (isRecordValue(value)) return "expected scalar value";
|
|
130
|
+
if (declaration.cardinality === "single" && Array.isArray(value)) {
|
|
131
|
+
return "expected single value";
|
|
132
|
+
}
|
|
133
|
+
if (
|
|
134
|
+
(declaration.cardinality === "multiple" || declaration.cardinality === "ordered") &&
|
|
135
|
+
!Array.isArray(value)
|
|
136
|
+
) {
|
|
137
|
+
return `expected ${declaration.cardinality} value container`;
|
|
138
|
+
}
|
|
139
|
+
if (!declaration.baseType) return undefined;
|
|
140
|
+
for (const entry of restoredValueEntries(value)) {
|
|
141
|
+
if (!restoredScalarMatchesBaseType(entry, declaration.baseType)) {
|
|
142
|
+
return `value ${String(entry)} is not valid for base-type ${declaration.baseType}`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function restoredValueEntries(value: QtiValue): QtiScalarValue[] {
|
|
149
|
+
if (value === null || isRecordValue(value)) return [];
|
|
150
|
+
return Array.isArray(value) ? value : [value];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function restoredScalarMatchesBaseType(
|
|
154
|
+
value: QtiScalarValue,
|
|
155
|
+
baseType: QtiVariableDeclaration["baseType"],
|
|
156
|
+
): boolean {
|
|
157
|
+
if (!baseType) return true;
|
|
158
|
+
if (baseType === "integer") {
|
|
159
|
+
return typeof value === "number" ? Number.isInteger(value) : /^-?\d+$/.test(String(value));
|
|
160
|
+
}
|
|
161
|
+
if (baseType === "float") {
|
|
162
|
+
return typeof value === "number" ? Number.isFinite(value) : Number.isFinite(Number(value));
|
|
163
|
+
}
|
|
164
|
+
if (baseType === "boolean")
|
|
165
|
+
return typeof value === "boolean" || value === "true" || value === "false";
|
|
166
|
+
if (baseType === "point") return isPoint(String(value));
|
|
167
|
+
if (baseType === "pair" || baseType === "directedPair") return isPair(String(value));
|
|
168
|
+
if (baseType === "identifier")
|
|
169
|
+
return typeof value === "string" && value.trim().length > 0 && !/\s/.test(value);
|
|
170
|
+
return typeof value === "string";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isAttemptStatus(value: string): value is QtiAttemptStatus {
|
|
174
|
+
return (
|
|
175
|
+
value === "initialized" ||
|
|
176
|
+
value === "interacting" ||
|
|
177
|
+
value === "suspended" ||
|
|
178
|
+
value === "completed"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function attemptStateErrors(value: unknown): string[] {
|
|
183
|
+
if (!isRecord(value)) return ["QTI attempt state must be an object."];
|
|
184
|
+
|
|
185
|
+
const schema = value.schema;
|
|
186
|
+
if (schema !== ATTEMPT_STATE_SCHEMA) {
|
|
187
|
+
return [`Unsupported QTI attempt state schema ${String(schema)}.`];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const errors: string[] = [];
|
|
191
|
+
if (typeof value.itemIdentifier !== "string" || value.itemIdentifier.length === 0) {
|
|
192
|
+
errors.push("QTI attempt state itemIdentifier must be a non-empty string.");
|
|
193
|
+
}
|
|
194
|
+
if (typeof value.status !== "string" || !isAttemptStatus(value.status)) {
|
|
195
|
+
errors.push(`QTI attempt state status ${String(value.status)} is not supported.`);
|
|
196
|
+
}
|
|
197
|
+
if (!isQtiValueRecord(value.responses)) {
|
|
198
|
+
errors.push("QTI attempt state responses must be a record of QTI values.");
|
|
199
|
+
}
|
|
200
|
+
if (!isQtiValueRecord(value.outcomes)) {
|
|
201
|
+
errors.push("QTI attempt state outcomes must be a record of QTI values.");
|
|
202
|
+
}
|
|
203
|
+
if (value.templateValues !== undefined && !isQtiValueRecord(value.templateValues)) {
|
|
204
|
+
errors.push("QTI attempt state templateValues must be a record of QTI values.");
|
|
205
|
+
}
|
|
206
|
+
if (
|
|
207
|
+
value.interactionStates !== undefined &&
|
|
208
|
+
!isPortableCustomStateRecord(value.interactionStates)
|
|
209
|
+
) {
|
|
210
|
+
errors.push("QTI attempt state interactionStates must be a record of JSON values.");
|
|
211
|
+
}
|
|
212
|
+
if (!isDiagnosticArray(value.validationMessages)) {
|
|
213
|
+
errors.push("QTI attempt state validationMessages must be an array of diagnostics.");
|
|
214
|
+
}
|
|
215
|
+
return errors;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isQtiValueRecord(value: unknown): value is Record<string, QtiValue> {
|
|
219
|
+
if (!isRecord(value)) return false;
|
|
220
|
+
return Object.values(value).every(isQtiValue);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isPortableCustomStateRecord(
|
|
224
|
+
value: unknown,
|
|
225
|
+
): value is Record<string, QtiPortableCustomStateValue> {
|
|
226
|
+
if (!isRecord(value)) return false;
|
|
227
|
+
return Object.values(value).every(isQtiPortableCustomStateValue);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isDiagnosticArray(value: unknown): value is QtiDiagnostic[] {
|
|
231
|
+
if (!Array.isArray(value)) return false;
|
|
232
|
+
return value.every((item) => {
|
|
233
|
+
if (!isRecord(item)) return false;
|
|
234
|
+
if (typeof item.code !== "string" || item.code.length === 0) return false;
|
|
235
|
+
if (item.severity !== "info" && item.severity !== "warning" && item.severity !== "error") {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (typeof item.message !== "string" || item.message.length === 0) return false;
|
|
239
|
+
return item.path === undefined || typeof item.path === "string";
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
244
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
245
|
+
}
|
package/src/catalog.ts
CHANGED
|
@@ -112,7 +112,7 @@ function selectedCandidates(
|
|
|
112
112
|
.filter((entry): entry is { candidate: Candidate; index: number; rank: number } =>
|
|
113
113
|
Number.isInteger(entry.rank),
|
|
114
114
|
)
|
|
115
|
-
.
|
|
115
|
+
.toSorted((a, b) => a.rank - b.rank || a.index - b.index);
|
|
116
116
|
if (languageMatches.length > 0) return languageMatches.map((entry) => entry.candidate);
|
|
117
117
|
if (options.includeDefaultFallback === false) return [];
|
|
118
118
|
const defaults = candidates.filter((candidate) => candidate.default);
|
package/src/content-text.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { QtiChoice, QtiContentNode, QtiSourceLocation } from "./types.js";
|
|
2
|
+
import { assertNever } from "./assert-never.js";
|
|
2
3
|
|
|
3
4
|
export interface FlatTextFromContentOptions {
|
|
4
5
|
excludeAnnotations?: boolean;
|
|
@@ -63,6 +64,8 @@ function flatTextFromContentNode(node: QtiContentNode, excludeAnnotations: boole
|
|
|
63
64
|
case "interaction":
|
|
64
65
|
case "printedVariable":
|
|
65
66
|
return "";
|
|
67
|
+
default:
|
|
68
|
+
return assertNever(node);
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { QtiProcessingExpression, QtiValue } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface QtiCustomOperatorContext {
|
|
4
|
+
definition?: string | undefined;
|
|
5
|
+
className?: string | undefined;
|
|
6
|
+
attributes: Record<string, string>;
|
|
7
|
+
values: QtiValue[];
|
|
8
|
+
expression: Extract<QtiProcessingExpression, { type: "customOperator" }>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type QtiCustomOperatorHandler = (context: QtiCustomOperatorContext) => QtiValue;
|
|
12
|
+
export type QtiCustomOperatorRegistry = Record<string, QtiCustomOperatorHandler>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { materializeAdaptiveCandidateView } from "./adaptive-turn-materializer.js";
|
|
2
|
+
import type {
|
|
3
|
+
QtiDeliverySecurityAnalysis,
|
|
4
|
+
QtiDeliverySecurityFindingKind,
|
|
5
|
+
RedactedDeliveryXmlResult,
|
|
6
|
+
} from "./delivery-redaction.js";
|
|
7
|
+
import { buildQtiDeliverySafeXml } from "./delivery-security.js";
|
|
8
|
+
import type { QtiDiagnostic, QtiValue } from "./types.js";
|
|
9
|
+
|
|
10
|
+
export type QtiDeliveryPreparationMode = "static" | "server-materialized-adaptive";
|
|
11
|
+
|
|
12
|
+
export type QtiDeliveryPreparationOptions =
|
|
13
|
+
| { mode: "static" }
|
|
14
|
+
| {
|
|
15
|
+
mode: "server-materialized-adaptive";
|
|
16
|
+
outcomes: Record<string, QtiValue>;
|
|
17
|
+
templateValues?: Record<string, QtiValue>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface QtiDeliveryPreparationResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
mode: QtiDeliveryPreparationMode;
|
|
23
|
+
diagnostics: QtiDeliverySecurityAnalysis["diagnostics"];
|
|
24
|
+
analysis: QtiDeliverySecurityAnalysis;
|
|
25
|
+
candidateSafeXml?: string | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Prepare candidate-safe XML for a supported QTI delivery mode. */
|
|
29
|
+
export function prepareQtiDeliveryXml(
|
|
30
|
+
xml: string,
|
|
31
|
+
options: QtiDeliveryPreparationOptions,
|
|
32
|
+
): QtiDeliveryPreparationResult {
|
|
33
|
+
const redaction =
|
|
34
|
+
options.mode === "static"
|
|
35
|
+
? buildQtiDeliverySafeXml(xml)
|
|
36
|
+
: materializeAdaptiveCandidateView({
|
|
37
|
+
itemXml: xml,
|
|
38
|
+
outcomes: options.outcomes,
|
|
39
|
+
...(options.templateValues === undefined
|
|
40
|
+
? {}
|
|
41
|
+
: { templateValues: options.templateValues }),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
ok: redaction.ok,
|
|
46
|
+
mode: options.mode,
|
|
47
|
+
diagnostics: normalizePreparationDiagnostics(redaction, options.mode),
|
|
48
|
+
analysis: redaction.analysis,
|
|
49
|
+
candidateSafeXml: redaction.xml,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizePreparationDiagnostics(
|
|
54
|
+
redaction: RedactedDeliveryXmlResult,
|
|
55
|
+
mode: QtiDeliveryPreparationMode,
|
|
56
|
+
): QtiDiagnostic[] {
|
|
57
|
+
const findingCodes = redaction.analysis.findings.map((finding) =>
|
|
58
|
+
preparationDiagnosticCodeForFinding(finding.kind, mode),
|
|
59
|
+
);
|
|
60
|
+
let findingIndex = 0;
|
|
61
|
+
|
|
62
|
+
return redaction.diagnostics.map((diagnostic) => {
|
|
63
|
+
if (!isDeliveryFindingDiagnosticCode(diagnostic.code)) return diagnostic;
|
|
64
|
+
|
|
65
|
+
if (diagnostic.code === "adaptiveTurn.materialization.unsupported") {
|
|
66
|
+
return {
|
|
67
|
+
...diagnostic,
|
|
68
|
+
code: preparationDiagnosticCodeForLowerLevelCode(diagnostic.code, mode),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const code = findingCodes[findingIndex];
|
|
73
|
+
findingIndex += 1;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
...diagnostic,
|
|
77
|
+
code: code ?? preparationDiagnosticCodeForLowerLevelCode(diagnostic.code, mode),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function preparationDiagnosticCodeForFinding(
|
|
83
|
+
kind: QtiDeliverySecurityFindingKind,
|
|
84
|
+
mode: QtiDeliveryPreparationMode,
|
|
85
|
+
): string {
|
|
86
|
+
if (kind === "forbidden-delivery-element") return "delivery.preparation.forbiddenElement";
|
|
87
|
+
if (kind === "unsupported-adaptive-response-processing") {
|
|
88
|
+
return "delivery.preparation.unsupportedAdaptiveResponseProcessing";
|
|
89
|
+
}
|
|
90
|
+
return mode === "server-materialized-adaptive"
|
|
91
|
+
? "delivery.preparation.unsupportedMaterialization"
|
|
92
|
+
: "delivery.preparation.unsupportedSecureDelivery";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isDeliveryFindingDiagnosticCode(code: string): boolean {
|
|
96
|
+
return (
|
|
97
|
+
code === "delivery.forbiddenElement" ||
|
|
98
|
+
code === "delivery.unsupportedSecureDelivery" ||
|
|
99
|
+
code === "delivery.unsupportedAdaptiveResponseProcessing" ||
|
|
100
|
+
code === "adaptiveTurn.materialization.forbiddenElement" ||
|
|
101
|
+
code === "adaptiveTurn.materialization.unsupported"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function preparationDiagnosticCodeForLowerLevelCode(
|
|
106
|
+
code: string,
|
|
107
|
+
mode: QtiDeliveryPreparationMode,
|
|
108
|
+
): string {
|
|
109
|
+
if (
|
|
110
|
+
code === "delivery.forbiddenElement" ||
|
|
111
|
+
code === "adaptiveTurn.materialization.forbiddenElement"
|
|
112
|
+
) {
|
|
113
|
+
return "delivery.preparation.forbiddenElement";
|
|
114
|
+
}
|
|
115
|
+
if (code === "delivery.unsupportedAdaptiveResponseProcessing") {
|
|
116
|
+
return "delivery.preparation.unsupportedAdaptiveResponseProcessing";
|
|
117
|
+
}
|
|
118
|
+
return mode === "server-materialized-adaptive"
|
|
119
|
+
? "delivery.preparation.unsupportedMaterialization"
|
|
120
|
+
: "delivery.preparation.unsupportedSecureDelivery";
|
|
121
|
+
}
|
|
@@ -167,7 +167,13 @@ export function redactDeliveryXml(
|
|
|
167
167
|
[parsed.root, ...descendants(parsed.root, () => true)],
|
|
168
168
|
(node, normalizedName) => policy.isForbiddenElement(node, normalizedName),
|
|
169
169
|
);
|
|
170
|
-
const redactedXml =
|
|
170
|
+
const redactedXml = applySourceRangeEdits(
|
|
171
|
+
xml,
|
|
172
|
+
redactionRanges.map((range) => ({
|
|
173
|
+
startOffset: range.startOffset,
|
|
174
|
+
endOffset: range.endOffset,
|
|
175
|
+
})),
|
|
176
|
+
);
|
|
171
177
|
const redactedAnalysis = analyzeDeliveryXml(parseDeliveryXml(redactedXml), policy);
|
|
172
178
|
if (!redactedAnalysis.deliverySafe || !redactedAnalysis.secureDeliverySupported) {
|
|
173
179
|
return {
|
|
@@ -203,16 +209,36 @@ export function sourceRangeFor(node: XmlNode): RedactionRange[] {
|
|
|
203
209
|
return [{ startOffset: node.sourceRange.startOffset, endOffset }];
|
|
204
210
|
}
|
|
205
211
|
|
|
206
|
-
export function
|
|
212
|
+
export function applySourceRangeEdits(xml: string, edits: readonly SourceRangeEdit[]): string {
|
|
213
|
+
if (edits.length === 0) return xml;
|
|
214
|
+
const sorted = edits.toSorted((left, right) => left.startOffset - right.startOffset);
|
|
215
|
+
assertNonOverlappingSourceRangeEdits(sorted);
|
|
207
216
|
let output = "";
|
|
208
217
|
let cursor = 0;
|
|
209
|
-
for (const
|
|
210
|
-
output += xml.slice(cursor,
|
|
211
|
-
|
|
218
|
+
for (const edit of sorted) {
|
|
219
|
+
output += xml.slice(cursor, edit.startOffset);
|
|
220
|
+
if (edit.text !== undefined) output += edit.text;
|
|
221
|
+
cursor = edit.endOffset;
|
|
212
222
|
}
|
|
213
223
|
return output + xml.slice(cursor);
|
|
214
224
|
}
|
|
215
225
|
|
|
226
|
+
function assertNonOverlappingSourceRangeEdits(edits: readonly SourceRangeEdit[]): void {
|
|
227
|
+
for (let index = 1; index < edits.length; index += 1) {
|
|
228
|
+
const previous = edits[index - 1];
|
|
229
|
+
const current = edits[index];
|
|
230
|
+
if (!previous || !current || current.startOffset >= previous.endOffset) continue;
|
|
231
|
+
throw new Error("Source range edits must not overlap.");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function removeSourceRanges(xml: string, ranges: readonly RedactionRange[]): string {
|
|
236
|
+
return applySourceRangeEdits(
|
|
237
|
+
xml,
|
|
238
|
+
ranges.map((range) => ({ startOffset: range.startOffset, endOffset: range.endOffset })),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
216
242
|
export function normalizedQtiElementName(localName: string): string {
|
|
217
243
|
const lower = localName.toLowerCase();
|
|
218
244
|
return lower.startsWith("qti-") ? lower.slice("qti-".length) : lower;
|
|
@@ -240,8 +266,13 @@ export interface RedactionRange {
|
|
|
240
266
|
endOffset: number;
|
|
241
267
|
}
|
|
242
268
|
|
|
269
|
+
export interface SourceRangeEdit extends RedactionRange {
|
|
270
|
+
/** When omitted, the source range is removed. When set, the range is replaced with this text. */
|
|
271
|
+
text?: string | undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
243
274
|
function mergeSourceRanges(ranges: readonly RedactionRange[]): RedactionRange[] {
|
|
244
|
-
const sorted =
|
|
275
|
+
const sorted = ranges.toSorted((left, right) => left.startOffset - right.startOffset);
|
|
245
276
|
const merged: RedactionRange[] = [];
|
|
246
277
|
for (const range of sorted) {
|
|
247
278
|
const last = merged.at(-1);
|
package/src/delivery-security.ts
CHANGED
|
@@ -82,13 +82,13 @@ const staticDeliveryPolicy: DeliveryRedactionPolicy = {
|
|
|
82
82
|
|
|
83
83
|
return null;
|
|
84
84
|
},
|
|
85
|
-
diagnosticCodeForFinding,
|
|
85
|
+
diagnosticCodeForFinding: staticDiagnosticCodeForFinding,
|
|
86
86
|
forbiddenElementMessage(node: XmlNode) {
|
|
87
87
|
return `${node.name} exposes answer keys, scoring, mapping, feedback, or solution information during delivery.`;
|
|
88
88
|
},
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
function
|
|
91
|
+
function staticDiagnosticCodeForFinding(kind: QtiDeliverySecurityFindingKind): string {
|
|
92
92
|
if (kind === "forbidden-delivery-element") return "delivery.forbiddenElement";
|
|
93
93
|
if (kind === "unsupported-adaptive-response-processing") {
|
|
94
94
|
return "delivery.unsupportedAdaptiveResponseProcessing";
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
type QtiAdaptiveTurnResponseInput,
|
|
5
5
|
type QtiAdaptiveTurnResult,
|
|
6
6
|
} from "./adaptive-turn.js";
|
|
7
|
+
export { assertNever } from "./assert-never.js";
|
|
7
8
|
export {
|
|
8
9
|
createCatalogSupportResolution,
|
|
9
10
|
type QtiCatalogSupportResolution,
|
|
@@ -20,6 +21,20 @@ export {
|
|
|
20
21
|
type QtiResolvedPhysicalCompanionMaterial,
|
|
21
22
|
} from "./companion-materials-resolution.js";
|
|
22
23
|
export { isResolvableAssetUrl } from "./asset-url.js";
|
|
24
|
+
export {
|
|
25
|
+
materializeQtiItemSubmission,
|
|
26
|
+
type QtiItemSubmissionMaterializationInput,
|
|
27
|
+
type QtiItemSubmissionMaterializationResult,
|
|
28
|
+
type QtiItemSubmissionResponseInput,
|
|
29
|
+
type QtiItemSubmissionScoringDisposition,
|
|
30
|
+
type QtiVariableSnapshot,
|
|
31
|
+
} from "./item-submission-materialization.js";
|
|
32
|
+
export {
|
|
33
|
+
prepareQtiDeliveryXml,
|
|
34
|
+
type QtiDeliveryPreparationMode,
|
|
35
|
+
type QtiDeliveryPreparationOptions,
|
|
36
|
+
type QtiDeliveryPreparationResult,
|
|
37
|
+
} from "./delivery-preparation.js";
|
|
23
38
|
export {
|
|
24
39
|
analyzeQtiDeliverySecurity,
|
|
25
40
|
buildQtiDeliverySafeXml,
|
|
@@ -34,7 +49,27 @@ export {
|
|
|
34
49
|
type QtiPackageXmlNode,
|
|
35
50
|
type QtiPackageXmlTree,
|
|
36
51
|
} from "./package-xml.js";
|
|
52
|
+
export {
|
|
53
|
+
parseQtiPackage,
|
|
54
|
+
type QtiAssessmentTestItemRef,
|
|
55
|
+
type QtiAssessmentTestPackageModel,
|
|
56
|
+
type QtiManifestFile,
|
|
57
|
+
type QtiManifestResource,
|
|
58
|
+
type QtiPackageAsset,
|
|
59
|
+
type QtiPackageAssetSource,
|
|
60
|
+
type QtiPackageInflateContext,
|
|
61
|
+
type QtiPackageInflateRaw,
|
|
62
|
+
type QtiPackageItem,
|
|
63
|
+
type QtiPackageItemSource,
|
|
64
|
+
type QtiPackageParseOptions,
|
|
65
|
+
type QtiPackageParseResult,
|
|
66
|
+
type QtiPackageShape,
|
|
67
|
+
type QtiStandardAlignment,
|
|
68
|
+
type QtiTimingMetadata,
|
|
69
|
+
} from "./qti-package.js";
|
|
70
|
+
export { detectPackageMediaType } from "./qti-package-assets.js";
|
|
37
71
|
export { parseQtiXml } from "./parser.js";
|
|
72
|
+
export { numericTuple3, numericTuple4 } from "./parser-values.js";
|
|
38
73
|
export { isConformanceParseDiagnostic } from "./parse-diagnostics.js";
|
|
39
74
|
export {
|
|
40
75
|
scoreQtiItemServerSide,
|
|
@@ -42,6 +77,38 @@ export {
|
|
|
42
77
|
type QtiServerScoringResponseInput,
|
|
43
78
|
type QtiServerScoringResult,
|
|
44
79
|
} from "./server-scoring.js";
|
|
80
|
+
export {
|
|
81
|
+
associationMaximumResponses,
|
|
82
|
+
choiceMatchLimitExceeded,
|
|
83
|
+
choiceMatchMaximum,
|
|
84
|
+
directedPairChoiceUseCount,
|
|
85
|
+
matchMaxDiagnostics,
|
|
86
|
+
maximumAllowedResponses,
|
|
87
|
+
maximumMediaPlays,
|
|
88
|
+
maximumResponseDiagnostic,
|
|
89
|
+
mediaPlayCount,
|
|
90
|
+
minimumMediaPlays,
|
|
91
|
+
minimumRequiredResponses,
|
|
92
|
+
orderSubsetLimitsActive,
|
|
93
|
+
parseUnlimitedMaximum,
|
|
94
|
+
requiredResponseDiagnostic,
|
|
95
|
+
responseCount,
|
|
96
|
+
responseIsEmpty,
|
|
97
|
+
responseLimitAttribute,
|
|
98
|
+
responseValidationPolicy,
|
|
99
|
+
wouldExceedChoiceMatchMaximum,
|
|
100
|
+
type DirectedPairUseSide,
|
|
101
|
+
type QtiResponseValidationPolicy,
|
|
102
|
+
} from "./response-validation-policy.js";
|
|
103
|
+
export {
|
|
104
|
+
validateQtiResponseVariables,
|
|
105
|
+
type QtiResponseValidationDiagnostic,
|
|
106
|
+
type QtiResponseValidationDiagnosticCode,
|
|
107
|
+
type QtiResponseValidationInput,
|
|
108
|
+
type QtiResponseValidationResult,
|
|
109
|
+
type QtiResponseVariableInput,
|
|
110
|
+
type QtiResponseVariablesInput,
|
|
111
|
+
} from "./response-validation.js";
|
|
45
112
|
export {
|
|
46
113
|
serializeResponseProcessing,
|
|
47
114
|
type QtiSerializeResponseProcessingResult,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { QtiAssessmentItem } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/** Items that declare automated scoring via response processing or a SCORE outcome. */
|
|
4
|
+
export function itemExpectsAutomatedScore(item: QtiAssessmentItem): boolean {
|
|
5
|
+
return (
|
|
6
|
+
Boolean(item.responseProcessing) ||
|
|
7
|
+
item.outcomeDeclarations.some((declaration) => declaration.identifier === "SCORE")
|
|
8
|
+
);
|
|
9
|
+
}
|