@qti-components/processing 1.4.1 → 1.6.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/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +4 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/qti-container-size/qti-container-size.d.ts +2 -0
- package/dist/components/qti-container-size/qti-container-size.js +3 -0
- package/dist/components/qti-container-size/qti-container-size.js.map +1 -1
- package/dist/components/qti-contains/qti-contains.d.ts +24 -3
- package/dist/components/qti-contains/qti-contains.js +82 -30
- package/dist/components/qti-contains/qti-contains.js.map +1 -1
- package/dist/components/qti-default/qti-default.js +0 -1
- package/dist/components/qti-default/qti-default.js.map +1 -1
- package/dist/components/qti-divide/qti-divide.d.ts +3 -0
- package/dist/components/qti-divide/qti-divide.js +4 -0
- package/dist/components/qti-divide/qti-divide.js.map +1 -1
- package/dist/components/qti-equal/qti-equal.d.ts +12 -1
- package/dist/components/qti-equal/qti-equal.js +51 -7
- package/dist/components/qti-equal/qti-equal.js.map +1 -1
- package/dist/components/qti-equal-rounded/qti-equal-rounded.js +9 -1
- package/dist/components/qti-equal-rounded/qti-equal-rounded.js.map +1 -1
- package/dist/components/qti-exit-response/qti-exit-response.d.ts +17 -0
- package/dist/components/qti-exit-response/qti-exit-response.js +16 -0
- package/dist/components/qti-exit-response/qti-exit-response.js.map +1 -0
- package/dist/components/qti-exit-test/qti-exit-test.d.ts +16 -0
- package/dist/components/qti-exit-test/qti-exit-test.js +15 -0
- package/dist/components/qti-exit-test/qti-exit-test.js.map +1 -0
- package/dist/components/qti-field-value/qti-field-value.d.ts +7 -1
- package/dist/components/qti-field-value/qti-field-value.js +15 -12
- package/dist/components/qti-field-value/qti-field-value.js.map +1 -1
- package/dist/components/qti-gte/qti-gte.js +1 -1
- package/dist/components/qti-gte/qti-gte.js.map +1 -1
- package/dist/components/qti-integer-divide/qti-integer-divide.d.ts +2 -0
- package/dist/components/qti-integer-divide/qti-integer-divide.js +3 -0
- package/dist/components/qti-integer-divide/qti-integer-divide.js.map +1 -1
- package/dist/components/qti-integer-modulus/qti-integer-modulus.d.ts +3 -1
- package/dist/components/qti-integer-modulus/qti-integer-modulus.js +9 -2
- package/dist/components/qti-integer-modulus/qti-integer-modulus.js.map +1 -1
- package/dist/components/qti-integer-to-float/qti-integer-to-float.d.ts +2 -0
- package/dist/components/qti-integer-to-float/qti-integer-to-float.js +3 -0
- package/dist/components/qti-integer-to-float/qti-integer-to-float.js.map +1 -1
- package/dist/components/qti-is-null/qti-is-null.js +4 -1
- package/dist/components/qti-is-null/qti-is-null.js.map +1 -1
- package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.d.ts +2 -1
- package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.js +29 -5
- package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.js.map +1 -1
- package/dist/components/qti-lte/qti-lte.js +1 -1
- package/dist/components/qti-lte/qti-lte.js.map +1 -1
- package/dist/components/qti-map-response-point/qti-map-response-point.js +6 -0
- package/dist/components/qti-map-response-point/qti-map-response-point.js.map +1 -1
- package/dist/components/qti-math-constant/qti-math-constant.d.ts +20 -0
- package/dist/components/qti-math-constant/qti-math-constant.js +40 -0
- package/dist/components/qti-math-constant/qti-math-constant.js.map +1 -0
- package/dist/components/qti-math-operator/qti-math-operator.d.ts +6 -0
- package/dist/components/qti-math-operator/qti-math-operator.js +43 -1
- package/dist/components/qti-math-operator/qti-math-operator.js.map +1 -1
- package/dist/components/qti-multiple/qti-multiple.js +0 -2
- package/dist/components/qti-multiple/qti-multiple.js.map +1 -1
- package/dist/components/qti-pattern-match/qti-pattern-match.d.ts +1 -0
- package/dist/components/qti-pattern-match/qti-pattern-match.js +28 -4
- package/dist/components/qti-pattern-match/qti-pattern-match.js.map +1 -1
- package/dist/components/qti-power/qti-power.d.ts +2 -0
- package/dist/components/qti-power/qti-power.js +3 -0
- package/dist/components/qti-power/qti-power.js.map +1 -1
- package/dist/components/qti-printed-variable/printed-variable-format.d.ts +19 -0
- package/dist/components/qti-printed-variable/printed-variable-format.js +113 -0
- package/dist/components/qti-printed-variable/printed-variable-format.js.map +1 -0
- package/dist/components/qti-printed-variable/qti-printed-variable.d.ts +26 -0
- package/dist/components/qti-printed-variable/qti-printed-variable.js +96 -2
- package/dist/components/qti-printed-variable/qti-printed-variable.js.map +1 -1
- package/dist/components/qti-random/qti-random.js +0 -1
- package/dist/components/qti-random/qti-random.js.map +1 -1
- package/dist/components/qti-repeat/qti-repeat.d.ts +0 -8
- package/dist/components/qti-repeat/qti-repeat.js +15 -2
- package/dist/components/qti-repeat/qti-repeat.js.map +1 -1
- package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.d.ts +19 -0
- package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.js +19 -0
- package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.js.map +1 -0
- package/dist/components/qti-round/qti-round.d.ts +2 -0
- package/dist/components/qti-round/qti-round.js +3 -0
- package/dist/components/qti-round/qti-round.js.map +1 -1
- package/dist/components/qti-round-to/qti-round-to.d.ts +2 -0
- package/dist/components/qti-round-to/qti-round-to.js +3 -0
- package/dist/components/qti-round-to/qti-round-to.js.map +1 -1
- package/dist/components/qti-stats-operator/qti-stats-operator.d.ts +3 -1
- package/dist/components/qti-stats-operator/qti-stats-operator.js +29 -7
- package/dist/components/qti-stats-operator/qti-stats-operator.js.map +1 -1
- package/dist/components/qti-truncate/qti-truncate.d.ts +2 -0
- package/dist/components/qti-truncate/qti-truncate.js +3 -0
- package/dist/components/qti-truncate/qti-truncate.js.map +1 -1
- package/dist/elements.d.ts +17 -1
- package/dist/elements.js +9 -1
- package/dist/elements.js.map +1 -1
- package/package.json +4 -4
|
@@ -19,6 +19,13 @@ export class QtiMathOperator extends QtiExpression {
|
|
|
19
19
|
super(...arguments);
|
|
20
20
|
this.name = '';
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Float for every function except the three that produce a whole number:
|
|
24
|
+
* `floor`, `ceil` and `signum`.
|
|
25
|
+
*/
|
|
26
|
+
get resultBaseType() {
|
|
27
|
+
return ['floor', 'ceil', 'signum'].includes(this.name?.toLowerCase()) ? 'integer' : 'float';
|
|
28
|
+
}
|
|
22
29
|
getResult() {
|
|
23
30
|
if (!this.name) {
|
|
24
31
|
console.error('qti-math-operator requires a name attribute');
|
|
@@ -61,6 +68,10 @@ export class QtiMathOperator extends QtiExpression {
|
|
|
61
68
|
// Perform the mathematical operation
|
|
62
69
|
return this.#performOperation(this.name, values);
|
|
63
70
|
}
|
|
71
|
+
/** A function with no finite result at this argument is NULL, not Infinity. */
|
|
72
|
+
#finite(value) {
|
|
73
|
+
return Number.isFinite(value) ? value : null;
|
|
74
|
+
}
|
|
64
75
|
#performOperation(operation, values) {
|
|
65
76
|
try {
|
|
66
77
|
switch (operation.toLowerCase()) {
|
|
@@ -105,12 +116,43 @@ export class QtiMathOperator extends QtiExpression {
|
|
|
105
116
|
if (values.length !== 1)
|
|
106
117
|
return null;
|
|
107
118
|
return Math.tanh(values[0]);
|
|
119
|
+
// Reciprocal trigonometric functions. Accepted under both the long
|
|
120
|
+
// names the QTI vocabulary spells out and the short ones items use in
|
|
121
|
+
// practice. Each is NULL where its reciprocal is undefined, which
|
|
122
|
+
// #finite catches as a non-finite result.
|
|
123
|
+
case 'secant':
|
|
124
|
+
case 'sec':
|
|
125
|
+
if (values.length !== 1)
|
|
126
|
+
return null;
|
|
127
|
+
return this.#finite(1 / Math.cos(values[0]));
|
|
128
|
+
case 'cosecant':
|
|
129
|
+
case 'csc':
|
|
130
|
+
if (values.length !== 1)
|
|
131
|
+
return null;
|
|
132
|
+
return this.#finite(1 / Math.sin(values[0]));
|
|
133
|
+
case 'cotangent':
|
|
134
|
+
case 'cot':
|
|
135
|
+
if (values.length !== 1)
|
|
136
|
+
return null;
|
|
137
|
+
return this.#finite(Math.cos(values[0]) / Math.sin(values[0]));
|
|
138
|
+
// Angle conversions. Every other function here works in radians.
|
|
139
|
+
case 'todegrees':
|
|
140
|
+
if (values.length !== 1)
|
|
141
|
+
return null;
|
|
142
|
+
return (values[0] * 180) / Math.PI;
|
|
143
|
+
case 'toradians':
|
|
144
|
+
if (values.length !== 1)
|
|
145
|
+
return null;
|
|
146
|
+
return (values[0] * Math.PI) / 180;
|
|
147
|
+
// `log` is the base-10 logarithm per the QTI vocabulary; `ln` is the
|
|
148
|
+
// natural one. They are not interchangeable — this used to return the
|
|
149
|
+
// natural log for both.
|
|
108
150
|
case 'log':
|
|
109
151
|
if (values.length !== 1)
|
|
110
152
|
return null;
|
|
111
153
|
if (values[0] <= 0)
|
|
112
154
|
return null; // Outside domain
|
|
113
|
-
return Math.
|
|
155
|
+
return Math.log10(values[0]);
|
|
114
156
|
case 'ln':
|
|
115
157
|
if (values.length !== 1)
|
|
116
158
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qti-math-operator.js","sourceRoot":"","sources":["../../../src/components/qti-math-operator/qti-math-operator.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\n\nimport { QtiExpression } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-math-operator performs mathematical operations on numerical values.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/impl#h.math-operator\n *\n * Takes 1 or more sub-expressions with single cardinality and numerical base-types.\n * The result is a single float, except for signum, floor and ceil which return integers.\n * Returns NULL if any argument is NULL or falls outside the function's natural domain.\n */\nexport class QtiMathOperator extends QtiExpression<number | null> {\n @property({ type: String }) name: string = '';\n\n public override getResult(): number | null {\n if (!this.name) {\n console.error('qti-math-operator requires a name attribute');\n return null;\n }\n\n const variables = this.getVariables() as ResponseVariable[];\n if (!variables || variables.length === 0) {\n return null;\n }\n\n // Extract numerical values and validate\n const values: number[] = [];\n for (const variable of variables) {\n // Check cardinality\n if (variable.cardinality !== 'single') {\n console.error('qti-math-operator requires single cardinality');\n return null;\n }\n\n // Check if value is array (shouldn't be for single cardinality)\n if (Array.isArray(variable.value)) {\n console.error('qti-math-operator unexpected array value for single cardinality');\n return null;\n }\n\n // Handle null values\n if (variable.value === null || variable.value === undefined) {\n return null;\n }\n\n // Check base type\n if (variable.baseType !== 'integer' && variable.baseType !== 'float') {\n console.error('qti-math-operator requires numerical base-type');\n return null;\n }\n\n // Convert to number\n const numValue = parseFloat(variable.value as string);\n if (isNaN(numValue)) {\n console.error('qti-math-operator non-numerical value');\n return null;\n }\n\n values.push(numValue);\n }\n\n // Perform the mathematical operation\n return this.#performOperation(this.name, values);\n }\n\n #performOperation(operation: string, values: number[]): number | null {\n try {\n switch (operation.toLowerCase()) {\n // Single argument functions\n case 'sin':\n if (values.length !== 1) return null;\n return Math.sin(values[0]);\n\n case 'cos':\n if (values.length !== 1) return null;\n return Math.cos(values[0]);\n\n case 'tan':\n if (values.length !== 1) return null;\n return Math.tan(values[0]);\n\n case 'asin':\n if (values.length !== 1) return null;\n if (values[0] < -1 || values[0] > 1) return null; // Outside domain\n return Math.asin(values[0]);\n\n case 'acos':\n if (values.length !== 1) return null;\n if (values[0] < -1 || values[0] > 1) return null; // Outside domain\n return Math.acos(values[0]);\n\n case 'atan':\n if (values.length !== 1) return null;\n return Math.atan(values[0]);\n\n case 'sinh':\n if (values.length !== 1) return null;\n return Math.sinh(values[0]);\n\n case 'cosh':\n if (values.length !== 1) return null;\n return Math.cosh(values[0]);\n\n case 'tanh':\n if (values.length !== 1) return null;\n return Math.tanh(values[0]);\n\n case 'log':\n if (values.length !== 1) return null;\n if (values[0] <= 0) return null; // Outside domain\n return Math.log(values[0]);\n\n case 'ln':\n if (values.length !== 1) return null;\n if (values[0] <= 0) return null; // Outside domain\n return Math.log(values[0]);\n\n case 'log10':\n if (values.length !== 1) return null;\n if (values[0] <= 0) return null; // Outside domain\n return Math.log10(values[0]);\n\n case 'exp':\n if (values.length !== 1) return null;\n return Math.exp(values[0]);\n\n case 'abs':\n if (values.length !== 1) return null;\n return Math.abs(values[0]);\n\n case 'sqrt':\n if (values.length !== 1) return null;\n if (values[0] < 0) return null; // Outside domain\n return Math.sqrt(values[0]);\n\n case 'signum':\n if (values.length !== 1) return null;\n return Math.sign(values[0]); // Returns -1, 0, or 1 (integer)\n\n case 'floor':\n if (values.length !== 1) return null;\n return Math.floor(values[0]); // Returns integer\n\n case 'ceil':\n if (values.length !== 1) return null;\n return Math.ceil(values[0]); // Returns integer\n\n case 'round':\n if (values.length !== 1) return null;\n return Math.round(values[0]);\n\n // Two argument functions\n case 'atan2':\n if (values.length !== 2) return null;\n return Math.atan2(values[0], values[1]);\n\n case 'pow': {\n if (values.length !== 2) {\n return null;\n }\n const base = values[0];\n const exponent = values[1];\n // Check for invalid cases\n if (base === 0 && exponent <= 0) return null;\n if (base < 0 && !Number.isInteger(exponent)) return null;\n return Math.pow(base, exponent);\n }\n case 'log_base': {\n if (values.length !== 2) {\n return null;\n }\n const value = values[0];\n const base = values[1];\n if (value <= 0 || base <= 0 || base === 1) return null; // Outside domain\n return Math.log(value) / Math.log(base);\n }\n case 'mod':\n if (values.length !== 2) return null;\n if (values[1] === 0) return null; // Division by zero\n return values[0] % values[1];\n\n case 'max':\n if (values.length < 1) return null;\n return Math.max(...values);\n\n case 'min':\n if (values.length < 1) return null;\n return Math.min(...values);\n\n default:\n console.error(`qti-math-operator: unknown operation \"${operation}\"`);\n return null;\n }\n } catch (error) {\n console.error(`qti-math-operator: error performing operation \"${operation}\":`, error);\n return null;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"qti-math-operator.js","sourceRoot":"","sources":["../../../src/components/qti-math-operator/qti-math-operator.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\n\nimport { QtiExpression } from '@qti-components/base';\n\nimport type { BaseType, ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-math-operator performs mathematical operations on numerical values.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/impl#h.math-operator\n *\n * Takes 1 or more sub-expressions with single cardinality and numerical base-types.\n * The result is a single float, except for signum, floor and ceil which return integers.\n * Returns NULL if any argument is NULL or falls outside the function's natural domain.\n */\nexport class QtiMathOperator extends QtiExpression<number | null> {\n @property({ type: String }) name: string = '';\n\n /**\n * Float for every function except the three that produce a whole number:\n * `floor`, `ceil` and `signum`.\n */\n public override get resultBaseType(): BaseType {\n return ['floor', 'ceil', 'signum'].includes(this.name?.toLowerCase()) ? 'integer' : 'float';\n }\n\n public override getResult(): number | null {\n if (!this.name) {\n console.error('qti-math-operator requires a name attribute');\n return null;\n }\n\n const variables = this.getVariables() as ResponseVariable[];\n if (!variables || variables.length === 0) {\n return null;\n }\n\n // Extract numerical values and validate\n const values: number[] = [];\n for (const variable of variables) {\n // Check cardinality\n if (variable.cardinality !== 'single') {\n console.error('qti-math-operator requires single cardinality');\n return null;\n }\n\n // Check if value is array (shouldn't be for single cardinality)\n if (Array.isArray(variable.value)) {\n console.error('qti-math-operator unexpected array value for single cardinality');\n return null;\n }\n\n // Handle null values\n if (variable.value === null || variable.value === undefined) {\n return null;\n }\n\n // Check base type\n if (variable.baseType !== 'integer' && variable.baseType !== 'float') {\n console.error('qti-math-operator requires numerical base-type');\n return null;\n }\n\n // Convert to number\n const numValue = parseFloat(variable.value as string);\n if (isNaN(numValue)) {\n console.error('qti-math-operator non-numerical value');\n return null;\n }\n\n values.push(numValue);\n }\n\n // Perform the mathematical operation\n return this.#performOperation(this.name, values);\n }\n\n /** A function with no finite result at this argument is NULL, not Infinity. */\n #finite(value: number): number | null {\n return Number.isFinite(value) ? value : null;\n }\n\n #performOperation(operation: string, values: number[]): number | null {\n try {\n switch (operation.toLowerCase()) {\n // Single argument functions\n case 'sin':\n if (values.length !== 1) return null;\n return Math.sin(values[0]);\n\n case 'cos':\n if (values.length !== 1) return null;\n return Math.cos(values[0]);\n\n case 'tan':\n if (values.length !== 1) return null;\n return Math.tan(values[0]);\n\n case 'asin':\n if (values.length !== 1) return null;\n if (values[0] < -1 || values[0] > 1) return null; // Outside domain\n return Math.asin(values[0]);\n\n case 'acos':\n if (values.length !== 1) return null;\n if (values[0] < -1 || values[0] > 1) return null; // Outside domain\n return Math.acos(values[0]);\n\n case 'atan':\n if (values.length !== 1) return null;\n return Math.atan(values[0]);\n\n case 'sinh':\n if (values.length !== 1) return null;\n return Math.sinh(values[0]);\n\n case 'cosh':\n if (values.length !== 1) return null;\n return Math.cosh(values[0]);\n\n case 'tanh':\n if (values.length !== 1) return null;\n return Math.tanh(values[0]);\n\n // Reciprocal trigonometric functions. Accepted under both the long\n // names the QTI vocabulary spells out and the short ones items use in\n // practice. Each is NULL where its reciprocal is undefined, which\n // #finite catches as a non-finite result.\n case 'secant':\n case 'sec':\n if (values.length !== 1) return null;\n return this.#finite(1 / Math.cos(values[0]));\n\n case 'cosecant':\n case 'csc':\n if (values.length !== 1) return null;\n return this.#finite(1 / Math.sin(values[0]));\n\n case 'cotangent':\n case 'cot':\n if (values.length !== 1) return null;\n return this.#finite(Math.cos(values[0]) / Math.sin(values[0]));\n\n // Angle conversions. Every other function here works in radians.\n case 'todegrees':\n if (values.length !== 1) return null;\n return (values[0] * 180) / Math.PI;\n\n case 'toradians':\n if (values.length !== 1) return null;\n return (values[0] * Math.PI) / 180;\n\n // `log` is the base-10 logarithm per the QTI vocabulary; `ln` is the\n // natural one. They are not interchangeable — this used to return the\n // natural log for both.\n case 'log':\n if (values.length !== 1) return null;\n if (values[0] <= 0) return null; // Outside domain\n return Math.log10(values[0]);\n\n case 'ln':\n if (values.length !== 1) return null;\n if (values[0] <= 0) return null; // Outside domain\n return Math.log(values[0]);\n\n case 'log10':\n if (values.length !== 1) return null;\n if (values[0] <= 0) return null; // Outside domain\n return Math.log10(values[0]);\n\n case 'exp':\n if (values.length !== 1) return null;\n return Math.exp(values[0]);\n\n case 'abs':\n if (values.length !== 1) return null;\n return Math.abs(values[0]);\n\n case 'sqrt':\n if (values.length !== 1) return null;\n if (values[0] < 0) return null; // Outside domain\n return Math.sqrt(values[0]);\n\n case 'signum':\n if (values.length !== 1) return null;\n return Math.sign(values[0]); // Returns -1, 0, or 1 (integer)\n\n case 'floor':\n if (values.length !== 1) return null;\n return Math.floor(values[0]); // Returns integer\n\n case 'ceil':\n if (values.length !== 1) return null;\n return Math.ceil(values[0]); // Returns integer\n\n case 'round':\n if (values.length !== 1) return null;\n return Math.round(values[0]);\n\n // Two argument functions\n case 'atan2':\n if (values.length !== 2) return null;\n return Math.atan2(values[0], values[1]);\n\n case 'pow': {\n if (values.length !== 2) {\n return null;\n }\n const base = values[0];\n const exponent = values[1];\n // Check for invalid cases\n if (base === 0 && exponent <= 0) return null;\n if (base < 0 && !Number.isInteger(exponent)) return null;\n return Math.pow(base, exponent);\n }\n case 'log_base': {\n if (values.length !== 2) {\n return null;\n }\n const value = values[0];\n const base = values[1];\n if (value <= 0 || base <= 0 || base === 1) return null; // Outside domain\n return Math.log(value) / Math.log(base);\n }\n case 'mod':\n if (values.length !== 2) return null;\n if (values[1] === 0) return null; // Division by zero\n return values[0] % values[1];\n\n case 'max':\n if (values.length < 1) return null;\n return Math.max(...values);\n\n case 'min':\n if (values.length < 1) return null;\n return Math.min(...values);\n\n default:\n console.error(`qti-math-operator: unknown operation \"${operation}\"`);\n return null;\n }\n } catch (error) {\n console.error(`qti-math-operator: error performing operation \"${operation}\":`, error);\n return null;\n }\n }\n}\n"]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { QtiExpression } from '@qti-components/base';
|
|
2
2
|
export class QtiMultiple extends QtiExpression {
|
|
3
3
|
getResult() {
|
|
4
|
-
console.debug('qti-multiple getResult called', this.innerHTML);
|
|
5
4
|
const variables = this.getVariables();
|
|
6
5
|
if (variables.length === 0) {
|
|
7
6
|
console.error('unexpected number of children in qti multiple');
|
|
@@ -14,7 +13,6 @@ export class QtiMultiple extends QtiExpression {
|
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
15
|
// const values = variables.map(v => v.value);
|
|
17
|
-
// console.log(variables);
|
|
18
16
|
// const flattenedArray = values.reduce((acc: string[], value: string | string[]) => {
|
|
19
17
|
// return acc.concat(Array.isArray(value) ? [...value] : value);
|
|
20
18
|
// }, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qti-multiple.js","sourceRoot":"","sources":["../../../src/components/qti-multiple/qti-multiple.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,MAAM,OAAO,WAAY,SAAQ,aAAuD;IACtE,SAAS;QACvB,
|
|
1
|
+
{"version":3,"file":"qti-multiple.js","sourceRoot":"","sources":["../../../src/components/qti-multiple/qti-multiple.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,MAAM,OAAO,WAAY,SAAQ,aAAuD;IACtE,SAAS;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAwB,CAAC;QAE5D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,QAAQ,CAAC,WAAW,KAAK,UAAU,IAAI,QAAQ,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;gBAC7E,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;gBACxD,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QAED,8CAA8C;QAC9C,sFAAsF;QACtF,kEAAkE;QAClE,UAAU;QACV,yBAAyB;QAEzB,OAAO,SAAS,CAAC;IACnB,CAAC;CACF","sourcesContent":["import { QtiExpression } from '@qti-components/base';\n\nimport type { ResponseVariable, VariableDeclaration } from '@qti-components/base';\nexport class QtiMultiple extends QtiExpression<VariableDeclaration<string | string[]>[]> {\n public override getResult(): ResponseVariable[] {\n const variables = this.getVariables() as ResponseVariable[];\n\n if (variables.length === 0) {\n console.error('unexpected number of children in qti multiple');\n return null;\n }\n for (const variable of variables) {\n if (variable.cardinality !== 'multiple' && variable.cardinality !== 'single') {\n console.error('unexpected cardinality in qti multiple');\n return [];\n }\n }\n\n // const values = variables.map(v => v.value);\n // const flattenedArray = values.reduce((acc: string[], value: string | string[]) => {\n // return acc.concat(Array.isArray(value) ? [...value] : value);\n // }, []);\n // return flattenedArray;\n\n return variables;\n }\n}\n"]}
|
|
@@ -8,6 +8,7 @@ import { QtiExpression } from '@qti-components/base';
|
|
|
8
8
|
* Special cases: Returns NULL if sub-expression is NULL.
|
|
9
9
|
*/
|
|
10
10
|
export declare class QtiPatternMatch extends QtiExpression<boolean | null> {
|
|
11
|
+
#private;
|
|
11
12
|
pattern: string;
|
|
12
13
|
getResult(): boolean | null;
|
|
13
14
|
}
|
|
@@ -37,12 +37,36 @@ export class QtiPatternMatch extends QtiExpression {
|
|
|
37
37
|
if (variable.value === null || variable.value === undefined) {
|
|
38
38
|
return null;
|
|
39
39
|
}
|
|
40
|
+
const anchored = this.#compile();
|
|
41
|
+
if (!anchored) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return anchored.test(variable.value.toString());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* XML Schema pattern semantics: the pattern has to match the *whole* value,
|
|
48
|
+
* not appear somewhere inside it. Without the anchors a pattern like `[0-9]+`
|
|
49
|
+
* accepts "abc123". The pattern is wrapped in a group so a top-level `|`
|
|
50
|
+
* cannot escape them — `a|b` has to anchor as `^(?:a|b)$`.
|
|
51
|
+
*
|
|
52
|
+
* Compiled with the unicode flag first, because XML Schema allows `\p{L}`
|
|
53
|
+
* category escapes and those only mean anything under `u`. That flag also
|
|
54
|
+
* rejects escapes JavaScript otherwise tolerates, so a pattern it refuses is
|
|
55
|
+
* retried without it rather than failing the whole comparison.
|
|
56
|
+
*/
|
|
57
|
+
#compile() {
|
|
58
|
+
const source = `^(?:${this.pattern})$`;
|
|
40
59
|
try {
|
|
41
|
-
return new RegExp(
|
|
60
|
+
return new RegExp(source, 'u');
|
|
42
61
|
}
|
|
43
|
-
catch
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
catch {
|
|
63
|
+
try {
|
|
64
|
+
return new RegExp(source);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error('qti-pattern-match requires a valid regular expression', error);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
46
70
|
}
|
|
47
71
|
}
|
|
48
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qti-pattern-match.js","sourceRoot":"","sources":["../../../src/components/qti-pattern-match/qti-pattern-match.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;GAOG;AACH,MAAM,OAAO,eAAgB,SAAQ,aAA6B;IAAlE;;QAC8B,YAAO,GAAG,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"qti-pattern-match.js","sourceRoot":"","sources":["../../../src/components/qti-pattern-match/qti-pattern-match.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;GAOG;AACH,MAAM,OAAO,eAAgB,SAAQ,aAA6B;IAAlE;;QAC8B,YAAO,GAAG,EAAE,CAAC;IAuD3C,CAAC;IArDiB,SAAS;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAwB,CAAC;QAE5D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;YACzE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,QAAQ,CAAC,WAAW,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACvE,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC5D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;;;;;OAUG;IACH,QAAQ;QACN,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC;QACvC,IAAI,CAAC;YACH,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC;gBACH,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,uDAAuD,EAAE,KAAK,CAAC,CAAC;gBAC9E,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAvD6B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;gDAAc","sourcesContent":["import { property } from 'lit/decorators.js';\n\nimport { QtiExpression } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-pattern-match operator checks a string against a regular expression.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#patternMatch\n *\n * Takes 1 string sub-expression; 'pattern' attribute contains the regex.\n * Returns true if the string matches the pattern, false otherwise.\n * Special cases: Returns NULL if sub-expression is NULL.\n */\nexport class QtiPatternMatch extends QtiExpression<boolean | null> {\n @property({ type: String }) pattern = '';\n\n public override getResult(): boolean | null {\n const variables = this.getVariables() as ResponseVariable[];\n\n if (variables.length !== 1) {\n console.error('qti-pattern-match requires exactly one child expression');\n return null;\n }\n\n if (!this.pattern) {\n console.error('qti-pattern-match requires a pattern attribute');\n return null;\n }\n\n const variable = variables[0];\n if (variable.cardinality !== 'single' || Array.isArray(variable.value)) {\n console.error('qti-pattern-match requires single cardinality');\n return null;\n }\n if (variable.value === null || variable.value === undefined) {\n return null;\n }\n\n const anchored = this.#compile();\n if (!anchored) {\n return null;\n }\n return anchored.test(variable.value.toString());\n }\n\n /**\n * XML Schema pattern semantics: the pattern has to match the *whole* value,\n * not appear somewhere inside it. Without the anchors a pattern like `[0-9]+`\n * accepts \"abc123\". The pattern is wrapped in a group so a top-level `|`\n * cannot escape them — `a|b` has to anchor as `^(?:a|b)$`.\n *\n * Compiled with the unicode flag first, because XML Schema allows `\\p{L}`\n * category escapes and those only mean anything under `u`. That flag also\n * rejects escapes JavaScript otherwise tolerates, so a pattern it refuses is\n * retried without it rather than failing the whole comparison.\n */\n #compile(): RegExp | null {\n const source = `^(?:${this.pattern})$`;\n try {\n return new RegExp(source, 'u');\n } catch {\n try {\n return new RegExp(source);\n } catch (error) {\n console.error('qti-pattern-match requires a valid regular expression', error);\n return null;\n }\n }\n }\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { QtiExpression } from '@qti-components/base';
|
|
2
|
+
import type { BaseType } from '@qti-components/base';
|
|
2
3
|
/**
|
|
3
4
|
* @summary The qti-power operator raises the first expression to the power of the second.
|
|
4
5
|
* @documentation https://www.imsglobal.org/spec/qti/v3p0/impl#h.power
|
|
@@ -8,5 +9,6 @@ import { QtiExpression } from '@qti-components/base';
|
|
|
8
9
|
* Returns NULL if either expression is NULL or if the result is outside the float value set.
|
|
9
10
|
*/
|
|
10
11
|
export declare class QtiPower extends QtiExpression<number | null> {
|
|
12
|
+
get resultBaseType(): BaseType;
|
|
11
13
|
getResult(): number | null;
|
|
12
14
|
}
|
|
@@ -8,6 +8,9 @@ import { QtiExpression } from '@qti-components/base';
|
|
|
8
8
|
* Returns NULL if either expression is NULL or if the result is outside the float value set.
|
|
9
9
|
*/
|
|
10
10
|
export class QtiPower extends QtiExpression {
|
|
11
|
+
get resultBaseType() {
|
|
12
|
+
return 'float';
|
|
13
|
+
}
|
|
11
14
|
getResult() {
|
|
12
15
|
if (this.children.length !== 2) {
|
|
13
16
|
console.error('qti-power must have exactly two child expressions');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qti-power.js","sourceRoot":"","sources":["../../../src/components/qti-power/qti-power.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;GAOG;AACH,MAAM,OAAO,QAAS,SAAQ,aAA4B;
|
|
1
|
+
{"version":3,"file":"qti-power.js","sourceRoot":"","sources":["../../../src/components/qti-power/qti-power.ts"],"names":[],"mappings":"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","sourcesContent":["import { QtiExpression } from '@qti-components/base';\n\nimport type { BaseType, ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-power operator raises the first expression to the power of the second.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/impl#h.power\n *\n * Takes 2 sub-expressions with single cardinality and numerical base-types.\n * Returns a single float corresponding to the first expression raised to the power of the second.\n * Returns NULL if either expression is NULL or if the result is outside the float value set.\n */\nexport class QtiPower extends QtiExpression<number | null> {\n public override get resultBaseType(): BaseType {\n return 'float';\n }\n\n public override getResult(): number | null {\n if (this.children.length !== 2) {\n console.error('qti-power must have exactly two child expressions');\n return null;\n }\n\n const variables = this.getVariables() as ResponseVariable[];\n if (!variables || variables.length !== 2) {\n return null;\n }\n\n const [base, exponent] = variables;\n\n // Check cardinality for both expressions\n if (base.cardinality !== 'single' || exponent.cardinality !== 'single') {\n console.error('qti-power requires single cardinality for both expressions');\n return null;\n }\n\n // Check if values are arrays (shouldn't be for single cardinality)\n if (Array.isArray(base.value) || Array.isArray(exponent.value)) {\n console.error('qti-power unexpected array value for single cardinality');\n return null;\n }\n\n // Handle null values\n if (base.value === null || base.value === undefined || exponent.value === null || exponent.value === undefined) {\n return null;\n }\n\n // Check base types\n if (\n (base.baseType !== 'integer' && base.baseType !== 'float') ||\n (exponent.baseType !== 'integer' && exponent.baseType !== 'float')\n ) {\n console.error('qti-power requires numerical base-types');\n return null;\n }\n\n // Convert to numbers\n const baseValue = parseFloat(base.value as string);\n const exponentValue = parseFloat(exponent.value as string);\n\n // Check for NaN values\n if (isNaN(baseValue) || isNaN(exponentValue)) {\n console.error('qti-power: non-numerical values encountered');\n return null;\n }\n\n // Handle special cases that should return NULL according to mathematical rules\n if (baseValue === 0 && exponentValue <= 0) {\n // 0^0, 0^(-n) are undefined\n return null;\n }\n\n if (baseValue < 0 && !Number.isInteger(exponentValue)) {\n // Negative base with non-integer exponent results in complex number\n return null;\n }\n\n // Calculate the power\n let result: number;\n try {\n result = Math.pow(baseValue, exponentValue);\n } catch (error) {\n console.error('qti-power: error calculating power:', error);\n return null;\n }\n\n // Check if result is within valid float range\n if (isNaN(result)) {\n return null;\n }\n\n // Allow positive and negative infinity as per spec\n if (result === Infinity || result === -Infinity) {\n return result;\n }\n\n // Check if result is within JavaScript's safe number range\n // This is a practical limit for what can be represented as a float\n if (!isFinite(result)) {\n return null;\n }\n\n // Additional check for extremely large or small numbers that might cause issues\n if (Math.abs(result) > Number.MAX_VALUE) {\n return null;\n }\n\n // Check for underflow (extremely small numbers that round to 0)\n if (result !== 0 && Math.abs(result) < Number.MIN_VALUE) {\n return null;\n }\n if (isFinite(result) && result !== 0) {\n // Determine appropriate precision based on the magnitude of the result\n const magnitude = Math.floor(Math.log10(Math.abs(result)));\n const precision = Math.max(0, 14 - magnitude); // Maintain 14 significant digits\n const factor = Math.pow(10, precision);\n result = Math.round(result * factor) / factor;\n }\n\n return result;\n }\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Number formatting for `qti-printed-variable`.
|
|
3
|
+
*
|
|
4
|
+
* QTI's `format` attribute carries a C `printf`-style conversion string — QTI 3.0 calls this
|
|
5
|
+
* "the QTI number formatting rules" and points at printf for the grammar. A printed variable has
|
|
6
|
+
* exactly one value to print, so every conversion specifier in the string receives that same
|
|
7
|
+
* value; `%%` is a literal percent and any other text is passed through.
|
|
8
|
+
*
|
|
9
|
+
* Supported: flags `-`, `+`, ` `, `0`, `#`; a width; a precision; and the conversions
|
|
10
|
+
* `d i u o x X f F e E g G s c`. Anything the grammar does not cover is left in place, so a
|
|
11
|
+
* malformed format string degrades to visible text rather than throwing inside `render()`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toExponential(value: number, precision?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Renders `value` through a QTI `format` string. The value is passed as the raw string held in
|
|
16
|
+
* the item context, so integers and floats arrive as `"5"` / `"3.14"` and are coerced per
|
|
17
|
+
* conversion.
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyFormat(format: string, value: string): string;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Number formatting for `qti-printed-variable`.
|
|
3
|
+
*
|
|
4
|
+
* QTI's `format` attribute carries a C `printf`-style conversion string — QTI 3.0 calls this
|
|
5
|
+
* "the QTI number formatting rules" and points at printf for the grammar. A printed variable has
|
|
6
|
+
* exactly one value to print, so every conversion specifier in the string receives that same
|
|
7
|
+
* value; `%%` is a literal percent and any other text is passed through.
|
|
8
|
+
*
|
|
9
|
+
* Supported: flags `-`, `+`, ` `, `0`, `#`; a width; a precision; and the conversions
|
|
10
|
+
* `d i u o x X f F e E g G s c`. Anything the grammar does not cover is left in place, so a
|
|
11
|
+
* malformed format string degrades to visible text rather than throwing inside `render()`.
|
|
12
|
+
*/
|
|
13
|
+
const CONVERSION = /%(?:(%)|([-+ 0#]*)(\d+)?(?:\.(\d+))?([diuoxXfFeEgGsc]))/g;
|
|
14
|
+
/** JS writes `1e+3`; C (and therefore QTI) writes at least two exponent digits. */
|
|
15
|
+
function padExponent(exponential) {
|
|
16
|
+
return exponential.replace(/e([+-])(\d)$/, 'e$10$2');
|
|
17
|
+
}
|
|
18
|
+
export function toExponential(value, precision) {
|
|
19
|
+
return padExponent(precision === undefined ? value.toExponential() : value.toExponential(precision));
|
|
20
|
+
}
|
|
21
|
+
function stripTrailingZeros(formatted) {
|
|
22
|
+
const [mantissa, exponent] = formatted.split(/e/i);
|
|
23
|
+
const trimmed = mantissa.includes('.') ? mantissa.replace(/\.?0+$/, '') : mantissa;
|
|
24
|
+
const suffix = exponent === undefined ? '' : `e${exponent}`;
|
|
25
|
+
return `${trimmed}${suffix}`;
|
|
26
|
+
}
|
|
27
|
+
/** C's `%g`: exponential when the exponent falls outside `[-4, precision)`, else fixed, zeros trimmed. */
|
|
28
|
+
function toGeneral(value, precision) {
|
|
29
|
+
const significant = precision === 0 ? 1 : precision;
|
|
30
|
+
const exponent = value === 0 ? 0 : Math.floor(Math.log10(value));
|
|
31
|
+
if (exponent < -4 || exponent >= significant) {
|
|
32
|
+
return stripTrailingZeros(toExponential(value, significant - 1));
|
|
33
|
+
}
|
|
34
|
+
return stripTrailingZeros(value.toFixed(Math.max(0, significant - 1 - exponent)));
|
|
35
|
+
}
|
|
36
|
+
function convert(conversion, flags, precision, raw) {
|
|
37
|
+
if (conversion === 's' || conversion === 'c') {
|
|
38
|
+
const text = conversion === 'c' ? raw.slice(0, 1) : raw;
|
|
39
|
+
return precision === undefined ? text : text.slice(0, precision);
|
|
40
|
+
}
|
|
41
|
+
const numeric = Number(raw);
|
|
42
|
+
// Not a number after all — print what the author gave us instead of `NaN`.
|
|
43
|
+
if (raw.trim() === '' || !Number.isFinite(numeric))
|
|
44
|
+
return raw;
|
|
45
|
+
const magnitude = Math.abs(numeric);
|
|
46
|
+
let body;
|
|
47
|
+
let negative = numeric < 0;
|
|
48
|
+
switch (conversion) {
|
|
49
|
+
case 'd':
|
|
50
|
+
case 'i':
|
|
51
|
+
body = String(Math.trunc(magnitude));
|
|
52
|
+
break;
|
|
53
|
+
case 'u':
|
|
54
|
+
body = String(Math.trunc(magnitude));
|
|
55
|
+
negative = false;
|
|
56
|
+
break;
|
|
57
|
+
case 'o':
|
|
58
|
+
body = `${flags.includes('#') ? '0' : ''}${Math.trunc(magnitude).toString(8)}`;
|
|
59
|
+
break;
|
|
60
|
+
case 'x':
|
|
61
|
+
body = `${flags.includes('#') ? '0x' : ''}${Math.trunc(magnitude).toString(16)}`;
|
|
62
|
+
break;
|
|
63
|
+
case 'X':
|
|
64
|
+
body = `${flags.includes('#') ? '0X' : ''}${Math.trunc(magnitude).toString(16).toUpperCase()}`;
|
|
65
|
+
break;
|
|
66
|
+
case 'f':
|
|
67
|
+
case 'F':
|
|
68
|
+
body = magnitude.toFixed(precision ?? 6);
|
|
69
|
+
break;
|
|
70
|
+
case 'e':
|
|
71
|
+
body = toExponential(magnitude, precision ?? 6);
|
|
72
|
+
break;
|
|
73
|
+
case 'E':
|
|
74
|
+
body = toExponential(magnitude, precision ?? 6).toUpperCase();
|
|
75
|
+
break;
|
|
76
|
+
case 'g':
|
|
77
|
+
body = toGeneral(magnitude, precision ?? 6);
|
|
78
|
+
break;
|
|
79
|
+
case 'G':
|
|
80
|
+
body = toGeneral(magnitude, precision ?? 6).toUpperCase();
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
return raw;
|
|
84
|
+
}
|
|
85
|
+
const sign = negative ? '-' : flags.includes('+') ? '+' : flags.includes(' ') ? ' ' : '';
|
|
86
|
+
return `${sign}${body}`;
|
|
87
|
+
}
|
|
88
|
+
function pad(text, flags, width) {
|
|
89
|
+
if (text.length >= width)
|
|
90
|
+
return text;
|
|
91
|
+
const fill = width - text.length;
|
|
92
|
+
if (flags.includes('-'))
|
|
93
|
+
return `${text}${' '.repeat(fill)}`;
|
|
94
|
+
if (flags.includes('0')) {
|
|
95
|
+
// Zero padding goes between the sign and the digits, never before the sign.
|
|
96
|
+
const [, sign, digits] = /^([-+ ]?)(.*)$/.exec(text);
|
|
97
|
+
return `${sign}${'0'.repeat(fill)}${digits}`;
|
|
98
|
+
}
|
|
99
|
+
return `${' '.repeat(fill)}${text}`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Renders `value` through a QTI `format` string. The value is passed as the raw string held in
|
|
103
|
+
* the item context, so integers and floats arrive as `"5"` / `"3.14"` and are coerced per
|
|
104
|
+
* conversion.
|
|
105
|
+
*/
|
|
106
|
+
export function applyFormat(format, value) {
|
|
107
|
+
return format.replace(CONVERSION, (_match, percent, flags, width, precision, conversion) => {
|
|
108
|
+
if (percent)
|
|
109
|
+
return '%';
|
|
110
|
+
return pad(convert(conversion, flags ?? '', precision === undefined ? undefined : Number(precision), value), flags ?? '', width === undefined ? 0 : Number(width));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=printed-variable-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"printed-variable-format.js","sourceRoot":"","sources":["../../../src/components/qti-printed-variable/printed-variable-format.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Number formatting for `qti-printed-variable`.\n *\n * QTI's `format` attribute carries a C `printf`-style conversion string — QTI 3.0 calls this\n * \"the QTI number formatting rules\" and points at printf for the grammar. A printed variable has\n * exactly one value to print, so every conversion specifier in the string receives that same\n * value; `%%` is a literal percent and any other text is passed through.\n *\n * Supported: flags `-`, `+`, ` `, `0`, `#`; a width; a precision; and the conversions\n * `d i u o x X f F e E g G s c`. Anything the grammar does not cover is left in place, so a\n * malformed format string degrades to visible text rather than throwing inside `render()`.\n */\n\nconst CONVERSION = /%(?:(%)|([-+ 0#]*)(\\d+)?(?:\\.(\\d+))?([diuoxXfFeEgGsc]))/g;\n\n/** JS writes `1e+3`; C (and therefore QTI) writes at least two exponent digits. */\nfunction padExponent(exponential: string): string {\n return exponential.replace(/e([+-])(\\d)$/, 'e$10$2');\n}\n\nexport function toExponential(value: number, precision?: number): string {\n return padExponent(precision === undefined ? value.toExponential() : value.toExponential(precision));\n}\n\nfunction stripTrailingZeros(formatted: string): string {\n const [mantissa, exponent] = formatted.split(/e/i);\n const trimmed = mantissa.includes('.') ? mantissa.replace(/\\.?0+$/, '') : mantissa;\n const suffix = exponent === undefined ? '' : `e${exponent}`;\n return `${trimmed}${suffix}`;\n}\n\n/** C's `%g`: exponential when the exponent falls outside `[-4, precision)`, else fixed, zeros trimmed. */\nfunction toGeneral(value: number, precision: number): string {\n const significant = precision === 0 ? 1 : precision;\n const exponent = value === 0 ? 0 : Math.floor(Math.log10(value));\n\n if (exponent < -4 || exponent >= significant) {\n return stripTrailingZeros(toExponential(value, significant - 1));\n }\n return stripTrailingZeros(value.toFixed(Math.max(0, significant - 1 - exponent)));\n}\n\nfunction convert(conversion: string, flags: string, precision: number | undefined, raw: string): string {\n if (conversion === 's' || conversion === 'c') {\n const text = conversion === 'c' ? raw.slice(0, 1) : raw;\n return precision === undefined ? text : text.slice(0, precision);\n }\n\n const numeric = Number(raw);\n // Not a number after all — print what the author gave us instead of `NaN`.\n if (raw.trim() === '' || !Number.isFinite(numeric)) return raw;\n\n const magnitude = Math.abs(numeric);\n let body: string;\n let negative = numeric < 0;\n\n switch (conversion) {\n case 'd':\n case 'i':\n body = String(Math.trunc(magnitude));\n break;\n case 'u':\n body = String(Math.trunc(magnitude));\n negative = false;\n break;\n case 'o':\n body = `${flags.includes('#') ? '0' : ''}${Math.trunc(magnitude).toString(8)}`;\n break;\n case 'x':\n body = `${flags.includes('#') ? '0x' : ''}${Math.trunc(magnitude).toString(16)}`;\n break;\n case 'X':\n body = `${flags.includes('#') ? '0X' : ''}${Math.trunc(magnitude).toString(16).toUpperCase()}`;\n break;\n case 'f':\n case 'F':\n body = magnitude.toFixed(precision ?? 6);\n break;\n case 'e':\n body = toExponential(magnitude, precision ?? 6);\n break;\n case 'E':\n body = toExponential(magnitude, precision ?? 6).toUpperCase();\n break;\n case 'g':\n body = toGeneral(magnitude, precision ?? 6);\n break;\n case 'G':\n body = toGeneral(magnitude, precision ?? 6).toUpperCase();\n break;\n default:\n return raw;\n }\n\n const sign = negative ? '-' : flags.includes('+') ? '+' : flags.includes(' ') ? ' ' : '';\n return `${sign}${body}`;\n}\n\nfunction pad(text: string, flags: string, width: number): string {\n if (text.length >= width) return text;\n const fill = width - text.length;\n\n if (flags.includes('-')) return `${text}${' '.repeat(fill)}`;\n if (flags.includes('0')) {\n // Zero padding goes between the sign and the digits, never before the sign.\n const [, sign, digits] = /^([-+ ]?)(.*)$/.exec(text)!;\n return `${sign}${'0'.repeat(fill)}${digits}`;\n }\n return `${' '.repeat(fill)}${text}`;\n}\n\n/**\n * Renders `value` through a QTI `format` string. The value is passed as the raw string held in\n * the item context, so integers and floats arrive as `\"5\"` / `\"3.14\"` and are coerced per\n * conversion.\n */\nexport function applyFormat(format: string, value: string): string {\n return format.replace(CONVERSION, (_match, percent, flags, width, precision, conversion) => {\n if (percent) return '%';\n return pad(\n convert(conversion, flags ?? '', precision === undefined ? undefined : Number(precision), value),\n flags ?? '',\n width === undefined ? 0 : Number(width)\n );\n });\n}\n"]}
|
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
import { LitElement, nothing } from 'lit';
|
|
2
2
|
import type { VariableDeclaration } from '@qti-components/base';
|
|
3
3
|
import type { ItemContext } from '@qti-components/base';
|
|
4
|
+
/**
|
|
5
|
+
* @summary Prints the value of an item variable into the item body.
|
|
6
|
+
* @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#printedVariable
|
|
7
|
+
*
|
|
8
|
+
* A container variable is printed as its values joined by `delimiter`; a record as
|
|
9
|
+
* `name<mapping-indicator>value` pairs joined by the same delimiter. A NULL variable — absent,
|
|
10
|
+
* null, or an empty container — prints nothing at all.
|
|
11
|
+
*
|
|
12
|
+
* @attr {string} identifier - Required. Identifier of the variable to print.
|
|
13
|
+
* @attr {string} [format] - printf-style conversion string, e.g. `%.2f` or `Score: %d`. Applies
|
|
14
|
+
* to each value printed. Takes precedence over `base` and `power-form`.
|
|
15
|
+
* @attr {number} [base=10] - Number base used when printing an integer variable.
|
|
16
|
+
* @attr {number} [index] - 1-based index selecting a single value of an ordered variable.
|
|
17
|
+
* @attr {boolean} [power-form=false] - Print numeric values in exponential form.
|
|
18
|
+
* @attr {string} [field] - Field of a record variable to print, instead of the whole record.
|
|
19
|
+
* @attr {string} [delimiter=;] - Separator between the values of an ordered/multiple/record variable.
|
|
20
|
+
* @attr {string} [mapping-indicator==] - Separator between a record field's name and its value.
|
|
21
|
+
*/
|
|
4
22
|
export declare class QtiPrintedVariable extends LitElement {
|
|
23
|
+
#private;
|
|
5
24
|
identifier: string;
|
|
25
|
+
format?: string;
|
|
26
|
+
base: number;
|
|
27
|
+
index?: number;
|
|
28
|
+
powerForm: boolean;
|
|
29
|
+
field?: string;
|
|
30
|
+
delimiter: string;
|
|
31
|
+
mappingIndicator: string;
|
|
6
32
|
protected context?: ItemContext;
|
|
7
33
|
render(): import("lit").TemplateResult<1> | typeof nothing;
|
|
8
34
|
calculate(): VariableDeclaration<string | string[]>;
|
|
@@ -8,10 +8,83 @@ import { consume } from '@lit/context';
|
|
|
8
8
|
import { LitElement, html, nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { itemContext } from '@qti-components/base';
|
|
11
|
+
import { applyFormat, toExponential } from './printed-variable-format';
|
|
12
|
+
/**
|
|
13
|
+
* @summary Prints the value of an item variable into the item body.
|
|
14
|
+
* @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#printedVariable
|
|
15
|
+
*
|
|
16
|
+
* A container variable is printed as its values joined by `delimiter`; a record as
|
|
17
|
+
* `name<mapping-indicator>value` pairs joined by the same delimiter. A NULL variable — absent,
|
|
18
|
+
* null, or an empty container — prints nothing at all.
|
|
19
|
+
*
|
|
20
|
+
* @attr {string} identifier - Required. Identifier of the variable to print.
|
|
21
|
+
* @attr {string} [format] - printf-style conversion string, e.g. `%.2f` or `Score: %d`. Applies
|
|
22
|
+
* to each value printed. Takes precedence over `base` and `power-form`.
|
|
23
|
+
* @attr {number} [base=10] - Number base used when printing an integer variable.
|
|
24
|
+
* @attr {number} [index] - 1-based index selecting a single value of an ordered variable.
|
|
25
|
+
* @attr {boolean} [power-form=false] - Print numeric values in exponential form.
|
|
26
|
+
* @attr {string} [field] - Field of a record variable to print, instead of the whole record.
|
|
27
|
+
* @attr {string} [delimiter=;] - Separator between the values of an ordered/multiple/record variable.
|
|
28
|
+
* @attr {string} [mapping-indicator==] - Separator between a record field's name and its value.
|
|
29
|
+
*/
|
|
11
30
|
export class QtiPrintedVariable extends LitElement {
|
|
31
|
+
constructor() {
|
|
32
|
+
super(...arguments);
|
|
33
|
+
this.base = 10;
|
|
34
|
+
this.powerForm = false;
|
|
35
|
+
this.delimiter = ';';
|
|
36
|
+
this.mappingIndicator = '=';
|
|
37
|
+
}
|
|
12
38
|
render() {
|
|
13
|
-
const
|
|
14
|
-
|
|
39
|
+
const variable = this.context?.variables.find(v => v.identifier === this.identifier);
|
|
40
|
+
const printed = variable ? this.#print(variable) : null;
|
|
41
|
+
return printed === null ? nothing : html `${printed}`;
|
|
42
|
+
}
|
|
43
|
+
/** Returns `null` for anything QTI treats as NULL, which prints as nothing. */
|
|
44
|
+
#print(variable) {
|
|
45
|
+
const value = variable.value;
|
|
46
|
+
if (value === null || value === undefined)
|
|
47
|
+
return null;
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
if (value.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
if (this.index !== undefined) {
|
|
52
|
+
const selected = value[this.index - 1];
|
|
53
|
+
return selected === null || selected === undefined ? null : this.#printValue(selected, variable.baseType);
|
|
54
|
+
}
|
|
55
|
+
return value.map(entry => this.#printValue(entry, variable.baseType)).join(this.delimiter);
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === 'object') {
|
|
58
|
+
const record = value;
|
|
59
|
+
if (this.field !== undefined) {
|
|
60
|
+
const selected = record[this.field];
|
|
61
|
+
return selected === null || selected === undefined ? null : this.#printValue(selected, variable.baseType);
|
|
62
|
+
}
|
|
63
|
+
const fields = Object.entries(record).filter(([, entry]) => entry !== null && entry !== undefined);
|
|
64
|
+
if (fields.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
return fields
|
|
67
|
+
.map(([name, entry]) => `${name}${this.mappingIndicator}${this.#printValue(entry, variable.baseType)}`)
|
|
68
|
+
.join(this.delimiter);
|
|
69
|
+
}
|
|
70
|
+
return this.#printValue(value, variable.baseType);
|
|
71
|
+
}
|
|
72
|
+
#printValue(value, baseType) {
|
|
73
|
+
// A pair/directedPair arrives as its two identifiers; QTI writes them space separated.
|
|
74
|
+
const raw = Array.isArray(value) ? value.join(' ') : String(value);
|
|
75
|
+
if (this.format)
|
|
76
|
+
return applyFormat(this.format, raw);
|
|
77
|
+
if (baseType === 'integer' && this.base !== 10) {
|
|
78
|
+
const integer = Number.parseInt(raw, 10);
|
|
79
|
+
if (Number.isFinite(integer))
|
|
80
|
+
return integer.toString(this.base);
|
|
81
|
+
}
|
|
82
|
+
if (this.powerForm) {
|
|
83
|
+
const numeric = Number(raw);
|
|
84
|
+
if (raw.trim() !== '' && Number.isFinite(numeric))
|
|
85
|
+
return toExponential(numeric);
|
|
86
|
+
}
|
|
87
|
+
return raw;
|
|
15
88
|
}
|
|
16
89
|
calculate() {
|
|
17
90
|
const result = this.context.variables.find(v => v.identifier === this.identifier) || null;
|
|
@@ -21,6 +94,27 @@ export class QtiPrintedVariable extends LitElement {
|
|
|
21
94
|
__decorate([
|
|
22
95
|
property({ type: String })
|
|
23
96
|
], QtiPrintedVariable.prototype, "identifier", void 0);
|
|
97
|
+
__decorate([
|
|
98
|
+
property({ type: String })
|
|
99
|
+
], QtiPrintedVariable.prototype, "format", void 0);
|
|
100
|
+
__decorate([
|
|
101
|
+
property({ type: Number })
|
|
102
|
+
], QtiPrintedVariable.prototype, "base", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
property({ type: Number })
|
|
105
|
+
], QtiPrintedVariable.prototype, "index", void 0);
|
|
106
|
+
__decorate([
|
|
107
|
+
property({ type: Boolean, attribute: 'power-form' })
|
|
108
|
+
], QtiPrintedVariable.prototype, "powerForm", void 0);
|
|
109
|
+
__decorate([
|
|
110
|
+
property({ type: String })
|
|
111
|
+
], QtiPrintedVariable.prototype, "field", void 0);
|
|
112
|
+
__decorate([
|
|
113
|
+
property({ type: String })
|
|
114
|
+
], QtiPrintedVariable.prototype, "delimiter", void 0);
|
|
115
|
+
__decorate([
|
|
116
|
+
property({ type: String, attribute: 'mapping-indicator' })
|
|
117
|
+
], QtiPrintedVariable.prototype, "mappingIndicator", void 0);
|
|
24
118
|
__decorate([
|
|
25
119
|
consume({ context: itemContext, subscribe: true }),
|
|
26
120
|
state()
|