@longsightgroup/qti3-core 0.3.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,19 +19,96 @@ import { createItemSession, parseQtiXml, validateAssessmentItem } from "@longsig
19
19
 
20
20
  const parsed = parseQtiXml(xml);
21
21
 
22
- if (parsed.document) {
23
- const validation = validateAssessmentItem(parsed.document);
24
- const session = createItemSession(parsed.document);
22
+ if (!parsed.ok || !parsed.document) {
23
+ throw new Error(parsed.diagnostics.map((item) => item.message).join("; "));
24
+ }
25
+
26
+ const validation = validateAssessmentItem(parsed.document);
27
+ const session = createItemSession(parsed.document);
28
+
29
+ session.respond("RESPONSE", "A");
30
+ const result = session.score();
31
+
32
+ console.log(validation.diagnostics);
33
+ console.log(result.outcomes);
34
+ console.log(result.state);
35
+ ```
36
+
37
+ ### Candidate-safe delivery XML
38
+
39
+ High-stakes delivery systems can redact answer-bearing item XML before sending it to
40
+ a browser:
41
+
42
+ ```ts
43
+ import { buildQtiDeliverySafeXml } from "@longsightgroup/qti3-core";
44
+
45
+ const delivery = buildQtiDeliverySafeXml(authoritativeItemXml);
46
+
47
+ if (!delivery.ok) {
48
+ throw new Error(delivery.diagnostics.map((item) => item.message).join("; "));
49
+ }
50
+
51
+ sendToCandidate(delivery.xml);
52
+ ```
53
+
54
+ Use `buildQtiDeliverySafeXml().ok` for deliverability. The
55
+ `analyzeQtiDeliverySecurity().deliverySafe` flag describes the exact XML being analyzed,
56
+ so it is normally `false` for an authoritative scorable item before redaction and `true`
57
+ only for the redacted output.
58
+
59
+ The redactor removes correct responses, response and area mappings, outcome lookup
60
+ tables, response/outcome/template declaration default values, response processing, and
61
+ authored feedback subtrees. It also reports secure-delivery v1 blockers such as
62
+ template processing, set-correct-response, and adaptive response processing.
63
+
64
+ String-range redaction aligns a private XML tag scan to the same stax parse tree used by
65
+ `parseQtiXml`. Alignment failures are reported as `xml.parse` error diagnostics; hosts
66
+ must treat those diagnostics, `parseQtiXml().ok === false`, and
67
+ `buildQtiDeliverySafeXml().ok === false` as non-deliverable. The redacted output is
68
+ re-analyzed before `ok` is returned, but hosts should still treat redacted XML as
69
+ untrusted presentation input.
70
+
71
+ The scanner adds a second full pass over each XML string. That is acceptable for
72
+ item-scale delivery and scoring, but package-level batch redaction should treat XML
73
+ parsing as a hot path if whole packages are processed repeatedly.
74
+
75
+ Candidate-safe XML is not a full content audit. It does not remove solution text an
76
+ author wrote directly into the item body, and it does not validate Portable Custom
77
+ Interaction module/config URLs or host runtime policy. If candidates should see item
78
+ point values, expose them intentionally through host metadata or visible item content;
79
+ do not rely on hidden QTI declaration defaults as the presentation channel.
80
+
81
+ ### Server-side scoring
25
82
 
26
- session.respond("RESPONSE", "A");
27
- const result = session.score();
83
+ Use full authoritative item XML on the server and pass only trusted response variables:
28
84
 
29
- console.log(validation.diagnostics);
30
- console.log(result.outcomes);
31
- console.log(result.state);
85
+ ```ts
86
+ import { scoreQtiItemServerSide } from "@longsightgroup/qti3-core";
87
+
88
+ const scored = scoreQtiItemServerSide({
89
+ itemXml: authoritativeItemXml,
90
+ trustedResponses: { RESPONSE: "A" },
91
+ });
92
+
93
+ if (!scored.ok) {
94
+ throw new Error(scored.diagnostics.map((item) => item.message).join("; "));
32
95
  }
96
+
97
+ console.log(scored.score);
98
+ console.log(scored.state);
33
99
  ```
34
100
 
101
+ This API does not accept restored outcomes or a full prior attempt state, so browser
102
+ submitted `SCORE`, `MAXSCORE`, or similar outcome variables cannot become trusted
103
+ server results. It validates trusted response identifiers and JSON-shaped QTI values,
104
+ then runs response processing. It does not run candidate response-validation policy such
105
+ as required interactions, cardinality limits, or min/max response counts; delivery hosts
106
+ should enforce that policy before accepting a submission or finalizing an attempt.
107
+
108
+ The delivery redaction and server-scoring APIs are library APIs in `0.5.x`. CLI commands
109
+ for delivery-safe XML generation and server-style scoring are not part of this release
110
+ line yet.
111
+
35
112
  ## Scope
36
113
 
37
114
  - Parse QTI XML into a typed item model.
@@ -0,0 +1,26 @@
1
+ import type { QtiDiagnostic, QtiSourceLocation } from "./types.js";
2
+ export type QtiDeliverySecurityFindingKind = "forbidden-delivery-element" | "unsupported-secure-delivery-element" | "unsupported-adaptive-response-processing";
3
+ export interface QtiDeliverySecurityFinding {
4
+ kind: QtiDeliverySecurityFindingKind;
5
+ qtiName: string;
6
+ localName: string;
7
+ message: string;
8
+ source?: QtiSourceLocation | undefined;
9
+ }
10
+ export interface QtiDeliverySecurityAnalysis {
11
+ diagnostics: QtiDiagnostic[];
12
+ findings: QtiDeliverySecurityFinding[];
13
+ /** True when this exact XML contains no known answer/scoring/feedback delivery leaks. */
14
+ deliverySafe: boolean;
15
+ /** True when secure-delivery redaction can be attempted for this XML. */
16
+ secureDeliverySupported: boolean;
17
+ }
18
+ export interface QtiDeliverySafeXmlResult {
19
+ ok: boolean;
20
+ diagnostics: QtiDiagnostic[];
21
+ analysis: QtiDeliverySecurityAnalysis;
22
+ xml?: string | undefined;
23
+ }
24
+ export declare function analyzeQtiDeliverySecurity(xml: string): QtiDeliverySecurityAnalysis;
25
+ export declare function buildQtiDeliverySafeXml(xml: string): QtiDeliverySafeXmlResult;
26
+ //# sourceMappingURL=delivery-security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-security.d.ts","sourceRoot":"","sources":["../src/delivery-security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA0BnE,MAAM,MAAM,8BAA8B,GACtC,4BAA4B,GAC5B,qCAAqC,GACrC,0CAA0C,CAAC;AAE/C,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,8BAA8B,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,QAAQ,EAAE,0BAA0B,EAAE,CAAC;IACvC,yFAAyF;IACzF,YAAY,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,QAAQ,EAAE,2BAA2B,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,2BAA2B,CAGnF;AAkED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,wBAAwB,CAuC7E"}
@@ -0,0 +1,213 @@
1
+ import { descendants, parseXmlTree } from "./xml.js";
2
+ const FORBIDDEN_DELIVERY_ELEMENT_NAMES = new Set([
3
+ "correct-response",
4
+ "mapping",
5
+ "area-mapping",
6
+ "match-table",
7
+ "interpolation-table",
8
+ "response-processing",
9
+ "feedback-inline",
10
+ "feedback-block",
11
+ "modal-feedback",
12
+ ]);
13
+ const UNSUPPORTED_SECURE_DELIVERY_ELEMENT_NAMES = new Set([
14
+ "template-processing",
15
+ "set-correct-response",
16
+ ]);
17
+ const DEFAULT_VALUE_DECLARATION_ELEMENT_NAMES = new Set([
18
+ "response-declaration",
19
+ "outcome-declaration",
20
+ "template-declaration",
21
+ ]);
22
+ export function analyzeQtiDeliverySecurity(xml) {
23
+ const parsed = parseDeliveryXml(xml);
24
+ return analyzeParsedDeliveryXml(parsed);
25
+ }
26
+ function analyzeParsedDeliveryXml(parsed) {
27
+ const diagnostics = [...parsed.diagnostics];
28
+ const findings = [];
29
+ if (parsed.root) {
30
+ const nodes = [parsed.root, ...descendants(parsed.root, () => true)];
31
+ for (const node of nodes) {
32
+ const normalizedName = normalizedQtiElementName(node.localName);
33
+ if (isForbiddenDeliveryElement(node, normalizedName)) {
34
+ findings.push({
35
+ kind: "forbidden-delivery-element",
36
+ qtiName: node.name,
37
+ localName: node.localName,
38
+ message: `${node.name} exposes answer keys, scoring, mapping, feedback, or solution information during delivery.`,
39
+ source: node.source,
40
+ });
41
+ }
42
+ if (UNSUPPORTED_SECURE_DELIVERY_ELEMENT_NAMES.has(normalizedName)) {
43
+ findings.push({
44
+ kind: "unsupported-secure-delivery-element",
45
+ qtiName: node.name,
46
+ localName: node.localName,
47
+ message: `${node.name} is not supported by secure delivery redaction v1.`,
48
+ source: node.source,
49
+ });
50
+ }
51
+ }
52
+ if (parseXmlBoolean(parsed.root.attributes.adaptive) === true &&
53
+ nodes.some((node) => normalizedQtiElementName(node.localName) === "response-processing")) {
54
+ findings.push({
55
+ kind: "unsupported-adaptive-response-processing",
56
+ qtiName: parsed.root.name,
57
+ localName: parsed.root.localName,
58
+ message: "Adaptive response processing requires server-side item materialization before secure delivery.",
59
+ source: parsed.root.source,
60
+ });
61
+ }
62
+ }
63
+ diagnostics.push(...findings.map(findingToDiagnostic));
64
+ const parseOk = parsed.diagnostics.every((diagnostic) => diagnostic.severity !== "error");
65
+ return {
66
+ diagnostics,
67
+ findings,
68
+ deliverySafe: parseOk && !findings.some((finding) => finding.kind === "forbidden-delivery-element"),
69
+ secureDeliverySupported: parseOk &&
70
+ !findings.some((finding) => finding.kind === "unsupported-secure-delivery-element" ||
71
+ finding.kind === "unsupported-adaptive-response-processing"),
72
+ };
73
+ }
74
+ export function buildQtiDeliverySafeXml(xml) {
75
+ const parsed = parseDeliveryXml(xml);
76
+ const analysis = analyzeParsedDeliveryXml(parsed);
77
+ if (!analysis.secureDeliverySupported) {
78
+ return {
79
+ ok: false,
80
+ diagnostics: analysis.diagnostics,
81
+ analysis,
82
+ };
83
+ }
84
+ if (!parsed.root) {
85
+ return {
86
+ ok: false,
87
+ diagnostics: parsed.diagnostics,
88
+ analysis,
89
+ };
90
+ }
91
+ const redactionRanges = readRedactionRanges([
92
+ parsed.root,
93
+ ...descendants(parsed.root, () => true),
94
+ ]);
95
+ const redactedXml = removeSourceRanges(xml, redactionRanges);
96
+ const redactedAnalysis = analyzeQtiDeliverySecurity(redactedXml);
97
+ if (!redactedAnalysis.deliverySafe) {
98
+ return {
99
+ ok: false,
100
+ diagnostics: [...analysis.diagnostics, ...redactedAnalysis.diagnostics],
101
+ analysis: redactedAnalysis,
102
+ };
103
+ }
104
+ return {
105
+ ok: true,
106
+ xml: redactedXml,
107
+ diagnostics: redactedAnalysis.diagnostics,
108
+ analysis: redactedAnalysis,
109
+ };
110
+ }
111
+ function parseDeliveryXml(xml) {
112
+ let parsed;
113
+ try {
114
+ parsed = parseXmlTree(xml);
115
+ }
116
+ catch (error) {
117
+ return {
118
+ root: undefined,
119
+ diagnostics: [
120
+ {
121
+ code: "xml.parse",
122
+ severity: "error",
123
+ message: error instanceof Error ? error.message : String(error),
124
+ },
125
+ ],
126
+ };
127
+ }
128
+ const diagnostics = parsed.errors.map((error) => ({
129
+ code: "xml.parse",
130
+ severity: "error",
131
+ message: error.message,
132
+ }));
133
+ if (!parsed.root) {
134
+ diagnostics.push({
135
+ code: "xml.empty",
136
+ severity: "error",
137
+ message: "No XML root element was found.",
138
+ });
139
+ }
140
+ return { root: parsed.root, diagnostics };
141
+ }
142
+ function normalizedQtiElementName(localName) {
143
+ const lower = localName.toLowerCase();
144
+ return lower.startsWith("qti-") ? lower.slice("qti-".length) : lower;
145
+ }
146
+ function parseXmlBoolean(value) {
147
+ if (value === undefined)
148
+ return undefined;
149
+ const normalized = value.trim().toLowerCase();
150
+ if (normalized === "true" || normalized === "1")
151
+ return true;
152
+ if (normalized === "false" || normalized === "0")
153
+ return false;
154
+ return undefined;
155
+ }
156
+ function isForbiddenDeliveryElement(node, normalizedName = normalizedQtiElementName(node.localName)) {
157
+ if (FORBIDDEN_DELIVERY_ELEMENT_NAMES.has(normalizedName))
158
+ return true;
159
+ return (normalizedName === "default-value" &&
160
+ DEFAULT_VALUE_DECLARATION_ELEMENT_NAMES.has(normalizedQtiElementName(node.parent?.localName ?? "")));
161
+ }
162
+ function findingToDiagnostic(finding) {
163
+ const code = diagnosticCodeForFinding(finding.kind);
164
+ return {
165
+ code,
166
+ severity: "error",
167
+ message: finding.message,
168
+ path: finding.source?.path,
169
+ source: finding.source,
170
+ };
171
+ }
172
+ function diagnosticCodeForFinding(kind) {
173
+ if (kind === "forbidden-delivery-element")
174
+ return "delivery.forbiddenElement";
175
+ if (kind === "unsupported-adaptive-response-processing") {
176
+ return "delivery.unsupportedAdaptiveResponseProcessing";
177
+ }
178
+ return "delivery.unsupportedSecureDelivery";
179
+ }
180
+ function readRedactionRanges(nodes) {
181
+ const ranges = nodes.flatMap((node) => {
182
+ if (!isForbiddenDeliveryElement(node))
183
+ return [];
184
+ const endOffset = node.sourceRange.endOffset;
185
+ if (node.sourceRange.startOffset < 0 || endOffset === undefined)
186
+ return [];
187
+ return [{ startOffset: node.sourceRange.startOffset, endOffset }];
188
+ });
189
+ return mergeSourceRanges(ranges);
190
+ }
191
+ function mergeSourceRanges(ranges) {
192
+ const sorted = [...ranges].sort((left, right) => left.startOffset - right.startOffset);
193
+ const merged = [];
194
+ for (const range of sorted) {
195
+ const last = merged.at(-1);
196
+ if (last && range.startOffset <= last.endOffset) {
197
+ last.endOffset = Math.max(last.endOffset, range.endOffset);
198
+ continue;
199
+ }
200
+ merged.push({ ...range });
201
+ }
202
+ return merged;
203
+ }
204
+ function removeSourceRanges(xml, ranges) {
205
+ let output = "";
206
+ let cursor = 0;
207
+ for (const range of ranges) {
208
+ output += xml.slice(cursor, range.startOffset);
209
+ cursor = range.endOffset;
210
+ }
211
+ return output + xml.slice(cursor);
212
+ }
213
+ //# sourceMappingURL=delivery-security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-security.js","sourceRoot":"","sources":["../src/delivery-security.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export { createCatalogSupportResolution, type QtiCatalogSupportResolution, type QtiCatalogSupportResolutionOptions, type QtiResolvedCatalogReference, type QtiResolvedCatalogSupport, } from "./catalog.js";
2
+ export { analyzeQtiDeliverySecurity, buildQtiDeliverySafeXml, type QtiDeliverySafeXmlResult, type QtiDeliverySecurityAnalysis, type QtiDeliverySecurityFinding, type QtiDeliverySecurityFindingKind, } from "./delivery-security.js";
2
3
  export { parseQtiXml } from "./parser.js";
4
+ export { scoreQtiItemServerSide, type QtiServerScoringInput, type QtiServerScoringResponseInput, type QtiServerScoringResult, } from "./server-scoring.js";
3
5
  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";
4
6
  export { assertQtiAttemptStateV1, createItemSession, isQtiAttemptStateV1, visibleModalFeedback, type QtiCustomOperatorContext, type QtiCustomOperatorHandler, type QtiCustomOperatorRegistry, type QtiItemSession, type QtiItemSessionOptions, } from "./session.js";
5
7
  export { deprecatedInteractionSupport, elementSupport, getInteractionSupport, interactionNameToType, interactionSupport, processingSupport, } from "./support.js";
6
8
  export { validateAssessmentItem } from "./validation.js";
7
9
  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";
8
- export { qtiScalarToString, qtiValueToIdentifierList, qtiValueToString, qtiValueToStringList, unknownToDisplayString, } from "./value-format.js";
10
+ export { isQtiPortableCustomStateValue, isQtiValue, qtiScalarToString, qtiValueToIdentifierList, qtiValueToString, qtiValueToStringList, readQtiPortableCustomStateValue, readQtiJsonValue, unknownToDisplayString, } from "./value-format.js";
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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;AACpB,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,mBAAmB,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,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,KAAK,8BAA8B,GACpC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,sBAAsB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,KAAK,sBAAsB,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,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;AACpB,OAAO,EACL,6BAA6B,EAC7B,UAAU,EACV,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,+BAA+B,EAC/B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  export { createCatalogSupportResolution, } from "./catalog.js";
2
+ export { analyzeQtiDeliverySecurity, buildQtiDeliverySafeXml, } from "./delivery-security.js";
2
3
  export { parseQtiXml } from "./parser.js";
4
+ export { scoreQtiItemServerSide, } from "./server-scoring.js";
3
5
  export { createTextToSpeechTraversal, parseQtiDataSsml, validateQtiDataSsmlMetadata, } from "./tts.js";
4
6
  export { assertQtiAttemptStateV1, createItemSession, isQtiAttemptStateV1, visibleModalFeedback, } from "./session.js";
5
7
  export { deprecatedInteractionSupport, elementSupport, getInteractionSupport, interactionNameToType, interactionSupport, processingSupport, } from "./support.js";
6
8
  export { validateAssessmentItem } from "./validation.js";
7
- export { qtiScalarToString, qtiValueToIdentifierList, qtiValueToString, qtiValueToStringList, unknownToDisplayString, } from "./value-format.js";
9
+ export { isQtiPortableCustomStateValue, isQtiValue, qtiScalarToString, qtiValueToIdentifierList, qtiValueToString, qtiValueToStringList, readQtiPortableCustomStateValue, readQtiJsonValue, unknownToDisplayString, } from "./value-format.js";
8
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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;AA0CzD,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,mBAAmB,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,EACL,0BAA0B,EAC1B,uBAAuB,GAKxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,sBAAsB,GAIvB,MAAM,qBAAqB,CAAC;AAC7B,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;AA0CzD,OAAO,EACL,6BAA6B,EAC7B,UAAU,EACV,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,+BAA+B,EAC/B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAuBV,cAAc,EAkBf,MAAM,YAAY,CAAC;AAkBpB,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAgDvD"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAuBV,cAAc,EAkBf,MAAM,YAAY,CAAC;AAkBpB,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAoDvD"}
package/dist/parser.js CHANGED
@@ -24,6 +24,9 @@ export function parseQtiXml(xml) {
24
24
  message: error.message,
25
25
  });
26
26
  }
27
+ if (tree.errors.length > 0) {
28
+ return { ok: false, diagnostics };
29
+ }
27
30
  if (!tree.root) {
28
31
  diagnostics.push({
29
32
  code: "xml.empty",