@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.
Files changed (92) hide show
  1. package/dist/components/index.d.ts +4 -0
  2. package/dist/components/index.js +4 -0
  3. package/dist/components/index.js.map +1 -1
  4. package/dist/components/qti-container-size/qti-container-size.d.ts +2 -0
  5. package/dist/components/qti-container-size/qti-container-size.js +3 -0
  6. package/dist/components/qti-container-size/qti-container-size.js.map +1 -1
  7. package/dist/components/qti-contains/qti-contains.d.ts +24 -3
  8. package/dist/components/qti-contains/qti-contains.js +82 -30
  9. package/dist/components/qti-contains/qti-contains.js.map +1 -1
  10. package/dist/components/qti-default/qti-default.js +0 -1
  11. package/dist/components/qti-default/qti-default.js.map +1 -1
  12. package/dist/components/qti-divide/qti-divide.d.ts +3 -0
  13. package/dist/components/qti-divide/qti-divide.js +4 -0
  14. package/dist/components/qti-divide/qti-divide.js.map +1 -1
  15. package/dist/components/qti-equal/qti-equal.d.ts +12 -1
  16. package/dist/components/qti-equal/qti-equal.js +51 -7
  17. package/dist/components/qti-equal/qti-equal.js.map +1 -1
  18. package/dist/components/qti-equal-rounded/qti-equal-rounded.js +9 -1
  19. package/dist/components/qti-equal-rounded/qti-equal-rounded.js.map +1 -1
  20. package/dist/components/qti-exit-response/qti-exit-response.d.ts +17 -0
  21. package/dist/components/qti-exit-response/qti-exit-response.js +16 -0
  22. package/dist/components/qti-exit-response/qti-exit-response.js.map +1 -0
  23. package/dist/components/qti-exit-test/qti-exit-test.d.ts +16 -0
  24. package/dist/components/qti-exit-test/qti-exit-test.js +15 -0
  25. package/dist/components/qti-exit-test/qti-exit-test.js.map +1 -0
  26. package/dist/components/qti-field-value/qti-field-value.d.ts +7 -1
  27. package/dist/components/qti-field-value/qti-field-value.js +15 -12
  28. package/dist/components/qti-field-value/qti-field-value.js.map +1 -1
  29. package/dist/components/qti-gte/qti-gte.js +1 -1
  30. package/dist/components/qti-gte/qti-gte.js.map +1 -1
  31. package/dist/components/qti-integer-divide/qti-integer-divide.d.ts +2 -0
  32. package/dist/components/qti-integer-divide/qti-integer-divide.js +3 -0
  33. package/dist/components/qti-integer-divide/qti-integer-divide.js.map +1 -1
  34. package/dist/components/qti-integer-modulus/qti-integer-modulus.d.ts +3 -1
  35. package/dist/components/qti-integer-modulus/qti-integer-modulus.js +9 -2
  36. package/dist/components/qti-integer-modulus/qti-integer-modulus.js.map +1 -1
  37. package/dist/components/qti-integer-to-float/qti-integer-to-float.d.ts +2 -0
  38. package/dist/components/qti-integer-to-float/qti-integer-to-float.js +3 -0
  39. package/dist/components/qti-integer-to-float/qti-integer-to-float.js.map +1 -1
  40. package/dist/components/qti-is-null/qti-is-null.js +4 -1
  41. package/dist/components/qti-is-null/qti-is-null.js.map +1 -1
  42. package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.d.ts +2 -1
  43. package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.js +29 -5
  44. package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.js.map +1 -1
  45. package/dist/components/qti-lte/qti-lte.js +1 -1
  46. package/dist/components/qti-lte/qti-lte.js.map +1 -1
  47. package/dist/components/qti-map-response-point/qti-map-response-point.js +6 -0
  48. package/dist/components/qti-map-response-point/qti-map-response-point.js.map +1 -1
  49. package/dist/components/qti-math-constant/qti-math-constant.d.ts +20 -0
  50. package/dist/components/qti-math-constant/qti-math-constant.js +40 -0
  51. package/dist/components/qti-math-constant/qti-math-constant.js.map +1 -0
  52. package/dist/components/qti-math-operator/qti-math-operator.d.ts +6 -0
  53. package/dist/components/qti-math-operator/qti-math-operator.js +43 -1
  54. package/dist/components/qti-math-operator/qti-math-operator.js.map +1 -1
  55. package/dist/components/qti-multiple/qti-multiple.js +0 -2
  56. package/dist/components/qti-multiple/qti-multiple.js.map +1 -1
  57. package/dist/components/qti-pattern-match/qti-pattern-match.d.ts +1 -0
  58. package/dist/components/qti-pattern-match/qti-pattern-match.js +28 -4
  59. package/dist/components/qti-pattern-match/qti-pattern-match.js.map +1 -1
  60. package/dist/components/qti-power/qti-power.d.ts +2 -0
  61. package/dist/components/qti-power/qti-power.js +3 -0
  62. package/dist/components/qti-power/qti-power.js.map +1 -1
  63. package/dist/components/qti-printed-variable/printed-variable-format.d.ts +19 -0
  64. package/dist/components/qti-printed-variable/printed-variable-format.js +113 -0
  65. package/dist/components/qti-printed-variable/printed-variable-format.js.map +1 -0
  66. package/dist/components/qti-printed-variable/qti-printed-variable.d.ts +26 -0
  67. package/dist/components/qti-printed-variable/qti-printed-variable.js +96 -2
  68. package/dist/components/qti-printed-variable/qti-printed-variable.js.map +1 -1
  69. package/dist/components/qti-random/qti-random.js +0 -1
  70. package/dist/components/qti-random/qti-random.js.map +1 -1
  71. package/dist/components/qti-repeat/qti-repeat.d.ts +0 -8
  72. package/dist/components/qti-repeat/qti-repeat.js +15 -2
  73. package/dist/components/qti-repeat/qti-repeat.js.map +1 -1
  74. package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.d.ts +19 -0
  75. package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.js +19 -0
  76. package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.js.map +1 -0
  77. package/dist/components/qti-round/qti-round.d.ts +2 -0
  78. package/dist/components/qti-round/qti-round.js +3 -0
  79. package/dist/components/qti-round/qti-round.js.map +1 -1
  80. package/dist/components/qti-round-to/qti-round-to.d.ts +2 -0
  81. package/dist/components/qti-round-to/qti-round-to.js +3 -0
  82. package/dist/components/qti-round-to/qti-round-to.js.map +1 -1
  83. package/dist/components/qti-stats-operator/qti-stats-operator.d.ts +3 -1
  84. package/dist/components/qti-stats-operator/qti-stats-operator.js +29 -7
  85. package/dist/components/qti-stats-operator/qti-stats-operator.js.map +1 -1
  86. package/dist/components/qti-truncate/qti-truncate.d.ts +2 -0
  87. package/dist/components/qti-truncate/qti-truncate.js +3 -0
  88. package/dist/components/qti-truncate/qti-truncate.js.map +1 -1
  89. package/dist/elements.d.ts +17 -1
  90. package/dist/elements.js +9 -1
  91. package/dist/elements.js.map +1 -1
  92. 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.log(values[0]);
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":";;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;GAOG;AACH,MAAM,OAAO,eAAgB,SAAQ,aAA4B;IAAjE;;QAC8B,SAAI,GAAW,EAAE,CAAC;IA0LhD,CAAC;IAxLiB,SAAS;QACvB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;YAC7D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAwB,CAAC;QAC5D,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,wCAAwC;QACxC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,oBAAoB;YACpB,IAAI,QAAQ,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;gBACtC,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;gBAC/D,OAAO,IAAI,CAAC;YACd,CAAC;YAED,gEAAgE;YAChE,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClC,OAAO,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;gBACjF,OAAO,IAAI,CAAC;YACd,CAAC;YAED,qBAAqB;YACrB,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC5D,OAAO,IAAI,CAAC;YACd,CAAC;YAED,kBAAkB;YAClB,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBACrE,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;gBAChE,OAAO,IAAI,CAAC;YACd,CAAC;YAED,oBAAoB;YACpB,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAe,CAAC,CAAC;YACtD,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpB,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;gBACvD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxB,CAAC;QAED,qCAAqC;QACrC,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAED,iBAAiB,CAAC,SAAiB,EAAE,MAAgB;QACnD,IAAI,CAAC;YACH,QAAQ,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;gBAChC,4BAA4B;gBAC5B,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE7B,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE7B,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE7B,KAAK,MAAM;oBACT,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;wBAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;oBACnE,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE9B,KAAK,MAAM;oBACT,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;wBAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;oBACnE,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE9B,KAAK,MAAM;oBACT,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE9B,KAAK,MAAM;oBACT,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE9B,KAAK,MAAM;oBACT,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE9B,KAAK,MAAM;oBACT,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE9B,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;wBAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;oBAClD,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE7B,KAAK,IAAI;oBACP,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;wBAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;oBAClD,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE7B,KAAK,OAAO;oBACV,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;wBAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;oBAClD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE/B,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE7B,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE7B,KAAK,MAAM;oBACT,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;wBAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;oBACjD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE9B,KAAK,QAAQ;oBACX,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,gCAAgC;gBAE/D,KAAK,OAAO;oBACV,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB;gBAElD,KAAK,MAAM;oBACT,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB;gBAEjD,KAAK,OAAO;oBACV,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE/B,yBAAyB;gBACzB,KAAK,OAAO;oBACV,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE1C,KAAK,KAAK,CAAC,CAAC,CAAC;oBACX,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBACxB,OAAO,IAAI,CAAC;oBACd,CAAC;oBACD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;oBACvB,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;oBAC3B,0BAA0B;oBAC1B,IAAI,IAAI,KAAK,CAAC,IAAI,QAAQ,IAAI,CAAC;wBAAE,OAAO,IAAI,CAAC;oBAC7C,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACzD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBAClC,CAAC;gBACD,KAAK,UAAU,CAAC,CAAC,CAAC;oBAChB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBACxB,OAAO,IAAI,CAAC;oBACd,CAAC;oBACD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;oBACxB,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;oBACvB,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;oBACzE,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC1C,CAAC;gBACD,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACrC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;wBAAE,OAAO,IAAI,CAAC,CAAC,mBAAmB;oBACrD,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBAE/B,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACnC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;gBAE7B,KAAK,KAAK;oBACR,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACnC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;gBAE7B;oBACE,OAAO,CAAC,KAAK,CAAC,yCAAyC,SAAS,GAAG,CAAC,CAAC;oBACrE,OAAO,IAAI,CAAC;YAChB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,kDAAkD,SAAS,IAAI,EAAE,KAAK,CAAC,CAAC;YACtF,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF;AA1L6B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;6CAAmB","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,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/D,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,0BAA0B;QAC1B,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 console.debug('qti-multiple getResult called', this.innerHTML);\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 // console.log(variables);\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"]}
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(this.pattern).test(variable.value.toString());
60
+ return new RegExp(source, 'u');
42
61
  }
43
- catch (error) {
44
- console.error('qti-pattern-match requires a valid regular expression', error);
45
- return null;
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;IA+B3C,CAAC;IA7BiB,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,IAAI,CAAC;YACH,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,uDAAuD,EAAE,KAAK,CAAC,CAAC;YAC9E,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF;AA/B6B;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 try {\n return new RegExp(this.pattern).test(variable.value.toString());\n } catch (error) {\n console.error('qti-pattern-match requires a valid regular expression', error);\n return null;\n }\n }\n}\n"]}
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":"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","sourcesContent":["import { QtiExpression } from '@qti-components/base';\n\nimport type { 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 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"]}
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 value = this.context?.variables.find(v => v.identifier === this.identifier)?.value;
14
- return value === null ? nothing : html `${JSON.stringify(value, null, 2)}`;
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()