@longsightgroup/qti3-core 0.1.1 → 0.2.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 +1 -0
- package/dist/catalog.d.ts +32 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +126 -0
- package/dist/catalog.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +293 -6
- package/dist/parser.js.map +1 -1
- package/dist/session.d.ts +3 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +68 -3
- package/dist/session.js.map +1 -1
- package/dist/support.js +7 -1
- package/dist/support.js.map +1 -1
- package/dist/tts.d.ts +61 -0
- package/dist/tts.d.ts.map +1 -0
- package/dist/tts.js +368 -0
- package/dist/tts.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +243 -11
- package/dist/validation.js.map +1 -1
- package/package.json +4 -2
- package/src/catalog.ts +193 -0
- package/src/index.ts +85 -0
- package/src/parser.ts +1859 -0
- package/src/session.ts +2284 -0
- package/src/support.ts +253 -0
- package/src/tts.ts +555 -0
- package/src/types.ts +703 -0
- package/src/validation.ts +2449 -0
- package/src/xml.ts +139 -0
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ if (parsed.document) {
|
|
|
38
38
|
- Validate item-level QTI behavior and emit structured diagnostics.
|
|
39
39
|
- Score supported response-processing patterns without a DOM.
|
|
40
40
|
- Serialize and restore attempt state through `qti3.attempt-state.v1`.
|
|
41
|
+
- Preserve QTI 3 Portable Custom Interaction metadata and opaque PCI interaction state.
|
|
41
42
|
- Publish support metadata for current and deprecated item interactions.
|
|
42
43
|
|
|
43
44
|
See the main repository README for the support matrix and release notes:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { QtiAssessmentItem, QtiCatalog, QtiCatalogFileHref, QtiCatalogHtmlContent, QtiDocument, QtiSourceLocation } from "./types.js";
|
|
2
|
+
export interface QtiCatalogSupportResolutionOptions {
|
|
3
|
+
supports?: string | readonly string[] | undefined;
|
|
4
|
+
languages?: string | readonly string[] | undefined;
|
|
5
|
+
includeDefaultFallback?: boolean | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface QtiCatalogSupportResolution {
|
|
8
|
+
itemIdentifier: string;
|
|
9
|
+
references: QtiResolvedCatalogReference[];
|
|
10
|
+
}
|
|
11
|
+
export interface QtiResolvedCatalogReference {
|
|
12
|
+
idref: string;
|
|
13
|
+
catalog?: QtiCatalog | undefined;
|
|
14
|
+
matches: QtiResolvedCatalogSupport[];
|
|
15
|
+
source?: QtiSourceLocation | undefined;
|
|
16
|
+
}
|
|
17
|
+
export interface QtiResolvedCatalogSupport {
|
|
18
|
+
catalogId: string;
|
|
19
|
+
support: string;
|
|
20
|
+
default: boolean;
|
|
21
|
+
fileHrefs: QtiCatalogFileHref[];
|
|
22
|
+
attributes: Record<string, string>;
|
|
23
|
+
cardAttributes: Record<string, string>;
|
|
24
|
+
catalogAttributes: Record<string, string>;
|
|
25
|
+
language?: string | undefined;
|
|
26
|
+
htmlContent?: QtiCatalogHtmlContent | undefined;
|
|
27
|
+
source?: QtiSourceLocation | undefined;
|
|
28
|
+
cardSource?: QtiSourceLocation | undefined;
|
|
29
|
+
catalogSource?: QtiSourceLocation | undefined;
|
|
30
|
+
}
|
|
31
|
+
export declare function createCatalogSupportResolution(model: QtiDocument | QtiAssessmentItem, options?: QtiCatalogSupportResolutionOptions): QtiCatalogSupportResolution;
|
|
32
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EAGV,kBAAkB,EAClB,qBAAqB,EACrB,WAAW,EACX,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAClD,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACnD,sBAAsB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9C;AAED,MAAM,WAAW,2BAA2B;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,2BAA2B,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACjC,OAAO,EAAE,yBAAyB,EAAE,CAAC;IACrC,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAChD,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACvC,UAAU,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CAC/C;AAYD,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,WAAW,GAAG,iBAAiB,EACtC,OAAO,GAAE,kCAAuC,GAC/C,2BAA2B,CAqB7B"}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export function createCatalogSupportResolution(model, options = {}) {
|
|
2
|
+
const item = "item" in model ? model.item : model;
|
|
3
|
+
const supportFilter = stringFilter(options.supports);
|
|
4
|
+
const languages = stringList(options.languages).map((language) => language.toLowerCase());
|
|
5
|
+
const catalogById = new Map(item.catalogInfo?.catalogs.map((catalog) => [catalog.id, catalog]) ?? []);
|
|
6
|
+
return {
|
|
7
|
+
itemIdentifier: item.identifier,
|
|
8
|
+
references: item.catalogReferences.map((reference) => {
|
|
9
|
+
const catalog = catalogById.get(reference.idref);
|
|
10
|
+
const resolved = {
|
|
11
|
+
idref: reference.idref,
|
|
12
|
+
matches: catalog ? matchingCatalogSupports(catalog, supportFilter, languages, options) : [],
|
|
13
|
+
};
|
|
14
|
+
if (catalog)
|
|
15
|
+
resolved.catalog = catalog;
|
|
16
|
+
if (reference.source)
|
|
17
|
+
resolved.source = reference.source;
|
|
18
|
+
return resolved;
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function matchingCatalogSupports(catalog, supportFilter, languages, options) {
|
|
23
|
+
return catalog.cards.flatMap((card) => {
|
|
24
|
+
if (supportFilter && !supportFilter.has(card.support.toLowerCase()))
|
|
25
|
+
return [];
|
|
26
|
+
return selectedCandidates(card, languages, options).map((candidate) => resolvedSupport(catalog, candidate));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function selectedCandidates(card, languages, options) {
|
|
30
|
+
const candidates = catalogCandidates(card);
|
|
31
|
+
if (languages.length === 0) {
|
|
32
|
+
const defaults = candidates.filter((candidate) => candidate.default);
|
|
33
|
+
return defaults.length > 0 ? defaults : candidates;
|
|
34
|
+
}
|
|
35
|
+
const languageMatches = candidates
|
|
36
|
+
.map((candidate, index) => ({
|
|
37
|
+
candidate,
|
|
38
|
+
index,
|
|
39
|
+
rank: languageMatchRank(candidate.language, languages),
|
|
40
|
+
}))
|
|
41
|
+
.filter((entry) => Number.isInteger(entry.rank))
|
|
42
|
+
.sort((a, b) => a.rank - b.rank || a.index - b.index);
|
|
43
|
+
if (languageMatches.length > 0)
|
|
44
|
+
return languageMatches.map((entry) => entry.candidate);
|
|
45
|
+
if (options.includeDefaultFallback === false)
|
|
46
|
+
return [];
|
|
47
|
+
const defaults = candidates.filter((candidate) => candidate.default);
|
|
48
|
+
return defaults.length > 0 ? defaults : candidates.filter((candidate) => !candidate.language);
|
|
49
|
+
}
|
|
50
|
+
function catalogCandidates(card) {
|
|
51
|
+
if (card.entries.length > 0) {
|
|
52
|
+
return card.entries.map((entry) => entryCandidate(card, entry));
|
|
53
|
+
}
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
card,
|
|
57
|
+
default: true,
|
|
58
|
+
fileHrefs: card.fileHrefs,
|
|
59
|
+
attributes: card.attributes,
|
|
60
|
+
htmlContent: card.htmlContent,
|
|
61
|
+
source: card.source,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
function entryCandidate(card, entry) {
|
|
66
|
+
const candidate = {
|
|
67
|
+
card,
|
|
68
|
+
default: entry.default,
|
|
69
|
+
fileHrefs: entry.fileHrefs,
|
|
70
|
+
attributes: entry.attributes,
|
|
71
|
+
};
|
|
72
|
+
if (entry.language)
|
|
73
|
+
candidate.language = entry.language;
|
|
74
|
+
if (entry.htmlContent)
|
|
75
|
+
candidate.htmlContent = entry.htmlContent;
|
|
76
|
+
if (entry.source)
|
|
77
|
+
candidate.source = entry.source;
|
|
78
|
+
return candidate;
|
|
79
|
+
}
|
|
80
|
+
function resolvedSupport(catalog, candidate) {
|
|
81
|
+
const resolved = {
|
|
82
|
+
catalogId: catalog.id,
|
|
83
|
+
support: candidate.card.support,
|
|
84
|
+
default: candidate.default,
|
|
85
|
+
fileHrefs: candidate.fileHrefs,
|
|
86
|
+
attributes: candidate.attributes,
|
|
87
|
+
cardAttributes: candidate.card.attributes,
|
|
88
|
+
catalogAttributes: catalog.attributes,
|
|
89
|
+
};
|
|
90
|
+
if (candidate.language)
|
|
91
|
+
resolved.language = candidate.language;
|
|
92
|
+
if (candidate.htmlContent)
|
|
93
|
+
resolved.htmlContent = candidate.htmlContent;
|
|
94
|
+
if (candidate.source)
|
|
95
|
+
resolved.source = candidate.source;
|
|
96
|
+
if (candidate.card.source)
|
|
97
|
+
resolved.cardSource = candidate.card.source;
|
|
98
|
+
if (catalog.source)
|
|
99
|
+
resolved.catalogSource = catalog.source;
|
|
100
|
+
return resolved;
|
|
101
|
+
}
|
|
102
|
+
function languageMatchRank(language, requestedLanguages) {
|
|
103
|
+
if (!language)
|
|
104
|
+
return undefined;
|
|
105
|
+
const normalizedLanguage = language.toLowerCase();
|
|
106
|
+
const primaryLanguage = normalizedLanguage.split("-")[0] ?? normalizedLanguage;
|
|
107
|
+
for (const [index, requestedLanguage] of requestedLanguages.entries()) {
|
|
108
|
+
if (normalizedLanguage === requestedLanguage)
|
|
109
|
+
return index * 2;
|
|
110
|
+
const requestedPrimary = requestedLanguage.split("-")[0] ?? requestedLanguage;
|
|
111
|
+
if (primaryLanguage === requestedPrimary)
|
|
112
|
+
return index * 2 + 1;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
function stringFilter(value) {
|
|
117
|
+
const values = stringList(value).map((entry) => entry.toLowerCase());
|
|
118
|
+
return values.length > 0 ? new Set(values) : undefined;
|
|
119
|
+
}
|
|
120
|
+
function stringList(value) {
|
|
121
|
+
if (value === undefined)
|
|
122
|
+
return [];
|
|
123
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
124
|
+
return entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export { createCatalogSupportResolution, type QtiCatalogSupportResolution, type QtiCatalogSupportResolutionOptions, type QtiResolvedCatalogReference, type QtiResolvedCatalogSupport, } from "./catalog.js";
|
|
1
2
|
export { parseQtiXml } from "./parser.js";
|
|
3
|
+
export { createTextToSpeechTraversal, parseQtiDataSsml, validateQtiDataSsmlMetadata, type QtiDataSsml, type QtiDataSsmlBreak, type QtiDataSsmlBreakStrength, type QtiDataSsmlParseResult, type QtiDataSsmlPhoneme, type QtiDataSsmlProsody, type QtiDataSsmlSayAs, type QtiDataSsmlSub, type QtiTextToSpeechSegment, type QtiTextToSpeechSegmentKind, type QtiTextToSpeechTraversal, } from "./tts.js";
|
|
2
4
|
export { assertQtiAttemptStateV1, createItemSession, isQtiAttemptStateV1, visibleModalFeedback, type QtiCustomOperatorContext, type QtiCustomOperatorHandler, type QtiCustomOperatorRegistry, type QtiItemSession, type QtiItemSessionOptions, } from "./session.js";
|
|
3
5
|
export { deprecatedInteractionSupport, elementSupport, getInteractionSupport, interactionNameToType, interactionSupport, processingSupport, } from "./support.js";
|
|
4
6
|
export { validateAssessmentItem } from "./validation.js";
|
|
5
|
-
export type { QtiAssessmentItem, QtiAttemptStatus, QtiAttemptStateV1, QtiChoice, QtiChoiceRole, QtiContentNode, QtiDiagnostic, QtiDocument, QtiElementSupport, QtiInteractionElementSupport, QtiInteraction, QtiInteractionType, QtiModalFeedback, QtiObjectAsset, QtiParseResult, QtiProcessingElementSupport, QtiResponseBranch, QtiScoreResult, QtiSupportStatus, QtiTemplateDeclaration, QtiTemplateBranch, QtiTemplateProcessing, QtiTemplateRule, QtiValidationResult, QtiValue, } from "./types.js";
|
|
7
|
+
export type { QtiAssessmentItem, QtiAttemptStatus, QtiAttemptStateV1, QtiCatalog, QtiCatalogCard, QtiCatalogCardEntry, QtiCatalogFileHref, QtiCatalogHtmlContent, QtiCatalogInfo, QtiCatalogReference, QtiChoice, QtiChoiceRole, QtiContentNode, QtiDiagnostic, QtiDocument, QtiElementSupport, QtiInteractionElementSupport, QtiInteraction, QtiInteractionType, QtiMediaSource, QtiMediaTrack, QtiModalFeedback, QtiObjectAsset, QtiParseResult, QtiProcessingElementSupport, QtiPortableCustomDefinition, QtiPortableCustomInteractionModule, QtiPortableCustomInteractionModules, QtiPortableCustomStateValue, QtiPortableCustomVariableBinding, QtiResponseBranch, QtiScoreResult, QtiSupportStatus, QtiTemplateDeclaration, QtiTemplateBranch, QtiTemplateProcessing, QtiTemplateRule, QtiValidationResult, QtiValue, } from "./types.js";
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,4BAA4B,EAC5B,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,4BAA4B,EAC5B,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,2BAA2B,EAC3B,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,QAAQ,GACT,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,KAAK,2BAA2B,EAChC,KAAK,kCAAkC,EACvC,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,2BAA2B,EAC3B,gBAAgB,EAChB,2BAA2B,EAC3B,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,GAC9B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,4BAA4B,EAC5B,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,4BAA4B,EAC5B,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,2BAA2B,EAC3B,2BAA2B,EAC3B,kCAAkC,EAClC,mCAAmC,EACnC,2BAA2B,EAC3B,gCAAgC,EAChC,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,QAAQ,GACT,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { createCatalogSupportResolution, } from "./catalog.js";
|
|
1
2
|
export { parseQtiXml } from "./parser.js";
|
|
3
|
+
export { createTextToSpeechTraversal, parseQtiDataSsml, validateQtiDataSsmlMetadata, } from "./tts.js";
|
|
2
4
|
export { assertQtiAttemptStateV1, createItemSession, isQtiAttemptStateV1, visibleModalFeedback, } from "./session.js";
|
|
3
5
|
export { deprecatedInteractionSupport, elementSupport, getInteractionSupport, interactionNameToType, interactionSupport, processingSupport, } from "./support.js";
|
|
4
6
|
export { validateAssessmentItem } from "./validation.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GAMrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,4BAA4B,EAC5B,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,GAK/B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,2BAA2B,EAC3B,gBAAgB,EAChB,2BAA2B,GAY5B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GAMrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,4BAA4B,EAC5B,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/parser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAuBV,cAAc,EAkBf,MAAM,YAAY,CAAC;AAiBpB,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CA0CvD"}
|
package/dist/parser.js
CHANGED
|
@@ -78,6 +78,7 @@ function parseAssessmentItem(node, diagnostics) {
|
|
|
78
78
|
return {
|
|
79
79
|
identifier,
|
|
80
80
|
title: node.attributes.title,
|
|
81
|
+
language: node.attributes["xml:lang"] ?? node.attributes.lang,
|
|
81
82
|
adaptive: node.attributes.adaptive === "true",
|
|
82
83
|
prompt: prompt ? textContent(prompt) : undefined,
|
|
83
84
|
responseDeclarations,
|
|
@@ -351,7 +352,11 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
|
|
|
351
352
|
}
|
|
352
353
|
const objectNode = interactionType === "positionObject"
|
|
353
354
|
? positionObjectInteractionObject(node)
|
|
354
|
-
:
|
|
355
|
+
: interactionType === "media"
|
|
356
|
+
? mediaInteractionObject(node)
|
|
357
|
+
: interactionType === "drawing"
|
|
358
|
+
? drawingInteractionObject(node)
|
|
359
|
+
: descendants(node, (child) => child.localName === "object" || child.localName === "img")[0];
|
|
355
360
|
return {
|
|
356
361
|
type: interactionType ?? "custom",
|
|
357
362
|
qtiName: node.localName,
|
|
@@ -359,11 +364,16 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
|
|
|
359
364
|
responseCardinality: responseDeclaration?.cardinality,
|
|
360
365
|
responseBaseType: responseDeclaration?.baseType,
|
|
361
366
|
prompt: prompt ? textContent(prompt) : undefined,
|
|
367
|
+
promptAttributes: prompt?.attributes,
|
|
368
|
+
promptSource: prompt?.source,
|
|
362
369
|
contextText: inlineInteractionContext(node, interactionType),
|
|
363
370
|
object: parseObjectAsset(objectNode),
|
|
364
371
|
positionObjectStage: interactionType === "positionObject"
|
|
365
372
|
? parseObjectAsset(positionObjectStageObject(node))
|
|
366
373
|
: undefined,
|
|
374
|
+
portableCustom: interactionType === "portableCustom"
|
|
375
|
+
? parsePortableCustomDefinition(node, diagnostics)
|
|
376
|
+
: undefined,
|
|
367
377
|
choices: parseChoices(node),
|
|
368
378
|
hottextSegments: interactionType === "hottext" ? parseHottextSegments(node) : undefined,
|
|
369
379
|
gapMatchSegments: interactionType === "gapMatch" || interactionType === "graphicGapMatch"
|
|
@@ -378,9 +388,147 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
|
|
|
378
388
|
source: node.source,
|
|
379
389
|
};
|
|
380
390
|
}
|
|
391
|
+
function parsePortableCustomDefinition(node, diagnostics) {
|
|
392
|
+
const markup = firstChildElement(node, "qti-interaction-markup", diagnostics);
|
|
393
|
+
const modules = firstChildElement(node, "qti-interaction-modules", diagnostics);
|
|
394
|
+
return {
|
|
395
|
+
responseIdentifier: node.attributes["response-identifier"],
|
|
396
|
+
customInteractionTypeIdentifier: node.attributes["custom-interaction-type-identifier"],
|
|
397
|
+
module: node.attributes.module,
|
|
398
|
+
interactionModules: modules ? parsePortableCustomInteractionModules(modules) : undefined,
|
|
399
|
+
interactionMarkup: markup ? parsePortableCustomMarkupChildren(markup, diagnostics) : [],
|
|
400
|
+
interactionMarkupRaw: markup ? serializeXmlContent(markup) : undefined,
|
|
401
|
+
templateVariables: childElements(node, "qti-template-variable").map((variable) => parsePortableCustomVariableBinding(variable, "template")),
|
|
402
|
+
contextVariables: childElements(node, "qti-context-variable").map((variable) => parsePortableCustomVariableBinding(variable, "context")),
|
|
403
|
+
stylesheets: childElements(node, "qti-stylesheet").map(parseStylesheet),
|
|
404
|
+
catalogInfo: parseCatalogInfo(childElements(node, "qti-catalog-info")[0]),
|
|
405
|
+
dataAttributes: Object.fromEntries(Object.entries(node.attributes).filter(([name]) => name.startsWith("data-"))),
|
|
406
|
+
attributes: node.attributes,
|
|
407
|
+
source: node.source,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function firstChildElement(node, name, diagnostics) {
|
|
411
|
+
const matches = childElements(node, name);
|
|
412
|
+
if (matches.length > 1) {
|
|
413
|
+
diagnostics.push({
|
|
414
|
+
code: "interaction.portableCustom.child.duplicate",
|
|
415
|
+
severity: "error",
|
|
416
|
+
message: `${node.localName} allows at most one ${name} child.`,
|
|
417
|
+
path: matches[1]?.source?.path ?? node.source?.path,
|
|
418
|
+
source: matches[1]?.source ?? node.source,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return matches[0];
|
|
422
|
+
}
|
|
423
|
+
function parsePortableCustomMarkupChildren(node, diagnostics) {
|
|
424
|
+
const content = [];
|
|
425
|
+
for (const entry of node.content) {
|
|
426
|
+
if (typeof entry === "string") {
|
|
427
|
+
if (entry.length > 0)
|
|
428
|
+
content.push({ kind: "text", text: entry, source: node.source });
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
content.push(parsePortableCustomMarkupNode(entry, diagnostics));
|
|
432
|
+
}
|
|
433
|
+
return content;
|
|
434
|
+
}
|
|
435
|
+
function parsePortableCustomMarkupNode(node, diagnostics) {
|
|
436
|
+
if (isInteractionElement(node)) {
|
|
437
|
+
diagnostics.push({
|
|
438
|
+
code: "interaction.portableCustom.markupInteraction",
|
|
439
|
+
severity: "error",
|
|
440
|
+
message: `qti-interaction-markup must not contain nested QTI interaction ${node.localName}.`,
|
|
441
|
+
path: node.source?.path,
|
|
442
|
+
source: node.source,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
if (node.localName === "qti-printed-variable") {
|
|
446
|
+
return {
|
|
447
|
+
kind: "printedVariable",
|
|
448
|
+
identifier: node.attributes.identifier ?? "",
|
|
449
|
+
format: node.attributes.format,
|
|
450
|
+
attributes: node.attributes,
|
|
451
|
+
source: node.source,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
if (node.localName === "qti-feedback-block" || node.localName === "qti-feedback-inline") {
|
|
455
|
+
return {
|
|
456
|
+
kind: "feedback",
|
|
457
|
+
feedbackType: node.localName === "qti-feedback-block" ? "block" : "inline",
|
|
458
|
+
identifier: node.attributes.identifier ?? "",
|
|
459
|
+
outcomeIdentifier: node.attributes["outcome-identifier"] ?? "",
|
|
460
|
+
showHide: node.attributes["show-hide"] === "hide" ? "hide" : "show",
|
|
461
|
+
attributes: node.attributes,
|
|
462
|
+
children: parsePortableCustomMarkupChildren(node, diagnostics),
|
|
463
|
+
source: node.source,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
kind: "element",
|
|
468
|
+
qtiName: node.localName,
|
|
469
|
+
attributes: node.attributes,
|
|
470
|
+
children: parsePortableCustomMarkupChildren(node, diagnostics),
|
|
471
|
+
source: node.source,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function parsePortableCustomInteractionModules(node) {
|
|
475
|
+
return {
|
|
476
|
+
primaryConfiguration: node.attributes["primary-configuration"],
|
|
477
|
+
secondaryConfiguration: node.attributes["secondary-configuration"] ?? node.attributes["fallback-configuration"],
|
|
478
|
+
modules: childElements(node, "qti-interaction-module").map(parsePortableCustomInteractionModule),
|
|
479
|
+
attributes: node.attributes,
|
|
480
|
+
source: node.source,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function parsePortableCustomInteractionModule(node) {
|
|
484
|
+
return {
|
|
485
|
+
id: node.attributes.id,
|
|
486
|
+
primaryPath: node.attributes["primary-path"],
|
|
487
|
+
fallbackPath: node.attributes["fallback-path"],
|
|
488
|
+
attributes: node.attributes,
|
|
489
|
+
source: node.source,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function parsePortableCustomVariableBinding(node, kind) {
|
|
493
|
+
return {
|
|
494
|
+
kind,
|
|
495
|
+
identifier: node.attributes.identifier ?? node.attributes["template-identifier"],
|
|
496
|
+
variableIdentifier: node.attributes["variable-identifier"],
|
|
497
|
+
attributes: node.attributes,
|
|
498
|
+
source: node.source,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function serializeXmlContent(node) {
|
|
502
|
+
return node.content
|
|
503
|
+
.map((entry) => (typeof entry === "string" ? escapeXmlText(entry) : serializeXmlNode(entry)))
|
|
504
|
+
.join("");
|
|
505
|
+
}
|
|
506
|
+
function serializeXmlNode(node) {
|
|
507
|
+
const attributes = Object.entries(node.attributes)
|
|
508
|
+
.map(([name, value]) => ` ${name}="${escapeXmlAttribute(value)}"`)
|
|
509
|
+
.join("");
|
|
510
|
+
if (node.content.length === 0)
|
|
511
|
+
return `<${node.name}${attributes}/>`;
|
|
512
|
+
return `<${node.name}${attributes}>${serializeXmlContent(node)}</${node.name}>`;
|
|
513
|
+
}
|
|
514
|
+
function escapeXmlText(value) {
|
|
515
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
516
|
+
}
|
|
517
|
+
function escapeXmlAttribute(value) {
|
|
518
|
+
return escapeXmlText(value).replaceAll('"', """);
|
|
519
|
+
}
|
|
381
520
|
function positionObjectInteractionObject(node) {
|
|
382
521
|
return childElements(node).find((child) => child.localName === "object" || child.localName === "img");
|
|
383
522
|
}
|
|
523
|
+
function mediaInteractionObject(node) {
|
|
524
|
+
return childElements(node).find((child) => child.localName === "audio" ||
|
|
525
|
+
child.localName === "video" ||
|
|
526
|
+
child.localName === "object" ||
|
|
527
|
+
child.localName === "img");
|
|
528
|
+
}
|
|
529
|
+
function drawingInteractionObject(node) {
|
|
530
|
+
return childElements(node).find((child) => child.localName === "object" || child.localName === "img" || child.localName === "picture");
|
|
531
|
+
}
|
|
384
532
|
function positionObjectStageObject(node) {
|
|
385
533
|
const ancestorStage = nearestAncestor(node, "qti-position-object-stage");
|
|
386
534
|
const stage = ancestorStage ?? childElements(node, "qti-position-object-stage")[0];
|
|
@@ -478,17 +626,148 @@ function normalizeInlineContext(value) {
|
|
|
478
626
|
function parseObjectAsset(node) {
|
|
479
627
|
if (!node)
|
|
480
628
|
return undefined;
|
|
481
|
-
const
|
|
629
|
+
const pictureImage = node.localName === "picture" ? childElements(node, "img")[0] : undefined;
|
|
630
|
+
const data = node.attributes.data ?? node.attributes.src ?? pictureImage?.attributes.src;
|
|
631
|
+
const sources = parseMediaSources(node);
|
|
632
|
+
const tracks = parseMediaTracks(node);
|
|
633
|
+
const inferredSvgDimensions = inlineSvgDimensions(data);
|
|
482
634
|
return {
|
|
483
635
|
data,
|
|
484
|
-
type: node.attributes.type ??
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
636
|
+
type: node.attributes.type ??
|
|
637
|
+
pictureImage?.attributes.type ??
|
|
638
|
+
assetTypeFromData(data) ??
|
|
639
|
+
firstSourceType(sources),
|
|
640
|
+
width: node.attributes.width ?? pictureImage?.attributes.width ?? inferredSvgDimensions?.width,
|
|
641
|
+
height: node.attributes.height ?? pictureImage?.attributes.height ?? inferredSvgDimensions?.height,
|
|
642
|
+
sources,
|
|
643
|
+
tracks,
|
|
644
|
+
text: textContent(node) || node.attributes.alt || pictureImage?.attributes.alt || "",
|
|
488
645
|
attributes: node.attributes,
|
|
489
646
|
source: node.source,
|
|
490
647
|
};
|
|
491
648
|
}
|
|
649
|
+
function inlineSvgDimensions(data) {
|
|
650
|
+
const svgText = decodeInlineSvgData(data);
|
|
651
|
+
if (!svgText)
|
|
652
|
+
return undefined;
|
|
653
|
+
const tree = parseXmlTree(svgText);
|
|
654
|
+
const svg = tree.root;
|
|
655
|
+
if (!svg || svg.localName !== "svg")
|
|
656
|
+
return undefined;
|
|
657
|
+
const width = svgLength(svg.attributes.width);
|
|
658
|
+
const height = svgLength(svg.attributes.height);
|
|
659
|
+
if (width !== undefined && height !== undefined) {
|
|
660
|
+
return { width: formatDimension(width), height: formatDimension(height) };
|
|
661
|
+
}
|
|
662
|
+
const viewBox = svgViewBoxDimensions(svg.attributes.viewBox);
|
|
663
|
+
if (!viewBox)
|
|
664
|
+
return undefined;
|
|
665
|
+
const inferredWidth = width ?? viewBox.width;
|
|
666
|
+
const inferredHeight = height ?? viewBox.height;
|
|
667
|
+
if (inferredWidth <= 0 || inferredHeight <= 0)
|
|
668
|
+
return undefined;
|
|
669
|
+
return { width: formatDimension(inferredWidth), height: formatDimension(inferredHeight) };
|
|
670
|
+
}
|
|
671
|
+
function decodeInlineSvgData(data) {
|
|
672
|
+
if (!data)
|
|
673
|
+
return undefined;
|
|
674
|
+
const comma = data.indexOf(",");
|
|
675
|
+
if (comma < 0)
|
|
676
|
+
return undefined;
|
|
677
|
+
const metadata = data.slice(0, comma);
|
|
678
|
+
if (!/^data:image\/svg\+xml(?:[;,]|$)/i.test(metadata))
|
|
679
|
+
return undefined;
|
|
680
|
+
const payload = data.slice(comma + 1);
|
|
681
|
+
if (/;base64(?:[;,]|$)/i.test(metadata))
|
|
682
|
+
return decodeBase64Ascii(payload);
|
|
683
|
+
try {
|
|
684
|
+
return decodeURIComponent(payload);
|
|
685
|
+
}
|
|
686
|
+
catch {
|
|
687
|
+
return payload;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function decodeBase64Ascii(value) {
|
|
691
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
692
|
+
const cleaned = value.replace(/\s+/g, "").replace(/=+$/, "");
|
|
693
|
+
let bits = 0;
|
|
694
|
+
let buffer = 0;
|
|
695
|
+
let output = "";
|
|
696
|
+
for (const char of cleaned) {
|
|
697
|
+
const digit = alphabet.indexOf(char);
|
|
698
|
+
if (digit < 0)
|
|
699
|
+
return undefined;
|
|
700
|
+
buffer = (buffer << 6) | digit;
|
|
701
|
+
bits += 6;
|
|
702
|
+
if (bits < 8)
|
|
703
|
+
continue;
|
|
704
|
+
bits -= 8;
|
|
705
|
+
output += String.fromCharCode((buffer >> bits) & 0xff);
|
|
706
|
+
}
|
|
707
|
+
return output;
|
|
708
|
+
}
|
|
709
|
+
function svgLength(value) {
|
|
710
|
+
if (!value || value.trim().endsWith("%"))
|
|
711
|
+
return undefined;
|
|
712
|
+
const match = value.trim().match(/^(\d+(?:\.\d+)?|\.\d+)(?:px)?$/i);
|
|
713
|
+
if (!match?.[1])
|
|
714
|
+
return undefined;
|
|
715
|
+
const parsed = Number(match[1]);
|
|
716
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
717
|
+
}
|
|
718
|
+
function svgViewBoxDimensions(value) {
|
|
719
|
+
const parts = value
|
|
720
|
+
?.trim()
|
|
721
|
+
.split(/[\s,]+/)
|
|
722
|
+
.map((part) => Number(part));
|
|
723
|
+
if (!parts || parts.length !== 4 || parts.some((part) => !Number.isFinite(part))) {
|
|
724
|
+
return undefined;
|
|
725
|
+
}
|
|
726
|
+
const width = parts[2];
|
|
727
|
+
const height = parts[3];
|
|
728
|
+
if (width === undefined || height === undefined || width <= 0 || height <= 0)
|
|
729
|
+
return undefined;
|
|
730
|
+
return { width, height };
|
|
731
|
+
}
|
|
732
|
+
function formatDimension(value) {
|
|
733
|
+
return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(4)));
|
|
734
|
+
}
|
|
735
|
+
function parseMediaSources(node) {
|
|
736
|
+
return childElements(node, "source").map((source) => {
|
|
737
|
+
const src = source.attributes.src ?? firstSrcsetCandidate(source.attributes.srcset);
|
|
738
|
+
return {
|
|
739
|
+
src,
|
|
740
|
+
type: source.attributes.type ?? assetTypeFromData(src),
|
|
741
|
+
attributes: source.attributes,
|
|
742
|
+
source: source.source,
|
|
743
|
+
};
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
function parseMediaTracks(node) {
|
|
747
|
+
return childElements(node, "track").map((track) => ({
|
|
748
|
+
kind: track.attributes.kind,
|
|
749
|
+
src: track.attributes.src,
|
|
750
|
+
srclang: track.attributes.srclang,
|
|
751
|
+
label: track.attributes.label,
|
|
752
|
+
default: track.attributes.default !== undefined,
|
|
753
|
+
attributes: track.attributes,
|
|
754
|
+
source: track.source,
|
|
755
|
+
}));
|
|
756
|
+
}
|
|
757
|
+
function firstSourceType(sources) {
|
|
758
|
+
const explicitType = sources.find((source) => source.type)?.type;
|
|
759
|
+
if (explicitType)
|
|
760
|
+
return explicitType;
|
|
761
|
+
return sources.find((source) => source.src)?.src
|
|
762
|
+
? assetTypeFromData(sources.find((source) => source.src)?.src)
|
|
763
|
+
: undefined;
|
|
764
|
+
}
|
|
765
|
+
function firstSrcsetCandidate(srcset) {
|
|
766
|
+
return srcset
|
|
767
|
+
?.split(",")
|
|
768
|
+
.map((candidate) => candidate.trim().split(/\s+/)[0])
|
|
769
|
+
.find((candidate) => candidate && candidate.length > 0);
|
|
770
|
+
}
|
|
492
771
|
function assetTypeFromData(data) {
|
|
493
772
|
if (!data)
|
|
494
773
|
return undefined;
|
|
@@ -496,8 +775,16 @@ function assetTypeFromData(data) {
|
|
|
496
775
|
return "image/svg+xml";
|
|
497
776
|
if (data.startsWith("data:image/"))
|
|
498
777
|
return "image/*";
|
|
778
|
+
if (data.startsWith("data:audio/"))
|
|
779
|
+
return "audio/*";
|
|
780
|
+
if (data.startsWith("data:video/"))
|
|
781
|
+
return "video/*";
|
|
499
782
|
if (/\.(svg|png|jpg|jpeg|gif|webp)(?:[?#].*)?$/i.test(data))
|
|
500
783
|
return "image/*";
|
|
784
|
+
if (/\.(aac|flac|m4a|mp3|oga|ogg|opus|wav)(?:[?#].*)?$/i.test(data))
|
|
785
|
+
return "audio/*";
|
|
786
|
+
if (/\.(m4v|mov|mp4|ogv|webm)(?:[?#].*)?$/i.test(data))
|
|
787
|
+
return "video/*";
|
|
501
788
|
return undefined;
|
|
502
789
|
}
|
|
503
790
|
function parseChoices(node) {
|