scorm-again 3.0.3 → 3.0.5
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 +19 -4
- package/dist/esm/scorm-again.js +133 -68
- package/dist/esm/scorm-again.js.map +1 -1
- package/dist/esm/scorm-again.min.js +1 -1
- package/dist/esm/scorm-again.min.js.map +1 -1
- package/dist/esm/scorm12.js +20 -5
- package/dist/esm/scorm12.js.map +1 -1
- package/dist/esm/scorm12.min.js +1 -1
- package/dist/esm/scorm12.min.js.map +1 -1
- package/dist/esm/scorm2004.js +133 -68
- package/dist/esm/scorm2004.js.map +1 -1
- package/dist/esm/scorm2004.min.js +1 -1
- package/dist/esm/scorm2004.min.js.map +1 -1
- package/dist/scorm-again.js +261 -166
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +1 -1
- package/dist/scorm-again.min.js.map +1 -1
- package/dist/scorm12.js +14 -6
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +1 -1
- package/dist/scorm12.min.js.map +1 -1
- package/dist/scorm2004.js +261 -166
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +1 -1
- package/dist/scorm2004.min.js.map +1 -1
- package/dist/types/cmi/scorm2004/interaction_delimiters.d.ts +4 -0
- package/package.json +46 -25
package/README.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# scorm-again
|
|
2
|
+
|
|
3
|
+
A modern, fully-tested JavaScript runtime for SCORM 1.2 and SCORM 2004.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
<details>
|
|
7
|
+
<summary><b>Audits & consulting →</b></summary>
|
|
8
|
+
|
|
9
|
+
I do one-week, fixed-price code audits for B2B SaaS companies on AWS:
|
|
10
|
+
|
|
11
|
+
- Multi-Tenant SaaS on AWS: architecture, cost, and SOC 2 alignment
|
|
12
|
+
- AI-on-AWS for Regulated SaaS: Bedrock, RAG, and guardrails under SOC 2, HIPAA, or 21 CFR Part 11
|
|
13
|
+
|
|
14
|
+
Fixed price, written report, walkthrough call. Details at [audits.putney.io](https://audits.putney.io).
|
|
15
|
+
|
|
16
|
+
</details>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
1
20
|
[](https://github.com/jcputney/scorm-again/actions)
|
|
2
21
|
[](https://codecov.io/gh/jcputney/scorm-again)
|
|
3
22
|

|
|
@@ -7,10 +26,6 @@
|
|
|
7
26
|

|
|
8
27
|
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NF5MPZJAV26LE)
|
|
9
28
|
|
|
10
|
-
# scorm-again
|
|
11
|
-
|
|
12
|
-
A modern, fully-tested JavaScript runtime for SCORM 1.2 and SCORM 2004.
|
|
13
|
-
|
|
14
29
|
> **Upgrading from v2.x?** See the [Migration Guide](MIGRATION.md) for breaking changes and migration steps.
|
|
15
30
|
|
|
16
31
|
## Overview
|
package/dist/esm/scorm-again.js
CHANGED
|
@@ -1160,6 +1160,11 @@ const scorm2004_regex = {
|
|
|
1160
1160
|
progress_range: "0#1"
|
|
1161
1161
|
};
|
|
1162
1162
|
|
|
1163
|
+
const PERFORMANCE_STEP_NAME = "^$|" + scorm2004_regex.CMIShortIdentifier;
|
|
1164
|
+
const PERFORMANCE_CHARACTERSTRING = "(?![\\s\\S]*(?:\\[,\\]|\\[\\.\\]|\\[:\\]))[\\s\\S]{1,250}";
|
|
1165
|
+
const PERFORMANCE_NUMERIC_RANGE = "(?:-?\\d+(?:\\.\\d+)?)?\\[:\\](?:-?\\d+(?:\\.\\d+)?)?";
|
|
1166
|
+
const CR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_NUMERIC_RANGE + "|" + PERFORMANCE_CHARACTERSTRING + ")$";
|
|
1167
|
+
const LR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_CHARACTERSTRING + ")$";
|
|
1163
1168
|
const LearnerResponses = {
|
|
1164
1169
|
"true-false": {
|
|
1165
1170
|
format: "^true$|^false$",
|
|
@@ -1194,8 +1199,8 @@ const LearnerResponses = {
|
|
|
1194
1199
|
unique: false
|
|
1195
1200
|
},
|
|
1196
1201
|
performance: {
|
|
1197
|
-
format:
|
|
1198
|
-
format2:
|
|
1202
|
+
format: PERFORMANCE_STEP_NAME,
|
|
1203
|
+
format2: LR_PERFORMANCE_STEP_ANSWER,
|
|
1199
1204
|
max: 250,
|
|
1200
1205
|
delimiter: "[,]",
|
|
1201
1206
|
delimiter2: "[.]",
|
|
@@ -1271,10 +1276,10 @@ const CorrectResponses = {
|
|
|
1271
1276
|
delimiter2: "[.]",
|
|
1272
1277
|
unique: false,
|
|
1273
1278
|
duplicate: false,
|
|
1274
|
-
// step_name
|
|
1275
|
-
format:
|
|
1276
|
-
// step_answer
|
|
1277
|
-
format2:
|
|
1279
|
+
// step_name: optional short_identifier_type
|
|
1280
|
+
format: PERFORMANCE_STEP_NAME,
|
|
1281
|
+
// step_answer: optional characterstring (spaces allowed) or numeric range
|
|
1282
|
+
format2: CR_PERFORMANCE_STEP_ANSWER
|
|
1278
1283
|
},
|
|
1279
1284
|
sequencing: {
|
|
1280
1285
|
max: 36,
|
|
@@ -2318,6 +2323,7 @@ class ActivityTreeQueries {
|
|
|
2318
2323
|
constructor(activityTree) {
|
|
2319
2324
|
this.activityTree = activityTree;
|
|
2320
2325
|
}
|
|
2326
|
+
activityTree;
|
|
2321
2327
|
/**
|
|
2322
2328
|
* Check if activity is in the activity tree
|
|
2323
2329
|
* @param {Activity} activity - Activity to check
|
|
@@ -2517,6 +2523,8 @@ class ChoiceConstraintValidator {
|
|
|
2517
2523
|
this.activityTree = activityTree;
|
|
2518
2524
|
this.treeQueries = treeQueries;
|
|
2519
2525
|
}
|
|
2526
|
+
activityTree;
|
|
2527
|
+
treeQueries;
|
|
2520
2528
|
/**
|
|
2521
2529
|
* Main entry point - consolidates ALL constraint validation for choice navigation
|
|
2522
2530
|
* @param {Activity | null} currentActivity - Current activity (may be null if no session started)
|
|
@@ -3869,6 +3877,8 @@ class FlowTraversalService {
|
|
|
3869
3877
|
this.activityTree = activityTree;
|
|
3870
3878
|
this.ruleEngine = ruleEngine;
|
|
3871
3879
|
}
|
|
3880
|
+
activityTree;
|
|
3881
|
+
ruleEngine;
|
|
3872
3882
|
/**
|
|
3873
3883
|
* Flow Subprocess (SB.2.3)
|
|
3874
3884
|
* Traverses the activity tree in the specified direction to find a deliverable activity
|
|
@@ -4167,6 +4177,8 @@ class FlowRequestHandler {
|
|
|
4167
4177
|
this.activityTree = activityTree;
|
|
4168
4178
|
this.traversalService = traversalService;
|
|
4169
4179
|
}
|
|
4180
|
+
activityTree;
|
|
4181
|
+
traversalService;
|
|
4170
4182
|
/**
|
|
4171
4183
|
* Start Sequencing Request Process (SB.2.5)
|
|
4172
4184
|
* Initiates a new sequencing session from the root
|
|
@@ -4298,6 +4310,10 @@ class ChoiceRequestHandler {
|
|
|
4298
4310
|
this.traversalService = traversalService;
|
|
4299
4311
|
this.treeQueries = treeQueries;
|
|
4300
4312
|
}
|
|
4313
|
+
activityTree;
|
|
4314
|
+
constraintValidator;
|
|
4315
|
+
traversalService;
|
|
4316
|
+
treeQueries;
|
|
4301
4317
|
/**
|
|
4302
4318
|
* Choice Sequencing Request Process (SB.2.9)
|
|
4303
4319
|
* Processes a choice navigation request to a specific activity
|
|
@@ -4511,6 +4527,8 @@ class ExitRequestHandler {
|
|
|
4511
4527
|
this.activityTree = activityTree;
|
|
4512
4528
|
this.ruleEngine = ruleEngine;
|
|
4513
4529
|
}
|
|
4530
|
+
activityTree;
|
|
4531
|
+
ruleEngine;
|
|
4514
4532
|
/**
|
|
4515
4533
|
* Exit Sequencing Request Process (SB.2.11)
|
|
4516
4534
|
* @param {Activity} currentActivity - The current activity
|
|
@@ -4626,6 +4644,8 @@ class RetryRequestHandler {
|
|
|
4626
4644
|
this.activityTree = activityTree;
|
|
4627
4645
|
this.traversalService = traversalService;
|
|
4628
4646
|
}
|
|
4647
|
+
activityTree;
|
|
4648
|
+
traversalService;
|
|
4629
4649
|
/**
|
|
4630
4650
|
* Retry Sequencing Request Process (SB.2.10)
|
|
4631
4651
|
* @param {Activity} currentActivity - The current activity
|
|
@@ -5568,7 +5588,19 @@ class CMIValueAccessService {
|
|
|
5568
5588
|
if (!scorm2004) {
|
|
5569
5589
|
if (isFinalAttribute) {
|
|
5570
5590
|
if (typeof attribute === "undefined" || !this.context.checkObjectHasProperty(refObject, attribute)) {
|
|
5571
|
-
|
|
5591
|
+
if (attribute === "_children") {
|
|
5592
|
+
this.context.throwSCORMError(
|
|
5593
|
+
CMIElement,
|
|
5594
|
+
getErrorCode(this.context.errorCodes, "CHILDREN_ERROR")
|
|
5595
|
+
);
|
|
5596
|
+
} else if (attribute === "_count") {
|
|
5597
|
+
this.context.throwSCORMError(
|
|
5598
|
+
CMIElement,
|
|
5599
|
+
getErrorCode(this.context.errorCodes, "COUNT_ERROR")
|
|
5600
|
+
);
|
|
5601
|
+
} else {
|
|
5602
|
+
this.context.throwSCORMError(CMIElement, invalidErrorCode, invalidErrorMessage);
|
|
5603
|
+
}
|
|
5572
5604
|
return { error: true };
|
|
5573
5605
|
}
|
|
5574
5606
|
}
|
|
@@ -5760,7 +5792,8 @@ class LoggingService {
|
|
|
5760
5792
|
getNumericLevel(level) {
|
|
5761
5793
|
if (level === void 0) return LogLevelEnum.NONE;
|
|
5762
5794
|
if (typeof level === "number") return level;
|
|
5763
|
-
|
|
5795
|
+
const normalized = typeof level === "string" ? level.toUpperCase() : level;
|
|
5796
|
+
switch (normalized) {
|
|
5764
5797
|
case "1":
|
|
5765
5798
|
case "DEBUG":
|
|
5766
5799
|
return LogLevelEnum.DEBUG;
|
|
@@ -6133,6 +6166,7 @@ class OfflineStorageService {
|
|
|
6133
6166
|
window.addEventListener("offline", this.boundOnlineStatusChangeHandler);
|
|
6134
6167
|
window.addEventListener("scorm-again:network-status", this.boundCustomNetworkStatusHandler);
|
|
6135
6168
|
}
|
|
6169
|
+
apiLog;
|
|
6136
6170
|
settings;
|
|
6137
6171
|
error_codes;
|
|
6138
6172
|
storeName = "scorm_again_offline_data";
|
|
@@ -15970,7 +16004,8 @@ class BaseAPI {
|
|
|
15970
16004
|
if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
|
|
15971
16005
|
} else {
|
|
15972
16006
|
const result = this.storeData(false);
|
|
15973
|
-
|
|
16007
|
+
const errorCode = result.errorCode ?? 0;
|
|
16008
|
+
if (errorCode > 0) {
|
|
15974
16009
|
if (result.errorMessage) {
|
|
15975
16010
|
this.apiLog(
|
|
15976
16011
|
"commit",
|
|
@@ -15985,12 +16020,12 @@ class BaseAPI {
|
|
|
15985
16020
|
LogLevelEnum.DEBUG
|
|
15986
16021
|
);
|
|
15987
16022
|
}
|
|
15988
|
-
this.throwSCORMError("api",
|
|
16023
|
+
this.throwSCORMError("api", errorCode);
|
|
15989
16024
|
}
|
|
15990
16025
|
const resultValue = result?.result ?? global_constants.SCORM_FALSE;
|
|
15991
16026
|
returnValue = typeof resultValue === "boolean" ? String(resultValue) : resultValue;
|
|
15992
16027
|
this.apiLog(callbackName, " Result: " + returnValue, LogLevelEnum.DEBUG, "HttpRequest");
|
|
15993
|
-
if (checkTerminated) this.lastErrorCode = "0";
|
|
16028
|
+
if (checkTerminated && errorCode === 0) this.lastErrorCode = "0";
|
|
15994
16029
|
this.processListeners(callbackName);
|
|
15995
16030
|
if (this.settings.enableOfflineSupport && this._offlineStorageService && this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
15996
16031
|
this._offlineStorageService.hasPendingOfflineData(this._courseId).then((hasPendingData) => {
|
|
@@ -18913,6 +18948,49 @@ class CMILearnerPreference extends BaseCMI {
|
|
|
18913
18948
|
}
|
|
18914
18949
|
}
|
|
18915
18950
|
|
|
18951
|
+
function stripBrackets(delim) {
|
|
18952
|
+
return delim.replace(/[[\]]/g, "");
|
|
18953
|
+
}
|
|
18954
|
+
function escapeRegex(s) {
|
|
18955
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18956
|
+
}
|
|
18957
|
+
function splitDelimited(value, bracketed) {
|
|
18958
|
+
if (!bracketed) {
|
|
18959
|
+
return [value];
|
|
18960
|
+
}
|
|
18961
|
+
if (value.includes(bracketed)) {
|
|
18962
|
+
return value.split(bracketed);
|
|
18963
|
+
}
|
|
18964
|
+
const bare = stripBrackets(bracketed);
|
|
18965
|
+
if (!bare) {
|
|
18966
|
+
return [value];
|
|
18967
|
+
}
|
|
18968
|
+
const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`, "g");
|
|
18969
|
+
const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
|
|
18970
|
+
return value.split(splitRe).map((part) => part.replace(unescapeRe, bare));
|
|
18971
|
+
}
|
|
18972
|
+
function splitFirstDelimited(value, bracketed) {
|
|
18973
|
+
if (!bracketed) {
|
|
18974
|
+
return [value];
|
|
18975
|
+
}
|
|
18976
|
+
if (value.includes(bracketed)) {
|
|
18977
|
+
const idx = value.indexOf(bracketed);
|
|
18978
|
+
return [value.slice(0, idx), value.slice(idx + bracketed.length)];
|
|
18979
|
+
}
|
|
18980
|
+
const bare = stripBrackets(bracketed);
|
|
18981
|
+
if (!bare) {
|
|
18982
|
+
return [value];
|
|
18983
|
+
}
|
|
18984
|
+
const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`);
|
|
18985
|
+
const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
|
|
18986
|
+
const parts = value.split(splitRe);
|
|
18987
|
+
const first = (parts[0] ?? "").replace(unescapeRe, bare);
|
|
18988
|
+
if (parts.length === 1) {
|
|
18989
|
+
return [first];
|
|
18990
|
+
}
|
|
18991
|
+
return [first, parts.slice(1).join(bare).replace(unescapeRe, bare)];
|
|
18992
|
+
}
|
|
18993
|
+
|
|
18916
18994
|
class CMIInteractions extends CMIArray {
|
|
18917
18995
|
/**
|
|
18918
18996
|
* Constructor for `cmi.interactions` Array
|
|
@@ -19122,8 +19200,7 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
19122
19200
|
const response_type = LearnerResponses[this.type];
|
|
19123
19201
|
if (response_type) {
|
|
19124
19202
|
if (response_type?.delimiter) {
|
|
19125
|
-
|
|
19126
|
-
nodes = learner_response.split(delimiter);
|
|
19203
|
+
nodes = splitDelimited(learner_response, response_type.delimiter);
|
|
19127
19204
|
} else {
|
|
19128
19205
|
nodes[0] = learner_response;
|
|
19129
19206
|
}
|
|
@@ -19131,10 +19208,10 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
19131
19208
|
const formatRegex = new RegExp(response_type.format);
|
|
19132
19209
|
for (let i = 0; i < nodes.length; i++) {
|
|
19133
19210
|
if (response_type?.delimiter2) {
|
|
19134
|
-
const
|
|
19135
|
-
const values =
|
|
19211
|
+
const node = nodes[i] ?? "";
|
|
19212
|
+
const values = this.type === "performance" ? splitFirstDelimited(node, response_type.delimiter2) : splitDelimited(node, response_type.delimiter2);
|
|
19136
19213
|
if (values?.length === 2) {
|
|
19137
|
-
if (this.type === "performance" &&
|
|
19214
|
+
if (this.type === "performance" && values[0] === "" && values[1] === "") {
|
|
19138
19215
|
throw new Scorm2004ValidationError(
|
|
19139
19216
|
this._cmi_element + ".learner_response",
|
|
19140
19217
|
scorm2004_errors.TYPE_MISMATCH
|
|
@@ -19353,30 +19430,13 @@ class CMIInteractionsObjectivesObject extends BaseCMI {
|
|
|
19353
19430
|
return result;
|
|
19354
19431
|
}
|
|
19355
19432
|
}
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
}
|
|
19362
|
-
function splitUnescaped(text, delim) {
|
|
19363
|
-
const reDelim = escapeRegex(delim);
|
|
19364
|
-
const splitRe = new RegExp(`(?<!\\\\)${reDelim}`, "g");
|
|
19365
|
-
const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
|
|
19366
|
-
return text.split(splitRe).map((part) => part.replace(unescapeRe, delim));
|
|
19367
|
-
}
|
|
19368
|
-
function splitFirstUnescaped(text, delim) {
|
|
19369
|
-
const reDelim = escapeRegex(delim);
|
|
19370
|
-
const splitRe = new RegExp(`(?<!\\\\)${reDelim}`);
|
|
19371
|
-
const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
|
|
19372
|
-
const parts = text.split(splitRe);
|
|
19373
|
-
const firstPart = parts[0] ?? "";
|
|
19374
|
-
if (parts.length === 1) {
|
|
19375
|
-
return [firstPart.replace(unescapeRe, delim)];
|
|
19433
|
+
const RESPONSE_PREFIX_RE = /^\{(?:lang|case_matters|order_matters)=[^}]+\}/;
|
|
19434
|
+
function stripResponsePrefixes(node) {
|
|
19435
|
+
let result = node;
|
|
19436
|
+
while (RESPONSE_PREFIX_RE.test(result)) {
|
|
19437
|
+
result = result.replace(RESPONSE_PREFIX_RE, "");
|
|
19376
19438
|
}
|
|
19377
|
-
|
|
19378
|
-
const part2 = parts.slice(1).join(delim).replace(unescapeRe, delim);
|
|
19379
|
-
return [part1, part2];
|
|
19439
|
+
return result;
|
|
19380
19440
|
}
|
|
19381
19441
|
function validatePattern(type, pattern, responseDef) {
|
|
19382
19442
|
if (pattern.trim() !== pattern) {
|
|
@@ -19385,8 +19445,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
19385
19445
|
scorm2004_errors.TYPE_MISMATCH
|
|
19386
19446
|
);
|
|
19387
19447
|
}
|
|
19388
|
-
const
|
|
19389
|
-
const rawNodes = subDelim1 ? splitUnescaped(pattern, subDelim1) : [pattern];
|
|
19448
|
+
const rawNodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
|
|
19390
19449
|
for (const raw of rawNodes) {
|
|
19391
19450
|
if (raw.trim() !== raw) {
|
|
19392
19451
|
throw new Scorm2004ValidationError(
|
|
@@ -19398,20 +19457,14 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
19398
19457
|
if (type === "fill-in" && pattern === "") {
|
|
19399
19458
|
return;
|
|
19400
19459
|
}
|
|
19401
|
-
const
|
|
19402
|
-
let nodes;
|
|
19403
|
-
if (delim1) {
|
|
19404
|
-
nodes = splitUnescaped(pattern, delim1);
|
|
19405
|
-
} else {
|
|
19406
|
-
nodes = [pattern];
|
|
19407
|
-
}
|
|
19460
|
+
const nodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
|
|
19408
19461
|
if (!responseDef.delimiter && pattern.includes(",")) {
|
|
19409
19462
|
throw new Scorm2004ValidationError(
|
|
19410
19463
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
19411
19464
|
scorm2004_errors.TYPE_MISMATCH
|
|
19412
19465
|
);
|
|
19413
19466
|
}
|
|
19414
|
-
if (responseDef.unique || responseDef.duplicate === false) {
|
|
19467
|
+
if (type !== "numeric" && (responseDef.unique || responseDef.duplicate === false)) {
|
|
19415
19468
|
const seen = new Set(nodes);
|
|
19416
19469
|
if (seen.size !== nodes.length) {
|
|
19417
19470
|
throw new Scorm2004ValidationError(
|
|
@@ -19443,8 +19496,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
19443
19496
|
scorm2004_errors.TYPE_MISMATCH
|
|
19444
19497
|
);
|
|
19445
19498
|
}
|
|
19446
|
-
const
|
|
19447
|
-
const parts = value.split(new RegExp(`(?<!\\\\)${escapeRegex(delim)}`, "g")).map((n) => n.replace(new RegExp(`\\\\${escapeRegex(delim)}`, "g"), delim));
|
|
19499
|
+
const parts = splitDelimited(value, delimBracketed);
|
|
19448
19500
|
if (parts.length !== 2 || parts[0] === "" || parts[1] === "") {
|
|
19449
19501
|
throw new Scorm2004ValidationError(
|
|
19450
19502
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
@@ -19461,15 +19513,17 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
19461
19513
|
for (const node of nodes) {
|
|
19462
19514
|
switch (type) {
|
|
19463
19515
|
case "numeric": {
|
|
19464
|
-
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19516
|
+
if (node === "") {
|
|
19517
|
+
const bracketedRange = nodes.length >= 2 && !!responseDef.delimiter && pattern.includes(responseDef.delimiter);
|
|
19518
|
+
if (!bracketedRange) {
|
|
19519
|
+
throw new Scorm2004ValidationError(
|
|
19520
|
+
"cmi.interactions.n.correct_responses.n.pattern",
|
|
19521
|
+
scorm2004_errors.TYPE_MISMATCH
|
|
19522
|
+
);
|
|
19523
|
+
}
|
|
19524
|
+
break;
|
|
19471
19525
|
}
|
|
19472
|
-
|
|
19526
|
+
checkSingle(node);
|
|
19473
19527
|
break;
|
|
19474
19528
|
}
|
|
19475
19529
|
case "performance": {
|
|
@@ -19480,8 +19534,8 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
19480
19534
|
scorm2004_errors.TYPE_MISMATCH
|
|
19481
19535
|
);
|
|
19482
19536
|
}
|
|
19483
|
-
const
|
|
19484
|
-
const parts =
|
|
19537
|
+
const record = stripResponsePrefixes(node);
|
|
19538
|
+
const parts = splitFirstDelimited(record, delimBracketed);
|
|
19485
19539
|
if (parts.length !== 2) {
|
|
19486
19540
|
throw new Scorm2004ValidationError(
|
|
19487
19541
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
@@ -19489,7 +19543,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
19489
19543
|
);
|
|
19490
19544
|
}
|
|
19491
19545
|
const [part1, part2] = parts;
|
|
19492
|
-
if (part1 === ""
|
|
19546
|
+
if (part1 === "" && part2 === "") {
|
|
19493
19547
|
throw new Scorm2004ValidationError(
|
|
19494
19548
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
19495
19549
|
scorm2004_errors.TYPE_MISMATCH
|
|
@@ -22368,7 +22422,7 @@ class Scorm2004ResponseValidator {
|
|
|
22368
22422
|
checkValidResponseType(CMIElement, response_type, value, interaction_type) {
|
|
22369
22423
|
let nodes = [];
|
|
22370
22424
|
if (response_type?.delimiter) {
|
|
22371
|
-
nodes = String(value)
|
|
22425
|
+
nodes = splitDelimited(String(value), response_type.delimiter);
|
|
22372
22426
|
} else {
|
|
22373
22427
|
nodes[0] = value;
|
|
22374
22428
|
}
|
|
@@ -22490,22 +22544,30 @@ class Scorm2004ResponseValidator {
|
|
|
22490
22544
|
nodes[i] = this.removeCorrectResponsePrefixes(CMIElement, nodes[i]);
|
|
22491
22545
|
}
|
|
22492
22546
|
if (response?.delimiter2) {
|
|
22493
|
-
const values = nodes[i].
|
|
22547
|
+
const values = interaction_type === "performance" ? splitFirstDelimited(nodes[i], response.delimiter2) : splitDelimited(nodes[i], response.delimiter2);
|
|
22494
22548
|
if (values.length === 2) {
|
|
22495
|
-
|
|
22496
|
-
if (!matches) {
|
|
22549
|
+
if (interaction_type === "performance" && values[0] === "" && values[1] === "") {
|
|
22497
22550
|
this.context.throwSCORMError(
|
|
22498
22551
|
CMIElement,
|
|
22499
22552
|
scorm2004_errors.TYPE_MISMATCH,
|
|
22500
22553
|
`${interaction_type}: ${value}`
|
|
22501
22554
|
);
|
|
22502
22555
|
} else {
|
|
22503
|
-
|
|
22556
|
+
const matches = values[0]?.match(formatRegex);
|
|
22557
|
+
if (!matches) {
|
|
22504
22558
|
this.context.throwSCORMError(
|
|
22505
22559
|
CMIElement,
|
|
22506
22560
|
scorm2004_errors.TYPE_MISMATCH,
|
|
22507
22561
|
`${interaction_type}: ${value}`
|
|
22508
22562
|
);
|
|
22563
|
+
} else {
|
|
22564
|
+
if (!response.format2 || !values[1]?.match(new RegExp(response.format2))) {
|
|
22565
|
+
this.context.throwSCORMError(
|
|
22566
|
+
CMIElement,
|
|
22567
|
+
scorm2004_errors.TYPE_MISMATCH,
|
|
22568
|
+
`${interaction_type}: ${value}`
|
|
22569
|
+
);
|
|
22570
|
+
}
|
|
22509
22571
|
}
|
|
22510
22572
|
}
|
|
22511
22573
|
} else {
|
|
@@ -22516,6 +22578,9 @@ class Scorm2004ResponseValidator {
|
|
|
22516
22578
|
);
|
|
22517
22579
|
}
|
|
22518
22580
|
} else {
|
|
22581
|
+
if (interaction_type === "numeric" && nodes.length > 1 && nodes[i] === "" && !!response.delimiter && String(value).includes(response.delimiter)) {
|
|
22582
|
+
continue;
|
|
22583
|
+
}
|
|
22519
22584
|
const matches = nodes[i].match(formatRegex);
|
|
22520
22585
|
if (!matches && value !== "" || !matches && interaction_type === "true-false") {
|
|
22521
22586
|
this.context.throwSCORMError(
|
|
@@ -22525,7 +22590,7 @@ class Scorm2004ResponseValidator {
|
|
|
22525
22590
|
);
|
|
22526
22591
|
} else {
|
|
22527
22592
|
if (interaction_type === "numeric" && nodes.length > 1) {
|
|
22528
|
-
if (Number(nodes[0]) > Number(nodes[1])) {
|
|
22593
|
+
if (nodes[0] !== "" && nodes[1] !== "" && Number(nodes[0]) > Number(nodes[1])) {
|
|
22529
22594
|
this.context.throwSCORMError(
|
|
22530
22595
|
CMIElement,
|
|
22531
22596
|
scorm2004_errors.TYPE_MISMATCH,
|