@longsightgroup/qti3-core 0.11.0 → 0.12.1
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 +7 -2
- package/dist/binary-choice.d.ts +9 -0
- package/dist/binary-choice.d.ts.map +1 -0
- package/dist/binary-choice.js +81 -0
- package/dist/binary-choice.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/qti-identifier.d.ts +3 -0
- package/dist/qti-identifier.d.ts.map +1 -0
- package/dist/qti-identifier.js +5 -0
- package/dist/qti-identifier.js.map +1 -0
- package/dist/qti-package-items.d.ts.map +1 -1
- package/dist/qti-package-items.js +22 -26
- package/dist/qti-package-items.js.map +1 -1
- package/dist/qti-package-paths.d.ts +2 -0
- package/dist/qti-package-paths.d.ts.map +1 -1
- package/dist/qti-package-paths.js +6 -0
- package/dist/qti-package-paths.js.map +1 -1
- package/dist/qti-package-types.d.ts +1 -0
- package/dist/qti-package-types.d.ts.map +1 -1
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +3 -1
- package/dist/support.js.map +1 -1
- package/dist/test-expression-parser.d.ts +6 -0
- package/dist/test-expression-parser.d.ts.map +1 -0
- package/dist/test-expression-parser.js +76 -0
- package/dist/test-expression-parser.js.map +1 -0
- package/dist/test-expression-serializer.d.ts +5 -0
- package/dist/test-expression-serializer.d.ts.map +1 -0
- package/dist/test-expression-serializer.js +54 -0
- package/dist/test-expression-serializer.js.map +1 -0
- package/dist/test-expression-validation.d.ts +14 -0
- package/dist/test-expression-validation.d.ts.map +1 -0
- package/dist/test-expression-validation.js +101 -0
- package/dist/test-expression-validation.js.map +1 -0
- package/dist/test-expression.d.ts +99 -0
- package/dist/test-expression.d.ts.map +1 -0
- package/dist/test-expression.js +28 -0
- package/dist/test-expression.js.map +1 -0
- package/dist/test-model.d.ts +62 -0
- package/dist/test-model.d.ts.map +1 -0
- package/dist/test-model.js +5 -0
- package/dist/test-model.js.map +1 -0
- package/dist/test-parser.d.ts +13 -0
- package/dist/test-parser.d.ts.map +1 -0
- package/dist/test-parser.js +144 -0
- package/dist/test-parser.js.map +1 -0
- package/dist/test-processing.d.ts +22 -0
- package/dist/test-processing.d.ts.map +1 -0
- package/dist/test-processing.js +84 -0
- package/dist/test-processing.js.map +1 -0
- package/dist/test-session.d.ts +31 -0
- package/dist/test-session.d.ts.map +1 -0
- package/dist/test-session.js +102 -0
- package/dist/test-session.js.map +1 -0
- package/dist/test-support.d.ts +4 -0
- package/dist/test-support.d.ts.map +1 -0
- package/dist/test-support.js +30 -0
- package/dist/test-support.js.map +1 -0
- package/dist/test-validation.d.ts +4 -0
- package/dist/test-validation.d.ts.map +1 -0
- package/dist/test-validation.js +133 -0
- package/dist/test-validation.js.map +1 -0
- package/dist/test-xml.d.ts +7 -0
- package/dist/test-xml.d.ts.map +1 -0
- package/dist/test-xml.js +23 -0
- package/dist/test-xml.js.map +1 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/binary-choice.ts +96 -0
- package/src/index.ts +29 -0
- package/src/qti-identifier.ts +4 -0
- package/src/qti-package-items.ts +28 -37
- package/src/qti-package-paths.ts +9 -0
- package/src/qti-package-types.ts +1 -0
- package/src/support.ts +3 -1
- package/src/test-expression-parser.ts +89 -0
- package/src/test-expression-serializer.ts +71 -0
- package/src/test-expression-validation.ts +139 -0
- package/src/test-expression.ts +66 -0
- package/src/test-model.ts +68 -0
- package/src/test-parser.ts +201 -0
- package/src/test-processing.ts +107 -0
- package/src/test-session.ts +160 -0
- package/src/test-support.ts +31 -0
- package/src/test-validation.ts +190 -0
- package/src/test-xml.ts +37 -0
- package/src/types.ts +6 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { assertNever } from "./assert-never.js";
|
|
2
|
+
import { coerceValue } from "./parser-values.js";
|
|
3
|
+
import { isTestBaseType, testExpressionSyntax } from "./test-expression.js";
|
|
4
|
+
import { checkTestXml, rejectTestXml } from "./test-xml.js";
|
|
5
|
+
import { textContent } from "./xml.js";
|
|
6
|
+
/** Read the single expression owned by a branch or outcome assignment. */
|
|
7
|
+
export function parseTestExpressionChild(node, diagnostics) {
|
|
8
|
+
const first = node.children[0];
|
|
9
|
+
if (node.children.length !== 1 || !first) {
|
|
10
|
+
rejectTestXml(node, diagnostics);
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return parseTestExpression(first, diagnostics);
|
|
14
|
+
}
|
|
15
|
+
function parseTestExpression(node, diagnostics) {
|
|
16
|
+
const syntax = testExpressionSyntax.find((entry) => entry.name === node.localName);
|
|
17
|
+
if (!syntax) {
|
|
18
|
+
rejectTestXml(node, diagnostics);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
checkTestXml(node, testExpressionSyntax.map((entry) => entry.name), syntax.attributes, diagnostics, syntax.type === "baseValue");
|
|
22
|
+
const arityValid = syntax.arity === "oneOrMore" ? node.children.length > 0 : node.children.length === syntax.arity;
|
|
23
|
+
if (!arityValid) {
|
|
24
|
+
rejectTestXml(node, diagnostics);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const expressions = [];
|
|
28
|
+
for (const child of node.children) {
|
|
29
|
+
const parsed = parseTestExpression(child, diagnostics);
|
|
30
|
+
if (!parsed)
|
|
31
|
+
return undefined;
|
|
32
|
+
expressions.push(parsed);
|
|
33
|
+
}
|
|
34
|
+
const source = node.source;
|
|
35
|
+
switch (syntax.type) {
|
|
36
|
+
case "baseValue": {
|
|
37
|
+
const baseType = node.attributes["base-type"];
|
|
38
|
+
if (!isTestBaseType(baseType)) {
|
|
39
|
+
rejectTestXml(node, diagnostics);
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
type: "baseValue",
|
|
44
|
+
baseType,
|
|
45
|
+
value: coerceValue(textContent(node), baseType),
|
|
46
|
+
source,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
case "variable":
|
|
50
|
+
return { type: "variable", identifier: node.attributes.identifier ?? "", source };
|
|
51
|
+
case "testVariables":
|
|
52
|
+
return {
|
|
53
|
+
type: "testVariables",
|
|
54
|
+
variableIdentifier: node.attributes["variable-identifier"] ?? "",
|
|
55
|
+
includeCategory: node.attributes["include-category"],
|
|
56
|
+
source,
|
|
57
|
+
};
|
|
58
|
+
case "sum":
|
|
59
|
+
case "and":
|
|
60
|
+
case "or":
|
|
61
|
+
return { type: syntax.type, expressions, source };
|
|
62
|
+
case "numericCompare": {
|
|
63
|
+
const [left, right] = expressions;
|
|
64
|
+
if (!left || !right)
|
|
65
|
+
return undefined;
|
|
66
|
+
return { type: "numericCompare", operator: syntax.operator, left, right, source };
|
|
67
|
+
}
|
|
68
|
+
case "not": {
|
|
69
|
+
const expression = expressions[0];
|
|
70
|
+
return expression ? { type: "not", expression, source } : undefined;
|
|
71
|
+
}
|
|
72
|
+
default:
|
|
73
|
+
return assertNever(syntax);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=test-expression-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-expression-parser.js","sourceRoot":"","sources":["../src/test-expression-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type QtiTestExpression } from "./test-expression.js";
|
|
2
|
+
import { type QtiTestResult } from "./test-model.js";
|
|
3
|
+
/** Serialize the closed test expression language without involving item processing. */
|
|
4
|
+
export declare function serializeTestExpression(expression: QtiTestExpression): QtiTestResult<string>;
|
|
5
|
+
//# sourceMappingURL=test-expression-serializer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-expression-serializer.d.ts","sourceRoot":"","sources":["../src/test-expression-serializer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGlE,uFAAuF;AACvF,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,iBAAiB,GAAG,aAAa,CAAC,MAAM,CAAC,CAG5F"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { assertNever } from "./assert-never.js";
|
|
2
|
+
import { renderElement } from "./serializer-processing-xml.js";
|
|
3
|
+
import { testExpressionSyntax } from "./test-expression.js";
|
|
4
|
+
import { testFailure } from "./test-model.js";
|
|
5
|
+
import { escapeXmlText } from "./xml.js";
|
|
6
|
+
/** Serialize the closed test expression language without involving item processing. */
|
|
7
|
+
export function serializeTestExpression(expression) {
|
|
8
|
+
const result = serializeExpression(expression, 0);
|
|
9
|
+
return result.ok ? { ok: true, value: result.value.join("\n") } : result;
|
|
10
|
+
}
|
|
11
|
+
function serializeExpression(expression, indent) {
|
|
12
|
+
const syntax = testExpressionSyntax.find((entry) => entry.type === expression.type &&
|
|
13
|
+
(expression.type !== "numericCompare" ||
|
|
14
|
+
(entry.type === "numericCompare" && entry.operator === expression.operator)));
|
|
15
|
+
if (!syntax)
|
|
16
|
+
return testFailure("expression.unsupported", "Cannot serialize an unsupported test expression.");
|
|
17
|
+
const attributes = [];
|
|
18
|
+
let children = [];
|
|
19
|
+
switch (expression.type) {
|
|
20
|
+
case "baseValue":
|
|
21
|
+
return {
|
|
22
|
+
ok: true,
|
|
23
|
+
value: renderElement(syntax.name, [["base-type", expression.baseType]], escapeXmlText(String(expression.value)), indent),
|
|
24
|
+
};
|
|
25
|
+
case "variable":
|
|
26
|
+
attributes.push(["identifier", expression.identifier]);
|
|
27
|
+
break;
|
|
28
|
+
case "testVariables":
|
|
29
|
+
attributes.push(["variable-identifier", expression.variableIdentifier], ["include-category", expression.includeCategory]);
|
|
30
|
+
break;
|
|
31
|
+
case "sum":
|
|
32
|
+
case "and":
|
|
33
|
+
case "or":
|
|
34
|
+
children = expression.expressions;
|
|
35
|
+
break;
|
|
36
|
+
case "numericCompare":
|
|
37
|
+
children = [expression.left, expression.right];
|
|
38
|
+
break;
|
|
39
|
+
case "not":
|
|
40
|
+
children = [expression.expression];
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
return assertNever(expression);
|
|
44
|
+
}
|
|
45
|
+
const content = [];
|
|
46
|
+
for (const child of children) {
|
|
47
|
+
const result = serializeExpression(child, indent + 1);
|
|
48
|
+
if (!result.ok)
|
|
49
|
+
return result;
|
|
50
|
+
content.push(...result.value);
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, value: renderElement(syntax.name, attributes, content, indent) };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=test-expression-serializer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-expression-serializer.js","sourceRoot":"","sources":["../src/test-expression-serializer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type QtiTestBaseType, type QtiTestExpression } from "./test-expression.js";
|
|
2
|
+
import type { QtiDiagnostic, QtiOutcomeDeclaration, QtiValue } from "./types.js";
|
|
3
|
+
/** The declared QTI type of an expression, including its cardinality. */
|
|
4
|
+
export interface TestExpressionType {
|
|
5
|
+
readonly baseType: QtiTestBaseType;
|
|
6
|
+
readonly cardinality: "single" | "multiple";
|
|
7
|
+
}
|
|
8
|
+
/** Check scalar literals and defaults against the supported base types. */
|
|
9
|
+
export declare function isTestScalarValue(baseType: string | undefined, value: QtiValue): boolean;
|
|
10
|
+
/** Infer a test expression's QTI type and diagnose invalid operands and scope. */
|
|
11
|
+
export declare function checkTestExpression(expression: QtiTestExpression, declarations: ReadonlyMap<string, QtiOutcomeDeclaration>, categories: ReadonlySet<string>, scope: "outcome" | "branch", diagnostics: QtiDiagnostic[]): TestExpressionType | undefined;
|
|
12
|
+
/** Whether a branch expression has the required single boolean type. */
|
|
13
|
+
export declare function isBoolean(type: TestExpressionType | undefined): boolean;
|
|
14
|
+
//# sourceMappingURL=test-expression-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-expression-validation.d.ts","sourceRoot":"","sources":["../src/test-expression-validation.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEjF,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,QAAQ,GAAG,UAAU,CAAC;CAC7C;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAexF;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,iBAAiB,EAC7B,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,qBAAqB,CAAC,EACxD,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,EAC/B,KAAK,EAAE,SAAS,GAAG,QAAQ,EAC3B,WAAW,EAAE,aAAa,EAAE,GAC3B,kBAAkB,GAAG,SAAS,CAyFhC;AAMD,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAEvE"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { assertNever } from "./assert-never.js";
|
|
2
|
+
import { isTestBaseType, testExpressionSyntax, } from "./test-expression.js";
|
|
3
|
+
/** Check scalar literals and defaults against the supported base types. */
|
|
4
|
+
export function isTestScalarValue(baseType, value) {
|
|
5
|
+
if (baseType === undefined)
|
|
6
|
+
return false;
|
|
7
|
+
switch (baseType) {
|
|
8
|
+
case "integer":
|
|
9
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
10
|
+
case "float":
|
|
11
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
12
|
+
case "boolean":
|
|
13
|
+
return typeof value === "boolean";
|
|
14
|
+
case "string":
|
|
15
|
+
case "identifier":
|
|
16
|
+
return typeof value === "string";
|
|
17
|
+
default:
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Infer a test expression's QTI type and diagnose invalid operands and scope. */
|
|
22
|
+
export function checkTestExpression(expression, declarations, categories, scope, diagnostics) {
|
|
23
|
+
const reject = (code, message) => {
|
|
24
|
+
diagnostics.push({
|
|
25
|
+
code: `test.${code}`,
|
|
26
|
+
severity: "error",
|
|
27
|
+
message,
|
|
28
|
+
source: expression.source,
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const check = (child) => checkTestExpression(child, declarations, categories, scope, diagnostics);
|
|
32
|
+
if (!testExpressionSyntax.some((syntax) => syntax.type === expression.type)) {
|
|
33
|
+
reject("expression.unsupported", "Expression is outside the supported test execution profile.");
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
switch (expression.type) {
|
|
37
|
+
case "baseValue":
|
|
38
|
+
if (!isTestScalarValue(expression.baseType, expression.value)) {
|
|
39
|
+
reject("expression.value", "Expression values must match a supported scalar base type.");
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return { baseType: expression.baseType, cardinality: "single" };
|
|
43
|
+
case "variable": {
|
|
44
|
+
const declaration = declarations.get(expression.identifier);
|
|
45
|
+
if (!declaration || !isTestBaseType(declaration.baseType)) {
|
|
46
|
+
reject("expression.variable", "Test expression references an undeclared or unsupported outcome.");
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return { baseType: declaration.baseType, cardinality: "single" };
|
|
50
|
+
}
|
|
51
|
+
case "testVariables":
|
|
52
|
+
if (scope === "branch")
|
|
53
|
+
reject("branch.scope", "Test-variable aggregation belongs in outcome processing, not a branch rule.");
|
|
54
|
+
if (expression.variableIdentifier !== "SCORE" ||
|
|
55
|
+
(expression.includeCategory !== undefined && !categories.has(expression.includeCategory)))
|
|
56
|
+
reject("expression.testVariables", "This test profile aggregates item SCORE values from declared categories.");
|
|
57
|
+
return { baseType: "float", cardinality: "multiple" };
|
|
58
|
+
case "sum": {
|
|
59
|
+
const operands = expression.expressions.map(check);
|
|
60
|
+
if (!operands.length || operands.some((type) => !isNumeric(type)))
|
|
61
|
+
reject("expression.sum", "Test sums require numeric operands.");
|
|
62
|
+
return {
|
|
63
|
+
baseType: operands.every((type) => type?.baseType === "integer") ? "integer" : "float",
|
|
64
|
+
cardinality: "single",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
case "numericCompare": {
|
|
68
|
+
const left = check(expression.left);
|
|
69
|
+
const right = check(expression.right);
|
|
70
|
+
if (!testExpressionSyntax.some((syntax) => syntax.type === "numericCompare" && syntax.operator === expression.operator))
|
|
71
|
+
reject("expression.compare", "Unsupported test comparison operator.");
|
|
72
|
+
if (!isNumeric(left) ||
|
|
73
|
+
left?.cardinality !== "single" ||
|
|
74
|
+
!isNumeric(right) ||
|
|
75
|
+
right?.cardinality !== "single")
|
|
76
|
+
reject("expression.compare", "Test comparisons require two scalar numbers.");
|
|
77
|
+
return { baseType: "boolean", cardinality: "single" };
|
|
78
|
+
}
|
|
79
|
+
case "and":
|
|
80
|
+
case "or": {
|
|
81
|
+
const operands = expression.expressions.map(check);
|
|
82
|
+
if (!operands.length || operands.some((type) => !isBoolean(type)))
|
|
83
|
+
reject("expression.boolean", "Test logic requires boolean operands.");
|
|
84
|
+
return { baseType: "boolean", cardinality: "single" };
|
|
85
|
+
}
|
|
86
|
+
case "not":
|
|
87
|
+
if (!isBoolean(check(expression.expression)))
|
|
88
|
+
reject("expression.boolean", "Test logic requires a boolean operand.");
|
|
89
|
+
return { baseType: "boolean", cardinality: "single" };
|
|
90
|
+
default:
|
|
91
|
+
return assertNever(expression);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isNumeric(type) {
|
|
95
|
+
return type?.baseType === "integer" || type?.baseType === "float";
|
|
96
|
+
}
|
|
97
|
+
/** Whether a branch expression has the required single boolean type. */
|
|
98
|
+
export function isBoolean(type) {
|
|
99
|
+
return type?.baseType === "boolean" && type.cardinality === "single";
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=test-expression-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-expression-validation.js","sourceRoot":"","sources":["../src/test-expression-validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { QtiSourceLocation } from "./types.js";
|
|
2
|
+
/** Scalar types supported by deterministic test outcome processing. */
|
|
3
|
+
export type QtiTestBaseType = "integer" | "float" | "boolean" | "string" | "identifier";
|
|
4
|
+
/** Test expressions form a closed language, independent of item processing. */
|
|
5
|
+
export type QtiTestExpression = ({
|
|
6
|
+
readonly type: "baseValue";
|
|
7
|
+
readonly baseType: QtiTestBaseType;
|
|
8
|
+
readonly value: number | boolean | string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly type: "variable";
|
|
11
|
+
readonly identifier: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly type: "testVariables";
|
|
14
|
+
readonly variableIdentifier: string;
|
|
15
|
+
readonly includeCategory?: string | undefined;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: "sum";
|
|
18
|
+
readonly expressions: readonly QtiTestExpression[];
|
|
19
|
+
} | {
|
|
20
|
+
readonly type: "and";
|
|
21
|
+
readonly expressions: readonly QtiTestExpression[];
|
|
22
|
+
} | {
|
|
23
|
+
readonly type: "or";
|
|
24
|
+
readonly expressions: readonly QtiTestExpression[];
|
|
25
|
+
} | {
|
|
26
|
+
readonly type: "numericCompare";
|
|
27
|
+
readonly operator: "gt" | "gte" | "lt" | "lte";
|
|
28
|
+
readonly left: QtiTestExpression;
|
|
29
|
+
readonly right: QtiTestExpression;
|
|
30
|
+
} | {
|
|
31
|
+
readonly type: "not";
|
|
32
|
+
readonly expression: QtiTestExpression;
|
|
33
|
+
}) & {
|
|
34
|
+
readonly source?: QtiSourceLocation | undefined;
|
|
35
|
+
};
|
|
36
|
+
/** XML names, AST variants, arities and attributes owned by the test language. */
|
|
37
|
+
export declare const testExpressionSyntax: readonly [{
|
|
38
|
+
readonly name: "qti-base-value";
|
|
39
|
+
readonly type: "baseValue";
|
|
40
|
+
readonly arity: 0;
|
|
41
|
+
readonly attributes: readonly ["base-type"];
|
|
42
|
+
}, {
|
|
43
|
+
readonly name: "qti-variable";
|
|
44
|
+
readonly type: "variable";
|
|
45
|
+
readonly arity: 0;
|
|
46
|
+
readonly attributes: readonly ["identifier"];
|
|
47
|
+
}, {
|
|
48
|
+
readonly name: "qti-test-variables";
|
|
49
|
+
readonly type: "testVariables";
|
|
50
|
+
readonly arity: 0;
|
|
51
|
+
readonly attributes: readonly ["variable-identifier", "include-category"];
|
|
52
|
+
}, {
|
|
53
|
+
readonly name: "qti-sum";
|
|
54
|
+
readonly type: "sum";
|
|
55
|
+
readonly arity: "oneOrMore";
|
|
56
|
+
readonly attributes: readonly [];
|
|
57
|
+
}, {
|
|
58
|
+
readonly name: "qti-gt";
|
|
59
|
+
readonly type: "numericCompare";
|
|
60
|
+
readonly operator: "gt";
|
|
61
|
+
readonly arity: 2;
|
|
62
|
+
readonly attributes: readonly [];
|
|
63
|
+
}, {
|
|
64
|
+
readonly name: "qti-gte";
|
|
65
|
+
readonly type: "numericCompare";
|
|
66
|
+
readonly operator: "gte";
|
|
67
|
+
readonly arity: 2;
|
|
68
|
+
readonly attributes: readonly [];
|
|
69
|
+
}, {
|
|
70
|
+
readonly name: "qti-lt";
|
|
71
|
+
readonly type: "numericCompare";
|
|
72
|
+
readonly operator: "lt";
|
|
73
|
+
readonly arity: 2;
|
|
74
|
+
readonly attributes: readonly [];
|
|
75
|
+
}, {
|
|
76
|
+
readonly name: "qti-lte";
|
|
77
|
+
readonly type: "numericCompare";
|
|
78
|
+
readonly operator: "lte";
|
|
79
|
+
readonly arity: 2;
|
|
80
|
+
readonly attributes: readonly [];
|
|
81
|
+
}, {
|
|
82
|
+
readonly name: "qti-and";
|
|
83
|
+
readonly type: "and";
|
|
84
|
+
readonly arity: "oneOrMore";
|
|
85
|
+
readonly attributes: readonly [];
|
|
86
|
+
}, {
|
|
87
|
+
readonly name: "qti-or";
|
|
88
|
+
readonly type: "or";
|
|
89
|
+
readonly arity: "oneOrMore";
|
|
90
|
+
readonly attributes: readonly [];
|
|
91
|
+
}, {
|
|
92
|
+
readonly name: "qti-not";
|
|
93
|
+
readonly type: "not";
|
|
94
|
+
readonly arity: 1;
|
|
95
|
+
readonly attributes: readonly [];
|
|
96
|
+
}];
|
|
97
|
+
/** Refine a supported test scalar type without accepting item-only base types. */
|
|
98
|
+
export declare function isTestBaseType(value: string | undefined): value is QtiTestBaseType;
|
|
99
|
+
//# sourceMappingURL=test-expression.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-expression.d.ts","sourceRoot":"","sources":["../src/test-expression.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,YAAY,CAAC;AAExF,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GAAG,CAC5B;IACE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC1D;IACE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAAE,GAC5E;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAAE,GAC5E;IAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAAE,GAC3E;IACE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;CACnC,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAA;CAAE,CACnE,GAAG;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAA;CAAE,CAAC;AAExD,kFAAkF;AAClF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuB9B,CAAC;AAEJ,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,eAAe,CAQlF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** XML names, AST variants, arities and attributes owned by the test language. */
|
|
2
|
+
export const testExpressionSyntax = [
|
|
3
|
+
{ name: "qti-base-value", type: "baseValue", arity: 0, attributes: ["base-type"] },
|
|
4
|
+
{ name: "qti-variable", type: "variable", arity: 0, attributes: ["identifier"] },
|
|
5
|
+
{
|
|
6
|
+
name: "qti-test-variables",
|
|
7
|
+
type: "testVariables",
|
|
8
|
+
arity: 0,
|
|
9
|
+
attributes: ["variable-identifier", "include-category"],
|
|
10
|
+
},
|
|
11
|
+
{ name: "qti-sum", type: "sum", arity: "oneOrMore", attributes: [] },
|
|
12
|
+
{ name: "qti-gt", type: "numericCompare", operator: "gt", arity: 2, attributes: [] },
|
|
13
|
+
{ name: "qti-gte", type: "numericCompare", operator: "gte", arity: 2, attributes: [] },
|
|
14
|
+
{ name: "qti-lt", type: "numericCompare", operator: "lt", arity: 2, attributes: [] },
|
|
15
|
+
{ name: "qti-lte", type: "numericCompare", operator: "lte", arity: 2, attributes: [] },
|
|
16
|
+
{ name: "qti-and", type: "and", arity: "oneOrMore", attributes: [] },
|
|
17
|
+
{ name: "qti-or", type: "or", arity: "oneOrMore", attributes: [] },
|
|
18
|
+
{ name: "qti-not", type: "not", arity: 1, attributes: [] },
|
|
19
|
+
];
|
|
20
|
+
/** Refine a supported test scalar type without accepting item-only base types. */
|
|
21
|
+
export function isTestBaseType(value) {
|
|
22
|
+
return (value === "integer" ||
|
|
23
|
+
value === "float" ||
|
|
24
|
+
value === "boolean" ||
|
|
25
|
+
value === "string" ||
|
|
26
|
+
value === "identifier");
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=test-expression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-expression.js","sourceRoot":"","sources":["../src/test-expression.ts"],"names":[],"mappings":"AA8BA,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,WAAW,CAAC,EAAE;IAClF,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,YAAY,CAAC,EAAE;IAChF;QACE,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACxD;IACD,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,EAAE;IACpE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;IACpF,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;IACtF,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;IACpF,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;IACtF,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,EAAE;IACpE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,EAAE;IAClE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;CAOzD,CAAC;AAEJ,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,KAAyB;IACtD,OAAO,CACL,KAAK,KAAK,SAAS;QACnB,KAAK,KAAK,OAAO;QACjB,KAAK,KAAK,SAAS;QACnB,KAAK,KAAK,QAAQ;QAClB,KAAK,KAAK,YAAY,CACvB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { QtiDiagnostic, QtiOutcomeDeclaration } from "./types.js";
|
|
2
|
+
import type { QtiTestBaseType, QtiTestExpression } from "./test-expression.js";
|
|
3
|
+
/** An ordered branch evaluated after the section's final item submission. */
|
|
4
|
+
export interface QtiTestBranch {
|
|
5
|
+
readonly target: string;
|
|
6
|
+
readonly expression: QtiTestExpression;
|
|
7
|
+
}
|
|
8
|
+
/** A fixed item reference; categories participate in test-variable aggregation. */
|
|
9
|
+
export interface QtiTestItemRef {
|
|
10
|
+
readonly identifier: string;
|
|
11
|
+
readonly href: string;
|
|
12
|
+
readonly categories: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
/** A flat section in the supported linear, individually submitted test profile. */
|
|
15
|
+
export interface QtiTestSection {
|
|
16
|
+
readonly identifier: string;
|
|
17
|
+
readonly title: string;
|
|
18
|
+
readonly items: readonly QtiTestItemRef[];
|
|
19
|
+
readonly branches: readonly QtiTestBranch[];
|
|
20
|
+
}
|
|
21
|
+
/** Framework-neutral QTI test definition. Validation establishes executable support. */
|
|
22
|
+
export interface QtiTestDefinition {
|
|
23
|
+
readonly identifier: string;
|
|
24
|
+
readonly title: string;
|
|
25
|
+
readonly partIdentifier: string;
|
|
26
|
+
readonly sections: readonly QtiTestSection[];
|
|
27
|
+
readonly outcomeDeclarations: readonly QtiOutcomeDeclaration[];
|
|
28
|
+
readonly outcomeProcessing: readonly QtiTestOutcomeRule[];
|
|
29
|
+
}
|
|
30
|
+
/** Sequential assignment of a test expression to a declared test outcome. */
|
|
31
|
+
export interface QtiTestOutcomeRule {
|
|
32
|
+
readonly type: "setOutcomeValue";
|
|
33
|
+
readonly identifier: string;
|
|
34
|
+
readonly expression: QtiTestExpression;
|
|
35
|
+
}
|
|
36
|
+
type ExecutableSection = QtiTestSection & {
|
|
37
|
+
readonly items: readonly [QtiTestItemRef, ...QtiTestItemRef[]];
|
|
38
|
+
};
|
|
39
|
+
type ExecutableOutcome = Readonly<QtiOutcomeDeclaration> & {
|
|
40
|
+
readonly cardinality: "single";
|
|
41
|
+
readonly baseType: QtiTestBaseType;
|
|
42
|
+
readonly defaultValue: number | boolean | string | null;
|
|
43
|
+
};
|
|
44
|
+
declare const executableTest: unique symbol;
|
|
45
|
+
/** A test checked for supported expressions, unique identifiers and forward routing. */
|
|
46
|
+
export type QtiExecutableTest = Omit<QtiTestDefinition, "sections" | "outcomeDeclarations"> & {
|
|
47
|
+
readonly [executableTest]: true;
|
|
48
|
+
readonly sections: readonly [ExecutableSection, ...ExecutableSection[]];
|
|
49
|
+
readonly outcomeDeclarations: readonly ExecutableOutcome[];
|
|
50
|
+
};
|
|
51
|
+
/** Typed failure channel shared by test construction, parsing and transitions. */
|
|
52
|
+
export type QtiTestResult<T> = {
|
|
53
|
+
readonly ok: true;
|
|
54
|
+
readonly value: T;
|
|
55
|
+
} | {
|
|
56
|
+
readonly ok: false;
|
|
57
|
+
readonly diagnostics: readonly QtiDiagnostic[];
|
|
58
|
+
};
|
|
59
|
+
/** Construct a safe test diagnostic without including submitted content. */
|
|
60
|
+
export declare function testFailure(code: string, message: string): QtiTestResult<never>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=test-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-model.d.ts","sourceRoot":"","sources":["../src/test-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE/E,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;CACxC;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;CAC7C;AAED,wFAAwF;AACxF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,QAAQ,CAAC,mBAAmB,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC/D,QAAQ,CAAC,iBAAiB,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC3D;AAED,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;CACxC;AAED,KAAK,iBAAiB,GAAG,cAAc,GAAG;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,cAAc,EAAE,GAAG,cAAc,EAAE,CAAC,CAAC;CAChE,CAAC;AACF,KAAK,iBAAiB,GAAG,QAAQ,CAAC,qBAAqB,CAAC,GAAG;IACzD,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;CACzD,CAAC;AAEF,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAE5C,wFAAwF;AACxF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,EAAE,UAAU,GAAG,qBAAqB,CAAC,GAAG;IAC5F,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAAC,CAAC;IACxE,QAAQ,CAAC,mBAAmB,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAC5D,CAAC;AAEF,kFAAkF;AAClF,MAAM,MAAM,aAAa,CAAC,CAAC,IACvB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACxC;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,CAAC;AAE3E,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAE/E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-model.js","sourceRoot":"","sources":["../src/test-model.ts"],"names":[],"mappings":"AAgEA,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAe;IACvD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AAC5F,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type QtiExecutableTest, type QtiTestResult } from "./test-model.js";
|
|
2
|
+
/** Whether a test requires runtime selection rather than ordinary fixed navigation. */
|
|
3
|
+
export type QtiTestExecution = {
|
|
4
|
+
readonly kind: "fixed";
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: "sequenced";
|
|
7
|
+
readonly test: QtiExecutableTest;
|
|
8
|
+
};
|
|
9
|
+
/** Classify sequencing on the parsed tree and reject unsupported executable routes. */
|
|
10
|
+
export declare function parseQtiTestExecution(xml: string): QtiTestResult<QtiTestExecution>;
|
|
11
|
+
/** Parse the supported executable test profile; never silently ignore routing features. */
|
|
12
|
+
export declare function parseQtiTest(xml: string): QtiTestResult<QtiExecutableTest>;
|
|
13
|
+
//# sourceMappingURL=test-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-parser.d.ts","sourceRoot":"","sources":["../src/test-parser.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,iBAAiB,EAGtB,KAAK,aAAa,EAEnB,MAAM,iBAAiB,CAAC;AAGzB,uFAAuF;AACvF,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAErE,uFAAuF;AACvF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAMlF;AAED,2FAA2F;AAC3F,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAG1E"}
|