@conform-ed/contracts 0.0.3

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 (187) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-format.log +6 -0
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/.turbo/turbo-test.log +196 -0
  5. package/.turbo/turbo-typecheck.log +2 -0
  6. package/README.md +52 -0
  7. package/caliper-v1_2-zod-templates.md +123 -0
  8. package/case-v1_1-zod-templates.md +290 -0
  9. package/cat-v1_0-zod-templates.md +73 -0
  10. package/cc-v1_3-zod-templates.md +531 -0
  11. package/cc-v1_4-zod-templates.md +247 -0
  12. package/clr-v2_0-zod-templates.md +117 -0
  13. package/cmi5-v1_0-zod-templates.md +9 -0
  14. package/lti-zod-templates.md +11 -0
  15. package/oneroster-v1_2-zod-templates.md +76 -0
  16. package/open-badges-v3_0-zod-templates.md +66 -0
  17. package/package.json +42 -0
  18. package/qti-v2_1-zod-templates.md +45 -0
  19. package/qti-v2_2-zod-templates.md +32 -0
  20. package/qti-v3_0_1-zod-templates.md +421 -0
  21. package/src/adapter.ts +83 -0
  22. package/src/caliper/v1_2/caliper_v1p2_bootcamp_schema.ts +781 -0
  23. package/src/caliper/v1_2/index.ts +37 -0
  24. package/src/caliper/v1_2/shared.ts +122 -0
  25. package/src/caliper/v1_2/textual_requirements.ts +219 -0
  26. package/src/case/v1_1/case_v1p1_cfassociation_jsonschema1.ts +1 -0
  27. package/src/case/v1_1/case_v1p1_cfassociationgrouping_jsonschema1.ts +1 -0
  28. package/src/case/v1_1/case_v1p1_cfassociationset_jsonschema1.ts +1 -0
  29. package/src/case/v1_1/case_v1p1_cfconceptset_jsonschema1.ts +1 -0
  30. package/src/case/v1_1/case_v1p1_cfdocument_jsonschema1.ts +1 -0
  31. package/src/case/v1_1/case_v1p1_cfdocumentset_jsonschema1.ts +1 -0
  32. package/src/case/v1_1/case_v1p1_cfitem_jsonschema1.ts +1 -0
  33. package/src/case/v1_1/case_v1p1_cfitemtypeset_jsonschema1.ts +1 -0
  34. package/src/case/v1_1/case_v1p1_cflicense_jsonschema1.ts +1 -0
  35. package/src/case/v1_1/case_v1p1_cfpackage_jsonschema1.ts +1 -0
  36. package/src/case/v1_1/case_v1p1_cfrubric_jsonschema1.ts +1 -0
  37. package/src/case/v1_1/case_v1p1_cfsubjectset_jsonschema1.ts +1 -0
  38. package/src/case/v1_1/case_v1p1_imsx_statusinfo_jsonschema1.ts +1 -0
  39. package/src/case/v1_1/case_v1p1_openapi3_restbinding_schema.ts +113 -0
  40. package/src/case/v1_1/index.ts +95 -0
  41. package/src/case/v1_1/shared.ts +384 -0
  42. package/src/cat/v1_0/cat_v1p0_restbinding_operations_schema.ts +75 -0
  43. package/src/cat/v1_0/index.ts +124 -0
  44. package/src/cat/v1_0/shared.ts +345 -0
  45. package/src/clr/v2_0/clr_v2p0_achievementcredential_schema.ts +16 -0
  46. package/src/clr/v2_0/clr_v2p0_clrcredential_schema.ts +9 -0
  47. package/src/clr/v2_0/clr_v2p0_endorsementcredential_schema.ts +11 -0
  48. package/src/clr/v2_0/clr_v2p0_getclrcredentialsresponse_schema.ts +1 -0
  49. package/src/clr/v2_0/clr_v2p0_imsx_statusinfo_schema.ts +8 -0
  50. package/src/clr/v2_0/clr_v2p0_profile_schema.ts +9 -0
  51. package/src/clr/v2_0/index.ts +22 -0
  52. package/src/clr/v2_0/shared.ts +206 -0
  53. package/src/cmi5/index.ts +3 -0
  54. package/src/cmi5/v1_0/index.ts +134 -0
  55. package/src/common-cartridge/v1_3/ccv1p3_imsccauth_v1p3.ts +26 -0
  56. package/src/common-cartridge/v1_3/ccv1p3_imscp_v1p2_v1p0.ts +352 -0
  57. package/src/common-cartridge/v1_3/ccv1p3_imscsmd_v1p0.ts +35 -0
  58. package/src/common-cartridge/v1_3/ccv1p3_imsdt_v1p3.ts +33 -0
  59. package/src/common-cartridge/v1_3/ccv1p3_imswl_v1p3.ts +23 -0
  60. package/src/common-cartridge/v1_3/ccv1p3_lomccltilink_v1p0.ts +14 -0
  61. package/src/common-cartridge/v1_3/ccv1p3_lommanifest_v1p0.ts +14 -0
  62. package/src/common-cartridge/v1_3/ccv1p3_lomresource_v1p0.ts +14 -0
  63. package/src/common-cartridge/v1_3/ccv1p3_qtiasiv1p2p1_v1p0.ts +964 -0
  64. package/src/common-cartridge/v1_3/index.ts +41 -0
  65. package/src/common-cartridge/v1_3/lom-internal.ts +396 -0
  66. package/src/common-cartridge/v1_3/shared.ts +68 -0
  67. package/src/common-cartridge/v1_4/core/ccv1p4_imscp_v1p2_v1p0.ts +360 -0
  68. package/src/common-cartridge/v1_4/core/ccv1p4_lommanifest_v1p0.ts +14 -0
  69. package/src/common-cartridge/v1_4/core/ccv1p4_lomresource_v1p0.ts +14 -0
  70. package/src/common-cartridge/v1_4/extension/cc_extresource_assignmentv1p0_v1p0.ts +61 -0
  71. package/src/common-cartridge/v1_4/extension/ccv1p4_cpextensionv1p2_v1p0.ts +20 -0
  72. package/src/common-cartridge/v1_4/extension/ims_openvideov1p0_v1p0.ts +325 -0
  73. package/src/common-cartridge/v1_4/index.ts +104 -0
  74. package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0.ts +19 -0
  75. package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0_thin.ts +17 -0
  76. package/src/common-cartridge/v1_4/k12/ccv1p4_lommanifest_v1p0.ts +14 -0
  77. package/src/common-cartridge/v1_4/k12/ccv1p4_lomresource_v1p0.ts +14 -0
  78. package/src/common-cartridge/v1_4/lom-internal.ts +476 -0
  79. package/src/common-cartridge/v1_4/shared/ccv1p4_imsccauth_v1p4.ts +26 -0
  80. package/src/common-cartridge/v1_4/shared/ccv1p4_imscsmd_v1p1.ts +36 -0
  81. package/src/common-cartridge/v1_4/shared/ccv1p4_imsdt_v1p4.ts +33 -0
  82. package/src/common-cartridge/v1_4/shared/ccv1p4_imslticc_v1p4.ts +45 -0
  83. package/src/common-cartridge/v1_4/shared/ccv1p4_imswl_v1p4.ts +23 -0
  84. package/src/common-cartridge/v1_4/shared/ccv1p4_lomccltilink_v1p0.ts +14 -0
  85. package/src/common-cartridge/v1_4/shared/ccv1p4_qtiasiv1p2p1_v1p0.ts +964 -0
  86. package/src/common-cartridge/v1_4/shared/imsbasiclti_v1p0p1.ts +24 -0
  87. package/src/common-cartridge/v1_4/shared/imslticm_v1p0.ts +23 -0
  88. package/src/common-cartridge/v1_4/shared/imslticp_v1p0.ts +57 -0
  89. package/src/common-cartridge/v1_4/shared/lineitem_v1p0.ts +35 -0
  90. package/src/common-cartridge/v1_4/shared/resourcea11ymetadata-20210915.ts +110 -0
  91. package/src/common-cartridge/v1_4/shared.ts +68 -0
  92. package/src/common-cartridge/v1_4/thin/ccv1p4_imscp_v1p2_v1p0.ts +243 -0
  93. package/src/common-cartridge/v1_4/thin/ccv1p4_lommanifest_v1p0.ts +14 -0
  94. package/src/common-cartridge/v1_4/thin/ccv1p4_lomresource_v1p0.ts +14 -0
  95. package/src/common-cartridge/v1_4/vdex/imsmd_loose_v1p3p2.ts +13 -0
  96. package/src/common-cartridge/v1_4/vdex/imsvdex_v1p0.ts +124 -0
  97. package/src/config.ts +121 -0
  98. package/src/index.ts +32 -0
  99. package/src/lti/ags/v2_0/index.ts +97 -0
  100. package/src/lti/deep-linking/v2_0/index.ts +100 -0
  101. package/src/lti/index.ts +26 -0
  102. package/src/lti/nrps/v2_0/index.ts +69 -0
  103. package/src/lti/proctoring/v1_0/index.ts +131 -0
  104. package/src/lti/shared.ts +66 -0
  105. package/src/lti/v1_3/index.ts +84 -0
  106. package/src/oneroster/v1_2/index.ts +156 -0
  107. package/src/oneroster/v1_2/or_v1p2_csv_binding_schema.ts +427 -0
  108. package/src/oneroster/v1_2/or_v1p2_gradebook_restbinding_schema.ts +120 -0
  109. package/src/oneroster/v1_2/or_v1p2_gradebook_service_schema.ts +243 -0
  110. package/src/oneroster/v1_2/or_v1p2_resource_restbinding_schema.ts +24 -0
  111. package/src/oneroster/v1_2/or_v1p2_resource_service_schema.ts +47 -0
  112. package/src/oneroster/v1_2/or_v1p2_rostering_restbinding_schema.ts +84 -0
  113. package/src/oneroster/v1_2/or_v1p2_rostering_service_schema.ts +288 -0
  114. package/src/oneroster/v1_2/shared.ts +90 -0
  115. package/src/open-badges/v3_0/index.ts +20 -0
  116. package/src/open-badges/v3_0/ob_v3p0_achievementcredential_schema.ts +17 -0
  117. package/src/open-badges/v3_0/ob_v3p0_endorsementcredential_schema.ts +11 -0
  118. package/src/open-badges/v3_0/ob_v3p0_getopenbadgecredentialsresponse_schema.ts +1 -0
  119. package/src/open-badges/v3_0/ob_v3p0_imsx_statusinfo_schema.ts +8 -0
  120. package/src/open-badges/v3_0/ob_v3p0_profile_schema.ts +9 -0
  121. package/src/open-badges/v3_0/shared.ts +458 -0
  122. package/src/qti/v2-internal.ts +1683 -0
  123. package/src/qti/v2_1/apipv1p0_qtiextv2p1_v1p0.ts +3 -0
  124. package/src/qti/v2_1/imsqti_metadata_v2p1.ts +3 -0
  125. package/src/qti/v2_1/imsqti_result_v2p1.ts +3 -0
  126. package/src/qti/v2_1/imsqti_usagedata_v2p1.ts +3 -0
  127. package/src/qti/v2_1/imsqti_v2p1p2.ts +71 -0
  128. package/src/qti/v2_1/index.ts +53 -0
  129. package/src/qti/v2_1/qtiv2p1_imscpv1p2_v1p0.ts +3 -0
  130. package/src/qti/v2_1/schemas.ts +15 -0
  131. package/src/qti/v2_1/shared.ts +3 -0
  132. package/src/qti/v2_2/apipv1p0_qtiextv2p2_v1p0p1.ts +3 -0
  133. package/src/qti/v2_2/imsqti_metadata_v2p2.ts +3 -0
  134. package/src/qti/v2_2/imsqti_result_v2p2.ts +3 -0
  135. package/src/qti/v2_2/imsqti_usagedata_v2p2.ts +3 -0
  136. package/src/qti/v2_2/imsqti_v2p2p4.ts +76 -0
  137. package/src/qti/v2_2/imsqtiv2p2p4_html5_v1p0.ts +8 -0
  138. package/src/qti/v2_2/index.ts +59 -0
  139. package/src/qti/v2_2/qtiv2p2_csm_v2p2.ts +4 -0
  140. package/src/qti/v2_2/qtiv2p2_imscpv1p2_v1p0.ts +3 -0
  141. package/src/qti/v2_2/schemas.ts +19 -0
  142. package/src/qti/v2_2/shared.ts +3 -0
  143. package/src/qti/v3_0_1/assessment-internal.ts +1509 -0
  144. package/src/qti/v3_0_1/imsqti_asiv3p0p1_v1p0.ts +57 -0
  145. package/src/qti/v3_0_1/imsqti_itemv3p0p1_v1p0.ts +60 -0
  146. package/src/qti/v3_0_1/imsqti_metadatav3p0_v1p0.ts +73 -0
  147. package/src/qti/v3_0_1/imsqti_outcomev3p0p1_v1p0.ts +11 -0
  148. package/src/qti/v3_0_1/imsqti_responseprocessingv3p0p1_v1p0.ts +17 -0
  149. package/src/qti/v3_0_1/imsqti_resultv3p0_v1p0.ts +222 -0
  150. package/src/qti/v3_0_1/imsqti_sectionv3p0p1_v1p0.ts +15 -0
  151. package/src/qti/v3_0_1/imsqti_stimulusv3p0p1_v1p0.ts +15 -0
  152. package/src/qti/v3_0_1/imsqti_testv3p0p1_v1p0.ts +49 -0
  153. package/src/qti/v3_0_1/imsqti_usagedatav3p0_v1p0.ts +77 -0
  154. package/src/qti/v3_0_1/imsqtiv3p0_afa3p0pnp_v1p0.ts +269 -0
  155. package/src/qti/v3_0_1/index.ts +50 -0
  156. package/src/qti/v3_0_1/processing-internal.ts +667 -0
  157. package/src/qti/v3_0_1/shared.ts +146 -0
  158. package/src/qti/v3_0_1/variables-internal.ts +274 -0
  159. package/src/shared/imsx-status.ts +49 -0
  160. package/src/summary.ts +70 -0
  161. package/src/vc-data-model/v2_0/index.ts +27 -0
  162. package/src/vc-data-model/v2_0/shared.ts +206 -0
  163. package/src/xapi/index.ts +12 -0
  164. package/src/xapi/shared.ts +444 -0
  165. package/src/xapi/v1_0_3/index.ts +169 -0
  166. package/src/xapi/v2_0/index.ts +256 -0
  167. package/test/caliper-v1_2.test.ts +270 -0
  168. package/test/case-v1_1.test.ts +147 -0
  169. package/test/cat-v1_0.test.ts +338 -0
  170. package/test/cc-v1_4.test.ts +239 -0
  171. package/test/clr-v2_0.test.ts +225 -0
  172. package/test/cmi5-v1_0.test.ts +196 -0
  173. package/test/contracts.test.ts +125 -0
  174. package/test/fixtures/cmi5/extended-cmi5.xml +72 -0
  175. package/test/fixtures/cmi5/simple-cmi5.xml +26 -0
  176. package/test/lti.test.ts +146 -0
  177. package/test/oneroster-v1_2.test.ts +234 -0
  178. package/test/open-badges-v3_0.test.ts +144 -0
  179. package/test/qti-v2_1.test.ts +146 -0
  180. package/test/qti-v2_2.test.ts +154 -0
  181. package/test/qti-v3_0_1.test.ts +576 -0
  182. package/test/schema-examples.test.ts +101 -0
  183. package/test/vc-data-model-v2_0.test.ts +63 -0
  184. package/test/xapi.test.ts +384 -0
  185. package/tsconfig.json +7 -0
  186. package/vc-data-model-v2_0-zod-templates.md +43 -0
  187. package/xapi-zod-templates.md +95 -0
