@longsightgroup/qti3-core 0.10.2 → 0.10.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 +21 -2
- package/dist/attempt-state.d.ts.map +1 -1
- package/dist/attempt-state.js +20 -27
- package/dist/attempt-state.js.map +1 -1
- package/dist/content-text.d.ts +4 -0
- package/dist/content-text.d.ts.map +1 -1
- package/dist/content-text.js +137 -12
- package/dist/content-text.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/media-definition.d.ts +55 -0
- package/dist/media-definition.d.ts.map +1 -0
- package/dist/media-definition.js +97 -0
- package/dist/media-definition.js.map +1 -0
- package/dist/package-xml.d.ts +1 -0
- package/dist/package-xml.d.ts.map +1 -1
- package/dist/package-xml.js +1 -0
- package/dist/package-xml.js.map +1 -1
- package/dist/parser-custom-interactions.js +1 -0
- package/dist/parser-custom-interactions.js.map +1 -1
- package/dist/parser-declarations.d.ts +4 -4
- package/dist/parser-declarations.d.ts.map +1 -1
- package/dist/parser-declarations.js +87 -46
- package/dist/parser-declarations.js.map +1 -1
- package/dist/parser-item-metadata.d.ts.map +1 -1
- package/dist/parser-item-metadata.js +5 -3
- package/dist/parser-item-metadata.js.map +1 -1
- package/dist/parser-processing.js +10 -9
- package/dist/parser-processing.js.map +1 -1
- package/dist/parser-values.d.ts +6 -0
- package/dist/parser-values.d.ts.map +1 -1
- package/dist/parser-values.js +35 -2
- package/dist/parser-values.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +81 -47
- package/dist/parser.js.map +1 -1
- package/dist/processing-evaluator-collection.d.ts.map +1 -1
- package/dist/processing-evaluator-collection.js +8 -2
- package/dist/processing-evaluator-collection.js.map +1 -1
- package/dist/processing-evaluator-comparison.d.ts.map +1 -1
- package/dist/processing-evaluator-comparison.js +22 -6
- package/dist/processing-evaluator-comparison.js.map +1 -1
- package/dist/processing-evaluator-numeric.d.ts.map +1 -1
- package/dist/processing-evaluator-numeric.js +8 -1
- package/dist/processing-evaluator-numeric.js.map +1 -1
- package/dist/processing-evaluator-variable.d.ts.map +1 -1
- package/dist/processing-evaluator-variable.js +1 -1
- package/dist/processing-evaluator-variable.js.map +1 -1
- package/dist/processing-expression-children.d.ts.map +1 -1
- package/dist/processing-expression-children.js +2 -1
- package/dist/processing-expression-children.js.map +1 -1
- package/dist/processing-limits.d.ts +3 -0
- package/dist/processing-limits.d.ts.map +1 -0
- package/dist/processing-limits.js +3 -0
- package/dist/processing-limits.js.map +1 -0
- package/dist/processing-mapping.d.ts.map +1 -1
- package/dist/processing-mapping.js +28 -9
- package/dist/processing-mapping.js.map +1 -1
- package/dist/processing-operators.d.ts +2 -0
- package/dist/processing-operators.d.ts.map +1 -1
- package/dist/processing-operators.js +34 -5
- package/dist/processing-operators.js.map +1 -1
- package/dist/processing-values.d.ts +4 -4
- package/dist/processing-values.d.ts.map +1 -1
- package/dist/processing-values.js +28 -14
- package/dist/processing-values.js.map +1 -1
- package/dist/processing-variables.d.ts +3 -1
- package/dist/processing-variables.d.ts.map +1 -1
- package/dist/processing-variables.js +38 -2
- package/dist/processing-variables.js.map +1 -1
- package/dist/qti-namespaces.d.ts +7 -0
- package/dist/qti-namespaces.d.ts.map +1 -0
- package/dist/qti-namespaces.js +7 -0
- package/dist/qti-namespaces.js.map +1 -0
- package/dist/qti-package-items.d.ts.map +1 -1
- package/dist/qti-package-items.js +23 -13
- package/dist/qti-package-items.js.map +1 -1
- package/dist/qti-package-manifest.d.ts.map +1 -1
- package/dist/qti-package-manifest.js +4 -3
- package/dist/qti-package-manifest.js.map +1 -1
- package/dist/qti-package-metadata.d.ts.map +1 -1
- package/dist/qti-package-metadata.js +3 -2
- package/dist/qti-package-metadata.js.map +1 -1
- package/dist/qti-package-xml.d.ts +4 -2
- package/dist/qti-package-xml.d.ts.map +1 -1
- package/dist/qti-package-xml.js +10 -5
- package/dist/qti-package-xml.js.map +1 -1
- package/dist/qti-package-zip.d.ts +17 -1
- package/dist/qti-package-zip.d.ts.map +1 -1
- package/dist/qti-package-zip.js +60 -5
- package/dist/qti-package-zip.js.map +1 -1
- package/dist/qti-package.d.ts +3 -3
- package/dist/qti-package.d.ts.map +1 -1
- package/dist/qti-package.js +10 -4
- package/dist/qti-package.js.map +1 -1
- package/dist/response-validation-policy.d.ts.map +1 -1
- package/dist/response-validation-policy.js +5 -6
- package/dist/response-validation-policy.js.map +1 -1
- package/dist/response-validation.d.ts +12 -2
- package/dist/response-validation.d.ts.map +1 -1
- package/dist/response-validation.js +122 -19
- package/dist/response-validation.js.map +1 -1
- package/dist/serializer-processing-expressions.js +1 -1
- package/dist/serializer-processing-expressions.js.map +1 -1
- package/dist/session.js +1 -1
- package/dist/session.js.map +1 -1
- package/dist/slider-definition.d.ts +59 -0
- package/dist/slider-definition.d.ts.map +1 -0
- package/dist/slider-definition.js +198 -0
- package/dist/slider-definition.js.map +1 -0
- package/dist/support-evidence.d.ts +6 -2
- package/dist/support-evidence.d.ts.map +1 -1
- package/dist/support-evidence.js +15 -11
- package/dist/support-evidence.js.map +1 -1
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +96 -100
- package/dist/support.js.map +1 -1
- package/dist/trusted-item-session.d.ts.map +1 -1
- package/dist/trusted-item-session.js +5 -3
- package/dist/trusted-item-session.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validation-declaration-entries.d.ts +13 -0
- package/dist/validation-declaration-entries.d.ts.map +1 -0
- package/dist/validation-declaration-entries.js +136 -0
- package/dist/validation-declaration-entries.js.map +1 -0
- package/dist/validation-interactions.d.ts.map +1 -1
- package/dist/validation-interactions.js +14 -46
- package/dist/validation-interactions.js.map +1 -1
- package/dist/validation-primitives.d.ts +5 -1
- package/dist/validation-primitives.d.ts.map +1 -1
- package/dist/validation-primitives.js +41 -3
- package/dist/validation-primitives.js.map +1 -1
- package/dist/validation-processing.d.ts.map +1 -1
- package/dist/validation-processing.js +125 -1
- package/dist/validation-processing.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +8 -134
- package/dist/validation.js.map +1 -1
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js +31 -2
- package/dist/xml.js.map +1 -1
- package/package.json +1 -1
- package/src/attempt-state.ts +22 -26
- package/src/content-text.ts +174 -14
- package/src/index.ts +28 -0
- package/src/media-definition.ts +174 -0
- package/src/package-xml.ts +2 -0
- package/src/parser-custom-interactions.ts +1 -0
- package/src/parser-declarations.ts +134 -40
- package/src/parser-item-metadata.ts +5 -3
- package/src/parser-processing.ts +10 -9
- package/src/parser-values.ts +35 -2
- package/src/parser.ts +137 -103
- package/src/processing-evaluator-collection.ts +7 -2
- package/src/processing-evaluator-comparison.ts +30 -5
- package/src/processing-evaluator-numeric.ts +5 -1
- package/src/processing-evaluator-variable.ts +1 -0
- package/src/processing-expression-children.ts +2 -1
- package/src/processing-limits.ts +2 -0
- package/src/processing-mapping.ts +32 -8
- package/src/processing-operators.ts +38 -5
- package/src/processing-values.ts +59 -14
- package/src/processing-variables.ts +47 -2
- package/src/qti-namespaces.ts +8 -0
- package/src/qti-package-items.ts +56 -49
- package/src/qti-package-manifest.ts +13 -4
- package/src/qti-package-metadata.ts +3 -2
- package/src/qti-package-xml.ts +16 -3
- package/src/qti-package-zip.ts +128 -3
- package/src/qti-package.ts +12 -3
- package/src/response-validation-policy.ts +5 -5
- package/src/response-validation.ts +185 -20
- package/src/serializer-processing-expressions.ts +1 -1
- package/src/session.ts +1 -0
- package/src/slider-definition.ts +363 -0
- package/src/support-evidence.ts +15 -17
- package/src/support.ts +106 -102
- package/src/trusted-item-session.ts +5 -3
- package/src/types.ts +10 -1
- package/src/validation-declaration-entries.ts +162 -0
- package/src/validation-interactions.ts +14 -64
- package/src/validation-primitives.ts +44 -4
- package/src/validation-processing.ts +145 -1
- package/src/validation.ts +11 -151
- package/src/xml.ts +40 -4
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
parseBaseType,
|
|
5
5
|
parseCardinality,
|
|
6
6
|
parseCoords,
|
|
7
|
+
parseFiniteNumber,
|
|
7
8
|
parseShape,
|
|
8
9
|
parseXmlBoolean,
|
|
9
10
|
} from "./parser-values.js";
|
|
10
11
|
import type {
|
|
11
12
|
QtiBaseType,
|
|
13
|
+
QtiDiagnostic,
|
|
12
14
|
QtiLookupTable,
|
|
13
15
|
QtiOutcomeDeclaration,
|
|
14
16
|
QtiResponseDeclaration,
|
|
@@ -16,9 +18,17 @@ import type {
|
|
|
16
18
|
QtiTemplateDeclaration,
|
|
17
19
|
QtiValue,
|
|
18
20
|
} from "./types.js";
|
|
21
|
+
import {
|
|
22
|
+
validateAreaMapEntryAttributes,
|
|
23
|
+
validateLookupTableEntryAttributes,
|
|
24
|
+
validateMapEntryAttributes,
|
|
25
|
+
} from "./validation-declaration-entries.js";
|
|
19
26
|
import { childElements, textContent, type XmlNode } from "./xml.js";
|
|
20
27
|
|
|
21
|
-
export function parseResponseDeclaration(
|
|
28
|
+
export function parseResponseDeclaration(
|
|
29
|
+
node: XmlNode,
|
|
30
|
+
diagnostics: QtiDiagnostic[],
|
|
31
|
+
): QtiResponseDeclaration {
|
|
22
32
|
const cardinality = parseCardinality(node.attributes.cardinality);
|
|
23
33
|
const baseType = parseBaseType(node.attributes["base-type"]);
|
|
24
34
|
return {
|
|
@@ -26,40 +36,66 @@ export function parseResponseDeclaration(node: XmlNode): QtiResponseDeclaration
|
|
|
26
36
|
identifier: node.attributes.identifier ?? "",
|
|
27
37
|
cardinality,
|
|
28
38
|
baseType,
|
|
29
|
-
defaultValue: parseVariableValue(
|
|
39
|
+
defaultValue: parseVariableValue(
|
|
40
|
+
childElements(node, "qti-default-value")[0],
|
|
41
|
+
baseType,
|
|
42
|
+
diagnostics,
|
|
43
|
+
),
|
|
30
44
|
correctResponse: normalizeValueForCardinality(
|
|
31
|
-
parseVariableValue(childElements(node, "qti-correct-response")[0], baseType),
|
|
45
|
+
parseVariableValue(childElements(node, "qti-correct-response")[0], baseType, diagnostics),
|
|
32
46
|
cardinality,
|
|
33
47
|
),
|
|
34
|
-
mapping: parseMapping(
|
|
35
|
-
|
|
48
|
+
mapping: parseMapping(
|
|
49
|
+
childElements(node, "qti-mapping")[0],
|
|
50
|
+
node.attributes.identifier ?? "",
|
|
51
|
+
diagnostics,
|
|
52
|
+
),
|
|
53
|
+
areaMapping: parseAreaMapping(
|
|
54
|
+
childElements(node, "qti-area-mapping")[0],
|
|
55
|
+
node.attributes.identifier ?? "",
|
|
56
|
+
diagnostics,
|
|
57
|
+
),
|
|
36
58
|
attributes: node.attributes,
|
|
37
59
|
source: node.source,
|
|
38
60
|
};
|
|
39
61
|
}
|
|
40
62
|
|
|
41
|
-
export function parseOutcomeDeclaration(
|
|
63
|
+
export function parseOutcomeDeclaration(
|
|
64
|
+
node: XmlNode,
|
|
65
|
+
diagnostics: QtiDiagnostic[],
|
|
66
|
+
): QtiOutcomeDeclaration {
|
|
42
67
|
const baseType = parseBaseType(node.attributes["base-type"]);
|
|
43
68
|
return {
|
|
44
69
|
kind: "outcome",
|
|
45
70
|
identifier: node.attributes.identifier ?? "",
|
|
46
71
|
cardinality: parseCardinality(node.attributes.cardinality),
|
|
47
72
|
baseType,
|
|
48
|
-
defaultValue: parseVariableValue(
|
|
49
|
-
|
|
73
|
+
defaultValue: parseVariableValue(
|
|
74
|
+
childElements(node, "qti-default-value")[0],
|
|
75
|
+
baseType,
|
|
76
|
+
diagnostics,
|
|
77
|
+
),
|
|
78
|
+
lookupTable: parseLookupTable(node, baseType, diagnostics),
|
|
50
79
|
attributes: node.attributes,
|
|
51
80
|
source: node.source,
|
|
52
81
|
};
|
|
53
82
|
}
|
|
54
83
|
|
|
55
|
-
export function parseTemplateDeclaration(
|
|
84
|
+
export function parseTemplateDeclaration(
|
|
85
|
+
node: XmlNode,
|
|
86
|
+
diagnostics: QtiDiagnostic[],
|
|
87
|
+
): QtiTemplateDeclaration {
|
|
56
88
|
const baseType = parseBaseType(node.attributes["base-type"]);
|
|
57
89
|
return {
|
|
58
90
|
kind: "template",
|
|
59
91
|
identifier: node.attributes.identifier ?? "",
|
|
60
92
|
cardinality: parseCardinality(node.attributes.cardinality),
|
|
61
93
|
baseType,
|
|
62
|
-
defaultValue: parseVariableValue(
|
|
94
|
+
defaultValue: parseVariableValue(
|
|
95
|
+
childElements(node, "qti-default-value")[0],
|
|
96
|
+
baseType,
|
|
97
|
+
diagnostics,
|
|
98
|
+
),
|
|
63
99
|
attributes: node.attributes,
|
|
64
100
|
source: node.source,
|
|
65
101
|
};
|
|
@@ -68,6 +104,7 @@ export function parseTemplateDeclaration(node: XmlNode): QtiTemplateDeclaration
|
|
|
68
104
|
function parseVariableValue(
|
|
69
105
|
node: XmlNode | undefined,
|
|
70
106
|
baseType: QtiBaseType | undefined,
|
|
107
|
+
diagnostics: QtiDiagnostic[],
|
|
71
108
|
): QtiValue {
|
|
72
109
|
if (!node) return null;
|
|
73
110
|
const valueNodes = childElements(node, "qti-value");
|
|
@@ -79,6 +116,16 @@ function parseVariableValue(
|
|
|
79
116
|
(entry): entry is { fieldIdentifier: string; value: QtiScalarValue } =>
|
|
80
117
|
Boolean(entry.fieldIdentifier),
|
|
81
118
|
);
|
|
119
|
+
const fieldedEntryCount = entries.filter((entry) => entry.fieldIdentifier !== undefined).length;
|
|
120
|
+
if (fieldedEntryCount > 0 && fieldedEntryCount < entries.length) {
|
|
121
|
+
diagnostics.push({
|
|
122
|
+
code: "declaration.value.fieldIdentifier.mixed",
|
|
123
|
+
severity: "error",
|
|
124
|
+
message: `${node.localName} must not mix qti-value children with and without field-identifier.`,
|
|
125
|
+
path: node.source.path,
|
|
126
|
+
source: node.source,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
82
129
|
if (recordEntries.length > 0) {
|
|
83
130
|
return Object.fromEntries(recordEntries.map((entry) => [entry.fieldIdentifier, entry.value]));
|
|
84
131
|
}
|
|
@@ -90,87 +137,134 @@ function parseVariableValue(
|
|
|
90
137
|
return entries.map((entry) => entry.value);
|
|
91
138
|
}
|
|
92
139
|
|
|
93
|
-
function parseMapping(
|
|
140
|
+
function parseMapping(
|
|
141
|
+
node: XmlNode | undefined,
|
|
142
|
+
declarationIdentifier: string,
|
|
143
|
+
diagnostics: QtiDiagnostic[],
|
|
144
|
+
): QtiResponseDeclaration["mapping"] | undefined {
|
|
94
145
|
if (!node) return undefined;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
146
|
+
const defaultValue = parseFiniteNumber(node.attributes["default-value"]);
|
|
147
|
+
const entries: NonNullable<QtiResponseDeclaration["mapping"]>["entries"] = [];
|
|
148
|
+
for (const entry of childElements(node, "qti-map-entry")) {
|
|
149
|
+
const mappedValue = parseFiniteNumber(entry.attributes["mapped-value"]);
|
|
150
|
+
if (mappedValue === undefined) {
|
|
151
|
+
validateMapEntryAttributes(declarationIdentifier, entry, diagnostics);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
entries.push({
|
|
100
155
|
mapKey: entry.attributes["map-key"],
|
|
101
|
-
mappedValue
|
|
156
|
+
mappedValue,
|
|
102
157
|
attributes: entry.attributes,
|
|
103
158
|
source: entry.source,
|
|
104
|
-
})
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
defaultValue: defaultValue ?? 0,
|
|
163
|
+
attributes: node.attributes,
|
|
164
|
+
source: node.source,
|
|
165
|
+
entries,
|
|
105
166
|
};
|
|
106
167
|
}
|
|
107
168
|
|
|
108
169
|
function parseAreaMapping(
|
|
109
170
|
node: XmlNode | undefined,
|
|
171
|
+
declarationIdentifier: string,
|
|
172
|
+
diagnostics: QtiDiagnostic[],
|
|
110
173
|
): QtiResponseDeclaration["areaMapping"] | undefined {
|
|
111
174
|
if (!node) return undefined;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
175
|
+
const defaultValue = parseFiniteNumber(node.attributes["default-value"]);
|
|
176
|
+
const entries: NonNullable<QtiResponseDeclaration["areaMapping"]>["entries"] = [];
|
|
177
|
+
for (const entry of childElements(node, "qti-area-map-entry")) {
|
|
178
|
+
const mappedValue = parseFiniteNumber(entry.attributes["mapped-value"]);
|
|
179
|
+
if (mappedValue === undefined) {
|
|
180
|
+
validateAreaMapEntryAttributes(declarationIdentifier, entry, diagnostics);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
entries.push({
|
|
117
184
|
shape: parseShape(entry.attributes.shape),
|
|
118
185
|
coords: parseCoords(entry.attributes.coords),
|
|
119
|
-
mappedValue
|
|
186
|
+
mappedValue,
|
|
120
187
|
attributes: entry.attributes,
|
|
121
188
|
source: entry.source,
|
|
122
|
-
})
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
defaultValue: defaultValue ?? 0,
|
|
193
|
+
attributes: node.attributes,
|
|
194
|
+
source: node.source,
|
|
195
|
+
entries,
|
|
123
196
|
};
|
|
124
197
|
}
|
|
125
198
|
|
|
126
199
|
function parseLookupTable(
|
|
127
200
|
node: XmlNode,
|
|
128
201
|
baseType: QtiOutcomeDeclaration["baseType"],
|
|
202
|
+
diagnostics: QtiDiagnostic[],
|
|
129
203
|
): QtiLookupTable | undefined {
|
|
130
204
|
const matchTable = childElements(node, "qti-match-table")[0];
|
|
131
|
-
if (matchTable) return parseMatchTable(matchTable, baseType);
|
|
205
|
+
if (matchTable) return parseMatchTable(matchTable, baseType, diagnostics);
|
|
132
206
|
const interpolationTable = childElements(node, "qti-interpolation-table")[0];
|
|
133
|
-
if (interpolationTable) return parseInterpolationTable(interpolationTable, baseType);
|
|
207
|
+
if (interpolationTable) return parseInterpolationTable(interpolationTable, baseType, diagnostics);
|
|
134
208
|
return undefined;
|
|
135
209
|
}
|
|
136
210
|
|
|
137
211
|
function parseMatchTable(
|
|
138
212
|
node: XmlNode,
|
|
139
213
|
baseType: QtiOutcomeDeclaration["baseType"],
|
|
214
|
+
diagnostics: QtiDiagnostic[],
|
|
140
215
|
): QtiLookupTable {
|
|
141
216
|
return {
|
|
142
217
|
type: "match",
|
|
143
218
|
defaultValue: parseLookupValue(node.attributes["default-value"], baseType),
|
|
144
219
|
attributes: node.attributes,
|
|
145
220
|
source: node.source,
|
|
146
|
-
entries:
|
|
147
|
-
sourceValue: Number(entry.attributes["source-value"]),
|
|
148
|
-
targetValue: parseLookupValue(entry.attributes["target-value"], baseType),
|
|
149
|
-
attributes: entry.attributes,
|
|
150
|
-
source: entry.source,
|
|
151
|
-
})),
|
|
221
|
+
entries: parseLookupEntries(node, "match", baseType, diagnostics),
|
|
152
222
|
};
|
|
153
223
|
}
|
|
154
224
|
|
|
155
225
|
function parseInterpolationTable(
|
|
156
226
|
node: XmlNode,
|
|
157
227
|
baseType: QtiOutcomeDeclaration["baseType"],
|
|
228
|
+
diagnostics: QtiDiagnostic[],
|
|
158
229
|
): QtiLookupTable {
|
|
159
230
|
return {
|
|
160
231
|
type: "interpolation",
|
|
161
232
|
defaultValue: parseLookupValue(node.attributes["default-value"], baseType),
|
|
162
233
|
attributes: node.attributes,
|
|
163
234
|
source: node.source,
|
|
164
|
-
entries:
|
|
165
|
-
sourceValue: Number(entry.attributes["source-value"]),
|
|
166
|
-
targetValue: parseLookupValue(entry.attributes["target-value"], baseType),
|
|
167
|
-
includeBoundary: parseXmlBoolean(entry.attributes["include-boundary"]) ?? true,
|
|
168
|
-
attributes: entry.attributes,
|
|
169
|
-
source: entry.source,
|
|
170
|
-
})),
|
|
235
|
+
entries: parseLookupEntries(node, "interpolation", baseType, diagnostics),
|
|
171
236
|
};
|
|
172
237
|
}
|
|
173
238
|
|
|
174
239
|
function parseLookupValue(value: string | undefined, baseType: string | undefined): QtiValue {
|
|
175
240
|
return value === undefined ? null : coerceValue(value, baseType);
|
|
176
241
|
}
|
|
242
|
+
|
|
243
|
+
function parseLookupEntries(
|
|
244
|
+
node: XmlNode,
|
|
245
|
+
tableType: QtiLookupTable["type"],
|
|
246
|
+
baseType: QtiOutcomeDeclaration["baseType"],
|
|
247
|
+
diagnostics: QtiDiagnostic[],
|
|
248
|
+
): QtiLookupTable["entries"] {
|
|
249
|
+
const entryName =
|
|
250
|
+
tableType === "match" ? "qti-match-table-entry" : "qti-interpolation-table-entry";
|
|
251
|
+
const entries: QtiLookupTable["entries"] = [];
|
|
252
|
+
for (const entry of childElements(node, entryName)) {
|
|
253
|
+
const sourceValue = parseFiniteNumber(entry.attributes["source-value"]);
|
|
254
|
+
if (sourceValue === undefined) {
|
|
255
|
+
validateLookupTableEntryAttributes(tableType, entry, diagnostics);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
entries.push({
|
|
259
|
+
sourceValue,
|
|
260
|
+
targetValue: parseLookupValue(entry.attributes["target-value"], baseType),
|
|
261
|
+
includeBoundary:
|
|
262
|
+
tableType === "interpolation"
|
|
263
|
+
? (parseXmlBoolean(entry.attributes["include-boundary"]) ?? true)
|
|
264
|
+
: undefined,
|
|
265
|
+
attributes: entry.attributes,
|
|
266
|
+
source: entry.source,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return entries;
|
|
270
|
+
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type ParsedCompanionMaterialChildQtiName,
|
|
20
20
|
pushCompanionMaterialParseWarning,
|
|
21
21
|
} from "./companion-materials.js";
|
|
22
|
+
import { visibleTextContent } from "./content-text.js";
|
|
22
23
|
import { parseXmlBoolean } from "./parser-values.js";
|
|
23
24
|
import { childElements, descendants, textContent, type XmlNode } from "./xml.js";
|
|
24
25
|
|
|
@@ -75,7 +76,7 @@ export function parseModalFeedback(node: XmlNode): QtiModalFeedback {
|
|
|
75
76
|
identifier: node.attributes.identifier ?? "",
|
|
76
77
|
outcomeIdentifier: node.attributes["outcome-identifier"] ?? "",
|
|
77
78
|
showHide,
|
|
78
|
-
text:
|
|
79
|
+
text: visibleTextContent(node),
|
|
79
80
|
source: node.source,
|
|
80
81
|
};
|
|
81
82
|
}
|
|
@@ -119,7 +120,7 @@ function parseCatalogCardEntry(node: XmlNode): QtiCatalogCardEntry {
|
|
|
119
120
|
function parseCatalogHtmlContent(node: XmlNode | undefined): QtiCatalogHtmlContent | undefined {
|
|
120
121
|
if (!node) return undefined;
|
|
121
122
|
return {
|
|
122
|
-
text:
|
|
123
|
+
text: visibleTextContent(node),
|
|
123
124
|
children: parseCatalogHtmlChildren(node),
|
|
124
125
|
attributes: node.attributes,
|
|
125
126
|
source: node.source,
|
|
@@ -136,6 +137,7 @@ function parseCatalogHtmlChildren(node: XmlNode): QtiContentNode[] {
|
|
|
136
137
|
content.push({
|
|
137
138
|
kind: "element",
|
|
138
139
|
qtiName: entry.localName,
|
|
140
|
+
namespaceUri: entry.uri,
|
|
139
141
|
attributes: entry.attributes,
|
|
140
142
|
children: parseCatalogHtmlChildren(entry),
|
|
141
143
|
source: entry.source,
|
|
@@ -202,7 +204,7 @@ function parsePhysicalMaterial(
|
|
|
202
204
|
node: XmlNode,
|
|
203
205
|
diagnostics: QtiDiagnostic[],
|
|
204
206
|
): QtiPhysicalMaterial | undefined {
|
|
205
|
-
const text =
|
|
207
|
+
const text = visibleTextContent(node);
|
|
206
208
|
if (text.length === 0) {
|
|
207
209
|
return pushCompanionMaterialParseWarning(
|
|
208
210
|
diagnostics,
|
package/src/parser-processing.ts
CHANGED
|
@@ -646,15 +646,16 @@ function parseExpression(node: XmlNode): QtiProcessingExpression | undefined {
|
|
|
646
646
|
}
|
|
647
647
|
|
|
648
648
|
if (node.localName === "qti-stats-operator") {
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
649
|
+
const expressions = childElements(node)
|
|
650
|
+
.map(parseExpression)
|
|
651
|
+
.filter((expression): expression is QtiProcessingExpression => expression !== undefined);
|
|
652
|
+
return {
|
|
653
|
+
type: "statsOperator",
|
|
654
|
+
name: node.attributes.name ?? "",
|
|
655
|
+
expression: expressions[0] ?? { type: "null", source: node.source },
|
|
656
|
+
expressions,
|
|
657
|
+
source: node.source,
|
|
658
|
+
};
|
|
658
659
|
}
|
|
659
660
|
|
|
660
661
|
if (node.localName === "qti-custom-operator") {
|
package/src/parser-values.ts
CHANGED
|
@@ -20,6 +20,7 @@ const QTI_BASE_TYPES = new Set<string>([
|
|
|
20
20
|
"file",
|
|
21
21
|
"uri",
|
|
22
22
|
]);
|
|
23
|
+
const QTI_FINITE_FLOAT_PATTERN = /^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[Ee][+-]?[0-9]+)?$/;
|
|
23
24
|
|
|
24
25
|
export function isQtiBaseType(value: string): value is QtiBaseType {
|
|
25
26
|
return QTI_BASE_TYPES.has(value);
|
|
@@ -43,8 +44,11 @@ export function numericTuple4(values: number[]): [number, number, number, number
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export function coerceValue(value: string, baseType: string | undefined): QtiScalarValue {
|
|
46
|
-
if (baseType === "integer") return
|
|
47
|
-
if (baseType === "float") return
|
|
47
|
+
if (baseType === "integer") return parseInteger(value) ?? value;
|
|
48
|
+
if (baseType === "float") return parseFiniteNumber(value) ?? value;
|
|
49
|
+
if (baseType === "pair" || baseType === "directedPair") {
|
|
50
|
+
return parseQtiPair(value, baseType) ?? value;
|
|
51
|
+
}
|
|
48
52
|
if (baseType === "boolean") {
|
|
49
53
|
const parsed = parseXmlBoolean(value);
|
|
50
54
|
if (parsed !== undefined) return parsed;
|
|
@@ -53,6 +57,26 @@ export function coerceValue(value: string, baseType: string | undefined): QtiSca
|
|
|
53
57
|
return value;
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
/** Parse and canonicalize a QTI pair while preserving directed-pair endpoint order. */
|
|
61
|
+
export function parseQtiPair(
|
|
62
|
+
value: string | undefined,
|
|
63
|
+
baseType: "pair" | "directedPair",
|
|
64
|
+
): string | undefined {
|
|
65
|
+
if (value === undefined) return undefined;
|
|
66
|
+
const parts = value.trim().split(/\s+/);
|
|
67
|
+
if (parts.length !== 2 || parts.some((part) => part.length === 0)) return undefined;
|
|
68
|
+
return (baseType === "pair" ? parts.toSorted() : parts).join(" ");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Parse a complete integer lexical value without accepting a numeric prefix. */
|
|
72
|
+
export function parseInteger(value: string | undefined): number | undefined {
|
|
73
|
+
if (value === undefined) return undefined;
|
|
74
|
+
const normalized = value.trim();
|
|
75
|
+
if (!/^-?\d+$/.test(normalized)) return undefined;
|
|
76
|
+
const parsed = Number(normalized);
|
|
77
|
+
return Number.isInteger(parsed) ? parsed : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
export function parseCardinality(value: string | undefined): QtiCardinality {
|
|
57
81
|
if (value === "multiple" || value === "ordered" || value === "record") return value;
|
|
58
82
|
return "single";
|
|
@@ -66,6 +90,15 @@ export function parseXmlBoolean(value: string | undefined): boolean | undefined
|
|
|
66
90
|
return undefined;
|
|
67
91
|
}
|
|
68
92
|
|
|
93
|
+
/** Parse the finite subset of the XML Schema float lexical space used by QTI values. */
|
|
94
|
+
export function parseFiniteNumber(value: string | undefined): number | undefined {
|
|
95
|
+
if (value === undefined) return undefined;
|
|
96
|
+
const normalized = value.trim();
|
|
97
|
+
if (!QTI_FINITE_FLOAT_PATTERN.test(normalized)) return undefined;
|
|
98
|
+
const parsed = Number(normalized);
|
|
99
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
69
102
|
export function normalizeValueForCardinality(
|
|
70
103
|
value: QtiValue,
|
|
71
104
|
cardinality: QtiCardinality,
|