@longsightgroup/qti3-core 0.9.9 → 0.9.10
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/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/parser-processing.d.ts.map +1 -1
- package/dist/parser-processing.js +4 -0
- package/dist/parser-processing.js.map +1 -1
- package/dist/processing-expression-collection.d.ts +4 -0
- package/dist/processing-expression-collection.d.ts.map +1 -0
- package/dist/processing-expression-collection.js +44 -0
- package/dist/processing-expression-collection.js.map +1 -0
- package/dist/qti-package-assets.d.ts +3 -1
- package/dist/qti-package-assets.d.ts.map +1 -1
- package/dist/qti-package-assets.js +22 -0
- package/dist/qti-package-assets.js.map +1 -1
- package/dist/qti-package-items.d.ts +2 -1
- package/dist/qti-package-items.d.ts.map +1 -1
- package/dist/qti-package-items.js +96 -18
- 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 +2 -0
- package/dist/qti-package-manifest.js.map +1 -1
- package/dist/qti-package-metadata.d.ts +5 -2
- package/dist/qti-package-metadata.d.ts.map +1 -1
- package/dist/qti-package-metadata.js +116 -8
- package/dist/qti-package-metadata.js.map +1 -1
- package/dist/qti-package-types.d.ts +62 -0
- package/dist/qti-package-types.d.ts.map +1 -1
- package/dist/qti-package-xml.d.ts.map +1 -1
- package/dist/qti-package-xml.js +1 -1
- package/dist/qti-package-xml.js.map +1 -1
- package/dist/qti-package.d.ts +2 -1
- package/dist/qti-package.d.ts.map +1 -1
- package/dist/qti-package.js +2 -0
- package/dist/qti-package.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +9 -0
- package/src/parser-processing.ts +4 -0
- package/src/processing-expression-collection.ts +57 -0
- package/src/qti-package-assets.ts +30 -0
- package/src/qti-package-items.ts +190 -29
- package/src/qti-package-manifest.ts +2 -0
- package/src/qti-package-metadata.ts +174 -7
- package/src/qti-package-types.ts +69 -0
- package/src/qti-package-xml.ts +2 -1
- package/src/qti-package.ts +9 -0
- package/src/types.ts +2 -0
package/src/index.ts
CHANGED
|
@@ -52,31 +52,40 @@ export {
|
|
|
52
52
|
} from "./package-xml.js";
|
|
53
53
|
export {
|
|
54
54
|
decodeUtf8,
|
|
55
|
+
discoverQtiPackageContentAssets,
|
|
55
56
|
normalizePackagePath,
|
|
56
57
|
QTI_ITEM_RESOURCE_TYPE,
|
|
57
58
|
QTI_PACKAGE_MANIFEST_PATH,
|
|
58
59
|
parseQtiPackage,
|
|
59
60
|
readQtiPackageZipEntries,
|
|
61
|
+
type QtiAssessmentSectionPackageModel,
|
|
60
62
|
type QtiAssessmentTestItemRef,
|
|
61
63
|
type QtiAssessmentTestPackageModel,
|
|
62
64
|
type QtiManifestFile,
|
|
63
65
|
type QtiManifestResource,
|
|
64
66
|
type QtiPackageAsset,
|
|
67
|
+
type QtiPackageContentAssetDiscovery,
|
|
65
68
|
type QtiPackageAssetSource,
|
|
66
69
|
type QtiPackageEntry,
|
|
67
70
|
type QtiPackageInflateContext,
|
|
68
71
|
type QtiPackageInflateRaw,
|
|
72
|
+
type QtiItemSessionControl,
|
|
69
73
|
type QtiPackageItem,
|
|
70
74
|
type QtiPackageItemSource,
|
|
71
75
|
type QtiPackageParseOptions,
|
|
72
76
|
type QtiPackageParseResult,
|
|
73
77
|
type QtiPackageShape,
|
|
74
78
|
type QtiStandardAlignment,
|
|
79
|
+
type QtiTestPartNavigationMode,
|
|
80
|
+
type QtiTestPartPackageModel,
|
|
81
|
+
type QtiTestPartSubmissionMode,
|
|
82
|
+
type QtiTimeLimits,
|
|
75
83
|
type QtiTimingMetadata,
|
|
76
84
|
} from "./qti-package.js";
|
|
77
85
|
export { detectPackageMediaType } from "./qti-package-assets.js";
|
|
78
86
|
export { parseQtiXml } from "./parser.js";
|
|
79
87
|
export { numericTuple3, numericTuple4 } from "./parser-values.js";
|
|
88
|
+
export { collectQtiResponseProcessingExpressions } from "./processing-expression-collection.js";
|
|
80
89
|
export { isConformanceParseDiagnostic } from "./parse-diagnostics.js";
|
|
81
90
|
export {
|
|
82
91
|
scoreQtiItemServerSide,
|
package/src/parser-processing.ts
CHANGED
|
@@ -21,6 +21,10 @@ export function parseResponseProcessing(
|
|
|
21
21
|
template: node.attributes.template,
|
|
22
22
|
rules,
|
|
23
23
|
conditions: responseConditionsFromRules(rules),
|
|
24
|
+
expressions: node.children.flatMap((child) => {
|
|
25
|
+
const expression = parseExpression(child);
|
|
26
|
+
return expression ? [expression] : [];
|
|
27
|
+
}),
|
|
24
28
|
};
|
|
25
29
|
}
|
|
26
30
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { expressionChildren } from "./processing-expression-children.js";
|
|
2
|
+
import type {
|
|
3
|
+
QtiProcessingExpression,
|
|
4
|
+
QtiResponseCondition,
|
|
5
|
+
QtiResponseProcessing,
|
|
6
|
+
QtiResponseRule,
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
|
|
9
|
+
/** Collect every processing expression in a parsed response-processing tree. */
|
|
10
|
+
export function collectQtiResponseProcessingExpressions(
|
|
11
|
+
processing: QtiResponseProcessing | undefined,
|
|
12
|
+
): QtiProcessingExpression[] {
|
|
13
|
+
if (!processing) return [];
|
|
14
|
+
const expressions: QtiProcessingExpression[] = [];
|
|
15
|
+
for (const expression of processing.expressions ?? []) collectExpression(expression, expressions);
|
|
16
|
+
collectRules(processing.rules, expressions);
|
|
17
|
+
for (const condition of processing.conditions) collectCondition(condition, expressions);
|
|
18
|
+
return expressions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function collectRules(
|
|
22
|
+
rules: readonly QtiResponseRule[],
|
|
23
|
+
expressions: QtiProcessingExpression[],
|
|
24
|
+
): void {
|
|
25
|
+
for (const rule of rules) {
|
|
26
|
+
if (rule.type === "setOutcomeValue" || rule.type === "lookupOutcomeValue") {
|
|
27
|
+
collectExpression(rule.expression, expressions);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (rule.type === "responseCondition") {
|
|
31
|
+
collectCondition(rule.condition, expressions);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (rule.type === "responseProcessingFragment") collectRules(rule.rules, expressions);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function collectCondition(
|
|
39
|
+
condition: QtiResponseCondition,
|
|
40
|
+
expressions: QtiProcessingExpression[],
|
|
41
|
+
): void {
|
|
42
|
+
if (condition.ifExpression) collectExpression(condition.ifExpression, expressions);
|
|
43
|
+
collectRules(condition.thenRules, expressions);
|
|
44
|
+
for (const branch of condition.elseIfs) {
|
|
45
|
+
if (branch.expression) collectExpression(branch.expression, expressions);
|
|
46
|
+
collectRules(branch.rules, expressions);
|
|
47
|
+
}
|
|
48
|
+
collectRules(condition.elseRules, expressions);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectExpression(
|
|
52
|
+
expression: QtiProcessingExpression,
|
|
53
|
+
expressions: QtiProcessingExpression[],
|
|
54
|
+
): void {
|
|
55
|
+
expressions.push(expression);
|
|
56
|
+
for (const child of expressionChildren(expression)) collectExpression(child, expressions);
|
|
57
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { pushPackageDiagnostic } from "./qti-package-paths.js";
|
|
2
|
+
import { parseQtiPackageXmlTree } from "./package-xml.js";
|
|
2
3
|
import type {
|
|
3
4
|
QtiAssessmentTestPackageModel,
|
|
4
5
|
QtiManifestResource,
|
|
5
6
|
QtiPackageAsset,
|
|
6
7
|
QtiPackageAssetSource,
|
|
8
|
+
QtiPackageContentAssetDiscovery,
|
|
7
9
|
QtiPackageItem,
|
|
8
10
|
} from "./qti-package-types.js";
|
|
9
11
|
import {
|
|
@@ -21,6 +23,27 @@ interface PendingAssetReference {
|
|
|
21
23
|
readonly referencedBy: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/** Discover and resolve package-local asset references in one QTI XML document. */
|
|
27
|
+
export function discoverQtiPackageContentAssets(
|
|
28
|
+
xml: string,
|
|
29
|
+
sourcePath: string,
|
|
30
|
+
): QtiPackageContentAssetDiscovery {
|
|
31
|
+
const parsed = parseQtiPackageXmlTree(xml);
|
|
32
|
+
const diagnostics: QtiDiagnostic[] = [];
|
|
33
|
+
for (const message of parsed.errors) {
|
|
34
|
+
pushPackageDiagnostic(diagnostics, "xml.parse", "error", message, sourcePath);
|
|
35
|
+
}
|
|
36
|
+
if (!parsed.root) return { hrefs: [], diagnostics };
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
hrefs: discoverContentAssetHrefs(
|
|
40
|
+
{ path: sourcePath, xml, root: parsed.root, errors: parsed.errors },
|
|
41
|
+
diagnostics,
|
|
42
|
+
),
|
|
43
|
+
diagnostics,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
type PackageAssetRefSpec =
|
|
25
48
|
| { readonly kind: "attribute"; readonly localName: string; readonly attribute: string }
|
|
26
49
|
| { readonly kind: "text"; readonly localName: string };
|
|
@@ -232,6 +255,8 @@ export function detectPackageMediaType(href: string): string | undefined {
|
|
|
232
255
|
return "image/jpeg";
|
|
233
256
|
case "mp3":
|
|
234
257
|
return "audio/mpeg";
|
|
258
|
+
case "m4a":
|
|
259
|
+
return "audio/mp4";
|
|
235
260
|
case "mp4":
|
|
236
261
|
return "video/mp4";
|
|
237
262
|
case "ogg":
|
|
@@ -250,6 +275,11 @@ export function detectPackageMediaType(href: string): string | undefined {
|
|
|
250
275
|
return "image/webp";
|
|
251
276
|
case "xml":
|
|
252
277
|
return "application/xml";
|
|
278
|
+
case "html":
|
|
279
|
+
case "htm":
|
|
280
|
+
return "text/html";
|
|
281
|
+
case "json":
|
|
282
|
+
return "application/json";
|
|
253
283
|
default:
|
|
254
284
|
return undefined;
|
|
255
285
|
}
|
package/src/qti-package-items.ts
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import type { QtiPackageXmlNode } from "./package-xml.js";
|
|
2
2
|
import { parseQtiXml } from "./parser.js";
|
|
3
|
+
import { parseXmlBoolean } from "./parser-values.js";
|
|
3
4
|
import { pushPackageDiagnostic } from "./qti-package-paths.js";
|
|
4
5
|
import { discoverContentAssetHrefs } from "./qti-package-assets.js";
|
|
5
6
|
import { QTI_PACKAGE_MANIFEST_PATH } from "./qti-package-manifest.js";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
parseItemSessionControl,
|
|
9
|
+
parseStandardAlignments,
|
|
10
|
+
parseTimeLimits,
|
|
11
|
+
parseTimingMetadata,
|
|
12
|
+
uniqueStandards,
|
|
13
|
+
} from "./qti-package-metadata.js";
|
|
7
14
|
import type {
|
|
15
|
+
QtiAssessmentSectionPackageModel,
|
|
8
16
|
QtiAssessmentTestItemRef,
|
|
9
17
|
QtiAssessmentTestPackageModel,
|
|
10
18
|
QtiManifestResource,
|
|
11
19
|
QtiPackageItem,
|
|
12
20
|
QtiPackageItemSource,
|
|
13
21
|
QtiPackageShape,
|
|
22
|
+
QtiStandardAlignment,
|
|
23
|
+
QtiTestPartNavigationMode,
|
|
24
|
+
QtiTestPartPackageModel,
|
|
25
|
+
QtiTestPartSubmissionMode,
|
|
14
26
|
} from "./qti-package-types.js";
|
|
15
27
|
import {
|
|
16
|
-
|
|
28
|
+
childPackageElements,
|
|
17
29
|
parsePackageXml,
|
|
18
30
|
pushXmlDiagnostics,
|
|
19
31
|
resolvePackageHref,
|
|
@@ -28,6 +40,7 @@ export interface PackageItemReference {
|
|
|
28
40
|
readonly source: QtiPackageItemSource;
|
|
29
41
|
readonly manifestResourceIdentifier?: string | undefined;
|
|
30
42
|
readonly assessmentItemRefIdentifier?: string | undefined;
|
|
43
|
+
readonly standards: readonly QtiStandardAlignment[];
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
export function parseAssessmentTestPackageModel(
|
|
@@ -81,8 +94,13 @@ export function parseAssessmentTestPackageModel(
|
|
|
81
94
|
);
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
const
|
|
97
|
+
const testParts = root ? parseAssessmentTestParts(root, href, testDiagnostics) : [];
|
|
98
|
+
const itemRefs = testParts.flatMap(flattenTestPartItemRefs);
|
|
85
99
|
const assetHrefs = root ? discoverContentAssetHrefs(xmlFile, testDiagnostics) : [];
|
|
100
|
+
const timeLimits = root
|
|
101
|
+
? parseTimeLimits(childPackageElements(root, "qti-time-limits")[0], href, testDiagnostics)
|
|
102
|
+
: undefined;
|
|
103
|
+
const timing = root ? parseTimingMetadata(root, href, testDiagnostics, timeLimits) : undefined;
|
|
86
104
|
const scopedDiagnostics = testDiagnostics.map((diagnostic) =>
|
|
87
105
|
scopeDiagnosticToPackagePath(href, diagnostic),
|
|
88
106
|
);
|
|
@@ -94,7 +112,9 @@ export function parseAssessmentTestPackageModel(
|
|
|
94
112
|
title: root?.attributes.title,
|
|
95
113
|
manifestResourceIdentifier: firstResource.identifier,
|
|
96
114
|
itemRefs,
|
|
97
|
-
|
|
115
|
+
testParts,
|
|
116
|
+
timing,
|
|
117
|
+
timeLimits,
|
|
98
118
|
standards: root ? parseStandardAlignments(root, href) : [],
|
|
99
119
|
assetHrefs,
|
|
100
120
|
diagnostics: scopedDiagnostics,
|
|
@@ -103,34 +123,158 @@ export function parseAssessmentTestPackageModel(
|
|
|
103
123
|
};
|
|
104
124
|
}
|
|
105
125
|
|
|
106
|
-
function
|
|
126
|
+
function parseAssessmentTestParts(
|
|
107
127
|
root: QtiPackageXmlNode,
|
|
108
128
|
sourcePath: string,
|
|
109
129
|
diagnostics: QtiDiagnostic[],
|
|
110
|
-
):
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
"qti-assessment-item-ref is missing href.",
|
|
130
|
+
): QtiTestPartPackageModel[] {
|
|
131
|
+
return childPackageElements(root, "qti-test-part").map((partNode) => {
|
|
132
|
+
const identifier = partNode.attributes.identifier ?? "";
|
|
133
|
+
return {
|
|
134
|
+
identifier,
|
|
135
|
+
navigationMode: parseNavigationMode(partNode, sourcePath, diagnostics),
|
|
136
|
+
submissionMode: parseSubmissionMode(partNode, sourcePath, diagnostics),
|
|
137
|
+
timeLimits: parseTimeLimits(
|
|
138
|
+
childPackageElements(partNode, "qti-time-limits")[0],
|
|
120
139
|
sourcePath,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
diagnostics,
|
|
141
|
+
),
|
|
142
|
+
sections: childPackageElements(partNode, "qti-assessment-section").map((sectionNode) =>
|
|
143
|
+
parseAssessmentSection(sectionNode, identifier, undefined, sourcePath, diagnostics),
|
|
144
|
+
),
|
|
145
|
+
attributes: { ...partNode.attributes },
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|
|
124
149
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
150
|
+
function parseAssessmentSection(
|
|
151
|
+
sectionNode: QtiPackageXmlNode,
|
|
152
|
+
testPartIdentifier: string,
|
|
153
|
+
parentSectionIdentifier: string | undefined,
|
|
154
|
+
sourcePath: string,
|
|
155
|
+
diagnostics: QtiDiagnostic[],
|
|
156
|
+
): QtiAssessmentSectionPackageModel {
|
|
157
|
+
const identifier = sectionNode.attributes.identifier ?? "";
|
|
158
|
+
const visible = parseOptionalXmlBoolean(sectionNode, "visible", sourcePath, diagnostics);
|
|
159
|
+
const itemRefs = childPackageElements(sectionNode, "qti-assessment-item-ref").flatMap(
|
|
160
|
+
(refNode) => {
|
|
161
|
+
const rawHref = refNode.attributes.href;
|
|
162
|
+
if (!rawHref) {
|
|
163
|
+
pushPackageDiagnostic(
|
|
164
|
+
diagnostics,
|
|
165
|
+
"package.assessmentTest.itemRef.href.missing",
|
|
166
|
+
"error",
|
|
167
|
+
"qti-assessment-item-ref is missing href.",
|
|
168
|
+
sourcePath,
|
|
169
|
+
);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const href = resolvePackageHref(sourcePath, rawHref, diagnostics);
|
|
174
|
+
if (!href) return [];
|
|
175
|
+
return [
|
|
176
|
+
{
|
|
177
|
+
identifier: refNode.attributes.identifier,
|
|
178
|
+
href,
|
|
179
|
+
testPartIdentifier,
|
|
180
|
+
sectionIdentifier: identifier,
|
|
181
|
+
timeLimits: parseTimeLimits(
|
|
182
|
+
childPackageElements(refNode, "qti-time-limits")[0],
|
|
183
|
+
sourcePath,
|
|
184
|
+
diagnostics,
|
|
185
|
+
),
|
|
186
|
+
itemSessionControl: parseItemSessionControl(
|
|
187
|
+
childPackageElements(refNode, "qti-item-session-control")[0],
|
|
188
|
+
sourcePath,
|
|
189
|
+
diagnostics,
|
|
190
|
+
),
|
|
191
|
+
attributes: { ...refNode.attributes },
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
identifier,
|
|
199
|
+
title: sectionNode.attributes.title,
|
|
200
|
+
visible,
|
|
201
|
+
testPartIdentifier,
|
|
202
|
+
parentSectionIdentifier,
|
|
203
|
+
timeLimits: parseTimeLimits(
|
|
204
|
+
childPackageElements(sectionNode, "qti-time-limits")[0],
|
|
205
|
+
sourcePath,
|
|
206
|
+
diagnostics,
|
|
207
|
+
),
|
|
208
|
+
itemRefs,
|
|
209
|
+
sections: childPackageElements(sectionNode, "qti-assessment-section").map((child) =>
|
|
210
|
+
parseAssessmentSection(child, testPartIdentifier, identifier, sourcePath, diagnostics),
|
|
211
|
+
),
|
|
212
|
+
attributes: { ...sectionNode.attributes },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function flattenTestPartItemRefs(part: QtiTestPartPackageModel): QtiAssessmentTestItemRef[] {
|
|
217
|
+
return part.sections.flatMap(flattenSectionItemRefs);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function flattenSectionItemRefs(
|
|
221
|
+
section: QtiAssessmentSectionPackageModel,
|
|
222
|
+
): QtiAssessmentTestItemRef[] {
|
|
223
|
+
return [...section.itemRefs, ...section.sections.flatMap(flattenSectionItemRefs)];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function parseNavigationMode(
|
|
227
|
+
node: QtiPackageXmlNode,
|
|
228
|
+
sourcePath: string,
|
|
229
|
+
diagnostics: QtiDiagnostic[],
|
|
230
|
+
): QtiTestPartNavigationMode | undefined {
|
|
231
|
+
const raw = node.attributes["navigation-mode"];
|
|
232
|
+
if (raw === "linear" || raw === "nonlinear") return raw;
|
|
233
|
+
pushPackageDiagnostic(
|
|
234
|
+
diagnostics,
|
|
235
|
+
"package.testPart.navigationMode.invalid",
|
|
236
|
+
"error",
|
|
237
|
+
`qti-test-part navigation-mode must be linear or nonlinear, found ${raw ?? "(missing)"}.`,
|
|
238
|
+
sourcePath,
|
|
239
|
+
);
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function parseSubmissionMode(
|
|
244
|
+
node: QtiPackageXmlNode,
|
|
245
|
+
sourcePath: string,
|
|
246
|
+
diagnostics: QtiDiagnostic[],
|
|
247
|
+
): QtiTestPartSubmissionMode | undefined {
|
|
248
|
+
const raw = node.attributes["submission-mode"];
|
|
249
|
+
if (raw === "individual" || raw === "simultaneous") return raw;
|
|
250
|
+
pushPackageDiagnostic(
|
|
251
|
+
diagnostics,
|
|
252
|
+
"package.testPart.submissionMode.invalid",
|
|
253
|
+
"error",
|
|
254
|
+
`qti-test-part submission-mode must be individual or simultaneous, found ${raw ?? "(missing)"}.`,
|
|
255
|
+
sourcePath,
|
|
256
|
+
);
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function parseOptionalXmlBoolean(
|
|
261
|
+
node: QtiPackageXmlNode,
|
|
262
|
+
name: string,
|
|
263
|
+
sourcePath: string,
|
|
264
|
+
diagnostics: QtiDiagnostic[],
|
|
265
|
+
): boolean | undefined {
|
|
266
|
+
const raw = node.attributes[name];
|
|
267
|
+
if (raw === undefined) return undefined;
|
|
268
|
+
const value = parseXmlBoolean(raw);
|
|
269
|
+
if (value !== undefined) return value;
|
|
270
|
+
pushPackageDiagnostic(
|
|
271
|
+
diagnostics,
|
|
272
|
+
`package.attribute.${name}.boolean`,
|
|
273
|
+
"error",
|
|
274
|
+
`${node.localName} ${name} must be an XML boolean, found ${raw}.`,
|
|
275
|
+
sourcePath,
|
|
276
|
+
);
|
|
277
|
+
return undefined;
|
|
134
278
|
}
|
|
135
279
|
|
|
136
280
|
export function itemReferencesForPackageShape(
|
|
@@ -144,6 +288,7 @@ export function itemReferencesForPackageShape(
|
|
|
144
288
|
href: itemRef.href,
|
|
145
289
|
source: "assessment-test",
|
|
146
290
|
assessmentItemRefIdentifier: itemRef.identifier,
|
|
291
|
+
standards: assessmentTest?.standards ?? [],
|
|
147
292
|
}));
|
|
148
293
|
}
|
|
149
294
|
|
|
@@ -169,6 +314,7 @@ export function itemReferencesForPackageShape(
|
|
|
169
314
|
href,
|
|
170
315
|
source: "manifest",
|
|
171
316
|
manifestResourceIdentifier: resource.identifier,
|
|
317
|
+
standards: resource.standards,
|
|
172
318
|
});
|
|
173
319
|
}
|
|
174
320
|
return references;
|
|
@@ -219,6 +365,13 @@ export function parsePackageItems(
|
|
|
219
365
|
const assetHrefs = xmlFile.root ? discoverContentAssetHrefs(xmlFile, diagnostics) : [];
|
|
220
366
|
const root = xmlFile.root?.localName === "qti-assessment-item" ? xmlFile.root : undefined;
|
|
221
367
|
const item = parsed.document?.item;
|
|
368
|
+
const timeLimits = root
|
|
369
|
+
? parseTimeLimits(
|
|
370
|
+
childPackageElements(root, "qti-time-limits")[0],
|
|
371
|
+
reference.href,
|
|
372
|
+
diagnostics,
|
|
373
|
+
)
|
|
374
|
+
: undefined;
|
|
222
375
|
|
|
223
376
|
items.push({
|
|
224
377
|
href: reference.href,
|
|
@@ -228,8 +381,16 @@ export function parsePackageItems(
|
|
|
228
381
|
identifier: item?.identifier,
|
|
229
382
|
title: item?.title,
|
|
230
383
|
document: parsed.document,
|
|
231
|
-
timing: root ? parseTimingMetadata(root, reference.href) : undefined,
|
|
232
|
-
|
|
384
|
+
timing: root ? parseTimingMetadata(root, reference.href, diagnostics, timeLimits) : undefined,
|
|
385
|
+
timeLimits,
|
|
386
|
+
standards: uniqueStandards([
|
|
387
|
+
...reference.standards.filter(
|
|
388
|
+
(standard) =>
|
|
389
|
+
standard.resourcePartIdentifier === undefined ||
|
|
390
|
+
standard.resourcePartIdentifier === item?.identifier,
|
|
391
|
+
),
|
|
392
|
+
...(root ? parseStandardAlignments(root, reference.href) : []),
|
|
393
|
+
]),
|
|
233
394
|
assetHrefs,
|
|
234
395
|
diagnostics: itemDiagnostics,
|
|
235
396
|
xml: xmlFile.xml,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { QtiPackageXmlNode } from "./package-xml.js";
|
|
2
2
|
import { normalizePackagePath, pushPackageDiagnostic } from "./qti-package-paths.js";
|
|
3
|
+
import { parseStandardAlignments } from "./qti-package-metadata.js";
|
|
3
4
|
import type { QtiManifestFile, QtiManifestResource, QtiPackageShape } from "./qti-package-types.js";
|
|
4
5
|
import { childPackageElements, packageDescendants, stripHrefSuffix } from "./qti-package-xml.js";
|
|
5
6
|
import type { QtiPackageEntry } from "./qti-package-zip.js";
|
|
@@ -56,6 +57,7 @@ export function parseManifestResources(
|
|
|
56
57
|
href,
|
|
57
58
|
files,
|
|
58
59
|
dependencies,
|
|
60
|
+
standards: parseStandardAlignments(node, QTI_PACKAGE_MANIFEST_PATH),
|
|
59
61
|
attributes: { ...node.attributes },
|
|
60
62
|
});
|
|
61
63
|
}
|