@@ -0,0 +1,1509 @@
1
+ import { z } from "zod";
2
+
3
+ import {
4
+ QtiDirectionSchema,
5
+ QtiIdentifierListSchema,
6
+ QtiIdentifierSchema,
7
+ QtiNavigationModeSchema,
8
+ QtiOrientationSchema,
9
+ QtiShapeSchema,
10
+ QtiShowHideSchema,
11
+ QtiStringListSchema,
12
+ QtiSuppressTtsSchema,
13
+ QtiSubmissionModeSchema,
14
+ QtiViewSchema,
15
+ XmlForeignAttributesSchema,
16
+ addIssue,
17
+ asArray,
18
+ collectDuplicates,
19
+ createXmlNodeSchema,
20
+ strictObject,
21
+ } from "./shared";
22
+ import {
23
+ QtiCatalogInfoSchema,
24
+ QtiContextDeclarationSchema,
25
+ QtiItemSessionControlSchema,
26
+ QtiOutcomeDeclarationSchema,
27
+ QtiResponseDeclarationSchema,
28
+ QtiStyleSheetSchema,
29
+ QtiTemplateDeclarationSchema,
30
+ QtiTimeLimitsSchema,
31
+ QtiVariableMappingSchema,
32
+ QtiWeightSchema,
33
+ } from "./variables-internal";
34
+ import {
35
+ QtiBranchRuleSchema,
36
+ QtiIncludeSchema,
37
+ QtiOutcomeProcessingSchema,
38
+ QtiPreConditionSchema,
39
+ QtiResponseProcessingSchema,
40
+ QtiTemplateDefaultSchema,
41
+ QtiTemplateProcessingSchema,
42
+ } from "./processing-internal";
43
+
44
+ const QtiCommonNodeShape = {
45
+ id: QtiIdentifierSchema.optional(),
46
+ class: QtiStringListSchema.optional(),
47
+ lang: z.string().optional(),
48
+ xmlLang: z.string().optional(),
49
+ label: z.string().optional(),
50
+ dir: QtiDirectionSchema.optional(),
51
+ xmlBase: z.string().optional(),
52
+ dataCatalogIdref: z.string().optional(),
53
+ dataQtiSuppressTts: QtiSuppressTtsSchema.optional(),
54
+ dataSsml: z.string().optional(),
55
+ ariaAttributes: XmlForeignAttributesSchema.optional(),
56
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
57
+ };
58
+
59
+ const QtiChoiceUseSchema = z.enum(["instructions", "scoring", "navigation"]);
60
+ const QtiTextFormatSchema = z.enum(["plain", "preformatted", "xhtml"]);
61
+
62
+ const interactionKinds = new Set<string>([
63
+ "choiceInteraction",
64
+ "inlineChoiceInteraction",
65
+ "textEntryInteraction",
66
+ "extendedTextInteraction",
67
+ "matchInteraction",
68
+ "gapMatchInteraction",
69
+ "mediaInteraction",
70
+ "uploadInteraction",
71
+ "orderInteraction",
72
+ "hotTextInteraction",
73
+ "hotspotInteraction",
74
+ "associateInteraction",
75
+ "graphicAssociateInteraction",
76
+ "graphicGapMatchInteraction",
77
+ "graphicOrderInteraction",
78
+ "selectPointInteraction",
79
+ "positionObjectInteraction",
80
+ "sliderInteraction",
81
+ "portableCustomInteraction",
82
+ "customInteraction",
83
+ "drawingInteraction",
84
+ "endAttemptInteraction",
85
+ ]);
86
+
87
+ export const QtiXmlContentNodeSchema: z.ZodTypeAny = z.lazy(() => createXmlNodeSchema(QtiContentFragmentSchema));
88
+
89
+ export const QtiPromptSchema: z.ZodTypeAny = z.lazy(() =>
90
+ strictObject({
91
+ kind: z.literal("prompt"),
92
+ ...QtiCommonNodeShape,
93
+ content: z.array(QtiContentFragmentSchema).optional(),
94
+ }),
95
+ );
96
+
97
+ export const QtiLabelElementSchema: z.ZodTypeAny = z.lazy(() =>
98
+ strictObject({
99
+ kind: z.literal("labelElement"),
100
+ ...QtiCommonNodeShape,
101
+ content: z.array(QtiContentFragmentSchema).optional(),
102
+ }),
103
+ );
104
+
105
+ export const QtiPrintedVariableSchema = strictObject({
106
+ kind: z.literal("printedVariable"),
107
+ id: QtiIdentifierSchema.optional(),
108
+ class: QtiStringListSchema.optional(),
109
+ xmlLang: z.string().optional(),
110
+ label: z.string().optional(),
111
+ xmlBase: z.string().optional(),
112
+ identifier: QtiIdentifierSchema,
113
+ format: z.string().optional(),
114
+ base: z.union([z.number().int(), z.string()]).optional(),
115
+ index: z.union([z.number().int(), z.string()]).optional(),
116
+ powerForm: z.boolean().optional(),
117
+ field: z.string().optional(),
118
+ delimiter: z.string().optional(),
119
+ mappingIndicator: z.string().optional(),
120
+ });
121
+
122
+ export const QtiInteractionModuleSchema = strictObject({
123
+ kind: z.literal("interactionModule"),
124
+ id: QtiIdentifierSchema,
125
+ primaryPath: z.string().optional(),
126
+ fallbackPath: z.string().optional(),
127
+ });
128
+
129
+ export const QtiInteractionModulesSchema = strictObject({
130
+ kind: z.literal("interactionModules"),
131
+ primaryConfiguration: z.string().optional(),
132
+ secondaryConfiguration: z.string().optional(),
133
+ modules: z.array(QtiInteractionModuleSchema).min(1),
134
+ });
135
+
136
+ export const QtiInteractionMarkupSchema: z.ZodTypeAny = z.lazy(() =>
137
+ strictObject({
138
+ kind: z.literal("interactionMarkup"),
139
+ content: z.array(QtiContentFragmentSchema).optional(),
140
+ }),
141
+ );
142
+
143
+ export const QtiFeedbackInlineSchema: z.ZodTypeAny = z.lazy(() =>
144
+ strictObject({
145
+ kind: z.literal("feedbackInline"),
146
+ ...QtiCommonNodeShape,
147
+ outcomeIdentifier: QtiIdentifierSchema,
148
+ identifier: QtiIdentifierSchema,
149
+ showHide: QtiShowHideSchema.optional(),
150
+ content: z.array(QtiContentFragmentSchema).optional(),
151
+ }),
152
+ );
153
+
154
+ export const QtiFeedbackBlockSchema: z.ZodTypeAny = z.lazy(() =>
155
+ strictObject({
156
+ kind: z.literal("feedbackBlock"),
157
+ ...QtiCommonNodeShape,
158
+ outcomeIdentifier: QtiIdentifierSchema,
159
+ identifier: QtiIdentifierSchema,
160
+ showHide: QtiShowHideSchema.optional(),
161
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
162
+ content: z.array(QtiContentFragmentSchema).optional(),
163
+ catalogInfo: QtiCatalogInfoSchema.optional(),
164
+ }),
165
+ );
166
+
167
+ export const QtiTemplateInlineSchema: z.ZodTypeAny = z.lazy(() =>
168
+ strictObject({
169
+ kind: z.literal("templateInline"),
170
+ ...QtiCommonNodeShape,
171
+ templateIdentifier: QtiIdentifierSchema,
172
+ identifier: QtiIdentifierSchema,
173
+ showHide: QtiShowHideSchema.optional(),
174
+ content: z.array(QtiContentFragmentSchema).optional(),
175
+ }),
176
+ );
177
+
178
+ export const QtiTemplateBlockFeedbackBlockSchema: z.ZodTypeAny = z.lazy(() =>
179
+ strictObject({
180
+ kind: z.literal("templateBlockFeedbackBlock"),
181
+ ...QtiCommonNodeShape,
182
+ outcomeIdentifier: QtiIdentifierSchema,
183
+ identifier: QtiIdentifierSchema,
184
+ showHide: QtiShowHideSchema.optional(),
185
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
186
+ content: z.array(QtiContentFragmentSchema).optional(),
187
+ catalogInfo: QtiCatalogInfoSchema.optional(),
188
+ }),
189
+ );
190
+
191
+ export const QtiTemplateBlockSchema: z.ZodTypeAny = z.lazy(() =>
192
+ strictObject({
193
+ kind: z.literal("templateBlock"),
194
+ ...QtiCommonNodeShape,
195
+ templateIdentifier: QtiIdentifierSchema,
196
+ identifier: QtiIdentifierSchema,
197
+ showHide: QtiShowHideSchema.optional(),
198
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
199
+ content: z.array(QtiContentFragmentSchema).optional(),
200
+ catalogInfo: QtiCatalogInfoSchema.optional(),
201
+ }),
202
+ );
203
+
204
+ export const QtiRubricBlockSchema: z.ZodTypeAny = z.lazy(() =>
205
+ strictObject({
206
+ kind: z.literal("rubricBlock"),
207
+ ...QtiCommonNodeShape,
208
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
209
+ content: z.array(QtiContentFragmentSchema).optional(),
210
+ catalogInfo: QtiCatalogInfoSchema.optional(),
211
+ use: QtiChoiceUseSchema.optional(),
212
+ view: z.array(QtiViewSchema).min(1),
213
+ }),
214
+ );
215
+
216
+ export const QtiTestRubricBlockSchema: z.ZodTypeAny = z.lazy(() =>
217
+ strictObject({
218
+ kind: z.literal("testRubricBlock"),
219
+ ...QtiCommonNodeShape,
220
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
221
+ content: z.array(QtiContentFragmentSchema).optional(),
222
+ catalogInfo: QtiCatalogInfoSchema.optional(),
223
+ printedVariable: QtiPrintedVariableSchema.optional(),
224
+ use: QtiChoiceUseSchema.optional(),
225
+ view: z.array(QtiViewSchema).min(1),
226
+ }),
227
+ );
228
+
229
+ export const QtiModalFeedbackSchema: z.ZodTypeAny = z.lazy(() =>
230
+ strictObject({
231
+ kind: z.literal("modalFeedback"),
232
+ outcomeIdentifier: QtiIdentifierSchema,
233
+ showHide: QtiShowHideSchema,
234
+ identifier: QtiIdentifierSchema,
235
+ title: z.string().optional(),
236
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
237
+ content: z.array(QtiContentFragmentSchema).optional(),
238
+ catalogInfo: QtiCatalogInfoSchema.optional(),
239
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
240
+ }),
241
+ );
242
+
243
+ export const QtiTestFeedbackSchema: z.ZodTypeAny = z.lazy(() =>
244
+ strictObject({
245
+ kind: z.literal("testFeedback"),
246
+ access: z.enum(["atEnd", "during"]),
247
+ outcomeIdentifier: QtiIdentifierSchema,
248
+ showHide: QtiShowHideSchema,
249
+ identifier: QtiIdentifierSchema,
250
+ title: z.string().optional(),
251
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
252
+ content: z.array(QtiContentFragmentSchema).optional(),
253
+ catalogInfo: QtiCatalogInfoSchema.optional(),
254
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
255
+ }),
256
+ );
257
+
258
+ export const QtiGapSchema = strictObject({
259
+ kind: z.literal("gap"),
260
+ ...QtiCommonNodeShape,
261
+ identifier: QtiIdentifierSchema,
262
+ templateIdentifier: QtiIdentifierSchema.optional(),
263
+ showHide: QtiShowHideSchema.optional(),
264
+ matchGroup: QtiIdentifierListSchema.optional(),
265
+ required: z.boolean().optional(),
266
+ });
267
+
268
+ export const QtiGapTextSchema: z.ZodTypeAny = z.lazy(() =>
269
+ strictObject({
270
+ kind: z.literal("gapText"),
271
+ ...QtiCommonNodeShape,
272
+ identifier: QtiIdentifierSchema,
273
+ templateIdentifier: QtiIdentifierSchema.optional(),
274
+ showHide: QtiShowHideSchema.optional(),
275
+ matchGroup: QtiIdentifierListSchema.optional(),
276
+ matchMax: z.number().int(),
277
+ matchMin: z.number().int().optional(),
278
+ content: z.array(QtiContentFragmentSchema).optional(),
279
+ }),
280
+ );
281
+
282
+ export const QtiGapImgSchema: z.ZodTypeAny = z.lazy(() =>
283
+ strictObject({
284
+ kind: z.literal("gapImg"),
285
+ ...QtiCommonNodeShape,
286
+ identifier: QtiIdentifierSchema,
287
+ templateIdentifier: QtiIdentifierSchema.optional(),
288
+ showHide: QtiShowHideSchema.optional(),
289
+ matchGroup: QtiIdentifierListSchema.optional(),
290
+ matchMax: z.number().int(),
291
+ matchMin: z.number().int().optional(),
292
+ objectLabel: z.string().optional(),
293
+ top: z.string().optional(),
294
+ left: z.string().optional(),
295
+ media: QtiXmlContentNodeSchema,
296
+ }),
297
+ );
298
+
299
+ export const QtiHotTextSchema: z.ZodTypeAny = z.lazy(() =>
300
+ strictObject({
301
+ kind: z.literal("hotText"),
302
+ ...QtiCommonNodeShape,
303
+ identifier: QtiIdentifierSchema,
304
+ templateIdentifier: QtiIdentifierSchema.optional(),
305
+ showHide: QtiShowHideSchema.optional(),
306
+ content: z.array(QtiContentFragmentSchema).optional(),
307
+ }),
308
+ );
309
+
310
+ export const QtiHotspotChoiceSchema = strictObject({
311
+ kind: z.literal("hotspotChoice"),
312
+ ...QtiCommonNodeShape,
313
+ identifier: QtiIdentifierSchema,
314
+ templateIdentifier: QtiIdentifierSchema.optional(),
315
+ showHide: QtiShowHideSchema.optional(),
316
+ shape: QtiShapeSchema,
317
+ coords: z.string(),
318
+ hotspotLabel: z.string().optional(),
319
+ });
320
+
321
+ export const QtiAssociableHotspotSchema = strictObject({
322
+ kind: z.literal("associableHotspot"),
323
+ ...QtiCommonNodeShape,
324
+ identifier: QtiIdentifierSchema,
325
+ templateIdentifier: QtiIdentifierSchema.optional(),
326
+ showHide: QtiShowHideSchema.optional(),
327
+ matchGroup: QtiIdentifierListSchema.optional(),
328
+ matchMax: z.number().int().optional(),
329
+ shape: QtiShapeSchema,
330
+ coords: z.string(),
331
+ hotspotLabel: z.string().optional(),
332
+ });
333
+
334
+ export const QtiSimpleChoiceSchema: z.ZodTypeAny = z.lazy(() =>
335
+ strictObject({
336
+ kind: z.literal("simpleChoice"),
337
+ ...QtiCommonNodeShape,
338
+ identifier: QtiIdentifierSchema,
339
+ fixed: z.boolean().optional(),
340
+ templateIdentifier: QtiIdentifierSchema.optional(),
341
+ showHide: QtiShowHideSchema.optional(),
342
+ content: z.array(QtiContentFragmentSchema).optional(),
343
+ }),
344
+ );
345
+
346
+ export const QtiSimpleAssociableChoiceSchema: z.ZodTypeAny = z.lazy(() =>
347
+ strictObject({
348
+ kind: z.literal("simpleAssociableChoice"),
349
+ ...QtiCommonNodeShape,
350
+ identifier: QtiIdentifierSchema,
351
+ fixed: z.boolean().optional(),
352
+ templateIdentifier: QtiIdentifierSchema.optional(),
353
+ showHide: QtiShowHideSchema.optional(),
354
+ matchGroup: QtiIdentifierListSchema.optional(),
355
+ matchMax: z.number().int(),
356
+ matchMin: z.number().int().optional(),
357
+ content: z.array(QtiContentFragmentSchema).optional(),
358
+ }),
359
+ );
360
+
361
+ export const QtiInlineChoiceSchema: z.ZodTypeAny = z.lazy(() =>
362
+ strictObject({
363
+ kind: z.literal("inlineChoice"),
364
+ ...QtiCommonNodeShape,
365
+ identifier: QtiIdentifierSchema,
366
+ fixed: z.boolean().optional(),
367
+ templateIdentifier: QtiIdentifierSchema.optional(),
368
+ showHide: QtiShowHideSchema.optional(),
369
+ content: z.array(QtiContentFragmentSchema).optional(),
370
+ }),
371
+ );
372
+
373
+ const QtiRenderableNodeSchema = z.union([QtiXmlContentNodeSchema, QtiIncludeSchema]);
374
+
375
+ export const QtiChoiceInteractionSchema: z.ZodTypeAny = z.lazy(() =>
376
+ strictObject({
377
+ kind: z.literal("choiceInteraction"),
378
+ ...QtiCommonNodeShape,
379
+ responseIdentifier: QtiIdentifierSchema,
380
+ prompt: QtiPromptSchema.optional(),
381
+ simpleChoices: z.array(QtiSimpleChoiceSchema).min(1),
382
+ shuffle: z.boolean().optional(),
383
+ maxChoices: z.number().int().optional(),
384
+ minChoices: z.number().int().optional(),
385
+ orientation: QtiOrientationSchema.optional(),
386
+ dataMinSelectionsMessage: z.string().optional(),
387
+ dataMaxSelectionsMessage: z.string().optional(),
388
+ }),
389
+ );
390
+
391
+ export const QtiInlineChoiceInteractionSchema: z.ZodTypeAny = z.lazy(() =>
392
+ strictObject({
393
+ kind: z.literal("inlineChoiceInteraction"),
394
+ ...QtiCommonNodeShape,
395
+ responseIdentifier: QtiIdentifierSchema,
396
+ labelElement: QtiLabelElementSchema.optional(),
397
+ inlineChoices: z.array(QtiInlineChoiceSchema).min(1),
398
+ shuffle: z.boolean().optional(),
399
+ required: z.boolean().optional(),
400
+ minChoices: z.number().int().optional(),
401
+ dataMinSelectionsMessage: z.string().optional(),
402
+ dataPrompt: z.string().optional(),
403
+ }),
404
+ );
405
+
406
+ export const QtiTextEntryInteractionSchema = strictObject({
407
+ kind: z.literal("textEntryInteraction"),
408
+ ...QtiCommonNodeShape,
409
+ responseIdentifier: QtiIdentifierSchema,
410
+ base: z.number().int().optional(),
411
+ stringIdentifier: QtiIdentifierSchema.optional(),
412
+ expectedLength: z.number().int().optional(),
413
+ patternMask: z.string().optional(),
414
+ placeholderText: z.string().optional(),
415
+ format: z.string().optional(),
416
+ dataPatternmaskMessage: z.string().optional(),
417
+ });
418
+
419
+ export const QtiExtendedTextInteractionSchema: z.ZodTypeAny = z.lazy(() =>
420
+ strictObject({
421
+ kind: z.literal("extendedTextInteraction"),
422
+ ...QtiCommonNodeShape,
423
+ responseIdentifier: QtiIdentifierSchema,
424
+ prompt: QtiPromptSchema.optional(),
425
+ base: z.number().int().optional(),
426
+ stringIdentifier: QtiIdentifierSchema.optional(),
427
+ expectedLength: z.number().int().optional(),
428
+ patternMask: z.string().optional(),
429
+ placeholderText: z.string().optional(),
430
+ maxStrings: z.number().int().optional(),
431
+ minStrings: z.number().int().optional(),
432
+ expectedLines: z.number().int().optional(),
433
+ format: QtiTextFormatSchema.optional(),
434
+ dataPatternmaskMessage: z.string().optional(),
435
+ }),
436
+ );
437
+
438
+ export const QtiSimpleMatchSetSchema: z.ZodTypeAny = z.lazy(() =>
439
+ strictObject({
440
+ kind: z.literal("simpleMatchSet"),
441
+ id: QtiIdentifierSchema.optional(),
442
+ simpleAssociableChoices: z.array(QtiSimpleAssociableChoiceSchema).optional(),
443
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
444
+ }),
445
+ );
446
+
447
+ export const QtiMatchInteractionSchema: z.ZodTypeAny = z.lazy(() =>
448
+ strictObject({
449
+ kind: z.literal("matchInteraction"),
450
+ ...QtiCommonNodeShape,
451
+ responseIdentifier: QtiIdentifierSchema,
452
+ prompt: QtiPromptSchema.optional(),
453
+ simpleMatchSets: z.array(QtiSimpleMatchSetSchema).length(2),
454
+ shuffle: z.boolean().optional(),
455
+ maxAssociations: z.number().int().optional(),
456
+ minAssociations: z.number().int().optional(),
457
+ dataMinSelectionsMessage: z.string().optional(),
458
+ dataMaxSelectionsMessage: z.string().optional(),
459
+ dataFirstColumnHeader: z.string().optional(),
460
+ }),
461
+ );
462
+
463
+ export const QtiGapChoiceSchema = z.union([QtiGapTextSchema, QtiGapImgSchema]);
464
+
465
+ export const QtiGapMatchInteractionSchema: z.ZodTypeAny = z.lazy(() =>
466
+ strictObject({
467
+ kind: z.literal("gapMatchInteraction"),
468
+ ...QtiCommonNodeShape,
469
+ responseIdentifier: QtiIdentifierSchema,
470
+ prompt: QtiPromptSchema.optional(),
471
+ gapChoices: z.array(QtiGapChoiceSchema).min(1),
472
+ content: z.array(QtiContentFragmentSchema).min(1),
473
+ shuffle: z.boolean().optional(),
474
+ minAssociations: z.number().int().optional(),
475
+ maxAssociations: z.number().int().optional(),
476
+ dataMinSelectionsMessage: z.string().optional(),
477
+ dataMaxSelectionsMessage: z.string().optional(),
478
+ dataChoicesContainerWidth: z.number().int().optional(),
479
+ }),
480
+ );
481
+
482
+ export const QtiMediaInteractionSchema: z.ZodTypeAny = z.lazy(() =>
483
+ strictObject({
484
+ kind: z.literal("mediaInteraction"),
485
+ ...QtiCommonNodeShape,
486
+ responseIdentifier: QtiIdentifierSchema,
487
+ prompt: QtiPromptSchema.optional(),
488
+ media: QtiRenderableNodeSchema,
489
+ autostart: z.boolean(),
490
+ minPlays: z.number().int().optional(),
491
+ maxPlays: z.number().int().optional(),
492
+ loop: z.boolean().optional(),
493
+ coords: z.string().optional(),
494
+ }),
495
+ );
496
+
497
+ export const QtiUploadInteractionSchema: z.ZodTypeAny = z.lazy(() =>
498
+ strictObject({
499
+ kind: z.literal("uploadInteraction"),
500
+ ...QtiCommonNodeShape,
501
+ responseIdentifier: QtiIdentifierSchema,
502
+ prompt: QtiPromptSchema.optional(),
503
+ acceptedTypes: z.array(z.string()).optional(),
504
+ }),
505
+ );
506
+
507
+ export const QtiOrderInteractionSchema: z.ZodTypeAny = z.lazy(() =>
508
+ strictObject({
509
+ kind: z.literal("orderInteraction"),
510
+ ...QtiCommonNodeShape,
511
+ responseIdentifier: QtiIdentifierSchema,
512
+ prompt: QtiPromptSchema.optional(),
513
+ simpleChoices: z.array(QtiSimpleChoiceSchema).min(1),
514
+ shuffle: z.boolean().optional(),
515
+ minChoices: z.number().int().optional(),
516
+ maxChoices: z.number().int().optional(),
517
+ orientation: QtiOrientationSchema.optional(),
518
+ dataMinSelectionsMessage: z.string().optional(),
519
+ dataMaxSelectionsMessage: z.string().optional(),
520
+ dataChoicesContainerWidth: z.number().int().optional(),
521
+ }),
522
+ );
523
+
524
+ export const QtiHotTextInteractionSchema: z.ZodTypeAny = z.lazy(() =>
525
+ strictObject({
526
+ kind: z.literal("hotTextInteraction"),
527
+ ...QtiCommonNodeShape,
528
+ responseIdentifier: QtiIdentifierSchema,
529
+ prompt: QtiPromptSchema.optional(),
530
+ content: z.array(QtiContentFragmentSchema).min(1),
531
+ maxChoices: z.number().int().optional(),
532
+ minChoices: z.number().int().optional(),
533
+ dataMinSelectionsMessage: z.string().optional(),
534
+ dataMaxSelectionsMessage: z.string().optional(),
535
+ }),
536
+ );
537
+
538
+ export const QtiHotspotInteractionSchema: z.ZodTypeAny = z.lazy(() =>
539
+ strictObject({
540
+ kind: z.literal("hotspotInteraction"),
541
+ ...QtiCommonNodeShape,
542
+ responseIdentifier: QtiIdentifierSchema,
543
+ prompt: QtiPromptSchema.optional(),
544
+ image: QtiRenderableNodeSchema,
545
+ hotspotChoices: z.array(QtiHotspotChoiceSchema).min(1),
546
+ minChoices: z.number().int().optional(),
547
+ maxChoices: z.number().int().optional(),
548
+ dataMinSelectionsMessage: z.string().optional(),
549
+ dataMaxSelectionsMessage: z.string().optional(),
550
+ }),
551
+ );
552
+
553
+ export const QtiAssociateInteractionSchema: z.ZodTypeAny = z.lazy(() =>
554
+ strictObject({
555
+ kind: z.literal("associateInteraction"),
556
+ ...QtiCommonNodeShape,
557
+ responseIdentifier: QtiIdentifierSchema,
558
+ prompt: QtiPromptSchema.optional(),
559
+ simpleAssociableChoices: z.array(QtiSimpleAssociableChoiceSchema).min(1),
560
+ shuffle: z.boolean().optional(),
561
+ maxAssociations: z.number().int().optional(),
562
+ minAssociations: z.number().int().optional(),
563
+ }),
564
+ );
565
+
566
+ export const QtiGraphicAssociateInteractionSchema: z.ZodTypeAny = z.lazy(() =>
567
+ strictObject({
568
+ kind: z.literal("graphicAssociateInteraction"),
569
+ ...QtiCommonNodeShape,
570
+ responseIdentifier: QtiIdentifierSchema,
571
+ prompt: QtiPromptSchema.optional(),
572
+ image: QtiRenderableNodeSchema,
573
+ associableHotspots: z.array(QtiAssociableHotspotSchema).min(1),
574
+ minAssociations: z.number().int().optional(),
575
+ maxAssociations: z.number().int().optional(),
576
+ }),
577
+ );
578
+
579
+ export const QtiGraphicGapMatchInteractionSchema: z.ZodTypeAny = z.lazy(() =>
580
+ strictObject({
581
+ kind: z.literal("graphicGapMatchInteraction"),
582
+ ...QtiCommonNodeShape,
583
+ responseIdentifier: QtiIdentifierSchema,
584
+ prompt: QtiPromptSchema.optional(),
585
+ image: QtiRenderableNodeSchema,
586
+ gapChoices: z.array(QtiGapChoiceSchema).min(1),
587
+ associableHotspots: z.array(QtiAssociableHotspotSchema).min(1),
588
+ minAssociations: z.number().int().optional(),
589
+ maxAssociations: z.number().int().optional(),
590
+ dataMinSelectionsMessage: z.string().optional(),
591
+ dataMaxSelectionsMessage: z.string().optional(),
592
+ dataChoicesContainerWidth: z.number().int().optional(),
593
+ }),
594
+ );
595
+
596
+ export const QtiGraphicOrderInteractionSchema: z.ZodTypeAny = z.lazy(() =>
597
+ strictObject({
598
+ kind: z.literal("graphicOrderInteraction"),
599
+ ...QtiCommonNodeShape,
600
+ responseIdentifier: QtiIdentifierSchema,
601
+ prompt: QtiPromptSchema.optional(),
602
+ image: QtiRenderableNodeSchema,
603
+ hotspotChoices: z.array(QtiHotspotChoiceSchema).min(1),
604
+ minChoices: z.number().int().optional(),
605
+ maxChoices: z.number().int().optional(),
606
+ }),
607
+ );
608
+
609
+ export const QtiSelectPointInteractionSchema: z.ZodTypeAny = z.lazy(() =>
610
+ strictObject({
611
+ kind: z.literal("selectPointInteraction"),
612
+ ...QtiCommonNodeShape,
613
+ responseIdentifier: QtiIdentifierSchema,
614
+ prompt: QtiPromptSchema.optional(),
615
+ image: QtiRenderableNodeSchema,
616
+ minChoices: z.number().int().optional(),
617
+ maxChoices: z.number().int().optional(),
618
+ }),
619
+ );
620
+
621
+ export const QtiSliderInteractionSchema: z.ZodTypeAny = z.lazy(() =>
622
+ strictObject({
623
+ kind: z.literal("sliderInteraction"),
624
+ ...QtiCommonNodeShape,
625
+ responseIdentifier: QtiIdentifierSchema,
626
+ prompt: QtiPromptSchema.optional(),
627
+ lowerBound: z.number().nonnegative(),
628
+ upperBound: z.number().nonnegative(),
629
+ step: z.number().nonnegative().optional(),
630
+ stepLabel: z.boolean().optional(),
631
+ orientation: QtiOrientationSchema.optional(),
632
+ reverse: z.boolean().optional(),
633
+ }),
634
+ );
635
+
636
+ export const QtiPositionObjectInteractionSchema: z.ZodTypeAny = z.lazy(() =>
637
+ strictObject({
638
+ kind: z.literal("positionObjectInteraction"),
639
+ ...QtiCommonNodeShape,
640
+ responseIdentifier: QtiIdentifierSchema,
641
+ image: QtiRenderableNodeSchema,
642
+ centerPoint: z.array(z.number().int()).optional(),
643
+ minChoices: z.number().int().optional(),
644
+ maxChoices: z.number().int().optional(),
645
+ }),
646
+ );
647
+
648
+ export const QtiPortableCustomInteractionSchema: z.ZodTypeAny = z.lazy(() =>
649
+ strictObject({
650
+ kind: z.literal("portableCustomInteraction"),
651
+ ...QtiCommonNodeShape,
652
+ responseIdentifier: QtiIdentifierSchema,
653
+ prompt: QtiPromptSchema.optional(),
654
+ interactionModules: QtiInteractionModulesSchema.optional(),
655
+ interactionMarkup: QtiInteractionMarkupSchema,
656
+ templateVariables: z.array(QtiIdentifierSchema).optional(),
657
+ contextVariables: z.array(QtiIdentifierSchema).optional(),
658
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
659
+ catalogInfo: QtiCatalogInfoSchema.optional(),
660
+ customInteractionTypeIdentifier: z.string(),
661
+ module: z.string().optional(),
662
+ }),
663
+ );
664
+
665
+ export const QtiCustomInteractionSchema: z.ZodTypeAny = z.lazy(() =>
666
+ strictObject({
667
+ kind: z.literal("customInteraction"),
668
+ ...QtiCommonNodeShape,
669
+ responseIdentifier: QtiIdentifierSchema,
670
+ content: z.array(QtiContentFragmentSchema).optional(),
671
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
672
+ }),
673
+ );
674
+
675
+ export const QtiDrawingInteractionSchema: z.ZodTypeAny = z.lazy(() =>
676
+ strictObject({
677
+ kind: z.literal("drawingInteraction"),
678
+ ...QtiCommonNodeShape,
679
+ responseIdentifier: QtiIdentifierSchema,
680
+ prompt: QtiPromptSchema.optional(),
681
+ content: z.array(QtiContentFragmentSchema).optional(),
682
+ attributes: XmlForeignAttributesSchema.optional(),
683
+ }),
684
+ );
685
+
686
+ export const QtiEndAttemptInteractionSchema: z.ZodTypeAny = z.lazy(() =>
687
+ strictObject({
688
+ kind: z.literal("endAttemptInteraction"),
689
+ ...QtiCommonNodeShape,
690
+ responseIdentifier: QtiIdentifierSchema.optional(),
691
+ content: z.array(QtiContentFragmentSchema).optional(),
692
+ attributes: XmlForeignAttributesSchema.optional(),
693
+ }),
694
+ );
695
+
696
+ export const QtiPositionObjectStageSchema: z.ZodTypeAny = z.lazy(() =>
697
+ strictObject({
698
+ kind: z.literal("positionObjectStage"),
699
+ id: QtiIdentifierSchema.optional(),
700
+ image: QtiRenderableNodeSchema,
701
+ positionObjectInteractions: z.array(QtiPositionObjectInteractionSchema).min(1),
702
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
703
+ }),
704
+ );
705
+
706
+ export const QtiContentFragmentSchema: z.ZodTypeAny = z.lazy(() =>
707
+ z.union([
708
+ z.string(),
709
+ QtiXmlContentNodeSchema,
710
+ QtiIncludeSchema,
711
+ QtiPrintedVariableSchema,
712
+ QtiFeedbackInlineSchema,
713
+ QtiFeedbackBlockSchema,
714
+ QtiTemplateInlineSchema,
715
+ QtiTemplateBlockSchema,
716
+ QtiTemplateBlockFeedbackBlockSchema,
717
+ QtiRubricBlockSchema,
718
+ QtiTestRubricBlockSchema,
719
+ QtiHotTextSchema,
720
+ QtiGapSchema,
721
+ QtiGapTextSchema,
722
+ QtiGapImgSchema,
723
+ QtiSimpleChoiceSchema,
724
+ QtiSimpleAssociableChoiceSchema,
725
+ QtiInlineChoiceSchema,
726
+ QtiPositionObjectStageSchema,
727
+ QtiChoiceInteractionSchema,
728
+ QtiInlineChoiceInteractionSchema,
729
+ QtiTextEntryInteractionSchema,
730
+ QtiExtendedTextInteractionSchema,
731
+ QtiMatchInteractionSchema,
732
+ QtiGapMatchInteractionSchema,
733
+ QtiMediaInteractionSchema,
734
+ QtiUploadInteractionSchema,
735
+ QtiOrderInteractionSchema,
736
+ QtiHotTextInteractionSchema,
737
+ QtiHotspotInteractionSchema,
738
+ QtiAssociateInteractionSchema,
739
+ QtiGraphicAssociateInteractionSchema,
740
+ QtiGraphicGapMatchInteractionSchema,
741
+ QtiGraphicOrderInteractionSchema,
742
+ QtiSelectPointInteractionSchema,
743
+ QtiSliderInteractionSchema,
744
+ QtiPositionObjectInteractionSchema,
745
+ QtiPortableCustomInteractionSchema,
746
+ QtiCustomInteractionSchema,
747
+ QtiDrawingInteractionSchema,
748
+ QtiEndAttemptInteractionSchema,
749
+ ]),
750
+ );
751
+
752
+ export const QtiItemBodySchema: z.ZodTypeAny = z.lazy(() =>
753
+ strictObject({
754
+ content: z.array(QtiContentFragmentSchema).min(1),
755
+ }),
756
+ );
757
+
758
+ export const QtiStimulusBodySchema: z.ZodTypeAny = z.lazy(() =>
759
+ strictObject({
760
+ ...QtiCommonNodeShape,
761
+ content: z.array(QtiContentFragmentSchema).min(1),
762
+ }),
763
+ );
764
+
765
+ export const QtiCompanionMaterialsInfoSchema = strictObject({
766
+ calculators: z.array(z.unknown()).optional(),
767
+ rules: z.array(z.unknown()).optional(),
768
+ protractors: z.array(z.unknown()).optional(),
769
+ digitalMaterials: z.array(z.unknown()).optional(),
770
+ physicalMaterials: z.array(z.string()).optional(),
771
+ extensions: z.array(z.unknown()).optional(),
772
+ });
773
+
774
+ export const QtiAdaptiveHrefSchema = strictObject({
775
+ identifier: QtiIdentifierSchema,
776
+ href: z.string(),
777
+ });
778
+
779
+ export const QtiAdaptiveSelectionSchema = strictObject({
780
+ adaptiveEngineRef: QtiAdaptiveHrefSchema,
781
+ adaptiveSettingsRef: QtiAdaptiveHrefSchema.optional(),
782
+ usagedataRef: QtiAdaptiveHrefSchema.optional(),
783
+ metadataRef: QtiAdaptiveHrefSchema.optional(),
784
+ });
785
+
786
+ export const QtiSelectionSchema = strictObject({
787
+ select: z.number().int(),
788
+ withReplacement: z.boolean().optional(),
789
+ extensions: z.array(z.unknown()).optional(),
790
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
791
+ });
792
+
793
+ export const QtiOrderingSchema = strictObject({
794
+ shuffle: z.boolean().optional(),
795
+ extensions: z.array(z.unknown()).optional(),
796
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
797
+ });
798
+
799
+ export const QtiAssessmentItemRefSchema = strictObject({
800
+ identifier: QtiIdentifierSchema,
801
+ required: z.boolean().optional(),
802
+ fixed: z.boolean().optional(),
803
+ class: QtiStringListSchema.optional(),
804
+ href: z.string(),
805
+ category: QtiIdentifierListSchema.optional(),
806
+ preConditions: z.array(QtiPreConditionSchema).optional(),
807
+ branchRules: z.array(QtiBranchRuleSchema).optional(),
808
+ itemSessionControl: QtiItemSessionControlSchema.optional(),
809
+ timeLimits: QtiTimeLimitsSchema.optional(),
810
+ variableMappings: z.array(QtiVariableMappingSchema).optional(),
811
+ weights: z.array(QtiWeightSchema).optional(),
812
+ templateDefaults: z.array(QtiTemplateDefaultSchema).optional(),
813
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
814
+ });
815
+
816
+ export const QtiAssessmentSectionRefSchema = strictObject({
817
+ identifier: QtiIdentifierSchema,
818
+ class: QtiStringListSchema.optional(),
819
+ href: z.string(),
820
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
821
+ });
822
+
823
+ export const QtiAssessmentStimulusRefSchema = strictObject({
824
+ identifier: QtiIdentifierSchema,
825
+ href: z.string(),
826
+ title: z.string().optional(),
827
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
828
+ });
829
+
830
+ function walkUnknown(value: unknown, visit: (node: Record<string, unknown>) => void) {
831
+ if (Array.isArray(value)) {
832
+ for (const entry of value) {
833
+ walkUnknown(entry, visit);
834
+ }
835
+ return;
836
+ }
837
+
838
+ if (!value || typeof value !== "object") {
839
+ return;
840
+ }
841
+
842
+ const node = value as Record<string, unknown>;
843
+ visit(node);
844
+
845
+ for (const [key, child] of Object.entries(node)) {
846
+ if (key === "foreignAttributes" || key === "ariaAttributes" || key === "attributes" || key === "extensions") {
847
+ continue;
848
+ }
849
+ walkUnknown(child, visit);
850
+ }
851
+ }
852
+
853
+ function containsInteraction(value: unknown): boolean {
854
+ let found = false;
855
+
856
+ walkUnknown(value, (node) => {
857
+ const kind = typeof node.kind === "string" ? node.kind : null;
858
+ if (kind && interactionKinds.has(kind)) {
859
+ found = true;
860
+ }
861
+ });
862
+
863
+ return found;
864
+ }
865
+
866
+ function collectInteractionNodes(value: unknown): Array<Record<string, unknown>> {
867
+ const interactions: Array<Record<string, unknown>> = [];
868
+
869
+ walkUnknown(value, (node) => {
870
+ const kind = typeof node.kind === "string" ? node.kind : null;
871
+ if (kind && interactionKinds.has(kind)) {
872
+ interactions.push(node);
873
+ }
874
+ });
875
+
876
+ return interactions;
877
+ }
878
+
879
+ function collectNodesByKind(value: unknown, kinds: readonly string[]): Array<Record<string, unknown>> {
880
+ const matches: Array<Record<string, unknown>> = [];
881
+ const kindSet = new Set(kinds);
882
+
883
+ walkUnknown(value, (node) => {
884
+ const kind = typeof node.kind === "string" ? node.kind : null;
885
+ if (kind && kindSet.has(kind)) {
886
+ matches.push(node);
887
+ }
888
+ });
889
+
890
+ return matches;
891
+ }
892
+
893
+ type QtiSectionValidationValue = {
894
+ adaptiveSelection?: unknown;
895
+ selection?: { select: number; withReplacement?: boolean };
896
+ ordering?: unknown;
897
+ children?: Array<{ identifier?: string }>;
898
+ };
899
+
900
+ type QtiModalFeedbackValidationValue = {
901
+ outcomeIdentifier: string;
902
+ content?: unknown;
903
+ };
904
+
905
+ type QtiItemValidationValue = {
906
+ contextDeclarations?: Array<{ identifier: string }>;
907
+ responseDeclarations?: Array<z.infer<typeof QtiResponseDeclarationSchema>>;
908
+ outcomeDeclarations?: Array<z.infer<typeof QtiOutcomeDeclarationSchema>>;
909
+ templateDeclarations?: Array<z.infer<typeof QtiTemplateDeclarationSchema>>;
910
+ itemBody?: { content?: unknown };
911
+ modalFeedbacks?: Array<QtiModalFeedbackValidationValue>;
912
+ responseProcessing?: unknown;
913
+ templateProcessing?: unknown;
914
+ };
915
+
916
+ type QtiTestPartValidationValue = {
917
+ identifier: string;
918
+ };
919
+
920
+ type QtiTestFeedbackValidationValue = {
921
+ outcomeIdentifier: string;
922
+ content?: unknown;
923
+ };
924
+
925
+ type QtiTestValidationValue = {
926
+ contextDeclarations?: Array<{ identifier: string }>;
927
+ outcomeDeclarations?: Array<z.infer<typeof QtiOutcomeDeclarationSchema>>;
928
+ testParts?: Array<QtiTestPartValidationValue>;
929
+ testFeedbacks?: Array<QtiTestFeedbackValidationValue>;
930
+ outcomeProcessing?: unknown;
931
+ };
932
+
933
+ function validateOutcomeDeclarationConventions(
934
+ declarations: readonly z.infer<typeof QtiOutcomeDeclarationSchema>[],
935
+ context: z.RefinementCtx,
936
+ path: Array<string | number>,
937
+ ) {
938
+ for (const [index, declaration] of declarations.entries()) {
939
+ if (
940
+ ["SCORE", "MAXSCORE"].includes(declaration.identifier) &&
941
+ (declaration.baseType !== "float" || declaration.cardinality !== "single")
942
+ ) {
943
+ addIssue(
944
+ context,
945
+ [...path, index],
946
+ `${declaration.identifier} should have baseType 'float' and cardinality 'single'.`,
947
+ );
948
+ }
949
+
950
+ if (
951
+ declaration.identifier === "PASSED" &&
952
+ (declaration.baseType !== "boolean" || declaration.cardinality !== "single")
953
+ ) {
954
+ addIssue(context, [...path, index], "PASSED should have baseType 'boolean' and cardinality 'single'.");
955
+ }
956
+ }
957
+ }
958
+
959
+ function addDuplicateSummaryIssue(
960
+ context: z.RefinementCtx,
961
+ path: Array<string | number>,
962
+ label: string,
963
+ values: string[],
964
+ ) {
965
+ const duplicates = collectDuplicates(values);
966
+ if (!duplicates.length) {
967
+ return;
968
+ }
969
+
970
+ addIssue(context, path, `${label} must be unique. Duplicates: ${duplicates.join(", ")}.`);
971
+ }
972
+
973
+ function validateResponseBinding(
974
+ interaction: Record<string, unknown>,
975
+ responsesById: Map<string, z.infer<typeof QtiResponseDeclarationSchema>>,
976
+ context: z.RefinementCtx,
977
+ path: Array<string | number>,
978
+ ) {
979
+ const kind = interaction.kind as string;
980
+ const responseIdentifier = typeof interaction.responseIdentifier === "string" ? interaction.responseIdentifier : null;
981
+
982
+ if (!responseIdentifier) {
983
+ return;
984
+ }
985
+
986
+ const declaration = responsesById.get(responseIdentifier);
987
+ if (!declaration) {
988
+ addIssue(context, path, `${kind} references undeclared response identifier '${responseIdentifier}'.`);
989
+ return;
990
+ }
991
+
992
+ const requireBaseAndCardinality = (baseTypes: readonly string[], cardinalities: readonly string[]) => {
993
+ if (!baseTypes.includes(declaration.baseType ?? "")) {
994
+ addIssue(
995
+ context,
996
+ path,
997
+ `${kind} requires response '${responseIdentifier}' to use baseType ${baseTypes.join(" or ")}.`,
998
+ );
999
+ }
1000
+
1001
+ if (!cardinalities.includes(declaration.cardinality)) {
1002
+ addIssue(
1003
+ context,
1004
+ path,
1005
+ `${kind} requires response '${responseIdentifier}' to use cardinality ${cardinalities.join(" or ")}.`,
1006
+ );
1007
+ }
1008
+ };
1009
+
1010
+ switch (kind) {
1011
+ case "choiceInteraction": {
1012
+ requireBaseAndCardinality(["identifier"], ["single", "multiple"]);
1013
+ addDuplicateSummaryIssue(
1014
+ context,
1015
+ path,
1016
+ "simpleChoice identifiers",
1017
+ asArray(interaction.simpleChoices as Array<{ identifier?: string }>).flatMap((choice) =>
1018
+ typeof choice.identifier === "string" ? [choice.identifier] : [],
1019
+ ),
1020
+ );
1021
+ const minChoices = typeof interaction.minChoices === "number" ? interaction.minChoices : undefined;
1022
+ const maxChoices = typeof interaction.maxChoices === "number" ? interaction.maxChoices : undefined;
1023
+ if (minChoices !== undefined && maxChoices !== undefined && minChoices > maxChoices) {
1024
+ addIssue(context, path, "choiceInteraction minChoices must not exceed maxChoices.");
1025
+ }
1026
+ break;
1027
+ }
1028
+
1029
+ case "inlineChoiceInteraction": {
1030
+ requireBaseAndCardinality(["identifier"], ["single"]);
1031
+ addDuplicateSummaryIssue(
1032
+ context,
1033
+ path,
1034
+ "inlineChoice identifiers",
1035
+ asArray(interaction.inlineChoices as Array<{ identifier?: string }>).flatMap((choice) =>
1036
+ typeof choice.identifier === "string" ? [choice.identifier] : [],
1037
+ ),
1038
+ );
1039
+ break;
1040
+ }
1041
+
1042
+ case "textEntryInteraction": {
1043
+ if (declaration.cardinality === "record") {
1044
+ break;
1045
+ }
1046
+ requireBaseAndCardinality(["string", "integer", "float"], ["single"]);
1047
+ break;
1048
+ }
1049
+
1050
+ case "extendedTextInteraction": {
1051
+ if (declaration.cardinality === "record") {
1052
+ break;
1053
+ }
1054
+ requireBaseAndCardinality(["string", "integer", "float"], ["single", "multiple", "ordered"]);
1055
+ const minStrings = typeof interaction.minStrings === "number" ? interaction.minStrings : undefined;
1056
+ const maxStrings = typeof interaction.maxStrings === "number" ? interaction.maxStrings : undefined;
1057
+ if (minStrings !== undefined && maxStrings !== undefined && minStrings > maxStrings) {
1058
+ addIssue(context, path, "extendedTextInteraction minStrings must not exceed maxStrings.");
1059
+ }
1060
+ break;
1061
+ }
1062
+
1063
+ case "matchInteraction": {
1064
+ requireBaseAndCardinality(["directedPair"], ["single", "multiple"]);
1065
+ const minAssociations = typeof interaction.minAssociations === "number" ? interaction.minAssociations : undefined;
1066
+ const maxAssociations = typeof interaction.maxAssociations === "number" ? interaction.maxAssociations : undefined;
1067
+ if (minAssociations !== undefined && maxAssociations !== undefined && minAssociations > maxAssociations) {
1068
+ addIssue(context, path, "matchInteraction minAssociations must not exceed maxAssociations.");
1069
+ }
1070
+ break;
1071
+ }
1072
+
1073
+ case "gapMatchInteraction": {
1074
+ requireBaseAndCardinality(["directedPair"], ["single", "multiple"]);
1075
+ addDuplicateSummaryIssue(
1076
+ context,
1077
+ path,
1078
+ "gap choice identifiers",
1079
+ asArray(interaction.gapChoices as Array<{ identifier?: string }>).flatMap((choice) =>
1080
+ typeof choice.identifier === "string" ? [choice.identifier] : [],
1081
+ ),
1082
+ );
1083
+ const minAssociations = typeof interaction.minAssociations === "number" ? interaction.minAssociations : undefined;
1084
+ const maxAssociations = typeof interaction.maxAssociations === "number" ? interaction.maxAssociations : undefined;
1085
+ if (minAssociations !== undefined && maxAssociations !== undefined && minAssociations > maxAssociations) {
1086
+ addIssue(context, path, "gapMatchInteraction minAssociations must not exceed maxAssociations.");
1087
+ }
1088
+ break;
1089
+ }
1090
+
1091
+ case "mediaInteraction": {
1092
+ requireBaseAndCardinality(["integer"], ["single"]);
1093
+ const minPlays = typeof interaction.minPlays === "number" ? interaction.minPlays : undefined;
1094
+ const maxPlays = typeof interaction.maxPlays === "number" ? interaction.maxPlays : undefined;
1095
+ if (minPlays !== undefined && maxPlays !== undefined && minPlays > maxPlays) {
1096
+ addIssue(context, path, "mediaInteraction minPlays must not exceed maxPlays.");
1097
+ }
1098
+ break;
1099
+ }
1100
+
1101
+ case "uploadInteraction": {
1102
+ requireBaseAndCardinality(["file"], ["single"]);
1103
+ break;
1104
+ }
1105
+
1106
+ case "orderInteraction": {
1107
+ requireBaseAndCardinality(["identifier"], ["ordered"]);
1108
+ addDuplicateSummaryIssue(
1109
+ context,
1110
+ path,
1111
+ "order interaction choice identifiers",
1112
+ asArray(interaction.simpleChoices as Array<{ identifier?: string }>).flatMap((choice) =>
1113
+ typeof choice.identifier === "string" ? [choice.identifier] : [],
1114
+ ),
1115
+ );
1116
+ break;
1117
+ }
1118
+
1119
+ case "hotTextInteraction": {
1120
+ requireBaseAndCardinality(["identifier"], ["single", "multiple"]);
1121
+ addDuplicateSummaryIssue(
1122
+ context,
1123
+ path,
1124
+ "hotText identifiers",
1125
+ collectNodesByKind(interaction.content, ["hotText"]).flatMap((node) =>
1126
+ typeof node.identifier === "string" ? [node.identifier] : [],
1127
+ ),
1128
+ );
1129
+ break;
1130
+ }
1131
+
1132
+ case "hotspotInteraction": {
1133
+ requireBaseAndCardinality(["identifier"], ["single", "multiple"]);
1134
+ addDuplicateSummaryIssue(
1135
+ context,
1136
+ path,
1137
+ "hotspotChoice identifiers",
1138
+ asArray(interaction.hotspotChoices as Array<{ identifier?: string }>).flatMap((choice) =>
1139
+ typeof choice.identifier === "string" ? [choice.identifier] : [],
1140
+ ),
1141
+ );
1142
+ const minChoices = typeof interaction.minChoices === "number" ? interaction.minChoices : undefined;
1143
+ const maxChoices = typeof interaction.maxChoices === "number" ? interaction.maxChoices : undefined;
1144
+ if (minChoices !== undefined && maxChoices !== undefined && minChoices > maxChoices) {
1145
+ addIssue(context, path, "hotspotInteraction minChoices must not exceed maxChoices.");
1146
+ }
1147
+ break;
1148
+ }
1149
+
1150
+ case "associateInteraction":
1151
+ case "graphicAssociateInteraction": {
1152
+ requireBaseAndCardinality(["pair"], ["single", "multiple"]);
1153
+ const minAssociations = typeof interaction.minAssociations === "number" ? interaction.minAssociations : undefined;
1154
+ const maxAssociations = typeof interaction.maxAssociations === "number" ? interaction.maxAssociations : undefined;
1155
+ if (minAssociations !== undefined && maxAssociations !== undefined && minAssociations > maxAssociations) {
1156
+ addIssue(context, path, `${kind} minAssociations must not exceed maxAssociations.`);
1157
+ }
1158
+ break;
1159
+ }
1160
+
1161
+ case "graphicGapMatchInteraction": {
1162
+ requireBaseAndCardinality(["directedPair"], ["multiple"]);
1163
+ break;
1164
+ }
1165
+
1166
+ case "graphicOrderInteraction": {
1167
+ requireBaseAndCardinality(["identifier"], ["ordered"]);
1168
+ break;
1169
+ }
1170
+
1171
+ case "selectPointInteraction":
1172
+ case "positionObjectInteraction": {
1173
+ requireBaseAndCardinality(["point"], ["single", "multiple"]);
1174
+ const minChoices = typeof interaction.minChoices === "number" ? interaction.minChoices : undefined;
1175
+ const maxChoices = typeof interaction.maxChoices === "number" ? interaction.maxChoices : undefined;
1176
+ if (minChoices !== undefined && maxChoices !== undefined && minChoices > maxChoices) {
1177
+ addIssue(context, path, `${kind} minChoices must not exceed maxChoices.`);
1178
+ }
1179
+ break;
1180
+ }
1181
+
1182
+ case "sliderInteraction": {
1183
+ requireBaseAndCardinality(["integer", "float"], ["single"]);
1184
+ const lowerBound = typeof interaction.lowerBound === "number" ? interaction.lowerBound : undefined;
1185
+ const upperBound = typeof interaction.upperBound === "number" ? interaction.upperBound : undefined;
1186
+ if (lowerBound !== undefined && upperBound !== undefined && lowerBound > upperBound) {
1187
+ addIssue(context, path, "sliderInteraction lowerBound must not exceed upperBound.");
1188
+ }
1189
+ break;
1190
+ }
1191
+ }
1192
+ }
1193
+
1194
+ function validateOutcomeReferences(
1195
+ value: unknown,
1196
+ declaredOutcomes: Map<string, unknown>,
1197
+ context: z.RefinementCtx,
1198
+ path: Array<string | number>,
1199
+ ) {
1200
+ for (const rule of collectNodesByKind(value, ["setOutcomeValue", "lookupOutcomeValue"])) {
1201
+ const identifier = typeof rule.identifier === "string" ? rule.identifier : undefined;
1202
+ if (identifier && !declaredOutcomes.has(identifier)) {
1203
+ addIssue(context, path, `Processing rule references undeclared outcome identifier '${identifier}'.`);
1204
+ }
1205
+ }
1206
+ }
1207
+
1208
+ function validateTemplateAndResponseReferences(
1209
+ value: unknown,
1210
+ declaredTemplates: Map<string, unknown>,
1211
+ declaredResponses: Map<string, unknown>,
1212
+ context: z.RefinementCtx,
1213
+ path: Array<string | number>,
1214
+ ) {
1215
+ for (const rule of collectNodesByKind(value, ["setTemplateValue"])) {
1216
+ const identifier = typeof rule.identifier === "string" ? rule.identifier : undefined;
1217
+ if (identifier && !declaredTemplates.has(identifier)) {
1218
+ addIssue(context, path, `Template processing references undeclared template identifier '${identifier}'.`);
1219
+ }
1220
+ }
1221
+
1222
+ for (const rule of collectNodesByKind(value, ["setDefaultValue", "setCorrectResponse"])) {
1223
+ const identifier = typeof rule.identifier === "string" ? rule.identifier : undefined;
1224
+ if (identifier && !declaredResponses.has(identifier)) {
1225
+ addIssue(context, path, `Template processing references undeclared response identifier '${identifier}'.`);
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ export const QtiAssessmentSectionRawSchema: z.ZodTypeAny = z.lazy(() =>
1231
+ strictObject({
1232
+ identifier: QtiIdentifierSchema,
1233
+ required: z.boolean().optional(),
1234
+ fixed: z.boolean().optional(),
1235
+ title: z.string(),
1236
+ class: QtiStringListSchema.optional(),
1237
+ visible: z.boolean(),
1238
+ keepTogether: z.boolean().optional(),
1239
+ preConditions: z.array(QtiPreConditionSchema).optional(),
1240
+ branchRules: z.array(QtiBranchRuleSchema).optional(),
1241
+ itemSessionControl: QtiItemSessionControlSchema.optional(),
1242
+ timeLimits: QtiTimeLimitsSchema.optional(),
1243
+ adaptiveSelection: QtiAdaptiveSelectionSchema.optional(),
1244
+ selection: QtiSelectionSchema.optional(),
1245
+ ordering: QtiOrderingSchema.optional(),
1246
+ rubricBlocks: z.array(QtiTestRubricBlockSchema).optional(),
1247
+ children: z
1248
+ .array(
1249
+ z.union([
1250
+ QtiIncludeSchema,
1251
+ QtiAssessmentItemRefSchema,
1252
+ QtiAssessmentSectionSchema,
1253
+ QtiAssessmentSectionRefSchema,
1254
+ ]),
1255
+ )
1256
+ .optional(),
1257
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
1258
+ }),
1259
+ );
1260
+
1261
+ export const QtiAssessmentSectionSchema: z.ZodTypeAny = QtiAssessmentSectionRawSchema.superRefine(
1262
+ (value: unknown, context) => {
1263
+ const section = value as QtiSectionValidationValue;
1264
+
1265
+ if (section.adaptiveSelection && (section.selection || section.ordering)) {
1266
+ addIssue(context, [], "adaptiveSelection cannot be combined with selection or ordering.");
1267
+ }
1268
+
1269
+ const childIdentifiers = asArray(section.children).flatMap((child) =>
1270
+ typeof child.identifier === "string" ? [child.identifier] : [],
1271
+ );
1272
+
1273
+ addDuplicateSummaryIssue(context, ["children"], "assessment section child identifiers", childIdentifiers);
1274
+
1275
+ if (
1276
+ section.selection &&
1277
+ section.selection.withReplacement !== true &&
1278
+ section.children?.length &&
1279
+ section.selection.select > section.children.length
1280
+ ) {
1281
+ addIssue(
1282
+ context,
1283
+ ["selection", "select"],
1284
+ "selection.select cannot exceed the number of available children when withReplacement is false.",
1285
+ );
1286
+ }
1287
+ },
1288
+ );
1289
+
1290
+ export const QtiTestPartSchema: z.ZodTypeAny = z.lazy(() =>
1291
+ strictObject({
1292
+ identifier: QtiIdentifierSchema,
1293
+ title: z.string().optional(),
1294
+ class: QtiStringListSchema.optional(),
1295
+ navigationMode: QtiNavigationModeSchema,
1296
+ submissionMode: QtiSubmissionModeSchema,
1297
+ preConditions: z.array(QtiPreConditionSchema).optional(),
1298
+ branchRules: z.array(QtiBranchRuleSchema).optional(),
1299
+ itemSessionControl: QtiItemSessionControlSchema.optional(),
1300
+ timeLimits: QtiTimeLimitsSchema.optional(),
1301
+ rubricBlocks: z.array(QtiTestRubricBlockSchema).optional(),
1302
+ children: z.array(z.union([QtiAssessmentSectionSchema, QtiAssessmentSectionRefSchema])).min(1),
1303
+ testFeedbacks: z.array(QtiTestFeedbackSchema).optional(),
1304
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
1305
+ }),
1306
+ );
1307
+
1308
+ export const QtiAssessmentStimulusSchema: z.ZodTypeAny = z.lazy(() =>
1309
+ strictObject({
1310
+ identifier: QtiIdentifierSchema,
1311
+ title: z.string(),
1312
+ label: z.string().optional(),
1313
+ xmlLang: z.string().optional(),
1314
+ toolName: z.string().optional(),
1315
+ toolVersion: z.string().optional(),
1316
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
1317
+ stimulusBody: QtiStimulusBodySchema,
1318
+ catalogInfo: QtiCatalogInfoSchema.optional(),
1319
+ }),
1320
+ );
1321
+
1322
+ export const QtiAssessmentItemRawSchema: z.ZodTypeAny = z.lazy(() =>
1323
+ strictObject({
1324
+ identifier: QtiIdentifierSchema,
1325
+ title: z.string(),
1326
+ label: z.string().optional(),
1327
+ xmlLang: z.string().optional(),
1328
+ toolName: z.string().optional(),
1329
+ toolVersion: z.string().optional(),
1330
+ adaptive: z.boolean().optional(),
1331
+ timeDependent: z.boolean(),
1332
+ contextDeclarations: z.array(QtiContextDeclarationSchema).optional(),
1333
+ responseDeclarations: z.array(QtiResponseDeclarationSchema).optional(),
1334
+ outcomeDeclarations: z.array(QtiOutcomeDeclarationSchema).optional(),
1335
+ templateDeclarations: z.array(QtiTemplateDeclarationSchema).optional(),
1336
+ templateProcessing: QtiTemplateProcessingSchema.optional(),
1337
+ assessmentStimulusRefs: z.array(QtiAssessmentStimulusRefSchema).optional(),
1338
+ companionMaterialsInfo: QtiCompanionMaterialsInfoSchema.optional(),
1339
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
1340
+ itemBody: QtiItemBodySchema.optional(),
1341
+ catalogInfo: QtiCatalogInfoSchema.optional(),
1342
+ responseProcessing: QtiResponseProcessingSchema.optional(),
1343
+ modalFeedbacks: z.array(QtiModalFeedbackSchema).optional(),
1344
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
1345
+ }),
1346
+ );
1347
+
1348
+ export const QtiAssessmentItemSchema: z.ZodTypeAny = QtiAssessmentItemRawSchema.superRefine(
1349
+ (value: unknown, context) => {
1350
+ const item = value as QtiItemValidationValue;
1351
+ const contextDeclarations = asArray(item.contextDeclarations);
1352
+ const responseDeclarationsList = asArray(item.responseDeclarations);
1353
+ const outcomeDeclarationsList = asArray(item.outcomeDeclarations);
1354
+ const templateDeclarationsList = asArray(item.templateDeclarations);
1355
+ const modalFeedbacks = asArray(item.modalFeedbacks);
1356
+
1357
+ const variableDeclarations = [
1358
+ ...contextDeclarations,
1359
+ ...responseDeclarationsList,
1360
+ ...outcomeDeclarationsList,
1361
+ ...templateDeclarationsList,
1362
+ ];
1363
+
1364
+ addDuplicateSummaryIssue(
1365
+ context,
1366
+ [],
1367
+ "assessment item variable declaration identifiers",
1368
+ variableDeclarations.map((declaration) => declaration.identifier),
1369
+ );
1370
+
1371
+ validateOutcomeDeclarationConventions(outcomeDeclarationsList, context, ["outcomeDeclarations"]);
1372
+
1373
+ const responseDeclarations = new Map(
1374
+ responseDeclarationsList.map((declaration) => [declaration.identifier, declaration]),
1375
+ );
1376
+ const outcomeDeclarations = new Map(
1377
+ outcomeDeclarationsList.map((declaration) => [declaration.identifier, declaration]),
1378
+ );
1379
+ const templateDeclarations = new Map(
1380
+ templateDeclarationsList.map((declaration) => [declaration.identifier, declaration]),
1381
+ );
1382
+
1383
+ const interactions = collectInteractionNodes(item.itemBody?.content);
1384
+ const responseBindings = new Map<string, string[]>();
1385
+
1386
+ for (const interaction of interactions) {
1387
+ const responseIdentifier =
1388
+ typeof interaction.responseIdentifier === "string" ? interaction.responseIdentifier : undefined;
1389
+ if (responseIdentifier) {
1390
+ const boundKinds = responseBindings.get(responseIdentifier) ?? [];
1391
+ boundKinds.push(String(interaction.kind));
1392
+ responseBindings.set(responseIdentifier, boundKinds);
1393
+ }
1394
+
1395
+ validateResponseBinding(interaction, responseDeclarations, context, ["itemBody"]);
1396
+ }
1397
+
1398
+ for (const [identifier, kinds] of responseBindings) {
1399
+ if (kinds.length > 1) {
1400
+ addIssue(
1401
+ context,
1402
+ ["itemBody"],
1403
+ `Response identifier '${identifier}' is bound by multiple interactions: ${kinds.join(", ")}.`,
1404
+ );
1405
+ }
1406
+ }
1407
+
1408
+ for (const [index, feedback] of modalFeedbacks.entries()) {
1409
+ if (!outcomeDeclarations.has(feedback.outcomeIdentifier)) {
1410
+ addIssue(
1411
+ context,
1412
+ ["modalFeedbacks", index, "outcomeIdentifier"],
1413
+ `modalFeedback references undeclared outcome identifier '${feedback.outcomeIdentifier}'.`,
1414
+ );
1415
+ }
1416
+
1417
+ if (containsInteraction(feedback.content)) {
1418
+ addIssue(context, ["modalFeedbacks", index, "content"], "modalFeedback content must not contain interactions.");
1419
+ }
1420
+ }
1421
+
1422
+ validateOutcomeReferences(item.responseProcessing, outcomeDeclarations, context, ["responseProcessing"]);
1423
+ validateTemplateAndResponseReferences(
1424
+ item.templateProcessing,
1425
+ templateDeclarations,
1426
+ responseDeclarations,
1427
+ context,
1428
+ ["templateProcessing"],
1429
+ );
1430
+ },
1431
+ );
1432
+
1433
+ export const QtiAssessmentTestRawSchema: z.ZodTypeAny = z.lazy(() =>
1434
+ strictObject({
1435
+ identifier: QtiIdentifierSchema,
1436
+ title: z.string(),
1437
+ class: QtiStringListSchema.optional(),
1438
+ toolName: z.string().optional(),
1439
+ toolVersion: z.string().optional(),
1440
+ contextDeclarations: z.array(QtiContextDeclarationSchema).optional(),
1441
+ outcomeDeclarations: z.array(QtiOutcomeDeclarationSchema).optional(),
1442
+ timeLimits: QtiTimeLimitsSchema.optional(),
1443
+ stylesheets: z.array(QtiStyleSheetSchema).optional(),
1444
+ rubricBlocks: z.array(QtiTestRubricBlockSchema).optional(),
1445
+ testParts: z.array(QtiTestPartSchema).min(1),
1446
+ outcomeProcessing: QtiOutcomeProcessingSchema.optional(),
1447
+ testFeedbacks: z.array(QtiTestFeedbackSchema).optional(),
1448
+ foreignAttributes: XmlForeignAttributesSchema.optional(),
1449
+ }),
1450
+ );
1451
+
1452
+ export const QtiAssessmentTestSchema: z.ZodTypeAny = QtiAssessmentTestRawSchema.superRefine(
1453
+ (value: unknown, context) => {
1454
+ const testValue = value as QtiTestValidationValue;
1455
+ const contextDeclarations = asArray(testValue.contextDeclarations);
1456
+ const outcomeDeclarationsList = asArray(testValue.outcomeDeclarations);
1457
+ const testParts = asArray(testValue.testParts);
1458
+ const testFeedbacks = asArray(testValue.testFeedbacks);
1459
+
1460
+ addDuplicateSummaryIssue(context, [], "assessment test variable declaration identifiers", [
1461
+ ...contextDeclarations.map((declaration) => declaration.identifier),
1462
+ ...outcomeDeclarationsList.map((declaration) => declaration.identifier),
1463
+ ]);
1464
+
1465
+ addDuplicateSummaryIssue(
1466
+ context,
1467
+ ["testParts"],
1468
+ "test part identifiers",
1469
+ testParts.map((testPart) => testPart.identifier),
1470
+ );
1471
+
1472
+ validateOutcomeDeclarationConventions(outcomeDeclarationsList, context, ["outcomeDeclarations"]);
1473
+
1474
+ const outcomes = new Map(outcomeDeclarationsList.map((declaration) => [declaration.identifier, declaration]));
1475
+
1476
+ for (const [index, feedback] of testFeedbacks.entries()) {
1477
+ if (!outcomes.has(feedback.outcomeIdentifier)) {
1478
+ addIssue(
1479
+ context,
1480
+ ["testFeedbacks", index, "outcomeIdentifier"],
1481
+ `testFeedback references undeclared outcome identifier '${feedback.outcomeIdentifier}'.`,
1482
+ );
1483
+ }
1484
+
1485
+ if (containsInteraction(feedback.content)) {
1486
+ addIssue(context, ["testFeedbacks", index, "content"], "testFeedback content must not contain interactions.");
1487
+ }
1488
+ }
1489
+
1490
+ validateOutcomeReferences(testValue.outcomeProcessing, outcomes, context, ["outcomeProcessing"]);
1491
+ },
1492
+ );
1493
+ // Inferred types from exported Zod validators.
1494
+ export type QtiPrintedVariable = z.infer<typeof QtiPrintedVariableSchema>;
1495
+ export type QtiInteractionModule = z.infer<typeof QtiInteractionModuleSchema>;
1496
+ export type QtiInteractionModules = z.infer<typeof QtiInteractionModulesSchema>;
1497
+ export type QtiGap = z.infer<typeof QtiGapSchema>;
1498
+ export type QtiHotspotChoice = z.infer<typeof QtiHotspotChoiceSchema>;
1499
+ export type QtiAssociableHotspot = z.infer<typeof QtiAssociableHotspotSchema>;
1500
+ export type QtiTextEntryInteraction = z.infer<typeof QtiTextEntryInteractionSchema>;
1501
+ export type QtiGapChoice = z.infer<typeof QtiGapChoiceSchema>;
1502
+ export type QtiCompanionMaterialsInfo = z.infer<typeof QtiCompanionMaterialsInfoSchema>;
1503
+ export type QtiAdaptiveHref = z.infer<typeof QtiAdaptiveHrefSchema>;
1504
+ export type QtiAdaptiveSelection = z.infer<typeof QtiAdaptiveSelectionSchema>;
1505
+ export type QtiSelection = z.infer<typeof QtiSelectionSchema>;
1506
+ export type QtiOrdering = z.infer<typeof QtiOrderingSchema>;
1507
+ export type QtiAssessmentItemRef = z.infer<typeof QtiAssessmentItemRefSchema>;
1508
+ export type QtiAssessmentSectionRef = z.infer<typeof QtiAssessmentSectionRefSchema>;
1509
+ export type QtiAssessmentStimulusRef = z.infer<typeof QtiAssessmentStimulusRefSchema>;