@getodk/xforms-engine 0.1.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/README.md +44 -0
- package/dist/.vite/manifest.json +7 -0
- package/dist/XFormDOM.d.ts +31 -0
- package/dist/XFormDataType.d.ts +26 -0
- package/dist/XFormDefinition.d.ts +14 -0
- package/dist/body/BodyDefinition.d.ts +52 -0
- package/dist/body/BodyElementDefinition.d.ts +32 -0
- package/dist/body/RepeatDefinition.d.ts +15 -0
- package/dist/body/UnsupportedBodyElementDefinition.d.ts +10 -0
- package/dist/body/control/ControlDefinition.d.ts +16 -0
- package/dist/body/control/InputDefinition.d.ts +5 -0
- package/dist/body/control/select/ItemDefinition.d.ts +13 -0
- package/dist/body/control/select/ItemsetDefinition.d.ts +16 -0
- package/dist/body/control/select/ItemsetNodesetContext.d.ts +11 -0
- package/dist/body/control/select/ItemsetNodesetExpression.d.ts +5 -0
- package/dist/body/control/select/ItemsetValueExpression.d.ts +6 -0
- package/dist/body/control/select/SelectDefinition.d.ts +23 -0
- package/dist/body/group/BaseGroupDefinition.d.ts +46 -0
- package/dist/body/group/LogicalGroupDefinition.d.ts +6 -0
- package/dist/body/group/PresentationGroupDefinition.d.ts +11 -0
- package/dist/body/group/RepeatGroupDefinition.d.ts +12 -0
- package/dist/body/group/StructuralGroupDefinition.d.ts +6 -0
- package/dist/body/text/HintDefinition.d.ts +11 -0
- package/dist/body/text/LabelDefinition.d.ts +20 -0
- package/dist/body/text/TextElementDefinition.d.ts +32 -0
- package/dist/body/text/TextElementOutputPart.d.ts +12 -0
- package/dist/body/text/TextElementPart.d.ts +12 -0
- package/dist/body/text/TextElementReferencePart.d.ts +6 -0
- package/dist/body/text/TextElementStaticPart.d.ts +6 -0
- package/dist/client/BaseNode.d.ts +138 -0
- package/dist/client/EngineConfig.d.ts +78 -0
- package/dist/client/FormLanguage.d.ts +63 -0
- package/dist/client/GroupNode.d.ts +24 -0
- package/dist/client/OpaqueReactiveObjectFactory.d.ts +70 -0
- package/dist/client/RepeatInstanceNode.d.ts +28 -0
- package/dist/client/RepeatRangeNode.d.ts +94 -0
- package/dist/client/RootNode.d.ts +31 -0
- package/dist/client/SelectNode.d.ts +60 -0
- package/dist/client/StringNode.d.ts +41 -0
- package/dist/client/SubtreeNode.d.ts +52 -0
- package/dist/client/TextRange.d.ts +55 -0
- package/dist/client/hierarchy.d.ts +48 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/node-types.d.ts +1 -0
- package/dist/expression/DependencyContext.d.ts +12 -0
- package/dist/expression/DependentExpression.d.ts +43 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +37622 -0
- package/dist/index.js.map +1 -0
- package/dist/instance/Group.d.ts +31 -0
- package/dist/instance/RepeatInstance.d.ts +60 -0
- package/dist/instance/RepeatRange.d.ts +81 -0
- package/dist/instance/Root.d.ts +70 -0
- package/dist/instance/SelectField.d.ts +45 -0
- package/dist/instance/StringField.d.ts +39 -0
- package/dist/instance/Subtree.d.ts +30 -0
- package/dist/instance/abstract/DescendantNode.d.ts +76 -0
- package/dist/instance/abstract/InstanceNode.d.ts +107 -0
- package/dist/instance/children.d.ts +2 -0
- package/dist/instance/hierarchy.d.ts +12 -0
- package/dist/instance/identity.d.ts +7 -0
- package/dist/instance/index.d.ts +8 -0
- package/dist/instance/internal-api/EvaluationContext.d.ts +34 -0
- package/dist/instance/internal-api/InstanceConfig.d.ts +8 -0
- package/dist/instance/internal-api/SubscribableDependency.d.ts +59 -0
- package/dist/instance/internal-api/TranslationContext.d.ts +4 -0
- package/dist/instance/internal-api/ValueContext.d.ts +22 -0
- package/dist/instance/resource.d.ts +10 -0
- package/dist/instance/text/FormattedTextStub.d.ts +1 -0
- package/dist/instance/text/TextChunk.d.ts +11 -0
- package/dist/instance/text/TextRange.d.ts +10 -0
- package/dist/lib/dom/query.d.ts +20 -0
- package/dist/lib/reactivity/createChildrenState.d.ts +36 -0
- package/dist/lib/reactivity/createComputedExpression.d.ts +12 -0
- package/dist/lib/reactivity/createSelectItems.d.ts +16 -0
- package/dist/lib/reactivity/createValueState.d.ts +44 -0
- package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +18 -0
- package/dist/lib/reactivity/node-state/createClientState.d.ts +9 -0
- package/dist/lib/reactivity/node-state/createCurrentState.d.ts +6 -0
- package/dist/lib/reactivity/node-state/createEngineState.d.ts +5 -0
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +22 -0
- package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +6 -0
- package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +139 -0
- package/dist/lib/reactivity/node-state/representations.d.ts +25 -0
- package/dist/lib/reactivity/scope.d.ts +23 -0
- package/dist/lib/reactivity/text/createFieldHint.d.ts +5 -0
- package/dist/lib/reactivity/text/createNodeLabel.d.ts +5 -0
- package/dist/lib/reactivity/text/createTextRange.d.ts +19 -0
- package/dist/lib/reactivity/types.d.ts +21 -0
- package/dist/lib/unique-id.d.ts +27 -0
- package/dist/lib/xpath/analysis.d.ts +22 -0
- package/dist/model/BindComputation.d.ts +30 -0
- package/dist/model/BindDefinition.d.ts +31 -0
- package/dist/model/BindElement.d.ts +6 -0
- package/dist/model/DescendentNodeDefinition.d.ts +25 -0
- package/dist/model/ModelBindMap.d.ts +15 -0
- package/dist/model/ModelDefinition.d.ts +10 -0
- package/dist/model/NodeDefinition.d.ts +74 -0
- package/dist/model/RepeatInstanceDefinition.d.ts +15 -0
- package/dist/model/RepeatSequenceDefinition.d.ts +19 -0
- package/dist/model/RepeatTemplateDefinition.d.ts +29 -0
- package/dist/model/RootDefinition.d.ts +24 -0
- package/dist/model/SubtreeDefinition.d.ts +14 -0
- package/dist/model/ValueNodeDefinition.d.ts +15 -0
- package/dist/solid.js +37273 -0
- package/dist/solid.js.map +1 -0
- package/package.json +87 -0
- package/src/XFormDOM.ts +224 -0
- package/src/XFormDataType.ts +64 -0
- package/src/XFormDefinition.ts +40 -0
- package/src/body/BodyDefinition.ts +202 -0
- package/src/body/BodyElementDefinition.ts +62 -0
- package/src/body/RepeatDefinition.ts +54 -0
- package/src/body/UnsupportedBodyElementDefinition.ts +17 -0
- package/src/body/control/ControlDefinition.ts +42 -0
- package/src/body/control/InputDefinition.ts +9 -0
- package/src/body/control/select/ItemDefinition.ts +31 -0
- package/src/body/control/select/ItemsetDefinition.ts +36 -0
- package/src/body/control/select/ItemsetNodesetContext.ts +26 -0
- package/src/body/control/select/ItemsetNodesetExpression.ts +8 -0
- package/src/body/control/select/ItemsetValueExpression.ts +11 -0
- package/src/body/control/select/SelectDefinition.ts +74 -0
- package/src/body/group/BaseGroupDefinition.ts +137 -0
- package/src/body/group/LogicalGroupDefinition.ts +11 -0
- package/src/body/group/PresentationGroupDefinition.ts +28 -0
- package/src/body/group/RepeatGroupDefinition.ts +91 -0
- package/src/body/group/StructuralGroupDefinition.ts +11 -0
- package/src/body/text/HintDefinition.ts +26 -0
- package/src/body/text/LabelDefinition.ts +54 -0
- package/src/body/text/TextElementDefinition.ts +97 -0
- package/src/body/text/TextElementOutputPart.ts +27 -0
- package/src/body/text/TextElementPart.ts +31 -0
- package/src/body/text/TextElementReferencePart.ts +21 -0
- package/src/body/text/TextElementStaticPart.ts +26 -0
- package/src/client/BaseNode.ts +180 -0
- package/src/client/EngineConfig.ts +83 -0
- package/src/client/FormLanguage.ts +77 -0
- package/src/client/GroupNode.ts +33 -0
- package/src/client/OpaqueReactiveObjectFactory.ts +100 -0
- package/src/client/README.md +39 -0
- package/src/client/RepeatInstanceNode.ts +41 -0
- package/src/client/RepeatRangeNode.ts +100 -0
- package/src/client/RootNode.ts +36 -0
- package/src/client/SelectNode.ts +69 -0
- package/src/client/StringNode.ts +46 -0
- package/src/client/SubtreeNode.ts +57 -0
- package/src/client/TextRange.ts +63 -0
- package/src/client/hierarchy.ts +63 -0
- package/src/client/index.ts +29 -0
- package/src/client/node-types.ts +10 -0
- package/src/expression/DependencyContext.ts +53 -0
- package/src/expression/DependentExpression.ts +102 -0
- package/src/index.ts +35 -0
- package/src/instance/Group.ts +82 -0
- package/src/instance/RepeatInstance.ts +164 -0
- package/src/instance/RepeatRange.ts +214 -0
- package/src/instance/Root.ts +264 -0
- package/src/instance/SelectField.ts +204 -0
- package/src/instance/StringField.ts +93 -0
- package/src/instance/Subtree.ts +79 -0
- package/src/instance/abstract/DescendantNode.ts +182 -0
- package/src/instance/abstract/InstanceNode.ts +257 -0
- package/src/instance/children.ts +52 -0
- package/src/instance/hierarchy.ts +54 -0
- package/src/instance/identity.ts +11 -0
- package/src/instance/index.ts +37 -0
- package/src/instance/internal-api/EvaluationContext.ts +41 -0
- package/src/instance/internal-api/InstanceConfig.ts +9 -0
- package/src/instance/internal-api/SubscribableDependency.ts +61 -0
- package/src/instance/internal-api/TranslationContext.ts +5 -0
- package/src/instance/internal-api/ValueContext.ts +27 -0
- package/src/instance/resource.ts +75 -0
- package/src/instance/text/FormattedTextStub.ts +8 -0
- package/src/instance/text/TextChunk.ts +20 -0
- package/src/instance/text/TextRange.ts +23 -0
- package/src/lib/dom/query.ts +49 -0
- package/src/lib/reactivity/createChildrenState.ts +60 -0
- package/src/lib/reactivity/createComputedExpression.ts +114 -0
- package/src/lib/reactivity/createSelectItems.ts +163 -0
- package/src/lib/reactivity/createValueState.ts +258 -0
- package/src/lib/reactivity/materializeCurrentStateChildren.ts +121 -0
- package/src/lib/reactivity/node-state/createClientState.ts +51 -0
- package/src/lib/reactivity/node-state/createCurrentState.ts +27 -0
- package/src/lib/reactivity/node-state/createEngineState.ts +18 -0
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +79 -0
- package/src/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.ts +85 -0
- package/src/lib/reactivity/node-state/createSpecifiedState.ts +229 -0
- package/src/lib/reactivity/node-state/representations.ts +64 -0
- package/src/lib/reactivity/scope.ts +106 -0
- package/src/lib/reactivity/text/createFieldHint.ts +16 -0
- package/src/lib/reactivity/text/createNodeLabel.ts +16 -0
- package/src/lib/reactivity/text/createTextRange.ts +155 -0
- package/src/lib/reactivity/types.ts +27 -0
- package/src/lib/unique-id.ts +34 -0
- package/src/lib/xpath/analysis.ts +241 -0
- package/src/model/BindComputation.ts +88 -0
- package/src/model/BindDefinition.ts +104 -0
- package/src/model/BindElement.ts +8 -0
- package/src/model/DescendentNodeDefinition.ts +56 -0
- package/src/model/ModelBindMap.ts +71 -0
- package/src/model/ModelDefinition.ts +19 -0
- package/src/model/NodeDefinition.ts +146 -0
- package/src/model/RepeatInstanceDefinition.ts +39 -0
- package/src/model/RepeatSequenceDefinition.ts +53 -0
- package/src/model/RepeatTemplateDefinition.ts +150 -0
- package/src/model/RootDefinition.ts +121 -0
- package/src/model/SubtreeDefinition.ts +50 -0
- package/src/model/ValueNodeDefinition.ts +39 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { UnreachableError } from '@getodk/common/lib/error/UnreachableError';
|
|
2
|
+
import { expressionParser } from '@getodk/xpath/expressionParser.js';
|
|
3
|
+
import type {
|
|
4
|
+
AbsoluteLocationPathNode,
|
|
5
|
+
AnyBinaryExprNode,
|
|
6
|
+
AnySyntaxNode,
|
|
7
|
+
FilterPathExprNode,
|
|
8
|
+
LocalPartNode,
|
|
9
|
+
PrefixedNameNode,
|
|
10
|
+
RelativeLocationPathNode,
|
|
11
|
+
UnprefixedNameNode,
|
|
12
|
+
} from '@getodk/xpath/static/grammar/SyntaxNode.js';
|
|
13
|
+
import type { AnyBinaryExprType } from '@getodk/xpath/static/grammar/type-names.js';
|
|
14
|
+
|
|
15
|
+
export type SingleChildNode = Extract<
|
|
16
|
+
AnySyntaxNode,
|
|
17
|
+
{ readonly children: readonly [AnySyntaxNode] }
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
const isSingleNodeChild = (node: AnySyntaxNode): node is SingleChildNode => {
|
|
21
|
+
return node.childCount === 1;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const binaryExprNodeTypes = new Set<AnyBinaryExprType>([
|
|
25
|
+
'addition_expr',
|
|
26
|
+
'and_expr',
|
|
27
|
+
'division_expr',
|
|
28
|
+
'eq_expr',
|
|
29
|
+
'gt_expr',
|
|
30
|
+
'gte_expr',
|
|
31
|
+
'lt_expr',
|
|
32
|
+
'lte_expr',
|
|
33
|
+
'modulo_expr',
|
|
34
|
+
'multiplication_expr',
|
|
35
|
+
'ne_expr',
|
|
36
|
+
'or_expr',
|
|
37
|
+
'subtraction_expr',
|
|
38
|
+
'union_expr',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const isBinaryExprNode = (node: AnySyntaxNode): node is AnyBinaryExprNode => {
|
|
42
|
+
return binaryExprNodeTypes.has(node.type as AnyBinaryExprType);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const matchesLocalName = (
|
|
46
|
+
localName: string,
|
|
47
|
+
nameNode: PrefixedNameNode | UnprefixedNameNode
|
|
48
|
+
): boolean => {
|
|
49
|
+
const localPartNode: LocalPartNode | UnprefixedNameNode =
|
|
50
|
+
nameNode.type === 'prefixed_name' ? nameNode.children[1] : nameNode;
|
|
51
|
+
|
|
52
|
+
return localPartNode.text === localName;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const isFunctionCalled = (localName: string, node: AnySyntaxNode): boolean => {
|
|
56
|
+
if (!node.text.includes(localName)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (isSingleNodeChild(node)) {
|
|
61
|
+
const [child] = node.children;
|
|
62
|
+
|
|
63
|
+
return isFunctionCalled(localName, child);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (isBinaryExprNode(node)) {
|
|
67
|
+
const [lhs, rhs] = node.children;
|
|
68
|
+
|
|
69
|
+
return isFunctionCalled(localName, lhs) || isFunctionCalled(localName, rhs);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
switch (node.type) {
|
|
73
|
+
// Terminal nodes
|
|
74
|
+
case 'number':
|
|
75
|
+
case 'string_literal':
|
|
76
|
+
case 'variable_reference':
|
|
77
|
+
|
|
78
|
+
// Path sub-nodes which could not have a function call child
|
|
79
|
+
//
|
|
80
|
+
// This only errors because this set of cases is commented:
|
|
81
|
+
// eslint-disable-next-line no-fallthrough
|
|
82
|
+
case '//':
|
|
83
|
+
case 'absolute_root_location_path':
|
|
84
|
+
case 'axis_name':
|
|
85
|
+
case 'node_type_test':
|
|
86
|
+
case 'parent':
|
|
87
|
+
case 'self':
|
|
88
|
+
|
|
89
|
+
// Name nodes are also not function call parents
|
|
90
|
+
//
|
|
91
|
+
// This only errors because this set of cases is commented:
|
|
92
|
+
// eslint-disable-next-line no-fallthrough
|
|
93
|
+
case 'local_part':
|
|
94
|
+
case 'prefixed_name':
|
|
95
|
+
case 'prefix':
|
|
96
|
+
case 'unprefixed_name':
|
|
97
|
+
case 'unprefixed_wildcard_name_test':
|
|
98
|
+
return false;
|
|
99
|
+
|
|
100
|
+
// Path sub-nodes which could have a function call child
|
|
101
|
+
case 'abbreviated_absolute_location_path':
|
|
102
|
+
case 'abbreviated_step':
|
|
103
|
+
case 'absolute_location_path':
|
|
104
|
+
case 'axis_test':
|
|
105
|
+
case 'filter_path_expr':
|
|
106
|
+
case 'relative_location_path':
|
|
107
|
+
case 'step':
|
|
108
|
+
return node.children.some((childNode) => isFunctionCalled(localName, childNode));
|
|
109
|
+
|
|
110
|
+
case 'function_call': {
|
|
111
|
+
const [functionNameNode] = node.children;
|
|
112
|
+
const [nameNode] = functionNameNode.children;
|
|
113
|
+
|
|
114
|
+
return matchesLocalName(localName, nameNode);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
default:
|
|
118
|
+
throw new UnreachableError(node);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const isItextFunctionCalled = (expression: string): boolean => {
|
|
123
|
+
const { rootNode } = expressionParser.parse(expression);
|
|
124
|
+
|
|
125
|
+
return isFunctionCalled('itext', rootNode);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
type LocationPathSubExpressionNode =
|
|
129
|
+
| AbsoluteLocationPathNode
|
|
130
|
+
| FilterPathExprNode
|
|
131
|
+
| RelativeLocationPathNode;
|
|
132
|
+
|
|
133
|
+
const isAnyLocationPathExprNode = (node: AnySyntaxNode): node is LocationPathSubExpressionNode => {
|
|
134
|
+
const { type } = node;
|
|
135
|
+
|
|
136
|
+
if (type === 'absolute_location_path' || type === 'relative_location_path') {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// TODO: this does not currently even attempt to find sub-expressions nested
|
|
144
|
+
// within sub-expressions.
|
|
145
|
+
const findLocationPathExprNodes = (
|
|
146
|
+
node: AnySyntaxNode
|
|
147
|
+
): readonly LocationPathSubExpressionNode[] => {
|
|
148
|
+
const results: LocationPathSubExpressionNode[] = [];
|
|
149
|
+
|
|
150
|
+
if (isAnyLocationPathExprNode(node)) {
|
|
151
|
+
results.push(node);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
results.push(
|
|
155
|
+
...node.children.flatMap((childNode) => {
|
|
156
|
+
return findLocationPathExprNodes(childNode);
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return results;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// TODO: this is a very small subset of resolution that needs to be supported,
|
|
164
|
+
// and it's a hamfisted hack. **This is temporary** to unblock progress on
|
|
165
|
+
// computations, but a longer term solution will need to address:
|
|
166
|
+
//
|
|
167
|
+
// - non-abbreviation axes (parent, child, self) according to XForms spec
|
|
168
|
+
// - non-leading axes
|
|
169
|
+
// - context expressions which are more complex than a series of explicit
|
|
170
|
+
// element name test steps (this may be fine for binds!)
|
|
171
|
+
const resolveRelativeSubExpression = (contextReference: string | null, expression: string) => {
|
|
172
|
+
if (contextReference == null) {
|
|
173
|
+
return expression;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const [, axisAbbreviation, relativeExpression = ''] = expression.match(/^(\.{1,2})(\/.*$)?/) ?? [
|
|
177
|
+
null,
|
|
178
|
+
'',
|
|
179
|
+
expression,
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
switch (axisAbbreviation) {
|
|
183
|
+
case '':
|
|
184
|
+
return expression;
|
|
185
|
+
|
|
186
|
+
case '.':
|
|
187
|
+
return `${contextReference}${relativeExpression}`;
|
|
188
|
+
|
|
189
|
+
case '..':
|
|
190
|
+
return `${contextReference.replace(/\/[^/]+$/, '')}${relativeExpression}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throw new Error(`Unexpected relative expression: ${expression}`);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
interface GetNodesetDependenciesOptions {
|
|
197
|
+
readonly contextReference?: string | null;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @default false
|
|
201
|
+
*/
|
|
202
|
+
readonly ignoreContextReference?: boolean;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Ignores location path sub-expressions whose full text is `null`. While this
|
|
206
|
+
* is technically a valid relative name test step, it seems that real forms in
|
|
207
|
+
* the wild use it as if XPath had an actual `null` token/value.
|
|
208
|
+
*
|
|
209
|
+
* @default true
|
|
210
|
+
*/
|
|
211
|
+
readonly ignoreNullExpressions?: boolean;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const getNodesetDependencies = (
|
|
215
|
+
expression: string,
|
|
216
|
+
options: GetNodesetDependenciesOptions = {}
|
|
217
|
+
): Set<string> => {
|
|
218
|
+
const { rootNode } = expressionParser.parse(expression);
|
|
219
|
+
const subExpressionNodes = findLocationPathExprNodes(rootNode);
|
|
220
|
+
const {
|
|
221
|
+
contextReference = null,
|
|
222
|
+
ignoreContextReference = false,
|
|
223
|
+
ignoreNullExpressions = true,
|
|
224
|
+
} = options;
|
|
225
|
+
|
|
226
|
+
const subExpressions = subExpressionNodes
|
|
227
|
+
.map((syntaxNode) => resolveRelativeSubExpression(contextReference, syntaxNode.text))
|
|
228
|
+
.filter((subExpression) => {
|
|
229
|
+
if (ignoreContextReference && subExpression === contextReference) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (ignoreNullExpressions && subExpression === 'null') {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return true;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return new Set(subExpressions);
|
|
241
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { DependentExpression } from '../expression/DependentExpression.ts';
|
|
2
|
+
import type { BindDefinition } from './BindDefinition.ts';
|
|
3
|
+
|
|
4
|
+
const defaultBindComputationExpressions = {
|
|
5
|
+
calculate: null,
|
|
6
|
+
constraint: 'true()',
|
|
7
|
+
readonly: 'false()',
|
|
8
|
+
relevant: 'true()',
|
|
9
|
+
required: 'false()',
|
|
10
|
+
saveIncomplete: 'false()',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
type DefaultBindComputationExpressions = typeof defaultBindComputationExpressions;
|
|
14
|
+
|
|
15
|
+
export type BindComputationType = keyof DefaultBindComputationExpressions;
|
|
16
|
+
|
|
17
|
+
type BindComputationFactoryResult<Type extends BindComputationType> =
|
|
18
|
+
DefaultBindComputationExpressions[Type] extends null
|
|
19
|
+
? BindComputation<Type> | null
|
|
20
|
+
: BindComputation<Type>;
|
|
21
|
+
|
|
22
|
+
const bindComputationResultTypes = {
|
|
23
|
+
calculate: 'string',
|
|
24
|
+
constraint: 'boolean',
|
|
25
|
+
readonly: 'boolean',
|
|
26
|
+
relevant: 'boolean',
|
|
27
|
+
required: 'boolean',
|
|
28
|
+
saveIncomplete: 'boolean',
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
type BindComputationResultTypes = typeof bindComputationResultTypes;
|
|
32
|
+
|
|
33
|
+
export type BindComputationResultType<Computation extends BindComputationType> =
|
|
34
|
+
BindComputationResultTypes[Computation];
|
|
35
|
+
|
|
36
|
+
export class BindComputation<Computation extends BindComputationType> extends DependentExpression<
|
|
37
|
+
BindComputationResultType<Computation>
|
|
38
|
+
> {
|
|
39
|
+
static forExpression<Type extends BindComputationType>(
|
|
40
|
+
bind: BindDefinition,
|
|
41
|
+
computation: Type
|
|
42
|
+
): BindComputationFactoryResult<Type> {
|
|
43
|
+
const expression =
|
|
44
|
+
bind.bindElement.getAttribute(computation) ?? defaultBindComputationExpressions[computation];
|
|
45
|
+
|
|
46
|
+
if (expression == null) {
|
|
47
|
+
return null as BindComputationFactoryResult<Type>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new this(bind, computation, expression);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
readonly isDefaultExpression: boolean;
|
|
54
|
+
|
|
55
|
+
protected constructor(
|
|
56
|
+
bind: BindDefinition,
|
|
57
|
+
readonly computation: Computation,
|
|
58
|
+
expression: string | null
|
|
59
|
+
) {
|
|
60
|
+
const isInherited = computation === 'readonly' || computation === 'relevant';
|
|
61
|
+
const ignoreContextReference = computation === 'constraint';
|
|
62
|
+
|
|
63
|
+
let isDefaultExpression: boolean;
|
|
64
|
+
let resolvedExpression: string;
|
|
65
|
+
|
|
66
|
+
if (expression == null) {
|
|
67
|
+
if (computation === 'calculate') {
|
|
68
|
+
throw new Error('No default expression for calculate');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
resolvedExpression =
|
|
72
|
+
defaultBindComputationExpressions[computation as Exclude<Computation, 'calculate'>];
|
|
73
|
+
isDefaultExpression = true;
|
|
74
|
+
} else {
|
|
75
|
+
isDefaultExpression = false;
|
|
76
|
+
resolvedExpression = expression;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
super(bind, bindComputationResultTypes[computation], resolvedExpression, {
|
|
80
|
+
ignoreContextReference,
|
|
81
|
+
semanticDependencies: {
|
|
82
|
+
parentContext: isInherited,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
this.isDefaultExpression = isDefaultExpression;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { XFormDataType } from '../XFormDataType.ts';
|
|
2
|
+
import { bindDataType } from '../XFormDataType.ts';
|
|
3
|
+
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
4
|
+
import { DependencyContext } from '../expression/DependencyContext.ts';
|
|
5
|
+
import type { DependentExpression } from '../expression/DependentExpression.ts';
|
|
6
|
+
import { BindComputation } from './BindComputation.ts';
|
|
7
|
+
import type { BindElement } from './BindElement.ts';
|
|
8
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
9
|
+
|
|
10
|
+
export class BindDefinition extends DependencyContext {
|
|
11
|
+
readonly bindType: string | null;
|
|
12
|
+
readonly dataType: XFormDataType;
|
|
13
|
+
readonly parentNodeset: string | null;
|
|
14
|
+
|
|
15
|
+
readonly calculate: BindComputation<'calculate'> | null;
|
|
16
|
+
readonly readonly: BindComputation<'readonly'>;
|
|
17
|
+
readonly relevant: BindComputation<'relevant'>;
|
|
18
|
+
readonly required: BindComputation<'required'>;
|
|
19
|
+
|
|
20
|
+
// According to
|
|
21
|
+
//
|
|
22
|
+
// this is not stored in the DAG. In contrast, we do compute constraint
|
|
23
|
+
// dependencies, but self-references are ignored (handled by `BindComputation`
|
|
24
|
+
// and its `DependentExpression` parent class).
|
|
25
|
+
/**
|
|
26
|
+
* Diverges from {@link https://github.com/getodk/javarosa/blob/059321160e6f8dbb3e81d9add61d68dd35b13cc8/dag.md | JavaRosa's}, which excludes `constraint` expressions. We compute `constraint` dependencies like the other <bind> computation expressions, but explicitly ignore self-references (this is currently handled by {@link BindComputation}, via its {@link DependentExpression} parent class).
|
|
27
|
+
*/
|
|
28
|
+
readonly constraint: BindComputation<'constraint'> & DependentExpression<'boolean'>;
|
|
29
|
+
|
|
30
|
+
// TODO: it is unclear whether this will need to be supported.
|
|
31
|
+
// https://github.com/getodk/collect/issues/3758 mentions deprecation.
|
|
32
|
+
readonly saveIncomplete: BindComputation<'saveIncomplete'>;
|
|
33
|
+
|
|
34
|
+
// TODO: these are deferred just to put off sharing namespace stuff
|
|
35
|
+
// readonly requiredMsg: string | null;
|
|
36
|
+
// readonly constraintMsg: string | null;
|
|
37
|
+
// readonly preload: string | null;
|
|
38
|
+
// readonly preloadParams: string | null;
|
|
39
|
+
// readonly 'max-pixels': string | null;
|
|
40
|
+
|
|
41
|
+
protected _parentBind: BindDefinition | null | undefined;
|
|
42
|
+
|
|
43
|
+
get parentBind(): BindDefinition | null {
|
|
44
|
+
let bind = this._parentBind;
|
|
45
|
+
|
|
46
|
+
if (typeof bind === 'undefined') {
|
|
47
|
+
const { parentNodeset } = this;
|
|
48
|
+
|
|
49
|
+
if (parentNodeset == null) {
|
|
50
|
+
bind = null;
|
|
51
|
+
} else {
|
|
52
|
+
bind = this.form.model.binds.get(parentNodeset) ?? null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this._parentBind = bind;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return bind;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// DependencyContext
|
|
62
|
+
get reference(): string {
|
|
63
|
+
return this.nodeset;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get parentReference(): string | null {
|
|
67
|
+
return this.parentNodeset;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
readonly form: XFormDefinition,
|
|
72
|
+
protected readonly model: ModelDefinition,
|
|
73
|
+
readonly nodeset: string,
|
|
74
|
+
readonly bindElement: BindElement
|
|
75
|
+
) {
|
|
76
|
+
super();
|
|
77
|
+
|
|
78
|
+
const bindType = (this.bindType = bindElement.getAttribute('type'));
|
|
79
|
+
|
|
80
|
+
this.dataType = bindDataType(bindType);
|
|
81
|
+
|
|
82
|
+
const parentNodeset = nodeset.replace(/\/[^/]+$/, '');
|
|
83
|
+
|
|
84
|
+
this.parentNodeset = parentNodeset.length > 1 ? parentNodeset : null;
|
|
85
|
+
this.calculate = BindComputation.forExpression(this, 'calculate');
|
|
86
|
+
this.readonly = BindComputation.forExpression(this, 'readonly');
|
|
87
|
+
this.relevant = BindComputation.forExpression(this, 'relevant');
|
|
88
|
+
this.required = BindComputation.forExpression(this, 'required');
|
|
89
|
+
this.constraint = BindComputation.forExpression(this, 'constraint');
|
|
90
|
+
this.saveIncomplete = BindComputation.forExpression(this, 'saveIncomplete');
|
|
91
|
+
|
|
92
|
+
// this.requiredMsg = BindComputation.forExpression(this, 'requiredMsg');
|
|
93
|
+
// this.constraintMsg = BindComputation.forExpression(this, 'constraintMsg');
|
|
94
|
+
// this.preload = BindComputation.forExpression(this, 'preload');
|
|
95
|
+
// this.preloadParams = BindComputation.forExpression(this, 'preloadParams');
|
|
96
|
+
// this['max-pixels'] = BindComputation.forExpression(this, 'max-pixels');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
toJSON() {
|
|
100
|
+
const { form, model, /* modelElement, */ bindElement, ...rest } = this;
|
|
101
|
+
|
|
102
|
+
return rest;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AnyBodyElementDefinition } from '../body/BodyDefinition.ts';
|
|
2
|
+
import type { RepeatDefinition } from '../body/RepeatDefinition.ts';
|
|
3
|
+
import type { BindDefinition } from './BindDefinition.ts';
|
|
4
|
+
import type {
|
|
5
|
+
ModelNode,
|
|
6
|
+
NodeChildren,
|
|
7
|
+
NodeDefaultValue,
|
|
8
|
+
NodeDefinition,
|
|
9
|
+
NodeDefinitionType,
|
|
10
|
+
NodeInstances,
|
|
11
|
+
NodeParent,
|
|
12
|
+
} from './NodeDefinition.ts';
|
|
13
|
+
import type { RootDefinition } from './RootDefinition.ts';
|
|
14
|
+
|
|
15
|
+
export type DescendentNodeType = Exclude<NodeDefinitionType, 'root'>;
|
|
16
|
+
|
|
17
|
+
type DescendentNodeBodyElement = AnyBodyElementDefinition | RepeatDefinition;
|
|
18
|
+
|
|
19
|
+
export abstract class DescendentNodeDefinition<
|
|
20
|
+
Type extends DescendentNodeType,
|
|
21
|
+
BodyElement extends DescendentNodeBodyElement | null = DescendentNodeBodyElement | null,
|
|
22
|
+
> implements NodeDefinition<Type>
|
|
23
|
+
{
|
|
24
|
+
abstract readonly type: Type;
|
|
25
|
+
abstract readonly children: NodeChildren<Type>;
|
|
26
|
+
abstract readonly instances: NodeInstances<Type>;
|
|
27
|
+
abstract readonly defaultValue: NodeDefaultValue<Type>;
|
|
28
|
+
abstract readonly node: ModelNode<Type>;
|
|
29
|
+
abstract readonly nodeName: string;
|
|
30
|
+
|
|
31
|
+
readonly root: RootDefinition;
|
|
32
|
+
readonly nodeset: string;
|
|
33
|
+
readonly dependencyExpressions: ReadonlySet<string>;
|
|
34
|
+
readonly isTranslated: boolean = false;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
readonly parent: NodeParent<Type>,
|
|
38
|
+
readonly bind: BindDefinition,
|
|
39
|
+
readonly bodyElement: BodyElement
|
|
40
|
+
) {
|
|
41
|
+
this.root = parent.root;
|
|
42
|
+
this.nodeset = bind.nodeset;
|
|
43
|
+
|
|
44
|
+
if (bind.isTranslated || bodyElement?.isTranslated) {
|
|
45
|
+
this.isTranslated = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.dependencyExpressions = new Set([
|
|
49
|
+
...bind.dependencyExpressions,
|
|
50
|
+
...(bodyElement?.dependencyExpressions ?? []),
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
+
export type AnyDescendantNodeDefinition = DescendentNodeDefinition<any, any>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
2
|
+
import { BindDefinition } from './BindDefinition.ts';
|
|
3
|
+
import type { BindElement, BindNodeset } from './BindElement.ts';
|
|
4
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
5
|
+
|
|
6
|
+
class ArtificialBindElement implements BindElement {
|
|
7
|
+
readonly localName = 'bind';
|
|
8
|
+
|
|
9
|
+
constructor(protected readonly ancestorNodeset: string) {}
|
|
10
|
+
|
|
11
|
+
getAttribute(name: 'nodeset'): string;
|
|
12
|
+
getAttribute(name: string): string | null;
|
|
13
|
+
getAttribute(name: string) {
|
|
14
|
+
if (name === 'nodeset') {
|
|
15
|
+
return this.ancestorNodeset;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type TopologicalSortIndex = number;
|
|
23
|
+
|
|
24
|
+
export type SortedNodesetIndexes = ReadonlyMap<BindNodeset, TopologicalSortIndex>;
|
|
25
|
+
|
|
26
|
+
export class ModelBindMap extends Map<BindNodeset, BindDefinition> {
|
|
27
|
+
// This is probably overkill, just produces a type that's readonly at call site.
|
|
28
|
+
static fromModel(model: ModelDefinition): ModelBindMap {
|
|
29
|
+
return new this(model.form, model);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected constructor(
|
|
33
|
+
protected readonly form: XFormDefinition,
|
|
34
|
+
protected readonly model: ModelDefinition
|
|
35
|
+
) {
|
|
36
|
+
const bindElements = form.xformDOM.rootEvaluator.evaluateNodes<BindElement & Element>(
|
|
37
|
+
'./xf:bind[@nodeset]',
|
|
38
|
+
{
|
|
39
|
+
contextNode: form.xformDOM.model,
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
super(
|
|
44
|
+
bindElements.map((bindElement) => {
|
|
45
|
+
const nodeset = bindElement.getAttribute('nodeset');
|
|
46
|
+
const bind = new BindDefinition(form, model, nodeset, bindElement);
|
|
47
|
+
|
|
48
|
+
return [nodeset, bind];
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
this.getOrCreateBindDefinition(form.rootReference);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getOrCreateBindDefinition(nodeset: string): BindDefinition {
|
|
56
|
+
let bind = this.get(nodeset);
|
|
57
|
+
|
|
58
|
+
if (bind == null) {
|
|
59
|
+
const bindElement = new ArtificialBindElement(nodeset);
|
|
60
|
+
|
|
61
|
+
bind = new BindDefinition(this.form, this.model, nodeset, bindElement);
|
|
62
|
+
this.set(nodeset, bind);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return bind;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
toJSON() {
|
|
69
|
+
return Array.from(this.entries());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
2
|
+
import { ModelBindMap } from './ModelBindMap.ts';
|
|
3
|
+
import { RootDefinition } from './RootDefinition.ts';
|
|
4
|
+
|
|
5
|
+
export class ModelDefinition {
|
|
6
|
+
readonly binds: ModelBindMap;
|
|
7
|
+
readonly root: RootDefinition;
|
|
8
|
+
|
|
9
|
+
constructor(readonly form: XFormDefinition) {
|
|
10
|
+
this.binds = ModelBindMap.fromModel(this);
|
|
11
|
+
this.root = new RootDefinition(form, this);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
toJSON() {
|
|
15
|
+
const { form, ...rest } = this;
|
|
16
|
+
|
|
17
|
+
return rest;
|
|
18
|
+
}
|
|
19
|
+
}
|