@longsightgroup/qti3-core 0.1.2 → 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 +225 -3
- 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 +44 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +200 -3
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
- package/src/catalog.ts +193 -0
- package/src/index.ts +35 -0
- package/src/parser.ts +266 -3
- package/src/session.ts +90 -0
- package/src/support.ts +9 -1
- package/src/tts.ts +555 -0
- package/src/types.ts +52 -0
- package/src/validation.ts +229 -8
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, QtiMediaSource, QtiMediaTrack, 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,cAAc,EACd,aAAa,EACb,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,EAuBV,cAAc,
|
|
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,
|
|
@@ -363,11 +364,16 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
|
|
|
363
364
|
responseCardinality: responseDeclaration?.cardinality,
|
|
364
365
|
responseBaseType: responseDeclaration?.baseType,
|
|
365
366
|
prompt: prompt ? textContent(prompt) : undefined,
|
|
367
|
+
promptAttributes: prompt?.attributes,
|
|
368
|
+
promptSource: prompt?.source,
|
|
366
369
|
contextText: inlineInteractionContext(node, interactionType),
|
|
367
370
|
object: parseObjectAsset(objectNode),
|
|
368
371
|
positionObjectStage: interactionType === "positionObject"
|
|
369
372
|
? parseObjectAsset(positionObjectStageObject(node))
|
|
370
373
|
: undefined,
|
|
374
|
+
portableCustom: interactionType === "portableCustom"
|
|
375
|
+
? parsePortableCustomDefinition(node, diagnostics)
|
|
376
|
+
: undefined,
|
|
371
377
|
choices: parseChoices(node),
|
|
372
378
|
hottextSegments: interactionType === "hottext" ? parseHottextSegments(node) : undefined,
|
|
373
379
|
gapMatchSegments: interactionType === "gapMatch" || interactionType === "graphicGapMatch"
|
|
@@ -382,6 +388,135 @@ function parseInteraction(node, diagnostics, responseDeclarationMap) {
|
|
|
382
388
|
source: node.source,
|
|
383
389
|
};
|
|
384
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
|
+
}
|
|
385
520
|
function positionObjectInteractionObject(node) {
|
|
386
521
|
return childElements(node).find((child) => child.localName === "object" || child.localName === "img");
|
|
387
522
|
}
|
|
@@ -495,21 +630,108 @@ function parseObjectAsset(node) {
|
|
|
495
630
|
const data = node.attributes.data ?? node.attributes.src ?? pictureImage?.attributes.src;
|
|
496
631
|
const sources = parseMediaSources(node);
|
|
497
632
|
const tracks = parseMediaTracks(node);
|
|
633
|
+
const inferredSvgDimensions = inlineSvgDimensions(data);
|
|
498
634
|
return {
|
|
499
635
|
data,
|
|
500
636
|
type: node.attributes.type ??
|
|
501
637
|
pictureImage?.attributes.type ??
|
|
502
638
|
assetTypeFromData(data) ??
|
|
503
639
|
firstSourceType(sources),
|
|
504
|
-
width: node.attributes.width ?? pictureImage?.attributes.width,
|
|
505
|
-
height: node.attributes.height ?? pictureImage?.attributes.height,
|
|
640
|
+
width: node.attributes.width ?? pictureImage?.attributes.width ?? inferredSvgDimensions?.width,
|
|
641
|
+
height: node.attributes.height ?? pictureImage?.attributes.height ?? inferredSvgDimensions?.height,
|
|
506
642
|
sources,
|
|
507
643
|
tracks,
|
|
508
|
-
text: textContent(node) || pictureImage?.attributes.alt || "",
|
|
644
|
+
text: textContent(node) || node.attributes.alt || pictureImage?.attributes.alt || "",
|
|
509
645
|
attributes: node.attributes,
|
|
510
646
|
source: node.source,
|
|
511
647
|
};
|
|
512
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
|
+
}
|
|
513
735
|
function parseMediaSources(node) {
|
|
514
736
|
return childElements(node, "source").map((source) => {
|
|
515
737
|
const src = source.attributes.src ?? firstSrcsetCandidate(source.attributes.srcset);
|