@getodk/xforms-engine 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/BaseNode.d.ts +4 -4
- package/dist/client/BaseValueNode.d.ts +7 -3
- package/dist/client/RootNode.d.ts +20 -16
- package/dist/client/constants.d.ts +11 -12
- package/dist/client/form/CreateFormInstance.d.ts +14 -0
- package/dist/client/form/EditFormInstance.d.ts +62 -0
- package/dist/client/form/FormInstance.d.ts +104 -0
- package/dist/client/form/FormInstanceConfig.d.ts +17 -0
- package/dist/client/form/FormResource.d.ts +1 -0
- package/dist/client/form/LoadForm.d.ts +79 -0
- package/dist/client/form/LoadFormResult.d.ts +67 -0
- package/dist/client/form/RestoreFormInstance.d.ts +8 -0
- package/dist/client/index.d.ts +37 -11
- package/dist/client/node-types.d.ts +2 -2
- package/dist/client/repeat/BaseRepeatRangeNode.d.ts +3 -4
- package/dist/client/repeat/RepeatInstanceNode.d.ts +4 -5
- package/dist/client/repeat/RepeatRangeControlledNode.d.ts +2 -2
- package/dist/client/repeat/RepeatRangeUncontrolledNode.d.ts +2 -2
- package/dist/client/resources.d.ts +1 -1
- package/dist/client/serialization/InstanceData.d.ts +12 -0
- package/dist/client/serialization/InstanceFile.d.ts +6 -0
- package/dist/client/serialization/InstancePayload.d.ts +93 -0
- package/dist/client/serialization/InstancePayloadOptions.d.ts +23 -0
- package/dist/client/serialization/InstanceState.d.ts +12 -0
- package/dist/client/submission/{SubmissionDefinition.d.ts → SubmissionMeta.d.ts} +1 -1
- package/dist/entrypoints/FormInstance.d.ts +20 -0
- package/dist/entrypoints/FormResult/BaseFormResult.d.ts +22 -0
- package/dist/entrypoints/FormResult/BaseInstantiableFormResult.d.ts +25 -0
- package/dist/entrypoints/FormResult/FormFailureResult.d.ts +17 -0
- package/dist/entrypoints/FormResult/FormSuccessResult.d.ts +15 -0
- package/dist/entrypoints/FormResult/FormWarningResult.d.ts +15 -0
- package/dist/entrypoints/createInstance.d.ts +9 -0
- package/dist/entrypoints/editInstance.d.ts +9 -0
- package/dist/entrypoints/index.d.ts +4 -0
- package/dist/entrypoints/loadForm.d.ts +4 -0
- package/dist/entrypoints/restoreInstance.d.ts +9 -0
- package/dist/error/LoadFormFailureError.d.ts +9 -0
- package/dist/error/MalformedInstanceDataError.d.ts +3 -0
- package/dist/error/TemplatedNodeAttributeSerializationError.d.ts +22 -0
- package/dist/index.d.ts +2 -33
- package/dist/index.js +1915 -1419
- package/dist/index.js.map +1 -1
- package/dist/instance/Group.d.ts +6 -5
- package/dist/instance/InputControl.d.ts +5 -4
- package/dist/instance/ModelValue.d.ts +5 -4
- package/dist/instance/Note.d.ts +4 -3
- package/dist/instance/PrimaryInstance.d.ts +35 -8
- package/dist/instance/RangeControl.d.ts +5 -4
- package/dist/instance/RankControl.d.ts +6 -5
- package/dist/instance/Root.d.ts +7 -9
- package/dist/instance/SelectControl.d.ts +4 -3
- package/dist/instance/Subtree.d.ts +6 -5
- package/dist/instance/TriggerControl.d.ts +4 -3
- package/dist/instance/abstract/DescendantNode.d.ts +10 -1
- package/dist/instance/abstract/InstanceNode.d.ts +7 -4
- package/dist/instance/abstract/ValueNode.d.ts +7 -5
- package/dist/instance/children/DescendantNodeInitOptions.d.ts +32 -0
- package/dist/instance/{children.d.ts → children/buildChildren.d.ts} +1 -1
- package/dist/instance/children/childrenInitOptions.d.ts +9 -0
- package/dist/instance/children/normalizeChildInitOptions.d.ts +2 -0
- package/dist/instance/input/InitialInstanceState.d.ts +13 -0
- package/dist/instance/input/InstanceAttachmentMap.d.ts +19 -0
- package/dist/instance/internal-api/InstanceConfig.d.ts +2 -9
- package/dist/instance/internal-api/InstanceValueContext.d.ts +8 -1
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableInstance.d.ts +14 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableLeafNode.d.ts +32 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableParentNode.d.ts +18 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.d.ts +13 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableValueNode.d.ts +21 -0
- package/dist/instance/repeat/BaseRepeatRange.d.ts +10 -8
- package/dist/instance/repeat/RepeatInstance.d.ts +10 -7
- package/dist/instance/repeat/RepeatRangeControlled.d.ts +27 -3
- package/dist/instance/repeat/RepeatRangeUncontrolled.d.ts +4 -3
- package/dist/instance/resource.d.ts +5 -1
- package/dist/instance/unsupported/UploadControl.d.ts +32 -4
- package/dist/integration/xpath/EngineXPathEvaluator.d.ts +2 -2
- package/dist/integration/xpath/adapter/kind.d.ts +4 -10
- package/dist/integration/xpath/static-dom/StaticAttribute.d.ts +4 -5
- package/dist/integration/xpath/static-dom/StaticDocument.d.ts +13 -10
- package/dist/integration/xpath/static-dom/StaticElement.d.ts +21 -22
- package/dist/integration/xpath/static-dom/StaticNode.d.ts +1 -1
- package/dist/integration/xpath/static-dom/StaticParentNode.d.ts +13 -0
- package/dist/integration/xpath/static-dom/staticNodeName.d.ts +3 -0
- package/dist/lib/client-reactivity/instance-state/createNodeRangeInstanceState.d.ts +4 -0
- package/dist/lib/client-reactivity/instance-state/createParentNodeInstanceState.d.ts +4 -0
- package/dist/lib/client-reactivity/instance-state/createPrimaryInstanceState.d.ts +3 -0
- package/dist/lib/client-reactivity/instance-state/createRootInstanceState.d.ts +3 -0
- package/dist/lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.d.ts +6 -0
- package/dist/lib/client-reactivity/instance-state/createValueNodeInstanceState.d.ts +3 -0
- package/dist/lib/client-reactivity/instance-state/prepareInstancePayload.d.ts +8 -0
- package/dist/lib/codecs/TempUnsupportedControlCodec.d.ts +7 -0
- package/dist/lib/names/UnprefixedXFormsName.d.ts +4 -0
- package/dist/lib/reactivity/createInstanceValueState.d.ts +2 -27
- package/dist/parse/XFormDOM.d.ts +1 -1
- package/dist/parse/model/BindDefinition.d.ts +1 -1
- package/dist/parse/model/ItextTranslationsDefinition.d.ts +18 -0
- package/dist/parse/model/LeafNodeDefinition.d.ts +4 -5
- package/dist/parse/model/ModelBindMap.d.ts +1 -1
- package/dist/parse/model/ModelDefinition.d.ts +9 -2
- package/dist/parse/model/NodeDefinition.d.ts +18 -30
- package/dist/parse/model/NoteNodeDefinition.d.ts +3 -2
- package/dist/parse/model/RangeNodeDefinition.d.ts +2 -1
- package/dist/parse/model/RepeatDefinition.d.ts +62 -0
- package/dist/parse/model/RootAttributeDefinition.d.ts +1 -4
- package/dist/parse/model/RootAttributeMap.d.ts +2 -1
- package/dist/parse/model/RootDefinition.d.ts +7 -8
- package/dist/parse/model/SecondaryInstance/SecondaryInstancesDefinition.d.ts +15 -2
- package/dist/parse/model/SecondaryInstance/assertSecondaryInstanceDefinition.d.ts +5 -0
- package/dist/parse/model/SecondaryInstance/defineSecondaryInstance.d.ts +5 -0
- package/dist/parse/model/SecondaryInstance/sources/BlankSecondaryInstanceSource.d.ts +1 -7
- package/dist/parse/model/SecondaryInstance/sources/CSVExternalSecondaryInstance.d.ts +1 -1
- package/dist/parse/model/SecondaryInstance/sources/GeoJSONExternalSecondaryInstance.d.ts +1 -1
- package/dist/parse/model/SecondaryInstance/sources/InternalSecondaryInstanceSource.d.ts +1 -1
- package/dist/parse/model/SecondaryInstance/sources/SecondaryInstanceSource.d.ts +1 -1
- package/dist/parse/model/SecondaryInstance/sources/XMLExternalSecondaryInstanceSource.d.ts +1 -1
- package/dist/parse/model/{FormSubmissionDefinition.d.ts → SubmissionDefinition.d.ts} +2 -2
- package/dist/parse/model/SubtreeDefinition.d.ts +4 -5
- package/dist/parse/model/nodeDefinitionMap.d.ts +5 -0
- package/dist/parse/shared/parseInstanceXML.d.ts +21 -0
- package/dist/parse/shared/parseStaticDocumentFromDOMSubtree.d.ts +4 -21
- package/dist/solid.js +1709 -1213
- package/dist/solid.js.map +1 -1
- package/package.json +1 -1
- package/src/client/BaseNode.ts +4 -4
- package/src/client/BaseValueNode.ts +7 -3
- package/src/client/RootNode.ts +27 -19
- package/src/client/constants.ts +12 -14
- package/src/client/form/CreateFormInstance.ts +19 -0
- package/src/client/form/EditFormInstance.ts +93 -0
- package/src/client/form/FormInstance.ts +114 -0
- package/src/client/form/FormInstanceConfig.ts +18 -0
- package/src/client/form/FormResource.ts +1 -0
- package/src/client/form/LoadForm.ts +92 -0
- package/src/client/form/LoadFormResult.ts +103 -0
- package/src/client/form/RestoreFormInstance.ts +14 -0
- package/src/client/index.ts +47 -29
- package/src/client/node-types.ts +2 -4
- package/src/client/repeat/BaseRepeatRangeNode.ts +3 -4
- package/src/client/repeat/RepeatInstanceNode.ts +4 -8
- package/src/client/repeat/RepeatRangeControlledNode.ts +2 -2
- package/src/client/repeat/RepeatRangeUncontrolledNode.ts +2 -2
- package/src/client/resources.ts +2 -2
- package/src/client/serialization/InstanceData.ts +16 -0
- package/src/client/serialization/InstanceFile.ts +9 -0
- package/src/client/serialization/InstancePayload.ts +126 -0
- package/src/client/serialization/InstancePayloadOptions.ts +28 -0
- package/src/client/serialization/InstanceState.ts +14 -0
- package/src/client/submission/{SubmissionDefinition.ts → SubmissionMeta.ts} +1 -1
- package/src/entrypoints/FormInstance.ts +55 -0
- package/src/entrypoints/FormResult/BaseFormResult.ts +40 -0
- package/src/entrypoints/FormResult/BaseInstantiableFormResult.ts +109 -0
- package/src/entrypoints/FormResult/FormFailureResult.ts +44 -0
- package/src/entrypoints/FormResult/FormSuccessResult.ts +25 -0
- package/src/entrypoints/FormResult/FormWarningResult.ts +25 -0
- package/src/entrypoints/createInstance.ts +23 -0
- package/src/entrypoints/editInstance.ts +24 -0
- package/src/entrypoints/index.ts +4 -0
- package/src/entrypoints/loadForm.ts +154 -0
- package/src/entrypoints/restoreInstance.ts +27 -0
- package/src/error/LoadFormFailureError.ts +114 -0
- package/src/error/MalformedInstanceDataError.ts +3 -0
- package/src/error/TemplatedNodeAttributeSerializationError.ts +24 -0
- package/src/index.ts +2 -46
- package/src/instance/Group.ts +16 -15
- package/src/instance/InputControl.ts +17 -11
- package/src/instance/ModelValue.ts +17 -11
- package/src/instance/Note.ts +10 -9
- package/src/instance/PrimaryInstance.ts +69 -31
- package/src/instance/RangeControl.ts +17 -11
- package/src/instance/RankControl.ts +28 -19
- package/src/instance/Root.ts +20 -31
- package/src/instance/SelectControl.ts +21 -12
- package/src/instance/Subtree.ts +16 -15
- package/src/instance/TriggerControl.ts +21 -12
- package/src/instance/abstract/DescendantNode.ts +12 -2
- package/src/instance/abstract/InstanceNode.ts +9 -5
- package/src/instance/abstract/ValueNode.ts +11 -13
- package/src/instance/children/DescendantNodeInitOptions.ts +35 -0
- package/src/instance/{children.ts → children/buildChildren.ts} +55 -48
- package/src/instance/children/childrenInitOptions.ts +117 -0
- package/src/instance/children/normalizeChildInitOptions.ts +332 -0
- package/src/instance/input/InitialInstanceState.ts +108 -0
- package/src/instance/input/InstanceAttachmentMap.ts +142 -0
- package/src/instance/internal-api/InstanceConfig.ts +3 -10
- package/src/instance/internal-api/InstanceValueContext.ts +9 -1
- package/src/instance/internal-api/serialization/ClientReactiveSerializableInstance.ts +20 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableLeafNode.ts +43 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableParentNode.ts +26 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.ts +24 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableValueNode.ts +28 -0
- package/src/instance/repeat/BaseRepeatRange.ts +19 -24
- package/src/instance/repeat/RepeatInstance.ts +26 -19
- package/src/instance/repeat/RepeatRangeControlled.ts +90 -17
- package/src/instance/repeat/RepeatRangeUncontrolled.ts +10 -9
- package/src/instance/resource.ts +14 -1
- package/src/instance/unsupported/UploadControl.ts +116 -5
- package/src/integration/xpath/EngineXPathEvaluator.ts +2 -2
- package/src/integration/xpath/adapter/kind.ts +1 -28
- package/src/integration/xpath/adapter/traversal.ts +2 -2
- package/src/integration/xpath/static-dom/StaticAttribute.ts +6 -5
- package/src/integration/xpath/static-dom/StaticDocument.ts +17 -16
- package/src/integration/xpath/static-dom/StaticElement.ts +196 -50
- package/src/integration/xpath/static-dom/StaticNode.ts +1 -1
- package/src/integration/xpath/static-dom/StaticParentNode.ts +22 -0
- package/src/integration/xpath/static-dom/staticNodeName.ts +20 -0
- package/src/lib/client-reactivity/instance-state/createNodeRangeInstanceState.ts +17 -0
- package/src/lib/client-reactivity/instance-state/createParentNodeInstanceState.ts +22 -0
- package/src/lib/client-reactivity/instance-state/createPrimaryInstanceState.ts +12 -0
- package/src/lib/client-reactivity/{submission/createRootSubmissionState.ts → instance-state/createRootInstanceState.ts} +4 -4
- package/src/lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.ts +31 -0
- package/src/lib/client-reactivity/instance-state/createValueNodeInstanceState.ts +21 -0
- package/src/lib/client-reactivity/instance-state/prepareInstancePayload.ts +173 -0
- package/src/lib/codecs/TempUnsupportedControlCodec.ts +32 -0
- package/src/lib/names/UnprefixedXFormsName.ts +12 -0
- package/src/lib/reactivity/createInstanceValueState.ts +27 -51
- package/src/parse/model/ItextTranslationsDefinition.ts +79 -0
- package/src/parse/model/LeafNodeDefinition.ts +3 -5
- package/src/parse/model/ModelDefinition.ts +36 -3
- package/src/parse/model/NodeDefinition.ts +19 -45
- package/src/parse/model/NoteNodeDefinition.ts +4 -3
- package/src/parse/model/RangeNodeDefinition.ts +3 -2
- package/src/parse/model/RepeatDefinition.ts +382 -0
- package/src/parse/model/RootAttributeDefinition.ts +6 -7
- package/src/parse/model/RootAttributeMap.ts +15 -10
- package/src/parse/model/RootDefinition.ts +17 -19
- package/src/parse/model/SecondaryInstance/SecondaryInstancesDefinition.ts +23 -2
- package/src/parse/model/SecondaryInstance/assertSecondaryInstanceDefinition.ts +14 -0
- package/src/parse/model/SecondaryInstance/defineSecondaryInstance.ts +32 -0
- package/src/parse/model/SecondaryInstance/sources/BlankSecondaryInstanceSource.ts +3 -24
- package/src/parse/model/SecondaryInstance/sources/CSVExternalSecondaryInstance.ts +33 -86
- package/src/parse/model/SecondaryInstance/sources/GeoJSONExternalSecondaryInstance.ts +64 -137
- package/src/parse/model/SecondaryInstance/sources/InternalSecondaryInstanceSource.ts +9 -7
- package/src/parse/model/SecondaryInstance/sources/SecondaryInstanceSource.ts +1 -1
- package/src/parse/model/SecondaryInstance/sources/XMLExternalSecondaryInstanceSource.ts +7 -7
- package/src/parse/model/{FormSubmissionDefinition.ts → SubmissionDefinition.ts} +2 -2
- package/src/parse/model/SubtreeDefinition.ts +4 -5
- package/src/parse/model/nodeDefinitionMap.ts +34 -0
- package/src/parse/shared/parseInstanceXML.ts +79 -0
- package/src/parse/shared/parseStaticDocumentFromDOMSubtree.ts +45 -130
- package/dist/client/EngineConfig.d.ts +0 -79
- package/dist/client/submission/SubmissionData.d.ts +0 -7
- package/dist/client/submission/SubmissionInstanceFile.d.ts +0 -6
- package/dist/client/submission/SubmissionOptions.d.ts +0 -23
- package/dist/client/submission/SubmissionResult.d.ts +0 -91
- package/dist/client/submission/SubmissionState.d.ts +0 -12
- package/dist/instance/abstract/UnsupportedControl.d.ts +0 -54
- package/dist/instance/index.d.ts +0 -8
- package/dist/instance/internal-api/ValueContext.d.ts +0 -23
- package/dist/instance/internal-api/submission/ClientReactiveSubmittableInstance.d.ts +0 -14
- package/dist/instance/internal-api/submission/ClientReactiveSubmittableLeafNode.d.ts +0 -32
- package/dist/instance/internal-api/submission/ClientReactiveSubmittableParentNode.d.ts +0 -19
- package/dist/instance/internal-api/submission/ClientReactiveSubmittableValueNode.d.ts +0 -18
- package/dist/lib/client-reactivity/submission/createInstanceSubmissionState.d.ts +0 -3
- package/dist/lib/client-reactivity/submission/createLeafNodeSubmissionState.d.ts +0 -3
- package/dist/lib/client-reactivity/submission/createNodeRangeSubmissionState.d.ts +0 -4
- package/dist/lib/client-reactivity/submission/createParentNodeSubmissionState.d.ts +0 -4
- package/dist/lib/client-reactivity/submission/createRootSubmissionState.d.ts +0 -3
- package/dist/lib/client-reactivity/submission/createValueNodeSubmissionState.d.ts +0 -3
- package/dist/lib/client-reactivity/submission/prepareSubmission.d.ts +0 -8
- package/dist/lib/reactivity/createValueState.d.ts +0 -40
- package/dist/parse/model/ItextTranslation/ItextTranslationDefinition.d.ts +0 -4
- package/dist/parse/model/ItextTranslation/ItextTranslationRootDefinition.d.ts +0 -9
- package/dist/parse/model/ItextTranslation/ItextTranslationsDefinition.d.ts +0 -8
- package/dist/parse/model/RepeatInstanceDefinition.d.ts +0 -17
- package/dist/parse/model/RepeatRangeDefinition.d.ts +0 -32
- package/dist/parse/model/RepeatTemplateDefinition.d.ts +0 -31
- package/dist/parse/model/SecondaryInstance/SecondaryInstanceDefinition.d.ts +0 -4
- package/dist/parse/model/SecondaryInstance/SecondaryInstanceRootDefinition.d.ts +0 -7
- package/src/client/EngineConfig.ts +0 -84
- package/src/client/submission/SubmissionData.ts +0 -12
- package/src/client/submission/SubmissionInstanceFile.ts +0 -9
- package/src/client/submission/SubmissionOptions.ts +0 -28
- package/src/client/submission/SubmissionResult.ts +0 -124
- package/src/client/submission/SubmissionState.ts +0 -14
- package/src/instance/abstract/UnsupportedControl.ts +0 -175
- package/src/instance/index.ts +0 -55
- package/src/instance/internal-api/ValueContext.ts +0 -28
- package/src/instance/internal-api/submission/ClientReactiveSubmittableInstance.ts +0 -20
- package/src/instance/internal-api/submission/ClientReactiveSubmittableLeafNode.ts +0 -43
- package/src/instance/internal-api/submission/ClientReactiveSubmittableParentNode.ts +0 -26
- package/src/instance/internal-api/submission/ClientReactiveSubmittableValueNode.ts +0 -24
- package/src/lib/client-reactivity/submission/createInstanceSubmissionState.ts +0 -12
- package/src/lib/client-reactivity/submission/createLeafNodeSubmissionState.ts +0 -20
- package/src/lib/client-reactivity/submission/createNodeRangeSubmissionState.ts +0 -17
- package/src/lib/client-reactivity/submission/createParentNodeSubmissionState.ts +0 -22
- package/src/lib/client-reactivity/submission/createValueNodeSubmissionState.ts +0 -21
- package/src/lib/client-reactivity/submission/prepareSubmission.ts +0 -172
- package/src/lib/reactivity/createValueState.ts +0 -200
- package/src/parse/model/ItextTranslation/ItextTranslationDefinition.ts +0 -4
- package/src/parse/model/ItextTranslation/ItextTranslationRootDefinition.ts +0 -42
- package/src/parse/model/ItextTranslation/ItextTranslationsDefinition.ts +0 -31
- package/src/parse/model/RepeatInstanceDefinition.ts +0 -38
- package/src/parse/model/RepeatRangeDefinition.ts +0 -98
- package/src/parse/model/RepeatTemplateDefinition.ts +0 -149
- package/src/parse/model/SecondaryInstance/SecondaryInstanceDefinition.ts +0 -4
- package/src/parse/model/SecondaryInstance/SecondaryInstanceRootDefinition.ts +0 -12
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { StaticDocument } from '../../integration/xpath/static-dom/StaticDocument.ts';
|
|
2
|
+
import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
3
|
+
import type { ModelDefinition } from '../../parse/model/ModelDefinition.ts';
|
|
4
|
+
import type {
|
|
5
|
+
AnyNodeDefinition,
|
|
6
|
+
ChildNodeDefinition,
|
|
7
|
+
NodeDefinition,
|
|
8
|
+
} from '../../parse/model/NodeDefinition.ts';
|
|
9
|
+
import type { InstanceNode } from '../abstract/InstanceNode.ts';
|
|
10
|
+
import type { GeneralParentNode } from '../hierarchy.ts';
|
|
11
|
+
import type { DescendantNodeInitOptions } from './DescendantNodeInitOptions.ts';
|
|
12
|
+
import { normalizeChildInitOptions } from './normalizeChildInitOptions.ts';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Child nodesets are collected from the {@link parent}'s
|
|
16
|
+
* {@link NodeDefinition.template}, ensuring that we produce
|
|
17
|
+
* {@link InstanceNode}s for every **model-defined** node, even if a
|
|
18
|
+
* corresponding node was not serialized in a {@link parent.instanceNode}.
|
|
19
|
+
*
|
|
20
|
+
* In other words, by referencing the model-defined template, we are able to
|
|
21
|
+
* reproduce nodes which were omitted as non-relevant in a prior serialization
|
|
22
|
+
* and/or submission.
|
|
23
|
+
*
|
|
24
|
+
* @todo Since we're building an instance node's children from the nodesets of
|
|
25
|
+
* the model-defined node's children, we are _implicitly dropping_ any excess
|
|
26
|
+
* nodes from non-model instance data. That's probably the right behavior, but
|
|
27
|
+
* we may want to warn for such nodes if/when we do drop them.
|
|
28
|
+
*/
|
|
29
|
+
const collectModelChildNodesets = (parentTemplate: StaticElement): readonly string[] => {
|
|
30
|
+
const nodesets = parentTemplate.childElements.map(({ nodeset }) => {
|
|
31
|
+
return nodeset;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return Array.from(new Set(nodesets));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type InstanceNodesByNodeset = ReadonlyMap<string, readonly [StaticElement, ...StaticElement[]]>;
|
|
38
|
+
|
|
39
|
+
const groupChildElementsByNodeset = (
|
|
40
|
+
parent: StaticDocument | StaticElement
|
|
41
|
+
): InstanceNodesByNodeset => {
|
|
42
|
+
const result = new Map<string, [StaticElement, ...StaticElement[]]>();
|
|
43
|
+
|
|
44
|
+
for (const child of parent.childElements) {
|
|
45
|
+
const { nodeset } = child;
|
|
46
|
+
const group = result.get(nodeset);
|
|
47
|
+
|
|
48
|
+
if (group == null) {
|
|
49
|
+
result.set(nodeset, [child]);
|
|
50
|
+
} else {
|
|
51
|
+
group.push(child);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type AssertChildNodeDefinition = (
|
|
59
|
+
definition: AnyNodeDefinition,
|
|
60
|
+
childNodeset: string
|
|
61
|
+
) => asserts definition is ChildNodeDefinition;
|
|
62
|
+
|
|
63
|
+
const assertChildNodeDefinition: AssertChildNodeDefinition = (definition, childNodeset) => {
|
|
64
|
+
if (definition.type === 'root') {
|
|
65
|
+
throw new Error(`Unexpected root definition for child nodeset: ${childNodeset}`);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export interface ChildrenInitOptions {
|
|
70
|
+
readonly parent: GeneralParentNode;
|
|
71
|
+
readonly model: ModelDefinition;
|
|
72
|
+
readonly children: readonly DescendantNodeInitOptions[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const childrenInitOptions = (parent: GeneralParentNode): ChildrenInitOptions => {
|
|
76
|
+
const { model } = parent.rootDocument;
|
|
77
|
+
const childNodesets = collectModelChildNodesets(parent.definition.template);
|
|
78
|
+
|
|
79
|
+
let instanceChildren: InstanceNodesByNodeset | null;
|
|
80
|
+
|
|
81
|
+
if (parent.instanceNode == null) {
|
|
82
|
+
instanceChildren = null;
|
|
83
|
+
} else {
|
|
84
|
+
instanceChildren = groupChildElementsByNodeset(parent.instanceNode);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const children = childNodesets.map((childNodeset) => {
|
|
88
|
+
const definition = model.getNodeDefinition(childNodeset);
|
|
89
|
+
|
|
90
|
+
assertChildNodeDefinition(definition, childNodeset);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get children of the target nodeset from {@link parent.instanceNode}, if
|
|
94
|
+
* that node exists, and if children with that nodeset exist.
|
|
95
|
+
*
|
|
96
|
+
* If either does not exist (e.g. it was omitted as non-relevant in a prior
|
|
97
|
+
* serialization), we continue to reference model-defined templates as we
|
|
98
|
+
* recurse down the {@link InstanceNode} subtree.
|
|
99
|
+
*
|
|
100
|
+
* @see {@link childNodesets}
|
|
101
|
+
*/
|
|
102
|
+
const instanceNodes = instanceChildren?.get(childNodeset) ?? [];
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
childNodeset,
|
|
106
|
+
definition,
|
|
107
|
+
instanceNodes,
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
const baseResult: ChildrenInitOptions = {
|
|
111
|
+
parent,
|
|
112
|
+
model,
|
|
113
|
+
children,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return normalizeChildInitOptions(baseResult);
|
|
117
|
+
};
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OPENROSA_XFORMS_NAMESPACE_URI,
|
|
3
|
+
XFORMS_NAMESPACE_URI,
|
|
4
|
+
} from '@getodk/common/constants/xmlns.ts';
|
|
5
|
+
import type { GroupDefinition } from '../../client/GroupNode.ts';
|
|
6
|
+
import type { SubtreeDefinition } from '../../client/SubtreeNode.ts';
|
|
7
|
+
import { ErrorProductionDesignPendingError } from '../../error/ErrorProductionDesignPendingError.ts';
|
|
8
|
+
import { StaticDocument } from '../../integration/xpath/static-dom/StaticDocument.ts';
|
|
9
|
+
import type { StaticLeafElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
10
|
+
import { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
11
|
+
import type { NamespaceURL } from '../../lib/names/NamespaceURL.ts';
|
|
12
|
+
import type { QualifiedName } from '../../lib/names/QualifiedName.ts';
|
|
13
|
+
import { LeafNodeDefinition } from '../../parse/model/LeafNodeDefinition.ts';
|
|
14
|
+
import type { SubtreeDefinition as ModelSubtreeDefinition } from '../../parse/model/SubtreeDefinition.ts';
|
|
15
|
+
import type { XFormDOM } from '../../parse/XFormDOM.ts';
|
|
16
|
+
import type { Group } from '../Group.ts';
|
|
17
|
+
import type { GeneralParentNode } from '../hierarchy.ts';
|
|
18
|
+
import type { PrimaryInstance } from '../PrimaryInstance.ts';
|
|
19
|
+
import type { Root } from '../Root.ts';
|
|
20
|
+
import type { Subtree } from '../Subtree.ts';
|
|
21
|
+
import type { ChildrenInitOptions } from './childrenInitOptions.ts';
|
|
22
|
+
import type { DescendantNodeInitOptions } from './DescendantNodeInitOptions.ts';
|
|
23
|
+
|
|
24
|
+
const META_LOCAL_NAME = 'meta';
|
|
25
|
+
const INSTANCE_ID_LOCAL_NAME = 'instanceID';
|
|
26
|
+
const DEPRECATED_ID_LOCAL_NAME = 'deprecatedID';
|
|
27
|
+
|
|
28
|
+
type MetaLocalName = typeof META_LOCAL_NAME;
|
|
29
|
+
|
|
30
|
+
const META_NAMESPACE_URIS = [OPENROSA_XFORMS_NAMESPACE_URI, XFORMS_NAMESPACE_URI] as const;
|
|
31
|
+
|
|
32
|
+
type MetaNamespaceURIValue = (typeof META_NAMESPACE_URIS)[number];
|
|
33
|
+
|
|
34
|
+
interface MetaNamespaceURI extends NamespaceURL {
|
|
35
|
+
readonly href: MetaNamespaceURIValue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface MetaNamespacedName extends QualifiedName {
|
|
39
|
+
readonly namespaceURI: MetaNamespaceURI;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isMetaNamespacedName = (
|
|
43
|
+
qualifiedName: QualifiedName
|
|
44
|
+
): qualifiedName is MetaNamespacedName => {
|
|
45
|
+
const namespaceURI = qualifiedName.namespaceURI?.href;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
namespaceURI != null && META_NAMESPACE_URIS.includes(namespaceURI as MetaNamespaceURIValue)
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
interface MetaName extends MetaNamespacedName {
|
|
53
|
+
readonly localName: MetaLocalName;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const isMetaName = (qualifiedName: QualifiedName): qualifiedName is MetaName => {
|
|
57
|
+
return isMetaNamespacedName(qualifiedName) && qualifiedName.localName === META_LOCAL_NAME;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const isDirectRootDescendant = (parent: GeneralParentNode) => {
|
|
61
|
+
return parent.parent === parent.root;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
interface BaseMetaDefinition {
|
|
65
|
+
readonly qualifiedName: MetaName;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const isBaseMetaDefinition = <T extends ModelSubtreeDefinition>(
|
|
69
|
+
definition: T
|
|
70
|
+
): definition is BaseMetaDefinition & T => {
|
|
71
|
+
return isMetaName(definition.qualifiedName);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
interface MetaSubtreeDefinition extends SubtreeDefinition {
|
|
75
|
+
readonly qualifiedName: MetaName;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface MetaSubtree extends Subtree {
|
|
79
|
+
readonly parent: Root;
|
|
80
|
+
readonly definition: MetaSubtreeDefinition;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface MetaGroupDefinition extends GroupDefinition {
|
|
84
|
+
readonly qualifiedName: MetaName;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface MetaGroup extends Group {
|
|
88
|
+
readonly parent: Root;
|
|
89
|
+
readonly definition: MetaGroupDefinition;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type MetaSubroot = MetaGroup | MetaSubtree;
|
|
93
|
+
|
|
94
|
+
interface MetaSubrootInitOptions extends ChildrenInitOptions {
|
|
95
|
+
readonly parent: MetaGroup | MetaSubtree;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const isMetaSubroot = (options: ChildrenInitOptions): options is MetaSubrootInitOptions => {
|
|
99
|
+
const { nodeType } = options.parent;
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
(nodeType === 'subtree' || nodeType === 'group') &&
|
|
103
|
+
isDirectRootDescendant(options.parent) &&
|
|
104
|
+
isBaseMetaDefinition(options.parent.definition)
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
interface EditModeInstance extends PrimaryInstance {
|
|
109
|
+
readonly initializationMode: 'edit';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const isEditModeInstance = (
|
|
113
|
+
primaryInstance: PrimaryInstance
|
|
114
|
+
): primaryInstance is EditModeInstance => {
|
|
115
|
+
return primaryInstance.initializationMode === 'edit';
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
interface EditModeInstanceDescendant {
|
|
119
|
+
readonly rootDocument: EditModeInstance;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
type EditModeMetaSubroot = EditModeInstanceDescendant & MetaSubroot;
|
|
123
|
+
|
|
124
|
+
interface EditModeMetaSubrootInitOptions extends MetaSubrootInitOptions {
|
|
125
|
+
readonly parent: EditModeMetaSubroot;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const isEditModeMetaSubroot = (
|
|
129
|
+
subroot: MetaSubrootInitOptions
|
|
130
|
+
): subroot is EditModeMetaSubrootInitOptions => {
|
|
131
|
+
return isEditModeInstance(subroot.parent.rootDocument);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
interface LeafNodeInitOptions extends DescendantNodeInitOptions {
|
|
135
|
+
readonly instanceNodes: readonly [StaticLeafElement];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const isLeafNodeInitOptions = (
|
|
139
|
+
options: DescendantNodeInitOptions
|
|
140
|
+
): options is LeafNodeInitOptions => {
|
|
141
|
+
const { instanceNodes } = options;
|
|
142
|
+
const [instanceNode, ...rest] = instanceNodes;
|
|
143
|
+
|
|
144
|
+
return instanceNode != null && rest.length === 0 && instanceNode.isLeafElement();
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
type MetaLeafChildEntry = readonly [index: number, child: LeafNodeInitOptions];
|
|
148
|
+
|
|
149
|
+
const findMetaLeafChild = (
|
|
150
|
+
subroot: MetaSubrootInitOptions,
|
|
151
|
+
localName: string
|
|
152
|
+
): MetaLeafChildEntry | null => {
|
|
153
|
+
const metaName = subroot.parent.definition.qualifiedName satisfies MetaName;
|
|
154
|
+
const namespaceURI = metaName.namespaceURI.href satisfies MetaNamespaceURIValue;
|
|
155
|
+
|
|
156
|
+
const result = Array.from(subroot.children.entries()).find(
|
|
157
|
+
(entry): entry is [number, LeafNodeInitOptions] => {
|
|
158
|
+
const [, child] = entry;
|
|
159
|
+
|
|
160
|
+
if (!isLeafNodeInitOptions(child)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const [instanceNode] = child.instanceNodes;
|
|
165
|
+
const { qualifiedName } = instanceNode;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
qualifiedName.localName === localName && qualifiedName.namespaceURI?.href === namespaceURI
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return result ?? null;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const getInstanceIDValue = (subroot: MetaSubrootInitOptions): string | null => {
|
|
177
|
+
const [, child = null] = findMetaLeafChild(subroot, INSTANCE_ID_LOCAL_NAME) ?? [];
|
|
178
|
+
|
|
179
|
+
if (child == null) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const [instanceIDNode] = child.instanceNodes;
|
|
184
|
+
|
|
185
|
+
return instanceIDNode.value satisfies string;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
type AssertStaticLeafElement = (element: StaticElement) => asserts element is StaticLeafElement;
|
|
189
|
+
|
|
190
|
+
const assertStaticLeafElement: AssertStaticLeafElement = (element) => {
|
|
191
|
+
if (!element.isLeafElement()) {
|
|
192
|
+
throw new ErrorProductionDesignPendingError(
|
|
193
|
+
`Expected a leaf element, got a non-leaf element at nodeset: ${element.nodeset}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @todo this name and signature are general, for the same reasons as the TODO
|
|
200
|
+
* on {@link populateDeprecatedID}.
|
|
201
|
+
*/
|
|
202
|
+
const buildMetaValueElement = (
|
|
203
|
+
subroot: MetaSubrootInitOptions,
|
|
204
|
+
localName: string,
|
|
205
|
+
value: string
|
|
206
|
+
): StaticLeafElement => {
|
|
207
|
+
const { qualifiedName, nodeset } = subroot.parent.definition;
|
|
208
|
+
const { namespaceURI, prefix } = qualifiedName;
|
|
209
|
+
const { root } = new StaticDocument({
|
|
210
|
+
documentRoot: {
|
|
211
|
+
name: {
|
|
212
|
+
namespaceURI,
|
|
213
|
+
prefix,
|
|
214
|
+
localName,
|
|
215
|
+
},
|
|
216
|
+
children: [value],
|
|
217
|
+
},
|
|
218
|
+
nodesetPrefix: nodeset,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
assertStaticLeafElement(root);
|
|
222
|
+
|
|
223
|
+
return root;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const buildDeprecatedIDDefinition = (
|
|
227
|
+
subroot: EditModeMetaSubrootInitOptions,
|
|
228
|
+
instanceNode: StaticLeafElement
|
|
229
|
+
): LeafNodeDefinition => {
|
|
230
|
+
const nodeset = instanceNode.nodeset;
|
|
231
|
+
const bind = subroot.model.binds.getOrCreateBindDefinition(nodeset);
|
|
232
|
+
|
|
233
|
+
return new LeafNodeDefinition(subroot.parent.definition, bind, null, instanceNode);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const buildDeprecatedID = (
|
|
237
|
+
subroot: EditModeMetaSubrootInitOptions,
|
|
238
|
+
value: string
|
|
239
|
+
): LeafNodeInitOptions => {
|
|
240
|
+
const instanceNode = buildMetaValueElement(subroot, DEPRECATED_ID_LOCAL_NAME, value);
|
|
241
|
+
const definition = buildDeprecatedIDDefinition(subroot, instanceNode);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
childNodeset: instanceNode.nodeset,
|
|
245
|
+
definition,
|
|
246
|
+
instanceNodes: [instanceNode],
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const updateDeprecatedID = (
|
|
251
|
+
subroot: EditModeMetaSubrootInitOptions,
|
|
252
|
+
child: LeafNodeInitOptions,
|
|
253
|
+
value: string
|
|
254
|
+
): LeafNodeInitOptions => {
|
|
255
|
+
const instanceNode = buildMetaValueElement(subroot, DEPRECATED_ID_LOCAL_NAME, value);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
childNodeset: child.childNodeset,
|
|
259
|
+
definition: child.definition,
|
|
260
|
+
instanceNodes: [instanceNode],
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Should be: values.with(index, value)
|
|
266
|
+
*
|
|
267
|
+
* @todo update tsconfig.json `lib` throughout project!
|
|
268
|
+
*/
|
|
269
|
+
const replace = <T>(values: readonly T[], index: number, value: T): readonly T[] => {
|
|
270
|
+
const results = values.slice();
|
|
271
|
+
|
|
272
|
+
results.splice(index, 1, value);
|
|
273
|
+
|
|
274
|
+
return results;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const replaceOrConcat = <T>(values: readonly T[], index: number | null, value: T): readonly T[] => {
|
|
278
|
+
if (index == null) {
|
|
279
|
+
return values.concat(value);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return replace(values, index, value);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @todo Whenever we have bandwidth to start migrating away from DOM usage in
|
|
287
|
+
* {@link XFormDOM}, this is a good place to start for logic equivalent to the
|
|
288
|
+
* hacky normalization for `instanceID`.
|
|
289
|
+
*/
|
|
290
|
+
const populateDeprecatedID = (
|
|
291
|
+
subroot: EditModeMetaSubrootInitOptions
|
|
292
|
+
): EditModeMetaSubrootInitOptions => {
|
|
293
|
+
const value = getInstanceIDValue(subroot);
|
|
294
|
+
|
|
295
|
+
if (value == null) {
|
|
296
|
+
return subroot;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const [index, currentDeprecatedID] = findMetaLeafChild(subroot, DEPRECATED_ID_LOCAL_NAME) ?? [
|
|
300
|
+
null,
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
let deprecatedID: LeafNodeInitOptions;
|
|
304
|
+
|
|
305
|
+
if (currentDeprecatedID == null) {
|
|
306
|
+
deprecatedID = buildDeprecatedID(subroot, value);
|
|
307
|
+
} else {
|
|
308
|
+
deprecatedID = updateDeprecatedID(subroot, currentDeprecatedID, value);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
model: subroot.model,
|
|
313
|
+
parent: subroot.parent,
|
|
314
|
+
children: replaceOrConcat(subroot.children, index, deprecatedID),
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const normalizeMetaSubroot = (subroot: MetaSubrootInitOptions): MetaSubrootInitOptions => {
|
|
319
|
+
if (isEditModeMetaSubroot(subroot)) {
|
|
320
|
+
return populateDeprecatedID(subroot);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return subroot;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export const normalizeChildInitOptions = (options: ChildrenInitOptions): ChildrenInitOptions => {
|
|
327
|
+
if (isMetaSubroot(options)) {
|
|
328
|
+
return normalizeMetaSubroot(options);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return options;
|
|
332
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { getBlobText } from '@getodk/common/lib/web-compat/blob.ts';
|
|
2
|
+
import { INSTANCE_FILE_NAME } from '../../client/constants.ts';
|
|
3
|
+
import type {
|
|
4
|
+
EditFormInstanceInput,
|
|
5
|
+
ResolvableFormInstanceInput,
|
|
6
|
+
} from '../../client/form/EditFormInstance.ts';
|
|
7
|
+
import type { InstanceData } from '../../client/serialization/InstanceData.ts';
|
|
8
|
+
import { ErrorProductionDesignPendingError } from '../../error/ErrorProductionDesignPendingError.ts';
|
|
9
|
+
import type { StaticDocument } from '../../integration/xpath/static-dom/StaticDocument.ts';
|
|
10
|
+
import type { ModelDefinition } from '../../parse/model/ModelDefinition.ts';
|
|
11
|
+
import { parseInstanceXML } from '../../parse/shared/parseInstanceXML.ts';
|
|
12
|
+
import type { XFormDOM } from '../../parse/XFormDOM.ts';
|
|
13
|
+
import { InstanceAttachmentMap } from './InstanceAttachmentMap.ts';
|
|
14
|
+
|
|
15
|
+
export type InitialInstanceStateSources = readonly [InstanceData, ...InstanceData[]];
|
|
16
|
+
|
|
17
|
+
interface InitialInstanceStateOptions {
|
|
18
|
+
readonly instanceXML: string;
|
|
19
|
+
readonly attachments: InstanceAttachmentMap;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const resolveInstanceXML = async (input: ResolvableFormInstanceInput): Promise<string> => {
|
|
23
|
+
const instanceResult = await input.resolveInstance();
|
|
24
|
+
|
|
25
|
+
if (typeof instanceResult === 'string') {
|
|
26
|
+
return instanceResult;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (instanceResult instanceof Blob) {
|
|
30
|
+
return getBlobText(instanceResult);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return instanceResult.text();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const parseInstanceDocument = (model: ModelDefinition, instanceXML: string): StaticDocument => {
|
|
37
|
+
const doc = parseInstanceXML(model, instanceXML);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Note: this is presently inferred as nullable. Its presence is _currently_
|
|
41
|
+
* enforced by {@link XFormDOM}, though that's definitely not ideal!
|
|
42
|
+
*
|
|
43
|
+
* @todo we would probably benefit from a single parse function responsible
|
|
44
|
+
* for both instance documents (form and input), with all `id` enforcement
|
|
45
|
+
* handled in one place (presence in both cases, matching in case of form +
|
|
46
|
+
* input). We could then also express this at the type level without weird DOM
|
|
47
|
+
* shenanigans in {@link XFormDOM} (which aren't likely to stick around in
|
|
48
|
+
* their current state forever!).
|
|
49
|
+
*/
|
|
50
|
+
const expectedId = model.instance.root.getAttributeValue('id');
|
|
51
|
+
const actualId = doc.root.getAttributeValue('id');
|
|
52
|
+
|
|
53
|
+
if (expectedId !== actualId) {
|
|
54
|
+
throw new ErrorProductionDesignPendingError(
|
|
55
|
+
`Invalid instance input. Expected instance id to be "${expectedId}", got: "${actualId}"`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return doc;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export class InitialInstanceState {
|
|
63
|
+
static async from(
|
|
64
|
+
model: ModelDefinition,
|
|
65
|
+
data: InitialInstanceStateSources
|
|
66
|
+
): Promise<InitialInstanceState> {
|
|
67
|
+
return this.resolve(model, {
|
|
68
|
+
inputType: 'FORM_INSTANCE_INPUT_RESOLVED',
|
|
69
|
+
data,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static async resolve(
|
|
74
|
+
model: ModelDefinition,
|
|
75
|
+
input: EditFormInstanceInput
|
|
76
|
+
): Promise<InitialInstanceState> {
|
|
77
|
+
if (input.inputType === 'FORM_INSTANCE_INPUT_RESOLVED') {
|
|
78
|
+
const { data } = input;
|
|
79
|
+
const [instanceData] = data;
|
|
80
|
+
const instanceFile = instanceData.get(INSTANCE_FILE_NAME);
|
|
81
|
+
const instanceXML = await getBlobText(instanceFile);
|
|
82
|
+
const attachments = InstanceAttachmentMap.from(data);
|
|
83
|
+
|
|
84
|
+
return new this(model, {
|
|
85
|
+
instanceXML,
|
|
86
|
+
attachments,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const [instanceXML, attachments] = await Promise.all([
|
|
91
|
+
resolveInstanceXML(input),
|
|
92
|
+
InstanceAttachmentMap.resolve(input.attachments),
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
return new this(model, {
|
|
96
|
+
instanceXML,
|
|
97
|
+
attachments,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
readonly document: StaticDocument;
|
|
102
|
+
readonly attachments: InstanceAttachmentMap;
|
|
103
|
+
|
|
104
|
+
private constructor(model: ModelDefinition, options: InitialInstanceStateOptions) {
|
|
105
|
+
this.document = parseInstanceDocument(model, options.instanceXML);
|
|
106
|
+
this.attachments = options.attachments;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { INSTANCE_FILE_NAME } from '../../client/constants.ts';
|
|
2
|
+
import type { ResolvableInstanceAttachmentsMap } from '../../client/form/EditFormInstance.ts';
|
|
3
|
+
import { MalformedInstanceDataError } from '../../error/MalformedInstanceDataError.ts';
|
|
4
|
+
|
|
5
|
+
type InstanceAttachmentMapSourceEntry = readonly [key: string, value: FormDataEntryValue];
|
|
6
|
+
|
|
7
|
+
interface InstanceAttachmentMapSource {
|
|
8
|
+
entries(): Iterable<InstanceAttachmentMapSourceEntry>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type InstanceAttachmentMapSources = readonly [
|
|
12
|
+
InstanceAttachmentMapSource,
|
|
13
|
+
...InstanceAttachmentMapSource[],
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @todo This currently short-circuits if there are actually any instance
|
|
18
|
+
* attachments to resolve. As described below, much of the approach is pretty
|
|
19
|
+
* naive now anyway, and none of it is really "ready" until we have something to
|
|
20
|
+
* actually _use the instance attachments_ once they're resolved! When we are
|
|
21
|
+
* ready, the functionality can be unblocked as in
|
|
22
|
+
* {@link https://github.com/getodk/web-forms/commit/88ee1b91c1f68d53ce9ba551bab334852e1e60cd | this commit}.
|
|
23
|
+
*
|
|
24
|
+
* @todo Everything about this is incredibly naive! We should almost certainly
|
|
25
|
+
* do _at least_ the following:
|
|
26
|
+
*
|
|
27
|
+
* - Limit how many attachments we attempt to resolve concurrently
|
|
28
|
+
* - Lazy resolution of large attachments (i.e. probably streaming, maybe range
|
|
29
|
+
* requests, ?)
|
|
30
|
+
*
|
|
31
|
+
* @todo Once lazy resolution is a thing, we will **also** need a clear path
|
|
32
|
+
* from there to eager resolution (i.e. for offline caching: it doesn't make
|
|
33
|
+
* sense to cache a stream in progress, as it won't load the resource once the
|
|
34
|
+
* user actually is offline/lacks network access). This may be something we can
|
|
35
|
+
* evolve gradually!
|
|
36
|
+
*/
|
|
37
|
+
const resolveInstanceAttachmentMapSource = async (
|
|
38
|
+
input: ResolvableInstanceAttachmentsMap
|
|
39
|
+
): Promise<InstanceAttachmentMapSource> => {
|
|
40
|
+
const inputEntries = Array.from(input.entries());
|
|
41
|
+
|
|
42
|
+
if (inputEntries.length > 0) {
|
|
43
|
+
const fileNames = Array.from(input.keys());
|
|
44
|
+
const errors = fileNames.map((fileName) => {
|
|
45
|
+
return new Error(`Failed to resolve instance attachment with file name "${fileName}"`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
throw new AggregateError(errors, 'Not implemented: instance attachment resource resolution');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const entries = await Promise.all<InstanceAttachmentMapSourceEntry>(
|
|
52
|
+
inputEntries.map(async ([fileName, resolveAttachment]) => {
|
|
53
|
+
const response = await resolveAttachment();
|
|
54
|
+
const blob = await response.blob();
|
|
55
|
+
const value = new File([blob], fileName);
|
|
56
|
+
|
|
57
|
+
return [fileName, value] as const;
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return { entries: () => entries };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
interface KeyedInstanceDataFile<Key extends string> extends File {
|
|
65
|
+
readonly name: Key;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type AssertKeyedInstanceDataFile = <Key extends string>(
|
|
69
|
+
key: Key,
|
|
70
|
+
file: File
|
|
71
|
+
) => asserts file is KeyedInstanceDataFile<Key>;
|
|
72
|
+
|
|
73
|
+
const assertKeyedInstanceDataFile: AssertKeyedInstanceDataFile = (key, file) => {
|
|
74
|
+
if (file.name !== key) {
|
|
75
|
+
throw new MalformedInstanceDataError(
|
|
76
|
+
`Unexpected InstanceData file. Expected file name to match key, got key: ${JSON.stringify(key)} and file name: ${JSON.stringify(file.name)}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type UnknownInstanceDataEntry = readonly [key: string, value: FormDataEntryValue];
|
|
82
|
+
|
|
83
|
+
type KeyedInstanceDataEntry<Key extends string> = readonly [key: Key, KeyedInstanceDataFile<Key>];
|
|
84
|
+
|
|
85
|
+
type AssertInstanceDataEntry = <Key extends string>(
|
|
86
|
+
entry: UnknownInstanceDataEntry
|
|
87
|
+
) => asserts entry is KeyedInstanceDataEntry<Key>;
|
|
88
|
+
|
|
89
|
+
const assertInstanceDataEntry: AssertInstanceDataEntry = (entry) => {
|
|
90
|
+
const [key, value] = entry;
|
|
91
|
+
|
|
92
|
+
if (!(value instanceof File)) {
|
|
93
|
+
throw new MalformedInstanceDataError(
|
|
94
|
+
`Unexpected non-file attachment in InstanceData for key: ${key}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
assertKeyedInstanceDataFile(key, value);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export class InstanceAttachmentMap extends Map<string, File> {
|
|
102
|
+
static from(sources: InstanceAttachmentMapSources): InstanceAttachmentMap {
|
|
103
|
+
return new this(sources);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @todo
|
|
108
|
+
* @see {@link resolveInstanceAttachmentMapSource}
|
|
109
|
+
*/
|
|
110
|
+
static async resolve(input: ResolvableInstanceAttachmentsMap): Promise<InstanceAttachmentMap> {
|
|
111
|
+
const source = await resolveInstanceAttachmentMapSource(input);
|
|
112
|
+
|
|
113
|
+
return new this([source]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private constructor(sources: InstanceAttachmentMapSources) {
|
|
117
|
+
super();
|
|
118
|
+
|
|
119
|
+
for (const source of sources) {
|
|
120
|
+
for (const entry of source.entries()) {
|
|
121
|
+
const [key] = entry;
|
|
122
|
+
|
|
123
|
+
// Skip the instance XML file: it's not an attachment!
|
|
124
|
+
if (key === INSTANCE_FILE_NAME) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this.has(key)) {
|
|
129
|
+
throw new MalformedInstanceDataError(
|
|
130
|
+
`Unexpected InstanceData entry. Duplicate instance attachment for key: ${JSON.stringify(key)}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
assertInstanceDataEntry(entry);
|
|
135
|
+
|
|
136
|
+
const [, value] = entry;
|
|
137
|
+
|
|
138
|
+
this.set(key, value);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|