@examplary/qti 1.0.1 → 1.3.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 +4 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/qti/interactions/index.d.ts +3 -0
- package/dist/qti/interactions/index.js +3 -0
- package/dist/qti/interactions/portable-custom-interaction.d.ts +35 -0
- package/dist/qti/interactions/portable-custom-interaction.js +62 -0
- package/dist/qti/qti-assessment-section.d.ts +26 -0
- package/dist/qti/qti-assessment-section.js +29 -0
- package/dist/qti/qti-element.d.ts +11 -3
- package/dist/qti/qti-element.js +18 -6
- package/dist/qti/qti-item.d.ts +11 -8
- package/dist/qti/qti-item.js +65 -77
- package/dist/qti/qti-test-part.d.ts +19 -0
- package/dist/qti/qti-test-part.js +25 -0
- package/dist/qti/qti-test.d.ts +11 -7
- package/dist/qti/qti-test.js +98 -43
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,8 +20,10 @@ const item = new QtiItem({
|
|
|
20
20
|
title: "Sample Question",
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
item.addResponseDeclaration({
|
|
24
|
-
|
|
23
|
+
item.addResponseDeclaration({
|
|
24
|
+
identifier: "RESPONSE",
|
|
25
|
+
correctResponse: ["4"],
|
|
26
|
+
});
|
|
25
27
|
|
|
26
28
|
item.addItemBodyFromHtml("<p>What is 2 + 2?</p>");
|
|
27
29
|
item.addInteraction(
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export * from "./ims/ims-manifest";
|
|
|
2
2
|
export * from "./ims/ims-package";
|
|
3
3
|
export * from "./qti/qti-element";
|
|
4
4
|
export * from "./qti/qti-test";
|
|
5
|
+
export * from "./qti/qti-test-part";
|
|
6
|
+
export * from "./qti/qti-assessment-section";
|
|
5
7
|
export * from "./qti/qti-item";
|
|
6
8
|
export * from "./qti/interactions";
|
|
7
9
|
export * from "./qti/types";
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,8 @@ __exportStar(require("./ims/ims-manifest"), exports);
|
|
|
18
18
|
__exportStar(require("./ims/ims-package"), exports);
|
|
19
19
|
__exportStar(require("./qti/qti-element"), exports);
|
|
20
20
|
__exportStar(require("./qti/qti-test"), exports);
|
|
21
|
+
__exportStar(require("./qti/qti-test-part"), exports);
|
|
22
|
+
__exportStar(require("./qti/qti-assessment-section"), exports);
|
|
21
23
|
__exportStar(require("./qti/qti-item"), exports);
|
|
22
24
|
__exportStar(require("./qti/interactions"), exports);
|
|
23
25
|
__exportStar(require("./qti/types"), exports);
|
|
@@ -13,6 +13,7 @@ import { InlineChoiceInteraction } from "./inline-choice-interaction";
|
|
|
13
13
|
import { MatchInteraction } from "./match-interaction";
|
|
14
14
|
import { MediaInteraction } from "./media-interaction";
|
|
15
15
|
import { OrderInteraction } from "./order-interaction";
|
|
16
|
+
import { PortableCustomInteraction } from "./portable-custom-interaction";
|
|
16
17
|
import { PositionObjectInteraction } from "./position-object-interaction";
|
|
17
18
|
import { SelectPointInteraction } from "./select-point-interaction";
|
|
18
19
|
import { SliderInteraction } from "./slider-interaction";
|
|
@@ -34,6 +35,7 @@ export * from "./interaction";
|
|
|
34
35
|
export * from "./match-interaction";
|
|
35
36
|
export * from "./media-interaction";
|
|
36
37
|
export * from "./order-interaction";
|
|
38
|
+
export * from "./portable-custom-interaction";
|
|
37
39
|
export * from "./position-object-interaction";
|
|
38
40
|
export * from "./select-point-interaction";
|
|
39
41
|
export * from "./slider-interaction";
|
|
@@ -55,6 +57,7 @@ export declare const qtiInteractionTypes: {
|
|
|
55
57
|
MatchInteraction: typeof MatchInteraction;
|
|
56
58
|
MediaInteraction: typeof MediaInteraction;
|
|
57
59
|
OrderInteraction: typeof OrderInteraction;
|
|
60
|
+
PortableCustomInteraction: typeof PortableCustomInteraction;
|
|
58
61
|
PositionObjectInteraction: typeof PositionObjectInteraction;
|
|
59
62
|
SelectPointInteraction: typeof SelectPointInteraction;
|
|
60
63
|
SliderInteraction: typeof SliderInteraction;
|
|
@@ -30,6 +30,7 @@ const inline_choice_interaction_1 = require("./inline-choice-interaction");
|
|
|
30
30
|
const match_interaction_1 = require("./match-interaction");
|
|
31
31
|
const media_interaction_1 = require("./media-interaction");
|
|
32
32
|
const order_interaction_1 = require("./order-interaction");
|
|
33
|
+
const portable_custom_interaction_1 = require("./portable-custom-interaction");
|
|
33
34
|
const position_object_interaction_1 = require("./position-object-interaction");
|
|
34
35
|
const select_point_interaction_1 = require("./select-point-interaction");
|
|
35
36
|
const slider_interaction_1 = require("./slider-interaction");
|
|
@@ -51,6 +52,7 @@ __exportStar(require("./interaction"), exports);
|
|
|
51
52
|
__exportStar(require("./match-interaction"), exports);
|
|
52
53
|
__exportStar(require("./media-interaction"), exports);
|
|
53
54
|
__exportStar(require("./order-interaction"), exports);
|
|
55
|
+
__exportStar(require("./portable-custom-interaction"), exports);
|
|
54
56
|
__exportStar(require("./position-object-interaction"), exports);
|
|
55
57
|
__exportStar(require("./select-point-interaction"), exports);
|
|
56
58
|
__exportStar(require("./slider-interaction"), exports);
|
|
@@ -72,6 +74,7 @@ exports.qtiInteractionTypes = {
|
|
|
72
74
|
MatchInteraction: match_interaction_1.MatchInteraction,
|
|
73
75
|
MediaInteraction: media_interaction_1.MediaInteraction,
|
|
74
76
|
OrderInteraction: order_interaction_1.OrderInteraction,
|
|
77
|
+
PortableCustomInteraction: portable_custom_interaction_1.PortableCustomInteraction,
|
|
75
78
|
PositionObjectInteraction: position_object_interaction_1.PositionObjectInteraction,
|
|
76
79
|
SelectPointInteraction: select_point_interaction_1.SelectPointInteraction,
|
|
77
80
|
SliderInteraction: slider_interaction_1.SliderInteraction,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { QtiPromptInteraction, QtiPromptInteractionOptions } from "./interaction";
|
|
2
|
+
export type PortableCustomInteractionOptions = QtiPromptInteractionOptions & {
|
|
3
|
+
module: string;
|
|
4
|
+
customInteractionTypeIdentifier: string;
|
|
5
|
+
dataExamplarySettings?: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
modules?: {
|
|
8
|
+
id: string;
|
|
9
|
+
primaryPath: string;
|
|
10
|
+
fallbackPath?: string;
|
|
11
|
+
}[];
|
|
12
|
+
markup?: string;
|
|
13
|
+
templateVariables?: {
|
|
14
|
+
templateIdentifier: string;
|
|
15
|
+
}[];
|
|
16
|
+
contextVariables?: {
|
|
17
|
+
identifier: string;
|
|
18
|
+
}[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A custom QTI portable custom interaction (PCI), allowing you to load arbitrary
|
|
22
|
+
* HTML and JavaScript for rendering the interaction.
|
|
23
|
+
*
|
|
24
|
+
* @see https://www.imsglobal.org/spec/qti/v3p0/impl#h.98xaka8g51za
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const interaction = new PortableCustomInteraction({
|
|
28
|
+
* responseIdentifier: "RESPONSE",
|
|
29
|
+
* module: "single-line-text",
|
|
30
|
+
* customInteractionTypeIdentifier: "urn:fdc:examplary.ai:pci:single-line-text",
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
export declare class PortableCustomInteraction extends QtiPromptInteraction {
|
|
34
|
+
constructor(options: PortableCustomInteractionOptions);
|
|
35
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PortableCustomInteraction = void 0;
|
|
4
|
+
const xmlbuilder2_1 = require("xmlbuilder2");
|
|
5
|
+
const interaction_1 = require("./interaction");
|
|
6
|
+
const html_1 = require("../../utils/html");
|
|
7
|
+
/**
|
|
8
|
+
* A custom QTI portable custom interaction (PCI), allowing you to load arbitrary
|
|
9
|
+
* HTML and JavaScript for rendering the interaction.
|
|
10
|
+
*
|
|
11
|
+
* @see https://www.imsglobal.org/spec/qti/v3p0/impl#h.98xaka8g51za
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const interaction = new PortableCustomInteraction({
|
|
15
|
+
* responseIdentifier: "RESPONSE",
|
|
16
|
+
* module: "single-line-text",
|
|
17
|
+
* customInteractionTypeIdentifier: "urn:fdc:examplary.ai:pci:single-line-text",
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
class PortableCustomInteraction extends interaction_1.QtiPromptInteraction {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super(options);
|
|
23
|
+
this.item = (0, xmlbuilder2_1.fragment)().ele("qti-portable-custom-interaction", {
|
|
24
|
+
"response-identifier": options.responseIdentifier,
|
|
25
|
+
label: options.label,
|
|
26
|
+
module: options.module,
|
|
27
|
+
"custom-interaction-type-identifier": options.customInteractionTypeIdentifier,
|
|
28
|
+
"data-examplary-settings": options.dataExamplarySettings,
|
|
29
|
+
class: options.class,
|
|
30
|
+
});
|
|
31
|
+
// Modules
|
|
32
|
+
if (options.modules?.length) {
|
|
33
|
+
const modules = this.item.ele("qti-interaction-modules");
|
|
34
|
+
for (const module of options.modules) {
|
|
35
|
+
modules.ele("qti-interaction-module", {
|
|
36
|
+
id: module.id,
|
|
37
|
+
"primary-path": module.primaryPath,
|
|
38
|
+
"fallback-path": module.fallbackPath,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Markup
|
|
43
|
+
(0, html_1.appendHtmlFragment)(options.markup || "", this.item.ele("qti-interaction-markup"));
|
|
44
|
+
// Template variables
|
|
45
|
+
if (options.templateVariables?.length) {
|
|
46
|
+
for (const variable of options.templateVariables) {
|
|
47
|
+
this.item.ele("qti-template-variable", {
|
|
48
|
+
"template-identifier": variable.templateIdentifier,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Context variables
|
|
53
|
+
if (options.contextVariables?.length) {
|
|
54
|
+
for (const variable of options.contextVariables) {
|
|
55
|
+
this.item.ele("qti-context-variable", {
|
|
56
|
+
identifier: variable.identifier,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.PortableCustomInteraction = PortableCustomInteraction;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type QtiAssessmentSectionOptions = {
|
|
2
|
+
identifier: string;
|
|
3
|
+
title: string;
|
|
4
|
+
visible: boolean;
|
|
5
|
+
class?: string;
|
|
6
|
+
fixed?: boolean;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
keepTogether?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type QtiItemReference = {
|
|
11
|
+
itemIdentifier: string;
|
|
12
|
+
href: string;
|
|
13
|
+
};
|
|
14
|
+
export declare class QtiAssessmentSection {
|
|
15
|
+
identifier: string;
|
|
16
|
+
title: string;
|
|
17
|
+
visible: boolean;
|
|
18
|
+
class?: string;
|
|
19
|
+
fixed?: boolean;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
keepTogether?: boolean;
|
|
22
|
+
protected itemReferences: QtiItemReference[];
|
|
23
|
+
constructor(options: QtiAssessmentSectionOptions);
|
|
24
|
+
addItemReference(itemIdentifier: string, href: string): void;
|
|
25
|
+
getItemReferences(): QtiItemReference[];
|
|
26
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QtiAssessmentSection = void 0;
|
|
4
|
+
class QtiAssessmentSection {
|
|
5
|
+
identifier;
|
|
6
|
+
title;
|
|
7
|
+
visible;
|
|
8
|
+
class;
|
|
9
|
+
fixed;
|
|
10
|
+
required;
|
|
11
|
+
keepTogether;
|
|
12
|
+
itemReferences = [];
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.identifier = options.identifier;
|
|
15
|
+
this.title = options.title;
|
|
16
|
+
this.visible = options.visible;
|
|
17
|
+
this.class = options.class;
|
|
18
|
+
this.fixed = options.fixed ?? false;
|
|
19
|
+
this.required = options.required ?? false;
|
|
20
|
+
this.keepTogether = options.keepTogether ?? true;
|
|
21
|
+
}
|
|
22
|
+
addItemReference(itemIdentifier, href) {
|
|
23
|
+
this.itemReferences.push({ itemIdentifier, href });
|
|
24
|
+
}
|
|
25
|
+
getItemReferences() {
|
|
26
|
+
return this.itemReferences;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.QtiAssessmentSection = QtiAssessmentSection;
|
|
@@ -2,10 +2,18 @@ import { XMLBuilder } from "xmlbuilder2/lib/interfaces";
|
|
|
2
2
|
export type NamespacedElementContent = string | {
|
|
3
3
|
[key: string]: NamespacedElementContent;
|
|
4
4
|
} | NamespacedElementContent[];
|
|
5
|
+
export type NamespacedElement = {
|
|
6
|
+
namespace: string;
|
|
7
|
+
elementName: string;
|
|
8
|
+
content: NamespacedElementContent;
|
|
9
|
+
attributes?: Record<string, string | undefined>;
|
|
10
|
+
};
|
|
5
11
|
export declare abstract class QtiElement {
|
|
6
|
-
protected
|
|
7
|
-
|
|
12
|
+
protected namespaces: Record<string, string>;
|
|
13
|
+
protected namespaceElements: NamespacedElement[];
|
|
14
|
+
abstract buildXml(): string;
|
|
8
15
|
registerNamespace(prefix: string, uri: string): void;
|
|
9
|
-
addNamespacedElement(namespace: string, elementName: string, content: NamespacedElementContent, attributes?: Record<string, string>): void;
|
|
16
|
+
addNamespacedElement(namespace: string, elementName: string, content: NamespacedElementContent, attributes?: Record<string, string | undefined>): void;
|
|
17
|
+
protected appendNamespacesAndElements(element: XMLBuilder): void;
|
|
10
18
|
private appendContent;
|
|
11
19
|
}
|
package/dist/qti/qti-element.js
CHANGED
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.QtiElement = void 0;
|
|
4
4
|
class QtiElement {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
5
|
+
namespaces = {};
|
|
6
|
+
namespaceElements = [];
|
|
8
7
|
registerNamespace(prefix, uri) {
|
|
9
|
-
this.
|
|
8
|
+
this.namespaces[prefix] = uri;
|
|
10
9
|
}
|
|
11
10
|
addNamespacedElement(namespace, elementName, content, attributes) {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
this.namespaceElements.push({
|
|
12
|
+
namespace,
|
|
13
|
+
elementName,
|
|
14
|
+
content,
|
|
15
|
+
attributes,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
appendNamespacesAndElements(element) {
|
|
19
|
+
for (const [prefix, uri] of Object.entries(this.namespaces)) {
|
|
20
|
+
element.att(`xmlns:${prefix}`, uri);
|
|
21
|
+
}
|
|
22
|
+
for (const nsElement of this.namespaceElements) {
|
|
23
|
+
const child = element.ele(`${nsElement.namespace}:${nsElement.elementName}`, nsElement.attributes);
|
|
24
|
+
this.appendContent(child, nsElement.namespace, nsElement.content);
|
|
25
|
+
}
|
|
14
26
|
}
|
|
15
27
|
appendContent(element, namespace, content) {
|
|
16
28
|
if (typeof content === "string") {
|
package/dist/qti/qti-item.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { XMLBuilder } from "xmlbuilder2/lib/interfaces";
|
|
2
1
|
import { QtiInteraction } from "./interactions/interaction";
|
|
3
2
|
import { QtiElement } from "./qti-element";
|
|
4
3
|
import { QtiTest } from "./qti-test";
|
|
@@ -25,6 +24,7 @@ export type ResponseDeclaration = {
|
|
|
25
24
|
identifier: string;
|
|
26
25
|
cardinality?: QtiCardinality;
|
|
27
26
|
baseType?: QtiBaseType;
|
|
27
|
+
correctResponse?: string[];
|
|
28
28
|
};
|
|
29
29
|
export type OutcomeDeclaration = {
|
|
30
30
|
identifier: string;
|
|
@@ -38,6 +38,11 @@ export declare enum ResponseProcessingTemplate {
|
|
|
38
38
|
MapResponse = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response.xml",
|
|
39
39
|
MapResponsePoint = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response_point.xml"
|
|
40
40
|
}
|
|
41
|
+
export type ItemBodyElement = {
|
|
42
|
+
html: string;
|
|
43
|
+
} | {
|
|
44
|
+
interaction: QtiInteraction;
|
|
45
|
+
};
|
|
41
46
|
export declare class QtiItem extends QtiElement {
|
|
42
47
|
identifier: string;
|
|
43
48
|
adaptive?: boolean;
|
|
@@ -47,19 +52,17 @@ export declare class QtiItem extends QtiElement {
|
|
|
47
52
|
label?: string;
|
|
48
53
|
toolName?: string;
|
|
49
54
|
toolVersion?: string;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
protected responseDeclarations: Map<string, ResponseDeclaration>;
|
|
56
|
+
protected responseProcessing: ResponseProcessingTemplate | null;
|
|
57
|
+
protected outcomeDeclarations: Map<string, OutcomeDeclaration>;
|
|
58
|
+
protected itemBodyElements: ItemBodyElement[];
|
|
53
59
|
constructor(options?: QtiItemOptions);
|
|
60
|
+
buildXml(): string;
|
|
54
61
|
addToPackage(pkg: ImsPackage): Promise<void>;
|
|
55
62
|
addToTest(test: QtiTest): void;
|
|
56
|
-
private getOrCreateItemBody;
|
|
57
63
|
addItemBodyFromHtml(html: string): void;
|
|
58
64
|
addInteraction(interaction: QtiInteraction): void;
|
|
59
|
-
addPciInteraction(interaction: PciInteraction, externalModules?: boolean): void;
|
|
60
65
|
addResponseDeclaration(responseDeclaration?: ResponseDeclaration): void;
|
|
61
|
-
addCorrectResponse(identifier: string, values: string[]): void;
|
|
62
66
|
addResponseProcessing(template: ResponseProcessingTemplate): void;
|
|
63
67
|
addOutcomeDeclaration(outcomeDeclaration?: OutcomeDeclaration): void;
|
|
64
|
-
protected getRootElement(): XMLBuilder;
|
|
65
68
|
}
|
package/dist/qti/qti-item.js
CHANGED
|
@@ -20,9 +20,10 @@ class QtiItem extends qti_element_1.QtiElement {
|
|
|
20
20
|
label;
|
|
21
21
|
toolName;
|
|
22
22
|
toolVersion;
|
|
23
|
-
item;
|
|
24
|
-
itemBody;
|
|
25
23
|
responseDeclarations = new Map();
|
|
24
|
+
responseProcessing = null;
|
|
25
|
+
outcomeDeclarations = new Map();
|
|
26
|
+
itemBodyElements = [];
|
|
26
27
|
constructor(options) {
|
|
27
28
|
super();
|
|
28
29
|
this.identifier = options?.identifier || "item-" + Date.now();
|
|
@@ -31,7 +32,11 @@ class QtiItem extends qti_element_1.QtiElement {
|
|
|
31
32
|
this.title = options?.title;
|
|
32
33
|
this.language = options?.language;
|
|
33
34
|
this.label = options?.label;
|
|
34
|
-
this.
|
|
35
|
+
this.toolName = options?.toolName;
|
|
36
|
+
this.toolVersion = options?.toolVersion;
|
|
37
|
+
}
|
|
38
|
+
buildXml() {
|
|
39
|
+
const item = (0, index_js_1.create)({ version: "1.0", encoding: "UTF-8" }).ele("qti-assessment-item", {
|
|
35
40
|
xmlns: "http://www.imsglobal.org/xsd/imsqtiasi_v3p0",
|
|
36
41
|
"xmlns:m": "http://www.w3.org/1998/Math/MathML",
|
|
37
42
|
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
@@ -46,6 +51,54 @@ class QtiItem extends qti_element_1.QtiElement {
|
|
|
46
51
|
toolVersion: this.toolVersion || "1.0.0",
|
|
47
52
|
"xml:lang": this.language,
|
|
48
53
|
});
|
|
54
|
+
// Extensions
|
|
55
|
+
this.appendNamespacesAndElements(item);
|
|
56
|
+
// Response declaration
|
|
57
|
+
// Note: some implementations expect the response to be defined before the body
|
|
58
|
+
for (const responseDeclaration of this.responseDeclarations.values()) {
|
|
59
|
+
const response = item.ele("qti-response-declaration", {
|
|
60
|
+
identifier: responseDeclaration.identifier,
|
|
61
|
+
cardinality: responseDeclaration.cardinality || "single",
|
|
62
|
+
"base-type": responseDeclaration.baseType || "string",
|
|
63
|
+
});
|
|
64
|
+
if (responseDeclaration.correctResponse?.length) {
|
|
65
|
+
const correctResponse = response.ele("qti-correct-response");
|
|
66
|
+
for (const value of responseDeclaration.correctResponse) {
|
|
67
|
+
correctResponse.ele("qti-value").txt(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Outcome declaration
|
|
72
|
+
for (const outcomeDeclaration of this.outcomeDeclarations.values()) {
|
|
73
|
+
const outcome = item.ele("qti-outcome-declaration", {
|
|
74
|
+
"base-type": outcomeDeclaration.baseType,
|
|
75
|
+
cardinality: outcomeDeclaration.cardinality,
|
|
76
|
+
identifier: outcomeDeclaration.identifier,
|
|
77
|
+
});
|
|
78
|
+
if (outcomeDeclaration.defaultValue !== undefined) {
|
|
79
|
+
outcome
|
|
80
|
+
.ele("qti-default-value")
|
|
81
|
+
.ele("qti-value")
|
|
82
|
+
.txt(outcomeDeclaration.defaultValue.toString());
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Item body
|
|
86
|
+
const itemBody = item.ele("qti-item-body");
|
|
87
|
+
for (const element of this.itemBodyElements) {
|
|
88
|
+
if ("html" in element) {
|
|
89
|
+
(0, html_1.appendHtmlFragment)(element.html, itemBody);
|
|
90
|
+
}
|
|
91
|
+
if ("interaction" in element) {
|
|
92
|
+
itemBody.import(element.interaction.getXmlBuilder());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Response processing
|
|
96
|
+
if (this.responseProcessing) {
|
|
97
|
+
item.ele("qti-response-processing", {
|
|
98
|
+
template: this.responseProcessing,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return item.end({ prettyPrint: true });
|
|
49
102
|
}
|
|
50
103
|
async addToPackage(pkg) {
|
|
51
104
|
await pkg.addResource({
|
|
@@ -61,78 +114,26 @@ class QtiItem extends qti_element_1.QtiElement {
|
|
|
61
114
|
addToTest(test) {
|
|
62
115
|
test.addItemReference(this.identifier, `item-${this.identifier}.xml`);
|
|
63
116
|
}
|
|
64
|
-
getOrCreateItemBody() {
|
|
65
|
-
if (!this.itemBody) {
|
|
66
|
-
this.itemBody = this.item.ele("qti-item-body");
|
|
67
|
-
}
|
|
68
|
-
return this.itemBody;
|
|
69
|
-
}
|
|
70
117
|
addItemBodyFromHtml(html) {
|
|
71
|
-
|
|
72
|
-
(0, html_1.appendHtmlFragment)(content, this.getOrCreateItemBody());
|
|
118
|
+
this.itemBodyElements.push({ html });
|
|
73
119
|
}
|
|
74
120
|
addInteraction(interaction) {
|
|
75
|
-
this.
|
|
76
|
-
}
|
|
77
|
-
addPciInteraction(interaction, externalModules = false) {
|
|
78
|
-
const pci = this.getOrCreateItemBody().ele("qti-portable-custom-interaction", {
|
|
79
|
-
"response-identifier": interaction.responseIdentifier,
|
|
80
|
-
module: interaction.module,
|
|
81
|
-
"custom-interaction-type-identifier": interaction.customInteractionTypeIdentifier,
|
|
82
|
-
"data-examplary-settings": interaction.dataExamplarySettings,
|
|
83
|
-
class: interaction.class,
|
|
84
|
-
});
|
|
85
|
-
if (externalModules) {
|
|
86
|
-
// Use external PCI runtime and HTTP hosted module
|
|
87
|
-
const modules = pci.ele("qti-interaction-modules");
|
|
88
|
-
modules.ele("qti-interaction-module", {
|
|
89
|
-
id: "examplaryPciRuntime",
|
|
90
|
-
"primary-path": "https://unpkg.com/@examplary/pci-runtime@latest/public/dist/runtime",
|
|
91
|
-
});
|
|
92
|
-
modules.ele("qti-interaction-module", {
|
|
93
|
-
id: "single-line-text",
|
|
94
|
-
"primary-path": `https://api.examplary.ai/question-types/${interaction.module}/export/qti3-pci`,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
pci.ele("qti-interaction-markup"); // empty markup
|
|
121
|
+
this.itemBodyElements.push({ interaction });
|
|
98
122
|
}
|
|
99
123
|
addResponseDeclaration(responseDeclaration = {
|
|
100
124
|
identifier: "RESPONSE",
|
|
101
125
|
cardinality: "single",
|
|
102
126
|
baseType: "string",
|
|
103
127
|
}) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const element = this.item.ele("qti-response-declaration", {
|
|
109
|
-
identifier: responseDeclaration.identifier,
|
|
110
|
-
cardinality: responseDeclaration.cardinality || "single",
|
|
111
|
-
"base-type": responseDeclaration.baseType || "string",
|
|
112
|
-
});
|
|
113
|
-
this.responseDeclarations.set(responseDeclaration.identifier, {
|
|
114
|
-
element,
|
|
115
|
-
responseDeclaration,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
addCorrectResponse(identifier, values) {
|
|
119
|
-
const responseDecl = this.responseDeclarations.get(identifier);
|
|
120
|
-
if (!responseDecl) {
|
|
121
|
-
throw new Error(`Response declaration with identifier ${identifier} does not exist.`);
|
|
122
|
-
}
|
|
123
|
-
if (values.length > 1 &&
|
|
124
|
-
responseDecl.responseDeclaration.cardinality === "single") {
|
|
125
|
-
throw new Error(`Cannot add multiple correct responses to a single cardinality response declaration (${identifier}).`);
|
|
126
|
-
}
|
|
127
|
-
const correctResponse = responseDecl.element.ele("qti-correct-response");
|
|
128
|
-
for (const value of values) {
|
|
129
|
-
correctResponse.ele("qti-value").txt(value);
|
|
128
|
+
if (responseDeclaration.correctResponse &&
|
|
129
|
+
responseDeclaration.correctResponse?.length > 1 &&
|
|
130
|
+
responseDeclaration.cardinality === "single") {
|
|
131
|
+
throw new Error(`Cannot add multiple correct responses to a single cardinality response declaration (${responseDeclaration.identifier}).`);
|
|
130
132
|
}
|
|
133
|
+
this.responseDeclarations.set(responseDeclaration.identifier, responseDeclaration);
|
|
131
134
|
}
|
|
132
135
|
addResponseProcessing(template) {
|
|
133
|
-
this.
|
|
134
|
-
template,
|
|
135
|
-
});
|
|
136
|
+
this.responseProcessing = template;
|
|
136
137
|
}
|
|
137
138
|
addOutcomeDeclaration(outcomeDeclaration = {
|
|
138
139
|
identifier: "SCORE",
|
|
@@ -140,20 +141,7 @@ class QtiItem extends qti_element_1.QtiElement {
|
|
|
140
141
|
baseType: "float",
|
|
141
142
|
defaultValue: 0,
|
|
142
143
|
}) {
|
|
143
|
-
|
|
144
|
-
"base-type": outcomeDeclaration.baseType,
|
|
145
|
-
cardinality: outcomeDeclaration.cardinality,
|
|
146
|
-
identifier: outcomeDeclaration.identifier,
|
|
147
|
-
});
|
|
148
|
-
if (outcomeDeclaration.defaultValue !== undefined) {
|
|
149
|
-
outcome
|
|
150
|
-
.ele("qti-default-value")
|
|
151
|
-
.ele("qti-value")
|
|
152
|
-
.txt(outcomeDeclaration.defaultValue.toString());
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
getRootElement() {
|
|
156
|
-
return this.item;
|
|
144
|
+
this.outcomeDeclarations.set(outcomeDeclaration.identifier, outcomeDeclaration);
|
|
157
145
|
}
|
|
158
146
|
}
|
|
159
147
|
exports.QtiItem = QtiItem;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { QtiAssessmentSection } from "./qti-assessment-section";
|
|
2
|
+
export type QtiTestPartOptions = {
|
|
3
|
+
identifier: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
class?: string;
|
|
6
|
+
navigationMode?: "linear" | "nonlinear";
|
|
7
|
+
submissionMode?: "individual" | "simultaneous";
|
|
8
|
+
};
|
|
9
|
+
export declare class QtiTestPart {
|
|
10
|
+
identifier: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
navigationMode: "linear" | "nonlinear";
|
|
14
|
+
submissionMode: "individual" | "simultaneous";
|
|
15
|
+
protected sections: QtiAssessmentSection[];
|
|
16
|
+
constructor(options: QtiTestPartOptions);
|
|
17
|
+
addSection(section: QtiAssessmentSection): void;
|
|
18
|
+
getSections(): QtiAssessmentSection[];
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QtiTestPart = void 0;
|
|
4
|
+
class QtiTestPart {
|
|
5
|
+
identifier;
|
|
6
|
+
title;
|
|
7
|
+
class;
|
|
8
|
+
navigationMode;
|
|
9
|
+
submissionMode;
|
|
10
|
+
sections = [];
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.identifier = options.identifier;
|
|
13
|
+
this.title = options.title;
|
|
14
|
+
this.class = options.class;
|
|
15
|
+
this.navigationMode = options.navigationMode ?? "linear";
|
|
16
|
+
this.submissionMode = options.submissionMode ?? "simultaneous";
|
|
17
|
+
}
|
|
18
|
+
addSection(section) {
|
|
19
|
+
this.sections.push(section);
|
|
20
|
+
}
|
|
21
|
+
getSections() {
|
|
22
|
+
return this.sections;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.QtiTestPart = QtiTestPart;
|
package/dist/qti/qti-test.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { XMLBuilder } from "xmlbuilder2/lib/interfaces";
|
|
2
1
|
import { QtiElement } from "./qti-element";
|
|
2
|
+
import { OutcomeDeclaration } from "./qti-item";
|
|
3
|
+
import { QtiTestPart } from "./qti-test-part";
|
|
3
4
|
import { ImsPackage } from "../ims/ims-package";
|
|
4
5
|
type QtiTestOptions = {
|
|
5
6
|
identifier?: string;
|
|
@@ -7,6 +8,7 @@ type QtiTestOptions = {
|
|
|
7
8
|
language?: string;
|
|
8
9
|
toolName?: string;
|
|
9
10
|
toolVersion?: string;
|
|
11
|
+
addDefaultOutcomes?: boolean;
|
|
10
12
|
};
|
|
11
13
|
export declare class QtiTest extends QtiElement {
|
|
12
14
|
identifier: string;
|
|
@@ -14,14 +16,16 @@ export declare class QtiTest extends QtiElement {
|
|
|
14
16
|
language?: string;
|
|
15
17
|
toolName?: string;
|
|
16
18
|
toolVersion?: string;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
private section;
|
|
20
|
-
private outcomeProcessing;
|
|
19
|
+
protected outcomeDeclarations: Map<string, OutcomeDeclaration>;
|
|
20
|
+
protected testParts: QtiTestPart[];
|
|
21
21
|
constructor(options?: QtiTestOptions);
|
|
22
|
+
buildXml(): string;
|
|
22
23
|
addToPackage(pkg: ImsPackage): Promise<void>;
|
|
24
|
+
addTestPart(testPart: QtiTestPart): void;
|
|
25
|
+
/**
|
|
26
|
+
* Convenience method to add an item reference to the first section of the first test part.
|
|
27
|
+
*/
|
|
23
28
|
addItemReference(itemIdentifier: string, href: string): void;
|
|
24
|
-
|
|
25
|
-
protected getRootElement(): XMLBuilder;
|
|
29
|
+
addOutcomeDeclaration(outcomeDeclaration: OutcomeDeclaration): void;
|
|
26
30
|
}
|
|
27
31
|
export {};
|
package/dist/qti/qti-test.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.QtiTest = void 0;
|
|
4
4
|
const index_js_1 = require("xmlbuilder2/lib/index.js");
|
|
5
|
+
const qti_assessment_section_1 = require("./qti-assessment-section");
|
|
5
6
|
const qti_element_1 = require("./qti-element");
|
|
7
|
+
const qti_test_part_1 = require("./qti-test-part");
|
|
6
8
|
const ims_manifest_1 = require("../ims/ims-manifest");
|
|
7
9
|
class QtiTest extends qti_element_1.QtiElement {
|
|
8
10
|
identifier;
|
|
@@ -10,10 +12,8 @@ class QtiTest extends qti_element_1.QtiElement {
|
|
|
10
12
|
language;
|
|
11
13
|
toolName;
|
|
12
14
|
toolVersion;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
section; // currently, we only support one section per part
|
|
16
|
-
outcomeProcessing;
|
|
15
|
+
outcomeDeclarations = new Map();
|
|
16
|
+
testParts = [];
|
|
17
17
|
constructor(options) {
|
|
18
18
|
super();
|
|
19
19
|
this.identifier = options?.identifier || "test-" + Date.now();
|
|
@@ -21,7 +21,23 @@ class QtiTest extends qti_element_1.QtiElement {
|
|
|
21
21
|
this.language = options?.language;
|
|
22
22
|
this.toolName = options?.toolName || "Examplary QTI Module";
|
|
23
23
|
this.toolVersion = options?.toolVersion || "1.0.0";
|
|
24
|
-
|
|
24
|
+
if (options?.addDefaultOutcomes !== false) {
|
|
25
|
+
this.addOutcomeDeclaration({
|
|
26
|
+
identifier: "SCORE",
|
|
27
|
+
cardinality: "single",
|
|
28
|
+
baseType: "float",
|
|
29
|
+
defaultValue: 0,
|
|
30
|
+
});
|
|
31
|
+
this.addOutcomeDeclaration({
|
|
32
|
+
identifier: "MAX_SCORE",
|
|
33
|
+
cardinality: "single",
|
|
34
|
+
baseType: "float",
|
|
35
|
+
defaultValue: 0,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
buildXml() {
|
|
40
|
+
const test = (0, index_js_1.create)({ version: "1.0", encoding: "UTF-8" }).ele("qti-assessment-test", {
|
|
25
41
|
xmlns: "http://www.imsglobal.org/xsd/imsqtiasi_v3p0",
|
|
26
42
|
"xmlns:m": "http://www.w3.org/1998/Math/MathML",
|
|
27
43
|
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
@@ -33,19 +49,64 @@ class QtiTest extends qti_element_1.QtiElement {
|
|
|
33
49
|
toolVersion: this.toolVersion,
|
|
34
50
|
"xml:lang": this.language,
|
|
35
51
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
// Extensions
|
|
53
|
+
this.appendNamespacesAndElements(test);
|
|
54
|
+
// Outcome declarations
|
|
55
|
+
for (const outcomeDeclaration of this.outcomeDeclarations.values()) {
|
|
56
|
+
const outcome = test.ele("qti-outcome-declaration", {
|
|
57
|
+
"base-type": outcomeDeclaration.baseType,
|
|
58
|
+
cardinality: outcomeDeclaration.cardinality,
|
|
59
|
+
identifier: outcomeDeclaration.identifier,
|
|
60
|
+
});
|
|
61
|
+
if (outcomeDeclaration.defaultValue !== undefined) {
|
|
62
|
+
outcome
|
|
63
|
+
.ele("qti-default-value")
|
|
64
|
+
.ele("qti-value")
|
|
65
|
+
.txt(outcomeDeclaration.defaultValue.toString());
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Parts
|
|
69
|
+
for (const testPart of this.testParts) {
|
|
70
|
+
const part = test.ele("qti-test-part", {
|
|
71
|
+
identifier: testPart.identifier,
|
|
72
|
+
title: testPart.title,
|
|
73
|
+
"navigation-mode": testPart.navigationMode,
|
|
74
|
+
"submission-mode": testPart.submissionMode,
|
|
75
|
+
class: testPart.class,
|
|
76
|
+
});
|
|
77
|
+
// Sections
|
|
78
|
+
for (const section of testPart.getSections()) {
|
|
79
|
+
const sec = part.ele("qti-assessment-section", {
|
|
80
|
+
identifier: section.identifier,
|
|
81
|
+
title: section.title,
|
|
82
|
+
class: section.class,
|
|
83
|
+
visible: section.visible ? "true" : "false",
|
|
84
|
+
fixed: section.fixed ? "true" : "false",
|
|
85
|
+
required: section.required ? "true" : "false",
|
|
86
|
+
keepTogether: section.keepTogether ? "true" : "false",
|
|
87
|
+
});
|
|
88
|
+
// Item references
|
|
89
|
+
for (const itemRef of section.getItemReferences()) {
|
|
90
|
+
sec.ele("qti-assessment-item-ref", {
|
|
91
|
+
identifier: itemRef.itemIdentifier,
|
|
92
|
+
href: itemRef.href,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Outcome processing
|
|
98
|
+
const outcomeProcessing = test.ele("qti-outcome-processing");
|
|
99
|
+
for (const outcomeDeclaration of this.outcomeDeclarations.values()) {
|
|
100
|
+
outcomeProcessing
|
|
101
|
+
.ele("qti-set-outcome-value", {
|
|
102
|
+
identifier: outcomeDeclaration.identifier,
|
|
103
|
+
})
|
|
104
|
+
.ele("qti-sum")
|
|
105
|
+
.ele("qti-test-variables", {
|
|
106
|
+
"variable-identifier": outcomeDeclaration.identifier,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return test.end({ prettyPrint: true });
|
|
49
110
|
}
|
|
50
111
|
async addToPackage(pkg) {
|
|
51
112
|
await pkg.addResource({
|
|
@@ -58,33 +119,27 @@ class QtiTest extends qti_element_1.QtiElement {
|
|
|
58
119
|
},
|
|
59
120
|
]);
|
|
60
121
|
}
|
|
61
|
-
|
|
62
|
-
this.
|
|
63
|
-
identifier: itemIdentifier,
|
|
64
|
-
href: href,
|
|
65
|
-
});
|
|
122
|
+
addTestPart(testPart) {
|
|
123
|
+
this.testParts.push(testPart);
|
|
66
124
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.ele("qti-test-variables", {
|
|
83
|
-
"variable-identifier": identifier,
|
|
84
|
-
});
|
|
125
|
+
/**
|
|
126
|
+
* Convenience method to add an item reference to the first section of the first test part.
|
|
127
|
+
*/
|
|
128
|
+
addItemReference(itemIdentifier, href) {
|
|
129
|
+
if (!this.testParts.length) {
|
|
130
|
+
this.addTestPart(new qti_test_part_1.QtiTestPart({ identifier: "PART-1" }));
|
|
131
|
+
}
|
|
132
|
+
if (!this.testParts[0].getSections().length) {
|
|
133
|
+
this.testParts[0].addSection(new qti_assessment_section_1.QtiAssessmentSection({
|
|
134
|
+
identifier: "SECTION-1",
|
|
135
|
+
title: "Section 1",
|
|
136
|
+
visible: true,
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
this.testParts[0].getSections()[0].addItemReference(itemIdentifier, href);
|
|
85
140
|
}
|
|
86
|
-
|
|
87
|
-
|
|
141
|
+
addOutcomeDeclaration(outcomeDeclaration) {
|
|
142
|
+
this.outcomeDeclarations.set(outcomeDeclaration.identifier, outcomeDeclaration);
|
|
88
143
|
}
|
|
89
144
|
}
|
|
90
145
|
exports.QtiTest = QtiTest;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@examplary/qti",
|
|
3
3
|
"description": "Utilities to generate QTI 3.0 assessment packages.",
|
|
4
4
|
"packageManager": "yarn@4.5.3",
|
|
5
|
-
"version": "1.0
|
|
5
|
+
"version": "1.3.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
12
|
"start": "yarn build --watch",
|
|
13
|
-
"test": "vitest
|
|
13
|
+
"test": "vitest",
|
|
14
14
|
"release": "semantic-release -e semantic-release-monorepo",
|
|
15
15
|
"build": "tsc"
|
|
16
16
|
},
|