@longsightgroup/qti3-core 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -10
- package/dist/content-text.d.ts +14 -0
- package/dist/content-text.d.ts.map +1 -0
- package/dist/content-text.js +58 -0
- package/dist/content-text.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/package-xml.d.ts +12 -0
- package/dist/package-xml.d.ts.map +1 -0
- package/dist/package-xml.js +17 -0
- package/dist/package-xml.js.map +1 -0
- package/dist/parser-custom-interactions.d.ts +8 -0
- package/dist/parser-custom-interactions.d.ts.map +1 -0
- package/dist/parser-custom-interactions.js +178 -0
- package/dist/parser-custom-interactions.js.map +1 -0
- package/dist/parser-declarations.d.ts +6 -0
- package/dist/parser-declarations.d.ts.map +1 -0
- package/dist/parser-declarations.js +136 -0
- package/dist/parser-declarations.js.map +1 -0
- package/dist/parser-item-metadata.d.ts +9 -0
- package/dist/parser-item-metadata.d.ts.map +1 -0
- package/dist/parser-item-metadata.js +158 -0
- package/dist/parser-item-metadata.js.map +1 -0
- package/dist/parser-processing.d.ts +5 -0
- package/dist/parser-processing.d.ts.map +1 -0
- package/dist/parser-processing.js +642 -0
- package/dist/parser-processing.js.map +1 -0
- package/dist/parser-values.d.ts +8 -0
- package/dist/parser-values.d.ts.map +1 -0
- package/dist/parser-values.js +49 -0
- package/dist/parser-values.js.map +1 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +32 -1086
- package/dist/parser.js.map +1 -1
- package/dist/processing-expression-tags.d.ts +56 -0
- package/dist/processing-expression-tags.d.ts.map +1 -0
- package/dist/processing-expression-tags.js +52 -0
- package/dist/processing-expression-tags.js.map +1 -0
- package/dist/processing-rules.d.ts +3 -0
- package/dist/processing-rules.d.ts.map +1 -0
- package/dist/processing-rules.js +10 -0
- package/dist/processing-rules.js.map +1 -0
- package/dist/serializer-processing-expressions.d.ts +8 -0
- package/dist/serializer-processing-expressions.d.ts.map +1 -0
- package/dist/serializer-processing-expressions.js +198 -0
- package/dist/serializer-processing-expressions.js.map +1 -0
- package/dist/serializer-processing-xml.d.ts +15 -0
- package/dist/serializer-processing-xml.d.ts.map +1 -0
- package/dist/serializer-processing-xml.js +46 -0
- package/dist/serializer-processing-xml.js.map +1 -0
- package/dist/serializer-processing.d.ts +8 -0
- package/dist/serializer-processing.d.ts.map +1 -0
- package/dist/serializer-processing.js +106 -0
- package/dist/serializer-processing.js.map +1 -0
- package/dist/shared-vocabulary-authoring.d.ts +50 -0
- package/dist/shared-vocabulary-authoring.d.ts.map +1 -0
- package/dist/shared-vocabulary-authoring.js +302 -0
- package/dist/shared-vocabulary-authoring.js.map +1 -0
- package/dist/shared-vocabulary-interaction-sets.d.ts +6 -0
- package/dist/shared-vocabulary-interaction-sets.d.ts.map +1 -0
- package/dist/shared-vocabulary-interaction-sets.js +23 -0
- package/dist/shared-vocabulary-interaction-sets.js.map +1 -0
- package/dist/shared-vocabulary-interaction-validation.d.ts +3 -0
- package/dist/shared-vocabulary-interaction-validation.d.ts.map +1 -0
- package/dist/shared-vocabulary-interaction-validation.js +99 -0
- package/dist/shared-vocabulary-interaction-validation.js.map +1 -0
- package/dist/shared-vocabulary-registry-validation.d.ts +5 -0
- package/dist/shared-vocabulary-registry-validation.d.ts.map +1 -0
- package/dist/shared-vocabulary-registry-validation.js +147 -0
- package/dist/shared-vocabulary-registry-validation.js.map +1 -0
- package/dist/shared-vocabulary-support.d.ts.map +1 -1
- package/dist/shared-vocabulary-support.js +96 -56
- package/dist/shared-vocabulary-support.js.map +1 -1
- package/dist/shared-vocabulary.d.ts +6 -0
- package/dist/shared-vocabulary.d.ts.map +1 -1
- package/dist/shared-vocabulary.js +12 -0
- package/dist/shared-vocabulary.js.map +1 -1
- package/dist/support.d.ts +4 -1
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +159 -10
- package/dist/support.js.map +1 -1
- package/dist/types.d.ts +38 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +29 -250
- package/dist/validation.js.map +1 -1
- package/dist/xml.d.ts +10 -0
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js +321 -249
- package/dist/xml.js.map +1 -1
- package/package.json +1 -4
- package/src/content-text.ts +80 -0
- package/src/index.ts +67 -0
- package/src/interaction-test-fixtures.ts +44 -0
- package/src/package-xml.ts +30 -0
- package/src/parser-custom-interactions.ts +309 -0
- package/src/parser-declarations.ts +174 -0
- package/src/parser-item-metadata.ts +194 -0
- package/src/parser-processing.ts +701 -0
- package/src/parser-values.ts +61 -0
- package/src/parser.ts +59 -1230
- package/src/processing-expression-tags.ts +65 -0
- package/src/processing-rules.ts +11 -0
- package/src/serializer-processing-expressions.ts +456 -0
- package/src/serializer-processing-xml.ts +67 -0
- package/src/serializer-processing.fixtures.ts +469 -0
- package/src/serializer-processing.ts +196 -0
- package/src/shared-vocabulary-authoring.ts +409 -0
- package/src/shared-vocabulary-interaction-sets.ts +28 -0
- package/src/shared-vocabulary-interaction-validation.ts +143 -0
- package/src/shared-vocabulary-registry-validation.ts +207 -0
- package/src/shared-vocabulary-support.ts +127 -101
- package/src/shared-vocabulary.ts +18 -0
- package/src/support.ts +174 -14
- package/src/types.ts +48 -7
- package/src/validation.ts +33 -341
- package/src/xml.ts +378 -255
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {
|
|
2
|
+
coerceValue,
|
|
3
|
+
normalizeValueForCardinality,
|
|
4
|
+
parseCardinality,
|
|
5
|
+
parseCoords,
|
|
6
|
+
parseShape,
|
|
7
|
+
} from "./parser-values.js";
|
|
8
|
+
import type {
|
|
9
|
+
QtiBaseType,
|
|
10
|
+
QtiLookupTable,
|
|
11
|
+
QtiOutcomeDeclaration,
|
|
12
|
+
QtiResponseDeclaration,
|
|
13
|
+
QtiScalarValue,
|
|
14
|
+
QtiTemplateDeclaration,
|
|
15
|
+
QtiValue,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
import { childElements, textContent, type XmlNode } from "./xml.js";
|
|
18
|
+
|
|
19
|
+
export function parseResponseDeclaration(node: XmlNode): QtiResponseDeclaration {
|
|
20
|
+
const cardinality = parseCardinality(node.attributes.cardinality);
|
|
21
|
+
const baseType = node.attributes["base-type"] as QtiResponseDeclaration["baseType"];
|
|
22
|
+
return {
|
|
23
|
+
kind: "response",
|
|
24
|
+
identifier: node.attributes.identifier ?? "",
|
|
25
|
+
cardinality,
|
|
26
|
+
baseType,
|
|
27
|
+
defaultValue: parseVariableValue(childElements(node, "qti-default-value")[0], baseType),
|
|
28
|
+
correctResponse: normalizeValueForCardinality(
|
|
29
|
+
parseVariableValue(childElements(node, "qti-correct-response")[0], baseType),
|
|
30
|
+
cardinality,
|
|
31
|
+
),
|
|
32
|
+
mapping: parseMapping(childElements(node, "qti-mapping")[0]),
|
|
33
|
+
areaMapping: parseAreaMapping(childElements(node, "qti-area-mapping")[0]),
|
|
34
|
+
attributes: node.attributes,
|
|
35
|
+
source: node.source,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseOutcomeDeclaration(node: XmlNode): QtiOutcomeDeclaration {
|
|
40
|
+
const baseType = node.attributes["base-type"] as QtiOutcomeDeclaration["baseType"];
|
|
41
|
+
return {
|
|
42
|
+
kind: "outcome",
|
|
43
|
+
identifier: node.attributes.identifier ?? "",
|
|
44
|
+
cardinality: parseCardinality(node.attributes.cardinality),
|
|
45
|
+
baseType,
|
|
46
|
+
defaultValue: parseVariableValue(childElements(node, "qti-default-value")[0], baseType),
|
|
47
|
+
lookupTable: parseLookupTable(node, baseType),
|
|
48
|
+
attributes: node.attributes,
|
|
49
|
+
source: node.source,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseTemplateDeclaration(node: XmlNode): QtiTemplateDeclaration {
|
|
54
|
+
const baseType = node.attributes["base-type"] as QtiTemplateDeclaration["baseType"];
|
|
55
|
+
return {
|
|
56
|
+
kind: "template",
|
|
57
|
+
identifier: node.attributes.identifier ?? "",
|
|
58
|
+
cardinality: parseCardinality(node.attributes.cardinality),
|
|
59
|
+
baseType,
|
|
60
|
+
defaultValue: parseVariableValue(childElements(node, "qti-default-value")[0], baseType),
|
|
61
|
+
attributes: node.attributes,
|
|
62
|
+
source: node.source,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseVariableValue(
|
|
67
|
+
node: XmlNode | undefined,
|
|
68
|
+
baseType: QtiBaseType | undefined,
|
|
69
|
+
): QtiValue {
|
|
70
|
+
if (!node) return null;
|
|
71
|
+
const valueNodes = childElements(node, "qti-value");
|
|
72
|
+
const entries = valueNodes.map((valueNode) => ({
|
|
73
|
+
fieldIdentifier: valueNode.attributes["field-identifier"],
|
|
74
|
+
value: coerceValue(textContent(valueNode), valueNode.attributes["base-type"] ?? baseType),
|
|
75
|
+
}));
|
|
76
|
+
const recordEntries = entries.filter(
|
|
77
|
+
(entry): entry is { fieldIdentifier: string; value: QtiScalarValue } =>
|
|
78
|
+
Boolean(entry.fieldIdentifier),
|
|
79
|
+
);
|
|
80
|
+
if (recordEntries.length > 0) {
|
|
81
|
+
return Object.fromEntries(recordEntries.map((entry) => [entry.fieldIdentifier, entry.value]));
|
|
82
|
+
}
|
|
83
|
+
if (entries.length === 0) {
|
|
84
|
+
const text = textContent(node);
|
|
85
|
+
return text.length > 0 ? coerceValue(text, baseType) : null;
|
|
86
|
+
}
|
|
87
|
+
if (entries.length === 1) return entries[0]?.value ?? null;
|
|
88
|
+
return entries.map((entry) => entry.value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseMapping(node: XmlNode | undefined): QtiResponseDeclaration["mapping"] | undefined {
|
|
92
|
+
if (!node) return undefined;
|
|
93
|
+
return {
|
|
94
|
+
defaultValue: Number(node.attributes["default-value"] ?? 0),
|
|
95
|
+
attributes: node.attributes,
|
|
96
|
+
source: node.source,
|
|
97
|
+
entries: childElements(node, "qti-map-entry").map((entry) => ({
|
|
98
|
+
mapKey: entry.attributes["map-key"],
|
|
99
|
+
mappedValue: Number(entry.attributes["mapped-value"] ?? 0),
|
|
100
|
+
attributes: entry.attributes,
|
|
101
|
+
source: entry.source,
|
|
102
|
+
})),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseAreaMapping(
|
|
107
|
+
node: XmlNode | undefined,
|
|
108
|
+
): QtiResponseDeclaration["areaMapping"] | undefined {
|
|
109
|
+
if (!node) return undefined;
|
|
110
|
+
return {
|
|
111
|
+
defaultValue: Number(node.attributes["default-value"] ?? 0),
|
|
112
|
+
attributes: node.attributes,
|
|
113
|
+
source: node.source,
|
|
114
|
+
entries: childElements(node, "qti-area-map-entry").map((entry) => ({
|
|
115
|
+
shape: parseShape(entry.attributes.shape),
|
|
116
|
+
coords: parseCoords(entry.attributes.coords),
|
|
117
|
+
mappedValue: Number(entry.attributes["mapped-value"] ?? 0),
|
|
118
|
+
attributes: entry.attributes,
|
|
119
|
+
source: entry.source,
|
|
120
|
+
})),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseLookupTable(
|
|
125
|
+
node: XmlNode,
|
|
126
|
+
baseType: QtiOutcomeDeclaration["baseType"],
|
|
127
|
+
): QtiLookupTable | undefined {
|
|
128
|
+
const matchTable = childElements(node, "qti-match-table")[0];
|
|
129
|
+
if (matchTable) return parseMatchTable(matchTable, baseType);
|
|
130
|
+
const interpolationTable = childElements(node, "qti-interpolation-table")[0];
|
|
131
|
+
if (interpolationTable) return parseInterpolationTable(interpolationTable, baseType);
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function parseMatchTable(
|
|
136
|
+
node: XmlNode,
|
|
137
|
+
baseType: QtiOutcomeDeclaration["baseType"],
|
|
138
|
+
): QtiLookupTable {
|
|
139
|
+
return {
|
|
140
|
+
type: "match",
|
|
141
|
+
defaultValue: parseLookupValue(node.attributes["default-value"], baseType),
|
|
142
|
+
attributes: node.attributes,
|
|
143
|
+
source: node.source,
|
|
144
|
+
entries: childElements(node, "qti-match-table-entry").map((entry) => ({
|
|
145
|
+
sourceValue: Number(entry.attributes["source-value"]),
|
|
146
|
+
targetValue: parseLookupValue(entry.attributes["target-value"], baseType),
|
|
147
|
+
attributes: entry.attributes,
|
|
148
|
+
source: entry.source,
|
|
149
|
+
})),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseInterpolationTable(
|
|
154
|
+
node: XmlNode,
|
|
155
|
+
baseType: QtiOutcomeDeclaration["baseType"],
|
|
156
|
+
): QtiLookupTable {
|
|
157
|
+
return {
|
|
158
|
+
type: "interpolation",
|
|
159
|
+
defaultValue: parseLookupValue(node.attributes["default-value"], baseType),
|
|
160
|
+
attributes: node.attributes,
|
|
161
|
+
source: node.source,
|
|
162
|
+
entries: childElements(node, "qti-interpolation-table-entry").map((entry) => ({
|
|
163
|
+
sourceValue: Number(entry.attributes["source-value"]),
|
|
164
|
+
targetValue: parseLookupValue(entry.attributes["target-value"], baseType),
|
|
165
|
+
includeBoundary: entry.attributes["include-boundary"] !== "false",
|
|
166
|
+
attributes: entry.attributes,
|
|
167
|
+
source: entry.source,
|
|
168
|
+
})),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parseLookupValue(value: string | undefined, baseType: string | undefined): QtiValue {
|
|
173
|
+
return value === undefined ? null : coerceValue(value, baseType);
|
|
174
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
QtiCatalogCard,
|
|
3
|
+
QtiCatalogCardEntry,
|
|
4
|
+
QtiCatalogFileHref,
|
|
5
|
+
QtiCatalogHtmlContent,
|
|
6
|
+
QtiCatalogInfo,
|
|
7
|
+
QtiCatalogReference,
|
|
8
|
+
QtiCompanionMaterialsInfo,
|
|
9
|
+
QtiCompanionMaterialsUnparsedChild,
|
|
10
|
+
QtiContentNode,
|
|
11
|
+
QtiDiagnostic,
|
|
12
|
+
QtiModalFeedback,
|
|
13
|
+
QtiPhysicalMaterial,
|
|
14
|
+
QtiStylesheet,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
import { childElements, descendants, textContent, type XmlNode } from "./xml.js";
|
|
17
|
+
|
|
18
|
+
export function firstChildElement(
|
|
19
|
+
parent: XmlNode,
|
|
20
|
+
childName: string,
|
|
21
|
+
diagnostics: QtiDiagnostic[],
|
|
22
|
+
duplicateCode: string,
|
|
23
|
+
): XmlNode | undefined {
|
|
24
|
+
const matches = childElements(parent, childName);
|
|
25
|
+
if (matches.length > 1) {
|
|
26
|
+
diagnostics.push({
|
|
27
|
+
code: duplicateCode,
|
|
28
|
+
severity: "error",
|
|
29
|
+
message: `${parent.localName} allows at most one ${childName} child.`,
|
|
30
|
+
path: matches[1]?.source?.path ?? parent.source?.path,
|
|
31
|
+
source: matches[1]?.source ?? parent.source,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return matches[0];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseStylesheet(node: XmlNode): QtiStylesheet {
|
|
38
|
+
return {
|
|
39
|
+
href: node.attributes.href ?? "",
|
|
40
|
+
type: node.attributes.type,
|
|
41
|
+
media: node.attributes.media,
|
|
42
|
+
title: node.attributes.title,
|
|
43
|
+
attributes: node.attributes,
|
|
44
|
+
source: node.source,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function parseCatalogReferences(node: XmlNode): QtiCatalogReference[] {
|
|
49
|
+
const references = [
|
|
50
|
+
...(node.attributes["data-catalog-idref"] ? [node] : []),
|
|
51
|
+
...descendants(node, (child) => Boolean(child.attributes["data-catalog-idref"])),
|
|
52
|
+
];
|
|
53
|
+
return references.map((reference) => ({
|
|
54
|
+
idref: reference.attributes["data-catalog-idref"] ?? "",
|
|
55
|
+
source: reference.source,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function parseModalFeedback(node: XmlNode): QtiModalFeedback {
|
|
60
|
+
const showHide = node.attributes["show-hide"] === "hide" ? "hide" : "show";
|
|
61
|
+
return {
|
|
62
|
+
identifier: node.attributes.identifier ?? "",
|
|
63
|
+
outcomeIdentifier: node.attributes["outcome-identifier"] ?? "",
|
|
64
|
+
showHide,
|
|
65
|
+
text: textContent(node),
|
|
66
|
+
source: node.source,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function parseCatalogInfo(node: XmlNode | undefined): QtiCatalogInfo | undefined {
|
|
71
|
+
if (!node) return undefined;
|
|
72
|
+
return {
|
|
73
|
+
catalogs: childElements(node, "qti-catalog").map((catalog) => ({
|
|
74
|
+
id: catalog.attributes.id ?? "",
|
|
75
|
+
attributes: catalog.attributes,
|
|
76
|
+
cards: childElements(catalog, "qti-card").map(parseCatalogCard),
|
|
77
|
+
source: catalog.source,
|
|
78
|
+
})),
|
|
79
|
+
source: node.source,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseCatalogCard(node: XmlNode): QtiCatalogCard {
|
|
84
|
+
return {
|
|
85
|
+
support: node.attributes.support ?? "",
|
|
86
|
+
htmlContent: parseCatalogHtmlContent(childElements(node, "qti-html-content")[0]),
|
|
87
|
+
fileHrefs: childElements(node, "qti-file-href").map(parseCatalogFileHref),
|
|
88
|
+
entries: childElements(node, "qti-card-entry").map(parseCatalogCardEntry),
|
|
89
|
+
attributes: node.attributes,
|
|
90
|
+
source: node.source,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseCatalogCardEntry(node: XmlNode): QtiCatalogCardEntry {
|
|
95
|
+
return {
|
|
96
|
+
language: node.attributes["xml:lang"] ?? node.attributes.lang,
|
|
97
|
+
default: node.attributes.default === "true",
|
|
98
|
+
htmlContent: parseCatalogHtmlContent(childElements(node, "qti-html-content")[0]),
|
|
99
|
+
fileHrefs: childElements(node, "qti-file-href").map(parseCatalogFileHref),
|
|
100
|
+
attributes: node.attributes,
|
|
101
|
+
source: node.source,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseCatalogHtmlContent(node: XmlNode | undefined): QtiCatalogHtmlContent | undefined {
|
|
106
|
+
if (!node) return undefined;
|
|
107
|
+
return {
|
|
108
|
+
text: textContent(node),
|
|
109
|
+
children: parseCatalogHtmlChildren(node),
|
|
110
|
+
attributes: node.attributes,
|
|
111
|
+
source: node.source,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseCatalogHtmlChildren(node: XmlNode): QtiContentNode[] {
|
|
116
|
+
const content: QtiContentNode[] = [];
|
|
117
|
+
for (const entry of node.content) {
|
|
118
|
+
if (typeof entry === "string") {
|
|
119
|
+
if (entry.length > 0) content.push({ kind: "text", text: entry, source: node.source });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
content.push({
|
|
123
|
+
kind: "element",
|
|
124
|
+
qtiName: entry.localName,
|
|
125
|
+
attributes: entry.attributes,
|
|
126
|
+
children: parseCatalogHtmlChildren(entry),
|
|
127
|
+
source: entry.source,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return content;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parseCatalogFileHref(node: XmlNode): QtiCatalogFileHref {
|
|
134
|
+
return {
|
|
135
|
+
href: textContent(node).trim(),
|
|
136
|
+
mimeType: node.attributes["mime-type"],
|
|
137
|
+
attributes: node.attributes,
|
|
138
|
+
source: node.source,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function parseCompanionMaterialsInfo(
|
|
143
|
+
node: XmlNode | undefined,
|
|
144
|
+
diagnostics: QtiDiagnostic[],
|
|
145
|
+
): QtiCompanionMaterialsInfo | undefined {
|
|
146
|
+
if (!node) return undefined;
|
|
147
|
+
|
|
148
|
+
const physicalMaterials: QtiPhysicalMaterial[] = [];
|
|
149
|
+
const unparsedChildren: QtiCompanionMaterialsUnparsedChild[] = [];
|
|
150
|
+
|
|
151
|
+
for (const child of childElements(node)) {
|
|
152
|
+
if (child.localName === "qti-physical-material") {
|
|
153
|
+
const material = parsePhysicalMaterial(child, diagnostics);
|
|
154
|
+
if (material) physicalMaterials.push(material);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
unparsedChildren.push({ qtiName: child.localName, source: child.source });
|
|
159
|
+
diagnostics.push({
|
|
160
|
+
code: "companionMaterials.child.unsupported",
|
|
161
|
+
severity: "warning",
|
|
162
|
+
message: `${child.localName} is not currently modeled in companion materials parsing.`,
|
|
163
|
+
path: child.source?.path,
|
|
164
|
+
source: child.source,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
physicalMaterials,
|
|
170
|
+
unparsedChildren,
|
|
171
|
+
source: node.source,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function parsePhysicalMaterial(
|
|
176
|
+
node: XmlNode,
|
|
177
|
+
diagnostics: QtiDiagnostic[],
|
|
178
|
+
): QtiPhysicalMaterial | undefined {
|
|
179
|
+
const text = textContent(node).trim();
|
|
180
|
+
if (text.length === 0) {
|
|
181
|
+
diagnostics.push({
|
|
182
|
+
code: "companionMaterials.physicalMaterial.empty",
|
|
183
|
+
severity: "warning",
|
|
184
|
+
message: "qti-physical-material requires non-empty text content.",
|
|
185
|
+
path: node.source?.path,
|
|
186
|
+
source: node.source,
|
|
187
|
+
});
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
text,
|
|
192
|
+
source: node.source,
|
|
193
|
+
};
|
|
194
|
+
}
|