@conform-ed/qti-xml 0.0.16
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 +25 -0
- package/dist/example-inventory.d.ts +2 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +5414 -0
- package/dist/normalize.d.ts +3 -0
- package/dist/parse-xml.d.ts +15 -0
- package/dist/root-detection.d.ts +2 -0
- package/dist/schema-selection.d.ts +12 -0
- package/dist/serialize-asi.d.ts +30 -0
- package/dist/serialize-document.d.ts +10 -0
- package/dist/serialize-manifest.d.ts +11 -0
- package/dist/serialize-pnp.d.ts +11 -0
- package/dist/serialize-result.d.ts +12 -0
- package/dist/serialize-usage-data.d.ts +8 -0
- package/dist/types.d.ts +53 -0
- package/dist/validate-package.d.ts +30 -0
- package/dist/validate.d.ts +10 -0
- package/dist/xinclude.d.ts +11 -0
- package/dist/xml-writer.d.ts +17 -0
- package/package.json +33 -0
- package/src/example-inventory.ts +194 -0
- package/src/index.ts +14 -0
- package/src/normalize.ts +2814 -0
- package/src/parse-xml.ts +147 -0
- package/src/root-detection.ts +214 -0
- package/src/schema-selection.ts +78 -0
- package/src/serialize-asi.ts +2030 -0
- package/src/serialize-document.ts +89 -0
- package/src/serialize-manifest.ts +215 -0
- package/src/serialize-pnp.ts +385 -0
- package/src/serialize-result.ts +215 -0
- package/src/serialize-usage-data.ts +85 -0
- package/src/types.ts +78 -0
- package/src/validate-package.ts +408 -0
- package/src/validate.ts +118 -0
- package/src/xinclude.ts +92 -0
- package/src/xml-writer.ts +68 -0
package/src/parse-xml.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2
|
+
import { SyntaxValidator } from "fast-xml-validator";
|
|
3
|
+
|
|
4
|
+
export interface QtiXmlTextNode {
|
|
5
|
+
type: "text";
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface QtiXmlElementNode {
|
|
10
|
+
type: "element";
|
|
11
|
+
name: string;
|
|
12
|
+
localName: string;
|
|
13
|
+
prefix?: string;
|
|
14
|
+
namespaceUri?: string;
|
|
15
|
+
attributes: Record<string, string>;
|
|
16
|
+
children: QtiXmlNode[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type QtiXmlNode = QtiXmlElementNode | QtiXmlTextNode;
|
|
20
|
+
|
|
21
|
+
const parser = new XMLParser({
|
|
22
|
+
preserveOrder: true,
|
|
23
|
+
ignoreAttributes: false,
|
|
24
|
+
attributeNamePrefix: "",
|
|
25
|
+
textNodeName: "#text",
|
|
26
|
+
commentPropName: "#comment",
|
|
27
|
+
trimValues: false,
|
|
28
|
+
parseTagValue: false,
|
|
29
|
+
parseAttributeValue: false,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function splitXmlName(name: string): { localName: string; prefix: string | undefined } {
|
|
33
|
+
const [prefix, localName] = name.includes(":") ? name.split(":", 2) : [undefined, name];
|
|
34
|
+
return {
|
|
35
|
+
prefix,
|
|
36
|
+
localName: localName ?? name,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseValidationError(error: unknown): string {
|
|
41
|
+
if (!error || typeof error !== "object") {
|
|
42
|
+
return "Invalid XML.";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const candidate = error as { err?: { msg?: string } };
|
|
46
|
+
return candidate.err?.msg ?? JSON.stringify(error);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildNamespaceScope(
|
|
50
|
+
parentScope: Record<string, string>,
|
|
51
|
+
attributes: Record<string, string>,
|
|
52
|
+
): Record<string, string> {
|
|
53
|
+
const nextScope = { ...parentScope };
|
|
54
|
+
|
|
55
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
56
|
+
if (name === "xmlns") {
|
|
57
|
+
nextScope[""] = value;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (name.startsWith("xmlns:")) {
|
|
62
|
+
nextScope[name.slice("xmlns:".length)] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return nextScope;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function stripNamespaceAttributes(attributes: Record<string, string>): Record<string, string> {
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
Object.entries(attributes).filter(([name]) => name !== "xmlns" && !name.startsWith("xmlns:")),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildXmlNodes(entries: unknown, parentScope: Record<string, string>): QtiXmlNode[] {
|
|
76
|
+
if (!Array.isArray(entries)) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const nodes: QtiXmlNode[] = [];
|
|
81
|
+
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (!entry || typeof entry !== "object") {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const record = entry as Record<string, unknown>;
|
|
88
|
+
|
|
89
|
+
const textValue = record["#text"];
|
|
90
|
+
if (
|
|
91
|
+
(typeof textValue === "string" || typeof textValue === "number" || typeof textValue === "boolean") &&
|
|
92
|
+
!Array.isArray(textValue)
|
|
93
|
+
) {
|
|
94
|
+
nodes.push({
|
|
95
|
+
type: "text",
|
|
96
|
+
value: String(textValue),
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const elementName = Object.keys(record).find((key) => key !== ":@" && key !== "#comment" && !key.startsWith("?"));
|
|
102
|
+
|
|
103
|
+
if (!elementName) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const rawAttributes = record[":@"];
|
|
108
|
+
const attributes =
|
|
109
|
+
rawAttributes && typeof rawAttributes === "object"
|
|
110
|
+
? Object.fromEntries(
|
|
111
|
+
Object.entries(rawAttributes as Record<string, unknown>).map(([name, value]) => [name, String(value)]),
|
|
112
|
+
)
|
|
113
|
+
: {};
|
|
114
|
+
const namespaceScope = buildNamespaceScope(parentScope, attributes);
|
|
115
|
+
const strippedAttributes = stripNamespaceAttributes(attributes);
|
|
116
|
+
const { localName, prefix } = splitXmlName(elementName);
|
|
117
|
+
const namespaceUri = prefix ? namespaceScope[prefix] : namespaceScope[""];
|
|
118
|
+
|
|
119
|
+
nodes.push({
|
|
120
|
+
type: "element",
|
|
121
|
+
name: elementName,
|
|
122
|
+
localName,
|
|
123
|
+
...(prefix !== undefined ? { prefix } : {}),
|
|
124
|
+
...(namespaceUri !== undefined ? { namespaceUri } : {}),
|
|
125
|
+
attributes: strippedAttributes,
|
|
126
|
+
children: buildXmlNodes(record[elementName], namespaceScope),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return nodes;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function parseXmlDocument(xml: string): QtiXmlElementNode {
|
|
134
|
+
const validationResult = SyntaxValidator.validate(xml);
|
|
135
|
+
if (validationResult !== true) {
|
|
136
|
+
throw new Error(parseValidationError(validationResult));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const nodes = buildXmlNodes(parser.parse(xml), {});
|
|
140
|
+
const root = nodes.find((node) => node.type === "element");
|
|
141
|
+
|
|
142
|
+
if (!root || root.type !== "element") {
|
|
143
|
+
throw new Error("XML document does not contain a root element.");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return root;
|
|
147
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { QtiRootDetection, QtiSchemaSelectionKey, QtiVersion } from "./types";
|
|
2
|
+
|
|
3
|
+
const qtiV22Namespace = "http://www.imsglobal.org/xsd/imsqti_v2p2";
|
|
4
|
+
const qtiV22MetadataNamespace = "http://www.imsglobal.org/xsd/imsqti_metadata_v2p2";
|
|
5
|
+
const qtiV22ResultsNamespace = "http://www.imsglobal.org/xsd/imsqti_result_v2p2";
|
|
6
|
+
const qtiV22UsageDataNamespace = "http://www.imsglobal.org/xsd/imsqti_usagedata_v2p2";
|
|
7
|
+
const qtiV22ManifestNamespace = "http://www.imsglobal.org/xsd/imscp_v1p1";
|
|
8
|
+
const qtiV30AsiNamespace = "http://www.imsglobal.org/xsd/imsqtiasi_v3p0";
|
|
9
|
+
const qtiV30ResultsNamespace = "http://www.imsglobal.org/xsd/imsqti_result_v3p0";
|
|
10
|
+
const qtiV30MetadataNamespace = "http://www.imsglobal.org/xsd/imsqti_metadata_v3p0";
|
|
11
|
+
const qtiV30ManifestNamespace = "http://www.imsglobal.org/xsd/qti/qtiv3p0/imscp_v1p1";
|
|
12
|
+
const qtiV30AfaPnpNamespace = "http://www.imsglobal.org/xsd/qti/qtiv3p0/imsafa3p0pnp_v1p0";
|
|
13
|
+
const qtiV30UsageDataNamespace = "http://www.imsglobal.org/xsd/imsqti_usagedata_v3p0";
|
|
14
|
+
|
|
15
|
+
const attributePattern = /([A-Za-z_][A-Za-z0-9:._-]*)\s*=\s*("([^"]*)"|'([^']*)')/gu;
|
|
16
|
+
|
|
17
|
+
function stripLeadingXmlProlog(value: string): string {
|
|
18
|
+
let remaining = value.replace(/^\uFEFF/u, "");
|
|
19
|
+
|
|
20
|
+
while (true) {
|
|
21
|
+
const next = remaining
|
|
22
|
+
.replace(/^\s+/u, "")
|
|
23
|
+
.replace(/^<\?xml[\s\S]*?\?>/u, "")
|
|
24
|
+
.replace(/^\s+/u, "")
|
|
25
|
+
.replace(/^<!--[\s\S]*?-->/u, "")
|
|
26
|
+
.replace(/^\s+/u, "")
|
|
27
|
+
.replace(/^<!DOCTYPE[\s\S]*?>/iu, "");
|
|
28
|
+
|
|
29
|
+
if (next === remaining) {
|
|
30
|
+
return remaining.replace(/^\s+/u, "");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
remaining = next;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseAttributes(rawAttributes: string): Record<string, string> {
|
|
38
|
+
const attributes: Record<string, string> = {};
|
|
39
|
+
|
|
40
|
+
for (const match of rawAttributes.matchAll(attributePattern)) {
|
|
41
|
+
const name = match[1];
|
|
42
|
+
if (!name) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const doubleQuoted = match[3];
|
|
46
|
+
const singleQuoted = match[4];
|
|
47
|
+
attributes[name] = doubleQuoted ?? singleQuoted ?? "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return attributes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function splitXmlName(name: string): { localName: string; prefix: string | undefined } {
|
|
54
|
+
const [prefix, localName] = name.includes(":") ? name.split(":", 2) : [undefined, name];
|
|
55
|
+
return {
|
|
56
|
+
prefix,
|
|
57
|
+
localName: localName ?? name,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function inferVersion(namespaceUri: string | undefined, localName: string, xml: string): QtiVersion | undefined {
|
|
62
|
+
if (
|
|
63
|
+
namespaceUri === qtiV22Namespace ||
|
|
64
|
+
namespaceUri === qtiV22MetadataNamespace ||
|
|
65
|
+
namespaceUri === qtiV22ResultsNamespace ||
|
|
66
|
+
namespaceUri === qtiV22UsageDataNamespace
|
|
67
|
+
) {
|
|
68
|
+
return "2.2";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (namespaceUri === qtiV22ManifestNamespace) {
|
|
72
|
+
if (xml.includes("qtiv2p2") || xml.includes("QTIv2.2 Package")) {
|
|
73
|
+
return "2.2";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
namespaceUri === qtiV30AsiNamespace ||
|
|
79
|
+
namespaceUri === qtiV30ResultsNamespace ||
|
|
80
|
+
namespaceUri === qtiV30MetadataNamespace ||
|
|
81
|
+
namespaceUri === qtiV30AfaPnpNamespace ||
|
|
82
|
+
namespaceUri === qtiV30UsageDataNamespace
|
|
83
|
+
) {
|
|
84
|
+
return "3.0.1";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (namespaceUri === qtiV30ManifestNamespace) {
|
|
88
|
+
return "3.0.1";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (localName === "manifest" && xml.includes("qtiv2p2")) {
|
|
92
|
+
return "2.2";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function inferSchemaSelectionKey(
|
|
99
|
+
version: QtiVersion | undefined,
|
|
100
|
+
namespaceUri: string | undefined,
|
|
101
|
+
localName: string,
|
|
102
|
+
): QtiSchemaSelectionKey | undefined {
|
|
103
|
+
if (!version) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (version === "2.2") {
|
|
108
|
+
if (namespaceUri === qtiV22Namespace) {
|
|
109
|
+
switch (localName) {
|
|
110
|
+
case "assessmentItem":
|
|
111
|
+
return "qtiAssessmentItemDocument";
|
|
112
|
+
case "assessmentSection":
|
|
113
|
+
return "qtiAssessmentSectionDocument";
|
|
114
|
+
case "assessmentStimulus":
|
|
115
|
+
return "qtiAssessmentStimulusDocument";
|
|
116
|
+
case "assessmentTest":
|
|
117
|
+
return "qtiAssessmentTestDocument";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (namespaceUri === qtiV22MetadataNamespace && localName === "qtiMetadata") {
|
|
122
|
+
return "qtiMetadataDocument";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (namespaceUri === qtiV22ResultsNamespace && localName === "assessmentResult") {
|
|
126
|
+
return "qtiAssessmentResultDocument";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (namespaceUri === qtiV22ManifestNamespace && localName === "manifest") {
|
|
130
|
+
return "qtiManifestDocument";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (namespaceUri === qtiV22UsageDataNamespace && localName === "usageData") {
|
|
134
|
+
return "qtiUsageDataDocument";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (version === "3.0.1") {
|
|
139
|
+
if (namespaceUri === qtiV30AsiNamespace) {
|
|
140
|
+
switch (localName) {
|
|
141
|
+
case "qti-assessment-item":
|
|
142
|
+
return "qtiAssessmentItemDocument";
|
|
143
|
+
case "qti-assessment-section":
|
|
144
|
+
return "qtiAssessmentSectionDocument";
|
|
145
|
+
case "qti-assessment-stimulus":
|
|
146
|
+
return "qtiAssessmentStimulusDocument";
|
|
147
|
+
case "qti-assessment-test":
|
|
148
|
+
return "qtiAssessmentTestDocument";
|
|
149
|
+
case "qti-outcome-declaration":
|
|
150
|
+
return "qtiOutcomeDeclarationDocument";
|
|
151
|
+
case "qti-outcome-processing":
|
|
152
|
+
return "qtiOutcomeProcessingDocument";
|
|
153
|
+
case "qti-response-processing":
|
|
154
|
+
return "qtiResponseProcessingDocument";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (namespaceUri === qtiV30MetadataNamespace && localName === "qtiMetadata") {
|
|
159
|
+
return "qtiMetadataDocument";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (namespaceUri === qtiV30ResultsNamespace && localName === "assessmentResult") {
|
|
163
|
+
return "qtiAssessmentResultDocument";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (namespaceUri === qtiV30UsageDataNamespace && localName === "usageData") {
|
|
167
|
+
return "qtiUsageDataDocument";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (namespaceUri === qtiV30ManifestNamespace && localName === "manifest") {
|
|
171
|
+
return "qtiManifestDocument";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (namespaceUri === qtiV30AfaPnpNamespace) {
|
|
175
|
+
switch (localName) {
|
|
176
|
+
case "access-for-all-pnp":
|
|
177
|
+
return "qtiAccessForAllPnpDocument";
|
|
178
|
+
case "access-for-all-pnp-records":
|
|
179
|
+
return "qtiAccessForAllPnpRecordsDocument";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function detectQtiRoot(xml: string): QtiRootDetection | undefined {
|
|
188
|
+
const stripped = stripLeadingXmlProlog(xml);
|
|
189
|
+
const match = stripped.match(/^<([A-Za-z_][A-Za-z0-9:._-]*)([\s\S]*?)(?:\/?)>/u);
|
|
190
|
+
|
|
191
|
+
if (!match) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const rootName = match[1];
|
|
196
|
+
if (!rootName) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
const rawAttributes = match[2] ?? "";
|
|
200
|
+
const attributes = parseAttributes(rawAttributes);
|
|
201
|
+
const { localName, prefix } = splitXmlName(rootName);
|
|
202
|
+
const namespaceUri = prefix ? attributes[`xmlns:${prefix}`] : attributes["xmlns"];
|
|
203
|
+
const inferredVersion = inferVersion(namespaceUri, localName, xml);
|
|
204
|
+
const schemaSelectionKey = inferSchemaSelectionKey(inferredVersion, namespaceUri, localName);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
rootName,
|
|
208
|
+
localName,
|
|
209
|
+
...(prefix !== undefined ? { prefix } : {}),
|
|
210
|
+
...(namespaceUri !== undefined ? { namespaceUri } : {}),
|
|
211
|
+
...(inferredVersion !== undefined ? { inferredVersion } : {}),
|
|
212
|
+
...(schemaSelectionKey !== undefined ? { schemaSelectionKey } : {}),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as QtiV2_2 from "@conform-ed/contracts/qti/v2_2";
|
|
2
|
+
import * as QtiV3_0_1 from "@conform-ed/contracts/qti/v3_0_1";
|
|
3
|
+
|
|
4
|
+
import type { QtiRootDetection, QtiSchemaSelectionKey, QtiVersion } from "./types";
|
|
5
|
+
|
|
6
|
+
type SafeParseSchema = {
|
|
7
|
+
safeParse(input: unknown): unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface QtiSchemaSelection {
|
|
11
|
+
version: QtiVersion;
|
|
12
|
+
key: QtiSchemaSelectionKey;
|
|
13
|
+
schema: SafeParseSchema;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const registry: Record<string, SafeParseSchema> = {
|
|
17
|
+
"2.2:qtiAssessmentItemDocument": QtiV2_2.QtiAssessmentItemDocumentSchema as SafeParseSchema,
|
|
18
|
+
"2.2:qtiAssessmentResultDocument": QtiV2_2.QtiAssessmentResultDocumentSchema as SafeParseSchema,
|
|
19
|
+
"2.2:qtiAssessmentSectionDocument": QtiV2_2.QtiAssessmentSectionDocumentSchema as SafeParseSchema,
|
|
20
|
+
"2.2:qtiAssessmentStimulusDocument": QtiV2_2.QtiAssessmentStimulusDocumentSchema as SafeParseSchema,
|
|
21
|
+
"2.2:qtiAssessmentTestDocument": QtiV2_2.QtiAssessmentTestDocumentSchema as SafeParseSchema,
|
|
22
|
+
"2.2:qtiManifestDocument": QtiV2_2.QtiManifestDocumentSchema as SafeParseSchema,
|
|
23
|
+
"2.2:qtiMetadataDocument": QtiV2_2.QtiMetadataDocumentSchema as SafeParseSchema,
|
|
24
|
+
"2.2:qtiUsageDataDocument": QtiV2_2.QtiUsageDataDocumentSchema as SafeParseSchema,
|
|
25
|
+
"3.0.1:qtiAccessForAllPnpDocument": QtiV3_0_1.QtiAccessForAllPnpDocumentSchema as SafeParseSchema,
|
|
26
|
+
"3.0.1:qtiAccessForAllPnpRecordsDocument": QtiV3_0_1.QtiAccessForAllPnpRecordsDocumentSchema as SafeParseSchema,
|
|
27
|
+
"3.0.1:qtiAssessmentItemDocument": QtiV3_0_1.QtiAssessmentItemDocumentSchema as SafeParseSchema,
|
|
28
|
+
"3.0.1:qtiAssessmentResultDocument": QtiV3_0_1.QtiAssessmentResultDocumentSchema as SafeParseSchema,
|
|
29
|
+
"3.0.1:qtiAssessmentSectionDocument": QtiV3_0_1.QtiAssessmentSectionDocumentSchema as SafeParseSchema,
|
|
30
|
+
"3.0.1:qtiAssessmentStimulusDocument": QtiV3_0_1.QtiAssessmentStimulusDocumentSchema as SafeParseSchema,
|
|
31
|
+
"3.0.1:qtiAssessmentTestDocument": QtiV3_0_1.QtiAssessmentTestDocumentSchema as SafeParseSchema,
|
|
32
|
+
"3.0.1:qtiManifestDocument": QtiV3_0_1.QtiManifestDocumentSchema as SafeParseSchema,
|
|
33
|
+
"3.0.1:qtiMetadataDocument": QtiV3_0_1.QtiMetadataDocumentSchema as SafeParseSchema,
|
|
34
|
+
"3.0.1:qtiOutcomeDeclarationDocument": QtiV3_0_1.QtiOutcomeDeclarationDocumentSchema as SafeParseSchema,
|
|
35
|
+
"3.0.1:qtiOutcomeProcessingDocument": QtiV3_0_1.QtiOutcomeProcessingDocumentSchema as SafeParseSchema,
|
|
36
|
+
"3.0.1:qtiResponseProcessingDocument": QtiV3_0_1.QtiResponseProcessingDocumentSchema as SafeParseSchema,
|
|
37
|
+
"3.0.1:qtiUsageDataDocument": QtiV3_0_1.QtiUsageDataDocumentSchema as SafeParseSchema,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const normalizationImplemented = new Set<string>([
|
|
41
|
+
"2.2:qtiAssessmentItemDocument",
|
|
42
|
+
"2.2:qtiUsageDataDocument",
|
|
43
|
+
"3.0.1:qtiAccessForAllPnpDocument",
|
|
44
|
+
"3.0.1:qtiAccessForAllPnpRecordsDocument",
|
|
45
|
+
"2.2:qtiManifestDocument",
|
|
46
|
+
"3.0.1:qtiAssessmentItemDocument",
|
|
47
|
+
"3.0.1:qtiAssessmentResultDocument",
|
|
48
|
+
"3.0.1:qtiAssessmentSectionDocument",
|
|
49
|
+
"3.0.1:qtiAssessmentStimulusDocument",
|
|
50
|
+
"3.0.1:qtiAssessmentTestDocument",
|
|
51
|
+
"3.0.1:qtiManifestDocument",
|
|
52
|
+
"3.0.1:qtiMetadataDocument",
|
|
53
|
+
"3.0.1:qtiOutcomeDeclarationDocument",
|
|
54
|
+
"3.0.1:qtiOutcomeProcessingDocument",
|
|
55
|
+
"3.0.1:qtiResponseProcessingDocument",
|
|
56
|
+
"3.0.1:qtiUsageDataDocument",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
export function isNormalizationImplemented(version: QtiVersion, key: QtiSchemaSelectionKey): boolean {
|
|
60
|
+
return normalizationImplemented.has(`${version}:${key}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function selectQtiSchema(rootDetection: QtiRootDetection): QtiSchemaSelection | undefined {
|
|
64
|
+
if (!rootDetection.inferredVersion || !rootDetection.schemaSelectionKey) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const schema = registry[`${rootDetection.inferredVersion}:${rootDetection.schemaSelectionKey}`];
|
|
69
|
+
if (!schema) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
version: rootDetection.inferredVersion,
|
|
75
|
+
key: rootDetection.schemaSelectionKey,
|
|
76
|
+
schema,
|
|
77
|
+
};
|
|
78
|
+
}
|