@getodk/xforms-engine 0.4.0 → 0.5.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 +14 -3
- package/dist/client/BaseValueNode.d.ts +32 -0
- package/dist/client/EngineConfig.d.ts +49 -48
- package/dist/client/InputNode.d.ts +53 -0
- package/dist/client/ModelValueNode.d.ts +12 -7
- package/dist/client/NoteNode.d.ts +2 -2
- package/dist/client/RootNode.d.ts +34 -7
- package/dist/client/SelectNode.d.ts +2 -2
- package/dist/client/ValueType.d.ts +3 -0
- package/dist/client/constants.d.ts +50 -0
- package/dist/client/hierarchy.d.ts +4 -4
- package/dist/client/identity.d.ts +14 -0
- package/dist/client/node-types.d.ts +2 -1
- package/dist/client/resources.d.ts +93 -0
- package/dist/client/submission/SubmissionData.d.ts +7 -0
- package/dist/client/submission/SubmissionDefinition.d.ts +14 -0
- package/dist/client/submission/SubmissionInstanceFile.d.ts +6 -0
- package/dist/client/submission/SubmissionOptions.d.ts +23 -0
- package/dist/client/submission/SubmissionResult.d.ts +91 -0
- package/dist/client/submission/SubmissionState.d.ts +12 -0
- package/dist/client/validation.d.ts +2 -2
- package/dist/error/ErrorProductionDesignPendingError.d.ts +7 -0
- package/dist/error/ValueTypeInvariantError.d.ts +17 -0
- package/dist/error/XPathFunctionalityError.d.ts +14 -0
- package/dist/error/XPathFunctionalityNotSupportedError.d.ts +7 -0
- package/dist/error/XPathFunctionalityPendingError.d.ts +7 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +10856 -8585
- package/dist/index.js.map +1 -1
- package/dist/instance/Group.d.ts +9 -4
- package/dist/instance/InputControl.d.ts +36 -0
- package/dist/instance/ModelValue.d.ts +16 -26
- package/dist/instance/Note.d.ts +10 -3
- package/dist/instance/PrimaryInstance.d.ts +84 -0
- package/dist/instance/Root.d.ts +29 -25
- package/dist/instance/SelectField.d.ts +10 -2
- package/dist/instance/Subtree.d.ts +10 -5
- package/dist/instance/TriggerControl.d.ts +10 -3
- package/dist/instance/abstract/DescendantNode.d.ts +31 -23
- package/dist/instance/abstract/InstanceNode.d.ts +49 -29
- package/dist/instance/abstract/UnsupportedControl.d.ts +10 -3
- package/dist/instance/abstract/ValueNode.d.ts +48 -0
- package/dist/instance/hierarchy.d.ts +9 -8
- package/dist/instance/identity.d.ts +2 -7
- package/dist/instance/internal-api/EvaluationContext.d.ts +53 -12
- package/dist/instance/internal-api/InstanceConfig.d.ts +8 -2
- package/dist/instance/internal-api/InstanceValueContext.d.ts +20 -0
- package/dist/instance/internal-api/PrimaryInstanceDocument.d.ts +36 -0
- package/dist/instance/internal-api/TranslationContext.d.ts +3 -2
- package/dist/instance/internal-api/ValidationContext.d.ts +3 -4
- package/dist/instance/internal-api/ValueContext.d.ts +2 -1
- package/dist/instance/internal-api/submission/ClientReactiveSubmittableInstance.d.ts +14 -0
- package/dist/instance/internal-api/submission/ClientReactiveSubmittableLeafNode.d.ts +31 -0
- package/dist/instance/internal-api/submission/ClientReactiveSubmittableParentNode.d.ts +18 -0
- package/dist/instance/internal-api/submission/ClientReactiveSubmittableValueNode.d.ts +17 -0
- package/dist/instance/repeat/BaseRepeatRange.d.ts +39 -83
- package/dist/instance/repeat/RepeatInstance.d.ts +11 -29
- package/dist/instance/repeat/RepeatRangeControlled.d.ts +2 -2
- package/dist/instance/repeat/RepeatRangeUncontrolled.d.ts +2 -2
- package/dist/instance/resource.d.ts +1 -1
- package/dist/instance/text/TextRange.d.ts +1 -1
- package/dist/instance/unsupported/RangeControl.d.ts +3 -1
- package/dist/instance/unsupported/RankControl.d.ts +3 -1
- package/dist/instance/unsupported/UploadControl.d.ts +3 -1
- package/dist/integration/xpath/EngineXPathEvaluator.d.ts +14 -0
- package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +71 -0
- package/dist/integration/xpath/adapter/engineDOMAdapter.d.ts +5 -0
- package/dist/integration/xpath/adapter/kind.d.ts +32 -0
- package/dist/integration/xpath/adapter/names.d.ts +16 -0
- package/dist/integration/xpath/adapter/traversal.d.ts +25 -0
- package/dist/integration/xpath/adapter/values.d.ts +2 -0
- package/dist/integration/xpath/static-dom/StaticAttribute.d.ts +19 -0
- package/dist/integration/xpath/static-dom/StaticDocument.d.ts +16 -0
- package/dist/integration/xpath/static-dom/StaticElement.d.ts +40 -0
- package/dist/integration/xpath/static-dom/StaticNamedNode.d.ts +17 -0
- package/dist/integration/xpath/static-dom/StaticNode.d.ts +33 -0
- package/dist/integration/xpath/static-dom/StaticText.d.ts +16 -0
- package/dist/lib/client-reactivity/submission/createInstanceSubmissionState.d.ts +3 -0
- package/dist/lib/client-reactivity/submission/createLeafNodeSubmissionState.d.ts +3 -0
- package/dist/lib/client-reactivity/submission/createNodeRangeSubmissionState.d.ts +4 -0
- package/dist/lib/client-reactivity/submission/createParentNodeSubmissionState.d.ts +4 -0
- package/dist/lib/client-reactivity/submission/createValueNodeSubmissionState.d.ts +3 -0
- package/dist/lib/client-reactivity/submission/prepareSubmission.d.ts +8 -0
- package/dist/lib/codecs/DecimalValueCodec.d.ts +6 -0
- package/dist/lib/codecs/IntValueCodec.d.ts +6 -0
- package/dist/lib/codecs/StringValueCodec.d.ts +4 -0
- package/dist/lib/codecs/ValueCodec.d.ts +30 -0
- package/dist/lib/codecs/ValueTypePlaceholderCodec.d.ts +12 -0
- package/dist/lib/codecs/getSharedValueCodec.d.ts +47 -0
- package/dist/lib/dom/query.d.ts +3 -0
- package/dist/lib/reactivity/createChildrenState.d.ts +7 -5
- package/dist/lib/reactivity/createComputedExpression.d.ts +18 -5
- package/dist/lib/reactivity/createInstanceValueState.d.ts +40 -0
- package/dist/lib/reactivity/createNoteReadonlyThunk.d.ts +2 -2
- package/dist/lib/reactivity/createTranslationState.d.ts +19 -0
- package/dist/lib/reactivity/createValueState.d.ts +6 -10
- package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +4 -4
- package/dist/lib/xml-serialization.d.ts +41 -0
- package/dist/parse/XFormDOM.d.ts +18 -7
- package/dist/parse/XFormDefinition.d.ts +1 -2
- package/dist/parse/attachments/FormAttachmentResource.d.ts +27 -0
- package/dist/parse/body/BodyDefinition.d.ts +3 -3
- package/dist/parse/body/RepeatElementDefinition.d.ts +1 -1
- package/dist/parse/body/appearance/inputAppearanceParser.d.ts +1 -1
- package/dist/parse/body/control/{InputDefinition.d.ts → InputControlDefinition.d.ts} +1 -1
- package/dist/parse/body/control/select/ItemsetNodesetContext.d.ts +0 -2
- package/dist/parse/expression/abstract/DependencyContext.d.ts +0 -5
- package/dist/parse/expression/abstract/DependentExpression.d.ts +2 -3
- package/dist/parse/model/BindDefinition.d.ts +4 -5
- package/dist/parse/model/BindTypeDefinition.d.ts +20 -0
- package/dist/parse/model/DescendentNodeDefinition.d.ts +0 -1
- package/dist/parse/model/FormSubmissionDefinition.d.ts +8 -0
- package/dist/parse/model/ItextTranslation/ItextTranslationDefinition.d.ts +4 -0
- package/dist/parse/model/ItextTranslation/ItextTranslationRootDefinition.d.ts +8 -0
- package/dist/parse/model/ItextTranslation/ItextTranslationsDefinition.d.ts +8 -0
- package/dist/parse/model/LeafNodeDefinition.d.ts +3 -1
- package/dist/parse/model/ModelBindMap.d.ts +1 -1
- package/dist/parse/model/ModelDefinition.d.ts +2 -0
- package/dist/parse/model/NodeDefinition.d.ts +0 -1
- package/dist/parse/model/NoteNodeDefinition.d.ts +4 -4
- package/dist/parse/model/RootDefinition.d.ts +3 -2
- package/dist/parse/model/SecondaryInstance/SecondaryInstanceDefinition.d.ts +4 -0
- package/dist/parse/model/SecondaryInstance/SecondaryInstanceRootDefinition.d.ts +7 -0
- package/dist/parse/model/SecondaryInstance/SecondaryInstancesDefinition.d.ts +13 -0
- package/dist/parse/model/SecondaryInstance/sources/BlankSecondaryInstanceSource.d.ts +14 -0
- package/dist/parse/model/SecondaryInstance/sources/CSVExternalSecondaryInstance.d.ts +22 -0
- package/dist/parse/model/SecondaryInstance/sources/ExternalSecondaryInstanceResource.d.ts +23 -0
- package/dist/parse/model/SecondaryInstance/sources/ExternalSecondaryInstanceSource.d.ts +9 -0
- package/dist/parse/model/SecondaryInstance/sources/GeoJSONExternalSecondaryInstance.d.ts +5 -0
- package/dist/parse/model/SecondaryInstance/sources/InternalSecondaryInstanceSource.d.ts +7 -0
- package/dist/parse/model/SecondaryInstance/sources/SecondaryInstanceSource.d.ts +13 -0
- package/dist/parse/model/SecondaryInstance/sources/XMLExternalSecondaryInstanceSource.d.ts +12 -0
- package/dist/parse/shared/parseStaticDocumentFromDOMSubtree.d.ts +24 -0
- package/dist/parse/text/abstract/TextRangeDefinition.d.ts +0 -2
- package/dist/parse/xpath/semantic-analysis.d.ts +5 -0
- package/dist/solid.js +8174 -5921
- package/dist/solid.js.map +1 -1
- package/package.json +13 -11
- package/src/client/BaseNode.ts +16 -6
- package/src/client/BaseValueNode.ts +35 -0
- package/src/client/EngineConfig.ts +52 -51
- package/src/client/InputNode.ts +77 -0
- package/src/client/ModelValueNode.ts +30 -7
- package/src/client/NoteNode.ts +2 -2
- package/src/client/RootNode.ts +37 -7
- package/src/client/SelectNode.ts +3 -3
- package/src/client/ValueType.ts +4 -0
- package/src/client/constants.ts +67 -0
- package/src/client/hierarchy.ts +5 -5
- package/src/client/identity.ts +16 -0
- package/src/client/node-types.ts +12 -6
- package/src/client/resources.ts +118 -0
- package/src/client/submission/SubmissionData.ts +12 -0
- package/src/client/submission/SubmissionDefinition.ts +16 -0
- package/src/client/submission/SubmissionInstanceFile.ts +9 -0
- package/src/client/submission/SubmissionOptions.ts +28 -0
- package/src/client/submission/SubmissionResult.ts +124 -0
- package/src/client/submission/SubmissionState.ts +14 -0
- package/src/client/validation.ts +2 -2
- package/src/error/ErrorProductionDesignPendingError.ts +6 -0
- package/src/error/ValueTypeInvariantError.ts +22 -0
- package/src/error/XPathFunctionalityError.ts +26 -0
- package/src/error/XPathFunctionalityNotSupportedError.ts +18 -0
- package/src/error/XPathFunctionalityPendingError.ts +18 -0
- package/src/index.ts +10 -1
- package/src/instance/Group.ts +17 -5
- package/src/instance/InputControl.ts +119 -0
- package/src/instance/ModelValue.ts +47 -54
- package/src/instance/Note.ts +24 -8
- package/src/instance/PrimaryInstance.ts +244 -0
- package/src/instance/Root.ts +62 -134
- package/src/instance/SelectField.ts +56 -19
- package/src/instance/Subtree.ts +19 -7
- package/src/instance/TriggerControl.ts +23 -6
- package/src/instance/abstract/DescendantNode.ts +90 -45
- package/src/instance/abstract/InstanceNode.ts +103 -105
- package/src/instance/abstract/UnsupportedControl.ts +28 -5
- package/src/instance/abstract/ValueNode.ts +127 -0
- package/src/instance/children.ts +9 -10
- package/src/instance/hierarchy.ts +19 -15
- package/src/instance/identity.ts +3 -9
- package/src/instance/index.ts +25 -7
- package/src/instance/internal-api/EvaluationContext.ts +60 -13
- package/src/instance/internal-api/InstanceConfig.ts +9 -2
- package/src/instance/internal-api/InstanceValueContext.ts +24 -0
- package/src/instance/internal-api/PrimaryInstanceDocument.ts +53 -0
- package/src/instance/internal-api/TranslationContext.ts +3 -2
- package/src/instance/internal-api/ValidationContext.ts +3 -4
- package/src/instance/internal-api/ValueContext.ts +2 -1
- package/src/instance/internal-api/submission/ClientReactiveSubmittableInstance.ts +20 -0
- package/src/instance/internal-api/submission/ClientReactiveSubmittableLeafNode.ts +42 -0
- package/src/instance/internal-api/submission/ClientReactiveSubmittableParentNode.ts +25 -0
- package/src/instance/internal-api/submission/ClientReactiveSubmittableValueNode.ts +23 -0
- package/src/instance/repeat/BaseRepeatRange.ts +50 -131
- package/src/instance/repeat/RepeatInstance.ts +20 -46
- package/src/instance/repeat/RepeatRangeControlled.ts +5 -5
- package/src/instance/repeat/RepeatRangeUncontrolled.ts +2 -4
- package/src/instance/resource.ts +1 -1
- package/src/instance/text/TextChunk.ts +1 -1
- package/src/instance/unsupported/RangeControl.ts +5 -1
- package/src/instance/unsupported/RankControl.ts +5 -1
- package/src/instance/unsupported/UploadControl.ts +5 -1
- package/src/integration/xpath/EngineXPathEvaluator.ts +22 -0
- package/src/integration/xpath/adapter/XFormsXPathNode.ts +126 -0
- package/src/integration/xpath/adapter/engineDOMAdapter.ts +57 -0
- package/src/integration/xpath/adapter/kind.ts +114 -0
- package/src/integration/xpath/adapter/names.ts +99 -0
- package/src/integration/xpath/adapter/traversal.ts +228 -0
- package/src/integration/xpath/adapter/values.ts +5 -0
- package/src/integration/xpath/static-dom/StaticAttribute.ts +33 -0
- package/src/integration/xpath/static-dom/StaticDocument.ts +38 -0
- package/src/integration/xpath/static-dom/StaticElement.ts +106 -0
- package/src/integration/xpath/static-dom/StaticNamedNode.ts +45 -0
- package/src/integration/xpath/static-dom/StaticNode.ts +68 -0
- package/src/integration/xpath/static-dom/StaticText.ts +28 -0
- package/src/lib/client-reactivity/README.md +0 -0
- package/src/lib/client-reactivity/submission/createInstanceSubmissionState.ts +12 -0
- package/src/lib/client-reactivity/submission/createLeafNodeSubmissionState.ts +20 -0
- package/src/lib/client-reactivity/submission/createNodeRangeSubmissionState.ts +17 -0
- package/src/lib/client-reactivity/submission/createParentNodeSubmissionState.ts +22 -0
- package/src/lib/client-reactivity/submission/createValueNodeSubmissionState.ts +21 -0
- package/src/lib/client-reactivity/submission/prepareSubmission.ts +172 -0
- package/src/lib/codecs/DecimalValueCodec.ts +46 -0
- package/src/lib/codecs/IntValueCodec.ts +100 -0
- package/src/lib/codecs/StringValueCodec.ts +11 -0
- package/src/lib/codecs/ValueCodec.ts +106 -0
- package/src/lib/codecs/ValueTypePlaceholderCodec.ts +19 -0
- package/src/lib/codecs/getSharedValueCodec.ts +77 -0
- package/src/lib/dom/query.ts +7 -0
- package/src/lib/reactivity/createChildrenState.ts +8 -6
- package/src/lib/reactivity/createComputedExpression.ts +57 -34
- package/src/lib/reactivity/createInstanceValueState.ts +166 -0
- package/src/lib/reactivity/createNoteReadonlyThunk.ts +12 -7
- package/src/lib/reactivity/createSelectItems.ts +21 -21
- package/src/lib/reactivity/createTranslationState.ts +61 -0
- package/src/lib/reactivity/createValueState.ts +62 -120
- package/src/lib/reactivity/materializeCurrentStateChildren.ts +7 -7
- package/src/lib/reactivity/text/createTextRange.ts +4 -3
- package/src/lib/reactivity/validation/createAggregatedViolations.ts +1 -1
- package/src/lib/reactivity/validation/createValidation.ts +2 -3
- package/src/lib/xml-serialization.ts +96 -0
- package/src/parse/XFormDOM.ts +110 -75
- package/src/parse/XFormDefinition.ts +1 -6
- package/src/parse/attachments/FormAttachmentResource.ts +40 -0
- package/src/parse/body/BodyDefinition.ts +3 -3
- package/src/parse/body/appearance/inputAppearanceParser.ts +1 -1
- package/src/parse/body/control/{InputDefinition.ts → InputControlDefinition.ts} +3 -5
- package/src/parse/body/control/select/ItemsetNodesetContext.ts +0 -5
- package/src/parse/expression/abstract/DependencyContext.ts +0 -26
- package/src/parse/expression/abstract/DependentExpression.ts +2 -13
- package/src/parse/model/BindDefinition.ts +5 -8
- package/src/parse/model/BindTypeDefinition.ts +175 -0
- package/src/parse/model/DescendentNodeDefinition.ts +0 -6
- package/src/parse/model/FormSubmissionDefinition.ts +44 -0
- package/src/parse/model/ItextTranslation/ItextTranslationDefinition.ts +4 -0
- package/src/parse/model/ItextTranslation/ItextTranslationRootDefinition.ts +41 -0
- package/src/parse/model/ItextTranslation/ItextTranslationsDefinition.ts +31 -0
- package/src/parse/model/LeafNodeDefinition.ts +4 -1
- package/src/parse/model/ModelBindMap.ts +6 -8
- package/src/parse/model/ModelDefinition.ts +6 -1
- package/src/parse/model/NodeDefinition.ts +0 -3
- package/src/parse/model/NoteNodeDefinition.ts +3 -3
- package/src/parse/model/RootDefinition.ts +2 -1
- package/src/parse/model/SecondaryInstance/SecondaryInstanceDefinition.ts +4 -0
- package/src/parse/model/SecondaryInstance/SecondaryInstanceRootDefinition.ts +12 -0
- package/src/parse/model/SecondaryInstance/SecondaryInstancesDefinition.ts +102 -0
- package/src/parse/model/SecondaryInstance/sources/BlankSecondaryInstanceSource.ts +40 -0
- package/src/parse/model/SecondaryInstance/sources/CSVExternalSecondaryInstance.ts +288 -0
- package/src/parse/model/SecondaryInstance/sources/ExternalSecondaryInstanceResource.ts +222 -0
- package/src/parse/model/SecondaryInstance/sources/ExternalSecondaryInstanceSource.ts +22 -0
- package/src/parse/model/SecondaryInstance/sources/GeoJSONExternalSecondaryInstance.ts +414 -0
- package/src/parse/model/SecondaryInstance/sources/InternalSecondaryInstanceSource.ts +19 -0
- package/src/parse/model/SecondaryInstance/sources/SecondaryInstanceSource.ts +29 -0
- package/src/parse/model/SecondaryInstance/sources/XMLExternalSecondaryInstanceSource.ts +32 -0
- package/src/parse/shared/parseStaticDocumentFromDOMSubtree.ts +149 -0
- package/src/parse/text/abstract/TextRangeDefinition.ts +0 -6
- package/src/parse/xpath/semantic-analysis.ts +29 -0
- package/dist/client/StringNode.d.ts +0 -46
- package/dist/instance/StringField.d.ts +0 -44
- package/dist/instance/internal-api/SubscribableDependency.d.ts +0 -59
- package/dist/parse/XFormDataType.d.ts +0 -25
- package/src/client/StringNode.ts +0 -52
- package/src/instance/StringField.ts +0 -120
- package/src/instance/internal-api/SubscribableDependency.ts +0 -61
- package/src/parse/XFormDataType.ts +0 -62
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { UpsertableMap } from '@getodk/common/lib/collections/UpsertableMap.ts';
|
|
2
|
-
import type { XFormsXPathEvaluator } from '@getodk/xpath';
|
|
3
2
|
import type { Accessor } from 'solid-js';
|
|
4
3
|
import { createMemo } from 'solid-js';
|
|
5
4
|
import type { TextRange as ClientTextRange } from '../../client/TextRange.ts';
|
|
6
|
-
import type { SelectItem } from '../../index.ts';
|
|
5
|
+
import type { ActiveLanguage, SelectItem } from '../../index.ts';
|
|
6
|
+
import type { EvaluationContext } from '../../instance/internal-api/EvaluationContext.ts';
|
|
7
|
+
import type { TranslationContext } from '../../instance/internal-api/TranslationContext.ts';
|
|
7
8
|
import type { SelectField } from '../../instance/SelectField.ts';
|
|
8
|
-
import type {
|
|
9
|
-
EvaluationContext,
|
|
10
|
-
EvaluationContextRoot,
|
|
11
|
-
} from '../../instance/internal-api/EvaluationContext.ts';
|
|
12
|
-
import type { SubscribableDependency } from '../../instance/internal-api/SubscribableDependency.ts';
|
|
13
9
|
import { TextChunk } from '../../instance/text/TextChunk.ts';
|
|
14
10
|
import { TextRange } from '../../instance/text/TextRange.ts';
|
|
11
|
+
import type { EngineXPathNode } from '../../integration/xpath/adapter/kind.ts';
|
|
12
|
+
import type { EngineXPathEvaluator } from '../../integration/xpath/EngineXPathEvaluator.ts';
|
|
15
13
|
import type { ItemDefinition } from '../../parse/body/control/select/ItemDefinition.ts';
|
|
16
14
|
import type { ItemsetDefinition } from '../../parse/body/control/select/ItemsetDefinition.ts';
|
|
17
15
|
import { createComputedExpression } from './createComputedExpression.ts';
|
|
@@ -20,8 +18,8 @@ import { createTextRange } from './text/createTextRange.ts';
|
|
|
20
18
|
|
|
21
19
|
type DerivedItemLabel = ClientTextRange<'item-label', 'form-derived'>;
|
|
22
20
|
|
|
23
|
-
const derivedItemLabel = (context:
|
|
24
|
-
const chunk = new TextChunk(context
|
|
21
|
+
const derivedItemLabel = (context: TranslationContext, value: string): DerivedItemLabel => {
|
|
22
|
+
const chunk = new TextChunk(context, 'literal', value);
|
|
25
23
|
|
|
26
24
|
return new TextRange('form-derived', 'item-label', [chunk]);
|
|
27
25
|
};
|
|
@@ -61,23 +59,21 @@ const createTranslatedStaticSelectItems = (
|
|
|
61
59
|
};
|
|
62
60
|
|
|
63
61
|
class ItemsetItemEvaluationContext implements EvaluationContext {
|
|
62
|
+
readonly isAttached: Accessor<boolean>;
|
|
64
63
|
readonly scope: ReactiveScope;
|
|
65
|
-
readonly evaluator:
|
|
66
|
-
readonly root: EvaluationContextRoot;
|
|
64
|
+
readonly evaluator: EngineXPathEvaluator;
|
|
67
65
|
readonly contextReference: Accessor<string>;
|
|
66
|
+
readonly getActiveLanguage: Accessor<ActiveLanguage>;
|
|
68
67
|
|
|
69
68
|
constructor(
|
|
70
|
-
|
|
71
|
-
readonly contextNode:
|
|
69
|
+
selectField: SelectField,
|
|
70
|
+
readonly contextNode: EngineXPathNode
|
|
72
71
|
) {
|
|
72
|
+
this.isAttached = selectField.isAttached;
|
|
73
73
|
this.scope = selectField.scope;
|
|
74
74
|
this.evaluator = selectField.evaluator;
|
|
75
|
-
this.root = selectField.root;
|
|
76
75
|
this.contextReference = selectField.contextReference;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
getSubscribableDependenciesByReference(reference: string): readonly SubscribableDependency[] {
|
|
80
|
-
return this.selectField.getSubscribableDependenciesByReference(reference);
|
|
76
|
+
this.getActiveLanguage = selectField.getActiveLanguage;
|
|
81
77
|
}
|
|
82
78
|
}
|
|
83
79
|
|
|
@@ -107,14 +103,18 @@ const createItemsetItems = (
|
|
|
107
103
|
itemset: ItemsetDefinition
|
|
108
104
|
): Accessor<readonly ItemsetItem[]> => {
|
|
109
105
|
return selectField.scope.runTask(() => {
|
|
110
|
-
const itemNodes = createComputedExpression(selectField, itemset.nodes
|
|
111
|
-
|
|
106
|
+
const itemNodes = createComputedExpression(selectField, itemset.nodes, {
|
|
107
|
+
defaultValue: [],
|
|
108
|
+
});
|
|
109
|
+
const itemsCache = new UpsertableMap<EngineXPathNode, ItemsetItem>();
|
|
112
110
|
|
|
113
111
|
return createMemo(() => {
|
|
114
112
|
return itemNodes().map((itemNode) => {
|
|
115
113
|
return itemsCache.upsert(itemNode, () => {
|
|
116
114
|
const context = new ItemsetItemEvaluationContext(selectField, itemNode);
|
|
117
|
-
const value = createComputedExpression(context, itemset.value
|
|
115
|
+
const value = createComputedExpression(context, itemset.value, {
|
|
116
|
+
defaultValue: '',
|
|
117
|
+
});
|
|
118
118
|
const label = createSelectItemsetItemLabel(context, itemset, value);
|
|
119
119
|
|
|
120
120
|
return {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { XFormsXPathEvaluator } from '@getodk/xpath';
|
|
2
|
+
import type { Accessor } from 'solid-js';
|
|
3
|
+
import { createComputed, createSignal } from 'solid-js';
|
|
4
|
+
import type { ActiveLanguage, FormLanguage, FormLanguages } from '../../client/FormLanguage.ts';
|
|
5
|
+
import type { EngineXPathEvaluator } from '../../integration/xpath/EngineXPathEvaluator.ts';
|
|
6
|
+
import type { ReactiveScope } from './scope.ts';
|
|
7
|
+
import type { SimpleAtomicStateSetter } from './types.ts';
|
|
8
|
+
|
|
9
|
+
interface TranslationState {
|
|
10
|
+
readonly languages: FormLanguages;
|
|
11
|
+
readonly getActiveLanguage: Accessor<ActiveLanguage>;
|
|
12
|
+
readonly setActiveLanguage: SimpleAtomicStateSetter<FormLanguage>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @todo It's been very silly all along that {@link XFormsXPathEvaluator} is
|
|
17
|
+
* responsible for parsing translation languages, and maintaining the active
|
|
18
|
+
* language state. It is especially silly now that we've moved _part of the
|
|
19
|
+
* parsing_ up to the constructor call site. Let's finish off that awkwardness
|
|
20
|
+
* in a subsequent refactor.
|
|
21
|
+
*/
|
|
22
|
+
export const createTranslationState = (
|
|
23
|
+
scope: ReactiveScope,
|
|
24
|
+
evaluator: EngineXPathEvaluator
|
|
25
|
+
): TranslationState => {
|
|
26
|
+
const activeLanguageName = evaluator.getActiveLanguage();
|
|
27
|
+
const languageNames = evaluator.getLanguages();
|
|
28
|
+
|
|
29
|
+
let defaultLanguage: ActiveLanguage;
|
|
30
|
+
let languages: FormLanguages;
|
|
31
|
+
|
|
32
|
+
if (activeLanguageName == null) {
|
|
33
|
+
defaultLanguage = { isSyntheticDefault: true, language: '' };
|
|
34
|
+
languages = [defaultLanguage];
|
|
35
|
+
} else {
|
|
36
|
+
const inactiveLanguages = languageNames
|
|
37
|
+
.filter((languageName) => languageName !== activeLanguageName)
|
|
38
|
+
.map((language) => ({ language }));
|
|
39
|
+
|
|
40
|
+
defaultLanguage = { language: activeLanguageName };
|
|
41
|
+
languages = [defaultLanguage, ...inactiveLanguages];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const [getActiveLanguage, baseSetActiveLanguage] = createSignal(defaultLanguage);
|
|
45
|
+
|
|
46
|
+
const setActiveLanguage: SimpleAtomicStateSetter<FormLanguage> = (value) => {
|
|
47
|
+
return baseSetActiveLanguage(value);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
scope.runTask(() => {
|
|
51
|
+
createComputed(() => {
|
|
52
|
+
evaluator.setActiveLanguage(getActiveLanguage().language);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
languages,
|
|
58
|
+
getActiveLanguage,
|
|
59
|
+
setActiveLanguage,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { createComputed, createMemo, createSignal, untrack } from 'solid-js';
|
|
2
|
-
import
|
|
1
|
+
import { createComputed, createMemo, createSignal, untrack, type Signal } from 'solid-js';
|
|
2
|
+
import { ErrorProductionDesignPendingError } from '../../error/ErrorProductionDesignPendingError.ts';
|
|
3
3
|
import type { ValueContext } from '../../instance/internal-api/ValueContext.ts';
|
|
4
4
|
import type { BindComputationExpression } from '../../parse/expression/BindComputationExpression.ts';
|
|
5
|
-
import type { DependentExpression } from '../../parse/expression/abstract/DependentExpression.ts';
|
|
6
5
|
import { createComputedExpression } from './createComputedExpression.ts';
|
|
7
6
|
import type { SimpleAtomicState, SimpleAtomicStateSetter } from './types.ts';
|
|
8
7
|
|
|
@@ -19,8 +18,7 @@ export interface ValueStateOptions {
|
|
|
19
18
|
* other words, this value should not be used for edits.
|
|
20
19
|
*
|
|
21
20
|
* - 'PRIMARY_INSTANCE': Derives the initial state from the current text
|
|
22
|
-
* content of the {@link ValueNode.contextNode}
|
|
23
|
-
* backing store/source of thruth for primary instance state). This option
|
|
21
|
+
* content of the {@link ValueNode.contextNode}. This option
|
|
24
22
|
* should be specified when initializing a form with existing primary
|
|
25
23
|
* instance data, such as when editing a previous submission.
|
|
26
24
|
*
|
|
@@ -33,110 +31,54 @@ export interface ValueStateOptions {
|
|
|
33
31
|
readonly initialValueSource?: InitialValueSource;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
readonly isRelevant: boolean;
|
|
38
|
-
readonly value: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type PrimaryInstanceValueState = SimpleAtomicState<string>;
|
|
42
|
-
|
|
43
|
-
type ValueState<RuntimeValue> = SimpleAtomicState<RuntimeValue>;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Creates a signal which:
|
|
47
|
-
*
|
|
48
|
-
* 1. Persists its state to the primary instance's
|
|
49
|
-
* {@link ValueContext.contextNode | contextNode} on state changes.
|
|
50
|
-
* 2. Propagates downstream reactivity only after that write is persisted.
|
|
51
|
-
*
|
|
52
|
-
* This ensures that reactive subscriptions get a consistent view of a node's
|
|
53
|
-
* current state, regardless of whether they derive state from values in the
|
|
54
|
-
* primary instance (currently: computed {@link DependentExpression}
|
|
55
|
-
* evaluations) or from other aspects of reactive runtime state (generally,
|
|
56
|
-
* everything besides computed {@link DependentExpression}s).
|
|
57
|
-
*/
|
|
58
|
-
const createPrimaryInstanceValueState = <RuntimeValue>(
|
|
34
|
+
const getInitialValue = <RuntimeValue>(
|
|
59
35
|
context: ValueContext<RuntimeValue>,
|
|
60
36
|
options: ValueStateOptions
|
|
61
|
-
):
|
|
62
|
-
const {
|
|
63
|
-
const { initialValueSource } = options;
|
|
64
|
-
const { defaultValue } = definition;
|
|
37
|
+
): string => {
|
|
38
|
+
const { initialValueSource = 'FORM_DEFAULT' } = options;
|
|
65
39
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
initialValueSource === 'PRIMARY_INSTANCE'
|
|
70
|
-
? contextNode.textContent ?? defaultValue
|
|
71
|
-
: defaultValue;
|
|
40
|
+
if (initialValueSource === 'FORM_DEFAULT') {
|
|
41
|
+
return context.definition.defaultValue;
|
|
42
|
+
}
|
|
72
43
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
isRelevant: context.isRelevant(),
|
|
76
|
-
value: initialValue,
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
// This could return a single boolean expression, but checking each
|
|
80
|
-
// equality condition separately feels like it more clearly expresses
|
|
81
|
-
// the intent.
|
|
82
|
-
equals: (previous, updated) => {
|
|
83
|
-
if (updated.isRelevant !== previous.isRelevant) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
44
|
+
throw new ErrorProductionDesignPendingError('Edit implementation pending');
|
|
45
|
+
};
|
|
86
46
|
|
|
87
|
-
|
|
88
|
-
},
|
|
89
|
-
}
|
|
90
|
-
);
|
|
91
|
-
const [persistedValue, setValueForPersistence] = persistedValueState;
|
|
47
|
+
type BaseValueState = Signal<string>;
|
|
92
48
|
|
|
93
|
-
|
|
94
|
-
const isRelevant = context.isRelevant();
|
|
49
|
+
type RelevantValueState = SimpleAtomicState<string>;
|
|
95
50
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Wraps {@link baseValueState} in a signal-like interface which:
|
|
53
|
+
*
|
|
54
|
+
* - produces a blank value for nodes ({@link context}) in a non-relevant state
|
|
55
|
+
* - persists, and restores, the most recent non-blank value state when a
|
|
56
|
+
* node/context's relevance is restored
|
|
57
|
+
*/
|
|
58
|
+
const createRelevantValueState = <RuntimeValue>(
|
|
59
|
+
context: ValueContext<RuntimeValue>,
|
|
60
|
+
baseValueState: BaseValueState
|
|
61
|
+
): RelevantValueState => {
|
|
62
|
+
return context.scope.runTask(() => {
|
|
63
|
+
const [getRelevantValue, setValue] = baseValueState;
|
|
103
64
|
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
return
|
|
65
|
+
const getValue = createMemo(() => {
|
|
66
|
+
if (context.isRelevant()) {
|
|
67
|
+
return getRelevantValue();
|
|
107
68
|
}
|
|
108
69
|
|
|
109
70
|
return '';
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const [signalValue, setSignalValue] = createSignal(toSignalValue(persistedValue()));
|
|
113
|
-
|
|
114
|
-
createComputed(() => {
|
|
115
|
-
const { isRelevant, value } = persistedValue();
|
|
116
|
-
const preparedTextContent = isRelevant ? value : '';
|
|
117
|
-
const assignedTextContent = (contextNode.textContent = preparedTextContent);
|
|
118
|
-
|
|
119
|
-
setSignalValue(assignedTextContent);
|
|
120
71
|
});
|
|
121
72
|
|
|
122
|
-
|
|
123
|
-
// TODO: Check (error?) for non-relevant value change?
|
|
124
|
-
const persisted = setValueForPersistence({
|
|
125
|
-
isRelevant: context.isRelevant(),
|
|
126
|
-
value,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
return persisted.value;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
return [signalValue, setPrimaryInstanceValue];
|
|
73
|
+
return [getValue, setValue];
|
|
133
74
|
});
|
|
134
75
|
};
|
|
135
76
|
|
|
77
|
+
type RuntimeValueState<RuntimeValue> = SimpleAtomicState<RuntimeValue>;
|
|
78
|
+
|
|
136
79
|
/**
|
|
137
|
-
* Wraps
|
|
138
|
-
*
|
|
139
|
-
* representation:
|
|
80
|
+
* Wraps {@link relevantValueState} in a signal-like interface which
|
|
81
|
+
* automatically encodes and decodes a node's runtime value representation:
|
|
140
82
|
*
|
|
141
83
|
* - Values read by a node will be read from the current state as persisted in
|
|
142
84
|
* the primary instance, then {@link ValueContext.decodeValue | decoded} (as
|
|
@@ -144,21 +86,17 @@ const createPrimaryInstanceValueState = <RuntimeValue>(
|
|
|
144
86
|
* functionality provided by that node.
|
|
145
87
|
*
|
|
146
88
|
* - Values written by a node will be {@link ValueContext.encodeValue | encoded}
|
|
147
|
-
* (also as implemented by that node) into a string appropriate
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* - Downstream reactive computations should subscribe to updates to this
|
|
151
|
-
* runtime state (suggesting the value node itself should access this state in
|
|
152
|
-
* its {@link SubscribableDependency.subscribe} implementation).
|
|
89
|
+
* (also as implemented by that node) into a string value appropriate for
|
|
90
|
+
* serialization in an instance submission.
|
|
153
91
|
*/
|
|
154
92
|
const createRuntimeValueState = <RuntimeValue>(
|
|
155
93
|
context: ValueContext<RuntimeValue>,
|
|
156
|
-
|
|
157
|
-
):
|
|
94
|
+
relevantValueState: RelevantValueState
|
|
95
|
+
): RuntimeValueState<RuntimeValue> => {
|
|
158
96
|
const { decodeValue, encodeValue } = context;
|
|
159
97
|
|
|
160
98
|
return context.scope.runTask(() => {
|
|
161
|
-
const [primaryInstanceValue, setPrimaryInstanceValue] =
|
|
99
|
+
const [primaryInstanceValue, setPrimaryInstanceValue] = relevantValueState;
|
|
162
100
|
const getRuntimeValue = createMemo(() => {
|
|
163
101
|
return decodeValue(primaryInstanceValue());
|
|
164
102
|
});
|
|
@@ -212,10 +150,12 @@ const createCalculation = <RuntimeValue>(
|
|
|
212
150
|
calculateDefinition: BindComputationExpression<'calculate'>
|
|
213
151
|
): void => {
|
|
214
152
|
context.scope.runTask(() => {
|
|
215
|
-
const calculate = createComputedExpression(context, calculateDefinition
|
|
153
|
+
const calculate = createComputedExpression(context, calculateDefinition, {
|
|
154
|
+
defaultValue: '',
|
|
155
|
+
});
|
|
216
156
|
|
|
217
157
|
createComputed(() => {
|
|
218
|
-
if (context.isRelevant()) {
|
|
158
|
+
if (context.isAttached() && context.isRelevant()) {
|
|
219
159
|
const calculated = calculate();
|
|
220
160
|
const value = context.decodeValue(calculated);
|
|
221
161
|
|
|
@@ -225,34 +165,36 @@ const createCalculation = <RuntimeValue>(
|
|
|
225
165
|
});
|
|
226
166
|
};
|
|
227
167
|
|
|
168
|
+
type ValueState<RuntimeValue> = SimpleAtomicState<RuntimeValue>;
|
|
169
|
+
|
|
228
170
|
/**
|
|
229
171
|
* Provides a consistent interface for value nodes of any type which:
|
|
230
172
|
*
|
|
231
|
-
* - derives initial state from either
|
|
232
|
-
*
|
|
233
|
-
* - decodes primary instance state into the value node's runtime type
|
|
234
|
-
* - encodes
|
|
173
|
+
* - derives initial state from either an existing instance (e.g. for edits) or
|
|
174
|
+
* the node's definition (e.g. initializing a new submission)
|
|
175
|
+
* - decodes current primary instance state into the value node's runtime type
|
|
176
|
+
* - encodes updated runtime values to store updated instance state
|
|
235
177
|
* - initializes reactive computation of `calculate` bind expressions for those
|
|
236
178
|
* nodes defined with one
|
|
237
|
-
* -
|
|
238
|
-
* (whether performed by a client, or by a reactive `calculate` computation)
|
|
239
|
-
* are persisted, ensuring a consistent view of state when downstream
|
|
240
|
-
* computations perform XPath evaluations against that primary instance state
|
|
179
|
+
* - prevents downstream writes to nodes in a readonly state
|
|
241
180
|
*/
|
|
242
181
|
export const createValueState = <RuntimeValue>(
|
|
243
182
|
context: ValueContext<RuntimeValue>,
|
|
244
183
|
options: ValueStateOptions = {}
|
|
245
184
|
): ValueState<RuntimeValue> => {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
185
|
+
return context.scope.runTask(() => {
|
|
186
|
+
const initialValue = getInitialValue(context, options);
|
|
187
|
+
const baseValueState = createSignal(initialValue);
|
|
188
|
+
const valueState = createRelevantValueState(context, baseValueState);
|
|
189
|
+
const runtimeState = createRuntimeValueState(context, valueState);
|
|
190
|
+
const { calculate } = context.definition.bind;
|
|
250
191
|
|
|
251
|
-
|
|
252
|
-
|
|
192
|
+
if (calculate != null) {
|
|
193
|
+
const [, setValue] = runtimeState;
|
|
253
194
|
|
|
254
|
-
|
|
255
|
-
|
|
195
|
+
createCalculation(context, setValue, calculate);
|
|
196
|
+
}
|
|
256
197
|
|
|
257
|
-
|
|
198
|
+
return guardDownstreamReadonlyWrites(context, runtimeState);
|
|
199
|
+
});
|
|
258
200
|
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import type { FormNodeID } from '../../client/identity.ts';
|
|
1
2
|
import type { AnyChildNode } from '../../instance/hierarchy.ts';
|
|
2
|
-
import type { NodeID } from '../../instance/identity.ts';
|
|
3
3
|
import type { ChildrenState, createChildrenState } from './createChildrenState.ts';
|
|
4
4
|
import type { ClientState } from './node-state/createClientState.ts';
|
|
5
5
|
import type { CurrentState } from './node-state/createCurrentState.ts';
|
|
6
6
|
import type { ReactiveScope } from './scope.ts';
|
|
7
7
|
|
|
8
8
|
interface InconsistentChildrenStateDetails {
|
|
9
|
-
readonly missingIds: readonly
|
|
10
|
-
readonly unexpectedIds: readonly
|
|
9
|
+
readonly missingIds: readonly FormNodeID[];
|
|
10
|
+
readonly unexpectedIds: readonly FormNodeID[];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
class InconsistentChildrenStateError extends Error {
|
|
@@ -37,7 +37,7 @@ class InconsistentChildrenStateError extends Error {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export interface EncodedParentState {
|
|
40
|
-
readonly children: readonly
|
|
40
|
+
readonly children: readonly FormNodeID[];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -58,7 +58,7 @@ export interface EncodedParentState {
|
|
|
58
58
|
* @todo should we throw rather than warn until we have this confidence?
|
|
59
59
|
*/
|
|
60
60
|
const reportInconsistentChildrenState = (
|
|
61
|
-
expectedClientIds: readonly
|
|
61
|
+
expectedClientIds: readonly FormNodeID[],
|
|
62
62
|
actualNodes: readonly AnyChildNode[]
|
|
63
63
|
): void => {
|
|
64
64
|
const actualIds = actualNodes.map((node) => node.nodeId);
|
|
@@ -88,8 +88,8 @@ export type MaterializedChildren<
|
|
|
88
88
|
/**
|
|
89
89
|
* Creates a wrapper proxy around a parent node's {@link CurrentState} to map
|
|
90
90
|
* `children` state, which is written to the node's (internal, synchronized)
|
|
91
|
-
* {@link ClientState} as an array of {@link
|
|
92
|
-
* corresponding to those IDs.
|
|
91
|
+
* {@link ClientState} as an array of {@link FormNodeID}s, back to the node
|
|
92
|
+
* objects corresponding to those IDs.
|
|
93
93
|
*
|
|
94
94
|
* @see {@link createChildrenState} for further detail.
|
|
95
95
|
*/
|
|
@@ -29,7 +29,9 @@ const createComputedTextChunk = (
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
return context.scope.runTask(() => {
|
|
32
|
-
const getText = createComputedExpression(context, textSource
|
|
32
|
+
const getText = createComputedExpression(context, textSource, {
|
|
33
|
+
defaultValue: '',
|
|
34
|
+
});
|
|
33
35
|
|
|
34
36
|
return {
|
|
35
37
|
source,
|
|
@@ -43,14 +45,13 @@ const createTextChunks = (
|
|
|
43
45
|
textSources: readonly AnyTextChunkExpression[]
|
|
44
46
|
): Accessor<readonly TextChunk[]> => {
|
|
45
47
|
return context.scope.runTask(() => {
|
|
46
|
-
const { root } = context;
|
|
47
48
|
const chunkComputations = textSources.map((textSource) => {
|
|
48
49
|
return createComputedTextChunk(context, textSource);
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
return createMemo(() => {
|
|
52
53
|
return chunkComputations.map(({ source, getText }) => {
|
|
53
|
-
return new TextChunk(
|
|
54
|
+
return new TextChunk(context, source, getText());
|
|
54
55
|
});
|
|
55
56
|
});
|
|
56
57
|
});
|
|
@@ -33,7 +33,7 @@ const engineViolationMessage = <Role extends ValidationTextRole>(
|
|
|
33
33
|
role: Role
|
|
34
34
|
): Accessor<EngineViolationMessage<Role>> => {
|
|
35
35
|
const messageText = VALIDATION_TEXT[role];
|
|
36
|
-
const chunk = new TextChunk(context
|
|
36
|
+
const chunk = new TextChunk(context, 'literal', messageText);
|
|
37
37
|
const message = new TextRange('engine', role, [chunk]);
|
|
38
38
|
|
|
39
39
|
return () => message;
|
|
@@ -75,9 +75,8 @@ const createConstraintValidation = (
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const isValid = createComputedExpression(context, constraint, {
|
|
78
|
-
|
|
78
|
+
defaultValue: true,
|
|
79
79
|
});
|
|
80
|
-
|
|
81
80
|
const message = createViolationMessage(context, 'constraintMsg', constraintMsg);
|
|
82
81
|
|
|
83
82
|
return createMemo(() => {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
declare const ESCAPED_XML_TEXT_BRAND: unique symbol;
|
|
2
|
+
|
|
3
|
+
export type EscapedXMLText = string & { readonly [ESCAPED_XML_TEXT_BRAND]: true };
|
|
4
|
+
|
|
5
|
+
const ATTR_REGEX = /[&<>"]/;
|
|
6
|
+
const CONTENT_REGEX = /[&<>]/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This is based on the `escapeHTML` implementation in
|
|
10
|
+
* {@link https://github.com/ryansolid/dom-expressions} (Solid's JSX transform).
|
|
11
|
+
*
|
|
12
|
+
* @see {@link https://github.com/ryansolid/dom-expressions/pull/27} for
|
|
13
|
+
* motivation to derive this implementation approach.
|
|
14
|
+
*
|
|
15
|
+
* The intent is that this can be updated easily if the base implementation
|
|
16
|
+
* changes. As such, some aspects of this implementation differ from some of our
|
|
17
|
+
* typical code style preferences.
|
|
18
|
+
*
|
|
19
|
+
* Notable changes from the base implementation:
|
|
20
|
+
*
|
|
21
|
+
* - Formatting: automated only.
|
|
22
|
+
* - Naming: the {@link text} parameter is named `html` in the base
|
|
23
|
+
* implementation. That would be confusing if preserved.
|
|
24
|
+
* - Types:
|
|
25
|
+
* - Parameter types are added (of course)
|
|
26
|
+
* - Return type is branded as {@link EscapedXMLText}, to allow downstream
|
|
27
|
+
* checks that escaping has been performed. Return statements are cast
|
|
28
|
+
* accordingly.
|
|
29
|
+
* - {@link text} attempts to minimize risk of double-escaping by excluding
|
|
30
|
+
* that same branded type.
|
|
31
|
+
* - The '>' character is also escaped, necessary for producing valid XML.
|
|
32
|
+
*
|
|
33
|
+
* As with the base implementation, we leave some characters unescaped:
|
|
34
|
+
*
|
|
35
|
+
* - " (double quote): except when {@link attr} is `true`.
|
|
36
|
+
*
|
|
37
|
+
* - ' (single quote): on the assumption that attributes are always serialized
|
|
38
|
+
* in double quotes. If we ever move this to `@getodk/common`, we'd want to
|
|
39
|
+
* reconsider this assumption.
|
|
40
|
+
*/
|
|
41
|
+
export const escapeXMLText = <Text extends string>(
|
|
42
|
+
text: Exclude<Text, EscapedXMLText>,
|
|
43
|
+
attr?: boolean
|
|
44
|
+
): EscapedXMLText => {
|
|
45
|
+
const match = (attr ? ATTR_REGEX : CONTENT_REGEX).exec(text);
|
|
46
|
+
if (!match) return text as string as EscapedXMLText;
|
|
47
|
+
let index = 0;
|
|
48
|
+
let lastIndex = 0;
|
|
49
|
+
let out = '';
|
|
50
|
+
let escape = '';
|
|
51
|
+
for (index = match.index; index < text.length; index++) {
|
|
52
|
+
switch (text.charCodeAt(index)) {
|
|
53
|
+
case 34: // "
|
|
54
|
+
if (!attr) continue;
|
|
55
|
+
escape = '"';
|
|
56
|
+
break;
|
|
57
|
+
case 38: // &
|
|
58
|
+
escape = '&';
|
|
59
|
+
break;
|
|
60
|
+
case 60: // <
|
|
61
|
+
escape = '<';
|
|
62
|
+
break;
|
|
63
|
+
case 62: // >
|
|
64
|
+
escape = '>';
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (lastIndex !== index) out += text.substring(lastIndex, index);
|
|
70
|
+
lastIndex = index + 1;
|
|
71
|
+
out += escape;
|
|
72
|
+
}
|
|
73
|
+
return lastIndex !== index
|
|
74
|
+
? ((out + text.substring(lastIndex, index)) as EscapedXMLText)
|
|
75
|
+
: (out as EscapedXMLText);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const serializeElementXML = (nodeName: string, children: string): string => {
|
|
79
|
+
if (children === '') {
|
|
80
|
+
return `<${nodeName}/>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// TODO: attributes
|
|
84
|
+
return `<${nodeName}>${children}</${nodeName}>`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const serializeParentElementXML = (
|
|
88
|
+
nodeName: string,
|
|
89
|
+
serializedChildren: readonly string[]
|
|
90
|
+
): string => {
|
|
91
|
+
return serializeElementXML(nodeName, serializedChildren.join(''));
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const serializeLeafElementXML = (nodeName: string, xmlValue: EscapedXMLText): string => {
|
|
95
|
+
return serializeElementXML(nodeName, xmlValue.normalize());
|
|
96
|
+
};
|