@timeback/qti 0.2.0 → 0.2.1-beta.20260313210405
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/parse.d.ts +124 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +126 -0
- package/dist/parse.types.d.ts +47 -0
- package/dist/parse.types.d.ts.map +1 -0
- package/package.json +6 -1
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QTI Parse Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for QTI XML parsing utilities.
|
|
5
|
+
*/
|
|
6
|
+
/** A single choice option extracted from a `<qti-simple-choice>` element. */
|
|
7
|
+
interface QtiChoice {
|
|
8
|
+
identifier: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
/** Inline feedback extracted from a `<qti-feedback-inline>` element. */
|
|
12
|
+
interface QtiInlineFeedback {
|
|
13
|
+
outcomeIdentifier: string;
|
|
14
|
+
identifier: string;
|
|
15
|
+
showHide: string;
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
/** Modal feedback extracted from a `<qti-modal-feedback>` element. */
|
|
19
|
+
interface QtiModalFeedback {
|
|
20
|
+
outcomeIdentifier: string;
|
|
21
|
+
identifier: string;
|
|
22
|
+
showHide: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}
|
|
25
|
+
/** Attributes from a `<qti-choice-interaction>` element. */
|
|
26
|
+
interface QtiInteractionAttributes {
|
|
27
|
+
responseIdentifier: string;
|
|
28
|
+
shuffle: boolean;
|
|
29
|
+
maxChoices: number;
|
|
30
|
+
}
|
|
31
|
+
/** Response declaration extracted from a `<qti-response-declaration>` element. */
|
|
32
|
+
interface QtiResponseDeclaration {
|
|
33
|
+
identifier: string;
|
|
34
|
+
cardinality: string;
|
|
35
|
+
baseType: string | null;
|
|
36
|
+
}
|
|
37
|
+
/** Complete parsed result from a QTI assessment item XML string. */
|
|
38
|
+
interface ParsedQtiItem {
|
|
39
|
+
prompt: string;
|
|
40
|
+
choices: QtiChoice[];
|
|
41
|
+
correctAnswer: string | null;
|
|
42
|
+
feedback: QtiInlineFeedback[];
|
|
43
|
+
modalFeedback: QtiModalFeedback[];
|
|
44
|
+
interaction: QtiInteractionAttributes | null;
|
|
45
|
+
responseDeclaration: QtiResponseDeclaration | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract the prompt text from a `<qti-prompt>` element.
|
|
50
|
+
*
|
|
51
|
+
* @param xml - Raw QTI XML string
|
|
52
|
+
* @returns The inner HTML of the prompt, or an empty string if not found
|
|
53
|
+
*/
|
|
54
|
+
declare function extractPrompt(xml: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* Extract simple choices from `<qti-simple-choice>` elements.
|
|
57
|
+
*
|
|
58
|
+
* Inline feedback elements within each choice are stripped from the returned
|
|
59
|
+
* text. Use {@link extractFeedback} to retrieve them separately.
|
|
60
|
+
*
|
|
61
|
+
* @param xml - Raw QTI XML string
|
|
62
|
+
* @returns Array of choices with identifier and cleaned text
|
|
63
|
+
*/
|
|
64
|
+
declare function extractChoices(xml: string): QtiChoice[];
|
|
65
|
+
/**
|
|
66
|
+
* Extract the correct answer identifier from `<qti-correct-response>`.
|
|
67
|
+
*
|
|
68
|
+
* @param xml - Raw QTI XML string
|
|
69
|
+
* @returns The correct answer identifier, or null if not found
|
|
70
|
+
*/
|
|
71
|
+
declare function extractCorrectResponse(xml: string): string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Extract inline feedback from `<qti-feedback-inline>` elements.
|
|
74
|
+
*
|
|
75
|
+
* @param xml - Raw QTI XML string
|
|
76
|
+
* @returns Array of inline feedback entries
|
|
77
|
+
*/
|
|
78
|
+
declare function extractFeedback(xml: string): QtiInlineFeedback[];
|
|
79
|
+
/**
|
|
80
|
+
* Extract modal feedback from `<qti-modal-feedback>` elements.
|
|
81
|
+
*
|
|
82
|
+
* @param xml - Raw QTI XML string
|
|
83
|
+
* @returns Array of modal feedback entries
|
|
84
|
+
*/
|
|
85
|
+
declare function extractModalFeedback(xml: string): QtiModalFeedback[];
|
|
86
|
+
/**
|
|
87
|
+
* Extract interaction attributes from `<qti-choice-interaction>`.
|
|
88
|
+
*
|
|
89
|
+
* @param xml - Raw QTI XML string
|
|
90
|
+
* @returns Interaction attributes, or null if no choice interaction found
|
|
91
|
+
*/
|
|
92
|
+
declare function extractInteractionAttributes(xml: string): QtiInteractionAttributes | null;
|
|
93
|
+
/**
|
|
94
|
+
* Extract the response declaration from `<qti-response-declaration>`.
|
|
95
|
+
*
|
|
96
|
+
* @param xml - Raw QTI XML string
|
|
97
|
+
* @returns Response declaration, or null if not found
|
|
98
|
+
*/
|
|
99
|
+
declare function extractResponseDeclaration(xml: string): QtiResponseDeclaration | null;
|
|
100
|
+
/**
|
|
101
|
+
* Parse a QTI 3.0 assessment item XML string into structured data.
|
|
102
|
+
*
|
|
103
|
+
* Combines all individual extractors into a single result. For more control,
|
|
104
|
+
* use the individual `extract*` functions directly.
|
|
105
|
+
*
|
|
106
|
+
* @param xml - Raw QTI XML string
|
|
107
|
+
* @returns Complete parsed item data
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* import { parseQtiXml } from '@timeback/qti/parse'
|
|
112
|
+
*
|
|
113
|
+
* const item = parseQtiXml(xml)
|
|
114
|
+
* console.log(item.prompt) // "What is 2 + 2?"
|
|
115
|
+
* console.log(item.choices) // [{ identifier: "A", text: "3" }, ...]
|
|
116
|
+
* console.log(item.correctAnswer) // "B"
|
|
117
|
+
* console.log(item.feedback) // [{ identifier: "A", content: "..." }, ...]
|
|
118
|
+
* console.log(item.modalFeedback) // [{ identifier: "CORRECT", content: "..." }, ...]
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
declare function parseQtiXml(xml: string): ParsedQtiItem;
|
|
122
|
+
|
|
123
|
+
export { extractChoices, extractCorrectResponse, extractFeedback, extractInteractionAttributes, extractModalFeedback, extractPrompt, extractResponseDeclaration, parseQtiXml };
|
|
124
|
+
export type { ParsedQtiItem, QtiChoice, QtiInlineFeedback, QtiInteractionAttributes, QtiModalFeedback, QtiResponseDeclaration };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,aAAa,EACb,SAAS,EACT,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,sBAAsB,EACtB,MAAM,eAAe,CAAA;AAEtB;;;;;;;;;;;;;;GAcG;AAEH,YAAY,EACX,aAAa,EACb,SAAS,EACT,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,sBAAsB,GACtB,MAAM,eAAe,CAAA;AAMtB;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAevD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKjE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAsBhE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,EAAE,CA4BpE;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAiBzF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB,GAAG,IAAI,CAiBrF;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAUtD"}
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import"./chunk-6jf1natv.js";
|
|
2
|
+
|
|
3
|
+
// src/parse.ts
|
|
4
|
+
function extractPrompt(xml) {
|
|
5
|
+
if (!xml)
|
|
6
|
+
return "";
|
|
7
|
+
const match = xml.match(/<qti-prompt[^>]*>([\s\S]*?)<\/qti-prompt>/i);
|
|
8
|
+
return match?.[1]?.trim() ?? "";
|
|
9
|
+
}
|
|
10
|
+
function extractChoices(xml) {
|
|
11
|
+
if (!xml)
|
|
12
|
+
return [];
|
|
13
|
+
const choices = [];
|
|
14
|
+
const regex = /<qti-simple-choice[^>]*identifier="([^"]+)"[^>]*>([\s\S]*?)<\/qti-simple-choice>/gi;
|
|
15
|
+
let match;
|
|
16
|
+
while ((match = regex.exec(xml)) !== null) {
|
|
17
|
+
let text = match[2] ?? "";
|
|
18
|
+
text = text.replace(/<qti-feedback-inline[\s\S]*?<\/qti-feedback-inline>/gi, "").trim();
|
|
19
|
+
choices.push({ identifier: match[1] ?? "", text });
|
|
20
|
+
}
|
|
21
|
+
return choices;
|
|
22
|
+
}
|
|
23
|
+
function extractCorrectResponse(xml) {
|
|
24
|
+
if (!xml)
|
|
25
|
+
return null;
|
|
26
|
+
const match = xml.match(/<qti-correct-response>\s*<qti-value>([^<]+)<\/qti-value>/i);
|
|
27
|
+
return match?.[1]?.trim() ?? null;
|
|
28
|
+
}
|
|
29
|
+
function extractFeedback(xml) {
|
|
30
|
+
if (!xml)
|
|
31
|
+
return [];
|
|
32
|
+
const feedback = [];
|
|
33
|
+
const regex = /<qti-feedback-inline([^>]*)>([\s\S]*?)<\/qti-feedback-inline>/gi;
|
|
34
|
+
let match;
|
|
35
|
+
while ((match = regex.exec(xml)) !== null) {
|
|
36
|
+
const attrs = match[1] ?? "";
|
|
37
|
+
const outcomeId = attrs.match(/outcome-identifier="([^"]*)"/);
|
|
38
|
+
const id = attrs.match(/(?:^|\s)identifier="([^"]*)"/);
|
|
39
|
+
const showHide = attrs.match(/show-hide="([^"]*)"/);
|
|
40
|
+
feedback.push({
|
|
41
|
+
outcomeIdentifier: outcomeId?.[1] ?? "",
|
|
42
|
+
identifier: id?.[1] ?? "",
|
|
43
|
+
showHide: showHide?.[1] ?? "show",
|
|
44
|
+
content: (match[2] ?? "").trim()
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return feedback;
|
|
48
|
+
}
|
|
49
|
+
function extractModalFeedback(xml) {
|
|
50
|
+
if (!xml)
|
|
51
|
+
return [];
|
|
52
|
+
const modalFeedback = [];
|
|
53
|
+
const regex = /<qti-modal-feedback([^>]*)>([\s\S]*?)<\/qti-modal-feedback>/gi;
|
|
54
|
+
let match;
|
|
55
|
+
while ((match = regex.exec(xml)) !== null) {
|
|
56
|
+
const attrs = match[1] ?? "";
|
|
57
|
+
const outcomeId = attrs.match(/outcome-identifier="([^"]*)"/);
|
|
58
|
+
const id = attrs.match(/(?:^|\s)identifier="([^"]*)"/);
|
|
59
|
+
const showHide = attrs.match(/show-hide="([^"]*)"/);
|
|
60
|
+
let content = match[2] ?? "";
|
|
61
|
+
const bodyMatch = content.match(/<qti-content-body>([\s\S]*?)<\/qti-content-body>/i);
|
|
62
|
+
if (bodyMatch) {
|
|
63
|
+
content = bodyMatch[1] ?? "";
|
|
64
|
+
}
|
|
65
|
+
modalFeedback.push({
|
|
66
|
+
outcomeIdentifier: outcomeId?.[1] ?? "",
|
|
67
|
+
identifier: id?.[1] ?? "",
|
|
68
|
+
showHide: showHide?.[1] ?? "show",
|
|
69
|
+
content: content.trim()
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return modalFeedback;
|
|
73
|
+
}
|
|
74
|
+
function extractInteractionAttributes(xml) {
|
|
75
|
+
if (!xml)
|
|
76
|
+
return null;
|
|
77
|
+
const match = xml.match(/<qti-choice-interaction([^>]*)>/i);
|
|
78
|
+
if (!match)
|
|
79
|
+
return null;
|
|
80
|
+
const attrs = match[1] ?? "";
|
|
81
|
+
const responseId = attrs.match(/response-identifier="([^"]*)"/);
|
|
82
|
+
const shuffle = attrs.match(/shuffle="([^"]*)"/);
|
|
83
|
+
const maxChoices = attrs.match(/max-choices="([^"]*)"/);
|
|
84
|
+
return {
|
|
85
|
+
responseIdentifier: responseId?.[1] ?? "",
|
|
86
|
+
shuffle: shuffle?.[1]?.toLowerCase() === "true",
|
|
87
|
+
maxChoices: maxChoices?.[1] ? parseInt(maxChoices[1], 10) : 1
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function extractResponseDeclaration(xml) {
|
|
91
|
+
if (!xml)
|
|
92
|
+
return null;
|
|
93
|
+
const match = xml.match(/<qti-response-declaration([^>]*)>/i);
|
|
94
|
+
if (!match)
|
|
95
|
+
return null;
|
|
96
|
+
const attrs = match[1] ?? "";
|
|
97
|
+
const identifier = attrs.match(/identifier="([^"]*)"/);
|
|
98
|
+
const cardinality = attrs.match(/cardinality="([^"]*)"/);
|
|
99
|
+
const baseType = attrs.match(/base-type="([^"]*)"/);
|
|
100
|
+
return {
|
|
101
|
+
identifier: identifier?.[1] ?? "",
|
|
102
|
+
cardinality: cardinality?.[1] ?? "",
|
|
103
|
+
baseType: baseType?.[1] ?? null
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function parseQtiXml(xml) {
|
|
107
|
+
return {
|
|
108
|
+
prompt: extractPrompt(xml),
|
|
109
|
+
choices: extractChoices(xml),
|
|
110
|
+
correctAnswer: extractCorrectResponse(xml),
|
|
111
|
+
feedback: extractFeedback(xml),
|
|
112
|
+
modalFeedback: extractModalFeedback(xml),
|
|
113
|
+
interaction: extractInteractionAttributes(xml),
|
|
114
|
+
responseDeclaration: extractResponseDeclaration(xml)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
parseQtiXml,
|
|
119
|
+
extractResponseDeclaration,
|
|
120
|
+
extractPrompt,
|
|
121
|
+
extractModalFeedback,
|
|
122
|
+
extractInteractionAttributes,
|
|
123
|
+
extractFeedback,
|
|
124
|
+
extractCorrectResponse,
|
|
125
|
+
extractChoices
|
|
126
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QTI Parse Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for QTI XML parsing utilities.
|
|
5
|
+
*/
|
|
6
|
+
/** A single choice option extracted from a `<qti-simple-choice>` element. */
|
|
7
|
+
export interface QtiChoice {
|
|
8
|
+
identifier: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
/** Inline feedback extracted from a `<qti-feedback-inline>` element. */
|
|
12
|
+
export interface QtiInlineFeedback {
|
|
13
|
+
outcomeIdentifier: string;
|
|
14
|
+
identifier: string;
|
|
15
|
+
showHide: string;
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
/** Modal feedback extracted from a `<qti-modal-feedback>` element. */
|
|
19
|
+
export interface QtiModalFeedback {
|
|
20
|
+
outcomeIdentifier: string;
|
|
21
|
+
identifier: string;
|
|
22
|
+
showHide: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}
|
|
25
|
+
/** Attributes from a `<qti-choice-interaction>` element. */
|
|
26
|
+
export interface QtiInteractionAttributes {
|
|
27
|
+
responseIdentifier: string;
|
|
28
|
+
shuffle: boolean;
|
|
29
|
+
maxChoices: number;
|
|
30
|
+
}
|
|
31
|
+
/** Response declaration extracted from a `<qti-response-declaration>` element. */
|
|
32
|
+
export interface QtiResponseDeclaration {
|
|
33
|
+
identifier: string;
|
|
34
|
+
cardinality: string;
|
|
35
|
+
baseType: string | null;
|
|
36
|
+
}
|
|
37
|
+
/** Complete parsed result from a QTI assessment item XML string. */
|
|
38
|
+
export interface ParsedQtiItem {
|
|
39
|
+
prompt: string;
|
|
40
|
+
choices: QtiChoice[];
|
|
41
|
+
correctAnswer: string | null;
|
|
42
|
+
feedback: QtiInlineFeedback[];
|
|
43
|
+
modalFeedback: QtiModalFeedback[];
|
|
44
|
+
interaction: QtiInteractionAttributes | null;
|
|
45
|
+
responseDeclaration: QtiResponseDeclaration | null;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=parse.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.types.d.ts","sourceRoot":"","sources":["../src/parse.types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IACjC,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CACf;AAED,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAChC,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CACf;AAED,4DAA4D;AAC5D,MAAM,WAAW,wBAAwB;IACxC,kBAAkB,EAAE,MAAM,CAAA;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,kFAAkF;AAClF,MAAM,WAAW,sBAAsB;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,EAAE,iBAAiB,EAAE,CAAA;IAC7B,aAAa,EAAE,gBAAgB,EAAE,CAAA;IACjC,WAAW,EAAE,wBAAwB,GAAG,IAAI,CAAA;IAC5C,mBAAmB,EAAE,sBAAsB,GAAG,IAAI,CAAA;CAClD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timeback/qti",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1-beta.20260313210405",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
"source": "./src/public-types.ts",
|
|
18
18
|
"types": "./dist/public-types.d.ts",
|
|
19
19
|
"import": "./dist/public-types.js"
|
|
20
|
+
},
|
|
21
|
+
"./parse": {
|
|
22
|
+
"source": "./src/parse.ts",
|
|
23
|
+
"types": "./dist/parse.d.ts",
|
|
24
|
+
"import": "./dist/parse.js"
|
|
20
25
|
}
|
|
21
26
|
},
|
|
22
27
|
"main": "dist/index.js",
|