@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,54 @@
|
|
|
1
|
+
import { JAVAROSA_NAMESPACE_URI } from '@getodk/common/constants/xmlns.ts';
|
|
2
|
+
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
3
|
+
import type { BodyElementDefinitionArray } from './BodyDefinition.ts';
|
|
4
|
+
import { BodyElementDefinition } from './BodyElementDefinition.ts';
|
|
5
|
+
import type { RepeatGroupDefinition } from './group/RepeatGroupDefinition.ts';
|
|
6
|
+
|
|
7
|
+
export class RepeatDefinition extends BodyElementDefinition<'repeat'> {
|
|
8
|
+
override readonly category = 'structure';
|
|
9
|
+
readonly type = 'repeat';
|
|
10
|
+
override readonly reference: string;
|
|
11
|
+
|
|
12
|
+
// TODO: this will fall into the growing category of non-`BindExpression`
|
|
13
|
+
// cases which have roughly the same design story.
|
|
14
|
+
readonly countExpression: string | null;
|
|
15
|
+
|
|
16
|
+
readonly isFixedCount: boolean;
|
|
17
|
+
|
|
18
|
+
readonly children: BodyElementDefinitionArray;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
form: XFormDefinition,
|
|
22
|
+
readonly groupDefinition: RepeatGroupDefinition,
|
|
23
|
+
element: Element
|
|
24
|
+
) {
|
|
25
|
+
super(form, groupDefinition, element);
|
|
26
|
+
|
|
27
|
+
const reference = element.getAttribute('nodeset');
|
|
28
|
+
|
|
29
|
+
if (reference == null) {
|
|
30
|
+
throw new Error('Invalid repeat: missing `nodeset` reference');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.reference = reference;
|
|
34
|
+
this.countExpression = element.getAttributeNS(JAVAROSA_NAMESPACE_URI, 'count');
|
|
35
|
+
this.children = groupDefinition.getChildren(element);
|
|
36
|
+
|
|
37
|
+
// Spec says this can be either `true()` or `false()`. That said, it
|
|
38
|
+
// could also presumably be `true ( )` or whatever.
|
|
39
|
+
const noAddRemove =
|
|
40
|
+
element
|
|
41
|
+
.getAttributeNS(JAVAROSA_NAMESPACE_URI, 'noAddRemove')
|
|
42
|
+
?.trim()
|
|
43
|
+
.replaceAll(/\s+/g, '') ?? 'false()';
|
|
44
|
+
|
|
45
|
+
// TODO: **probably** safe to disregard anything else?
|
|
46
|
+
this.isFixedCount = noAddRemove === 'true()';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override toJSON() {
|
|
50
|
+
const { form, groupDefinition, parent, ...rest } = this;
|
|
51
|
+
|
|
52
|
+
return rest;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
2
|
+
import type { BodyElementParentContext } from './BodyDefinition.ts';
|
|
3
|
+
import { BodyElementDefinition } from './BodyElementDefinition.ts';
|
|
4
|
+
|
|
5
|
+
export class UnsupportedBodyElementDefinition extends BodyElementDefinition<'UNSUPPORTED'> {
|
|
6
|
+
static override isCompatible(): boolean {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override readonly category = 'UNSUPPORTED';
|
|
11
|
+
readonly type = 'UNSUPPORTED';
|
|
12
|
+
override readonly reference: null = null;
|
|
13
|
+
|
|
14
|
+
constructor(form: XFormDefinition, parent: BodyElementParentContext, element: Element) {
|
|
15
|
+
super(form, parent, element);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
2
|
+
import type { BodyElementParentContext } from '../BodyDefinition.ts';
|
|
3
|
+
import { BodyElementDefinition } from '../BodyElementDefinition.ts';
|
|
4
|
+
import { HintDefinition } from '../text/HintDefinition.ts';
|
|
5
|
+
import { LabelDefinition } from '../text/LabelDefinition.ts';
|
|
6
|
+
|
|
7
|
+
// prettier-ignore
|
|
8
|
+
type ControlType =
|
|
9
|
+
| 'input'
|
|
10
|
+
| 'range'
|
|
11
|
+
| 'rank'
|
|
12
|
+
| 'select'
|
|
13
|
+
| 'select1'
|
|
14
|
+
| 'trigger'
|
|
15
|
+
| 'upload';
|
|
16
|
+
|
|
17
|
+
export abstract class ControlDefinition<
|
|
18
|
+
Type extends ControlType,
|
|
19
|
+
> extends BodyElementDefinition<Type> {
|
|
20
|
+
override readonly category = 'control';
|
|
21
|
+
abstract override readonly type: Type;
|
|
22
|
+
override readonly reference: string;
|
|
23
|
+
override readonly label: LabelDefinition | null;
|
|
24
|
+
override readonly hint: HintDefinition | null;
|
|
25
|
+
|
|
26
|
+
constructor(form: XFormDefinition, parent: BodyElementParentContext, element: Element) {
|
|
27
|
+
super(form, parent, element);
|
|
28
|
+
|
|
29
|
+
const reference = element.getAttribute('ref');
|
|
30
|
+
|
|
31
|
+
if (reference == null) {
|
|
32
|
+
throw new Error(`Invalid control: missing ref attribute`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.reference = reference;
|
|
36
|
+
this.label = LabelDefinition.forControl(form, this);
|
|
37
|
+
this.hint = HintDefinition.forElement(form, this);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
+
export type AnyControlDefinition = ControlDefinition<any>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getValueElement, type ItemElement } from '../../../lib/dom/query.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../../XFormDefinition.ts';
|
|
3
|
+
import { BodyElementDefinition } from '../../BodyElementDefinition.ts';
|
|
4
|
+
import { LabelDefinition } from '../../text/LabelDefinition.ts';
|
|
5
|
+
import type { AnySelectDefinition } from './SelectDefinition.ts';
|
|
6
|
+
|
|
7
|
+
export class ItemDefinition extends BodyElementDefinition<'item'> {
|
|
8
|
+
override readonly category = 'support';
|
|
9
|
+
override readonly type = 'item';
|
|
10
|
+
|
|
11
|
+
override readonly label: LabelDefinition | null;
|
|
12
|
+
readonly value: string;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
form: XFormDefinition,
|
|
16
|
+
override readonly parent: AnySelectDefinition,
|
|
17
|
+
element: ItemElement
|
|
18
|
+
) {
|
|
19
|
+
const valueElement = getValueElement(element);
|
|
20
|
+
const value = valueElement?.textContent;
|
|
21
|
+
|
|
22
|
+
if (value == null) {
|
|
23
|
+
throw new Error('<item> has no <value>');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
super(form, parent, element);
|
|
27
|
+
|
|
28
|
+
this.label = LabelDefinition.forItem(form, this);
|
|
29
|
+
this.value = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getValueElement, type ItemsetElement } from '../../../lib/dom/query.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../../XFormDefinition.ts';
|
|
3
|
+
import { BodyElementDefinition } from '../../BodyElementDefinition.ts';
|
|
4
|
+
import { LabelDefinition } from '../../text/LabelDefinition.ts';
|
|
5
|
+
import { ItemsetNodesetExpression } from './ItemsetNodesetExpression.ts';
|
|
6
|
+
import { ItemsetValueExpression } from './ItemsetValueExpression.ts';
|
|
7
|
+
import type { AnySelectDefinition } from './SelectDefinition.ts';
|
|
8
|
+
|
|
9
|
+
export class ItemsetDefinition extends BodyElementDefinition<'itemset'> {
|
|
10
|
+
override readonly category = 'support';
|
|
11
|
+
readonly type = 'itemset';
|
|
12
|
+
|
|
13
|
+
override readonly reference: string;
|
|
14
|
+
override readonly label: LabelDefinition | null;
|
|
15
|
+
|
|
16
|
+
readonly nodes: ItemsetNodesetExpression;
|
|
17
|
+
readonly value: ItemsetValueExpression;
|
|
18
|
+
|
|
19
|
+
constructor(form: XFormDefinition, select: AnySelectDefinition, element: ItemsetElement) {
|
|
20
|
+
const valueElement = getValueElement(element);
|
|
21
|
+
const valueExpression = valueElement?.getAttribute('ref');
|
|
22
|
+
|
|
23
|
+
if (valueExpression == null) {
|
|
24
|
+
throw new Error(`<itemset> has no <value>`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
super(form, select, element);
|
|
28
|
+
|
|
29
|
+
const nodesetExpression = element.getAttribute('nodeset');
|
|
30
|
+
|
|
31
|
+
this.reference = nodesetExpression;
|
|
32
|
+
this.nodes = new ItemsetNodesetExpression(this, nodesetExpression);
|
|
33
|
+
this.value = new ItemsetValueExpression(this, valueExpression);
|
|
34
|
+
this.label = LabelDefinition.forItemset(form, this);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { DependencyContext } from '../../../expression/DependencyContext.ts';
|
|
2
|
+
import type { AnyDependentExpression } from '../../../expression/DependentExpression.ts';
|
|
3
|
+
import type { ItemsetDefinition } from './ItemsetDefinition.ts';
|
|
4
|
+
|
|
5
|
+
export class ItemsetNodesetContext extends DependencyContext {
|
|
6
|
+
override readonly parentReference = null;
|
|
7
|
+
override readonly reference: string;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
protected readonly itemset: ItemsetDefinition,
|
|
11
|
+
nodesetExpression: string
|
|
12
|
+
) {
|
|
13
|
+
super();
|
|
14
|
+
|
|
15
|
+
this.reference = nodesetExpression;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override set isTranslated(value: true) {
|
|
19
|
+
super.isTranslated = value;
|
|
20
|
+
this.itemset.isTranslated = value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override registerDependentExpression(expression: AnyDependentExpression): void {
|
|
24
|
+
this.itemset.registerDependentExpression(expression);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DependentExpression } from '../../../expression/DependentExpression.ts';
|
|
2
|
+
import type { ItemsetDefinition } from './ItemsetDefinition.ts';
|
|
3
|
+
|
|
4
|
+
export class ItemsetNodesetExpression extends DependentExpression<'nodes'> {
|
|
5
|
+
constructor(itemset: ItemsetDefinition, nodesetExpression: string) {
|
|
6
|
+
super(itemset, 'nodes', nodesetExpression);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DependentExpression } from '../../../expression/DependentExpression.ts';
|
|
2
|
+
import type { ItemsetDefinition } from './ItemsetDefinition.ts';
|
|
3
|
+
|
|
4
|
+
export class ItemsetValueExpression extends DependentExpression<'string'> {
|
|
5
|
+
constructor(
|
|
6
|
+
readonly itemset: ItemsetDefinition,
|
|
7
|
+
expression: string
|
|
8
|
+
) {
|
|
9
|
+
super(itemset, 'string', expression);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { CollectionValues } from '@getodk/common/types/collections/CollectionValues.ts';
|
|
2
|
+
import type { LocalNamedElement } from '@getodk/common/types/dom.ts';
|
|
3
|
+
import type { XFormDefinition } from '../../../XFormDefinition.ts';
|
|
4
|
+
import { getItemElements, getItemsetElement } from '../../../lib/dom/query.ts';
|
|
5
|
+
import type { AnyBodyElementDefinition, BodyElementParentContext } from '../../BodyDefinition.ts';
|
|
6
|
+
import { ControlDefinition } from '../ControlDefinition.ts';
|
|
7
|
+
import { ItemDefinition } from './ItemDefinition.ts';
|
|
8
|
+
import { ItemsetDefinition } from './ItemsetDefinition.ts';
|
|
9
|
+
|
|
10
|
+
// TODO: `<trigger>` is *almost* reasonable to support here too. The main
|
|
11
|
+
// hesitation is that its single, implicit "item" does not have a distinct
|
|
12
|
+
// <label>, and presumably has different UX **and translation** considerations.
|
|
13
|
+
const selectLocalNames = new Set(['rank', 'select', 'select1'] as const);
|
|
14
|
+
|
|
15
|
+
export type SelectType = CollectionValues<typeof selectLocalNames>;
|
|
16
|
+
|
|
17
|
+
export interface SelectElement extends LocalNamedElement<SelectType> {}
|
|
18
|
+
|
|
19
|
+
const isSelectElement = (
|
|
20
|
+
element: Element,
|
|
21
|
+
localName: string = element.localName
|
|
22
|
+
): element is SelectElement => {
|
|
23
|
+
return selectLocalNames.has(localName as SelectType);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export class SelectDefinition<Type extends SelectType> extends ControlDefinition<Type> {
|
|
27
|
+
static override isCompatible(localName: string, element: Element): boolean {
|
|
28
|
+
return isSelectElement(element, localName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static isSelect(element: AnyBodyElementDefinition): element is AnySelectDefinition {
|
|
32
|
+
return selectLocalNames.has(element.type as SelectType);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override readonly type: Type;
|
|
36
|
+
override readonly element: SelectElement;
|
|
37
|
+
|
|
38
|
+
readonly itemset: ItemsetDefinition | null;
|
|
39
|
+
readonly items: readonly ItemDefinition[];
|
|
40
|
+
|
|
41
|
+
constructor(form: XFormDefinition, parent: BodyElementParentContext, element: Element) {
|
|
42
|
+
if (!isSelectElement(element)) {
|
|
43
|
+
throw new Error(`Invalid select element: <${element.nodeName}>`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
super(form, parent, element);
|
|
47
|
+
|
|
48
|
+
this.element = element;
|
|
49
|
+
this.type = element.localName as Type;
|
|
50
|
+
|
|
51
|
+
const itemsetElement = getItemsetElement(element);
|
|
52
|
+
const itemElements = getItemElements(element);
|
|
53
|
+
|
|
54
|
+
if (itemsetElement == null) {
|
|
55
|
+
this.itemset = null;
|
|
56
|
+
this.items = itemElements.map((itemElement) => {
|
|
57
|
+
return new ItemDefinition(form, this, itemElement);
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
if (itemElements.length > 0) {
|
|
61
|
+
throw new Error(`<${element.nodeName}> has both <itemset> and <item> children`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.items = [];
|
|
65
|
+
this.itemset = new ItemsetDefinition(form, this, itemsetElement);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override toJSON() {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type AnySelectDefinition = SelectDefinition<SelectType>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { UpsertableMap } from '@getodk/common/lib/collections/UpsertableMap.ts';
|
|
2
|
+
import type { XFormDOM } from '../../XFormDOM.ts';
|
|
3
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
4
|
+
import { getLabelElement, getRepeatElement } from '../../lib/dom/query.ts';
|
|
5
|
+
import {
|
|
6
|
+
BodyDefinition,
|
|
7
|
+
type BodyElementDefinitionArray,
|
|
8
|
+
type BodyElementParentContext,
|
|
9
|
+
} from '../BodyDefinition.ts';
|
|
10
|
+
import { BodyElementDefinition } from '../BodyElementDefinition.ts';
|
|
11
|
+
import { LabelDefinition } from '../text/LabelDefinition.ts';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* These type names are derived from **and expand upon** the language used in
|
|
15
|
+
* the ODK XForms spec to describe various group usage. Whereas the spec
|
|
16
|
+
* language allows for a group to be described as more than one case, the intent
|
|
17
|
+
* here is to establish exclusive naming which may or may not compound. As such:
|
|
18
|
+
*
|
|
19
|
+
* - `logical-group`, per spec language, is a group with a `ref`; its usage here
|
|
20
|
+
* differs from the spec language in that it _may_ have a `<label>` child but
|
|
21
|
+
* is not also treated as a `presentation-group` (which is only used for
|
|
22
|
+
* groups which do not have a `ref`)
|
|
23
|
+
* - `presentation-group` is a group with a `<label>` child; its usage here
|
|
24
|
+
* differs from the spec language in that `presentation-group` does **not**
|
|
25
|
+
* have a `ref`
|
|
26
|
+
* - `repeat-group` is not mentioned by the spec; it is an extension of
|
|
27
|
+
* `logical-group`, wherein its `ref` is the same as its immediate `<repeat>`
|
|
28
|
+
* child's `nodeset` (usage of each attribute is normalized during
|
|
29
|
+
* initialization, in {@link XFormDOM})
|
|
30
|
+
* - `structural-group` is any `<group>` element which does not satisfy any of
|
|
31
|
+
* the other usage scenarios; this isn't exactly the terminology used, but is
|
|
32
|
+
* the most closely fitting name for the concept where the other sceanarios
|
|
33
|
+
* do not apply
|
|
34
|
+
*
|
|
35
|
+
* A more succinct decision tree:
|
|
36
|
+
*
|
|
37
|
+
* - `<group ref="$ref"><repeat nodeset="$ref">` -> `repeat-group`, else
|
|
38
|
+
* - `<group ref="$ref">` -> `logical-group`, else
|
|
39
|
+
* - `<group><label>` -> `presentation-group`, else
|
|
40
|
+
* - `<group>` -> `structural-group`
|
|
41
|
+
*/
|
|
42
|
+
export type GroupType =
|
|
43
|
+
| 'logical-group'
|
|
44
|
+
| 'presentation-group'
|
|
45
|
+
| 'repeat-group'
|
|
46
|
+
| 'structural-group';
|
|
47
|
+
|
|
48
|
+
export abstract class BaseGroupDefinition<
|
|
49
|
+
Type extends GroupType,
|
|
50
|
+
> extends BodyElementDefinition<Type> {
|
|
51
|
+
private static groupTypes = new UpsertableMap<Element, GroupType | null>();
|
|
52
|
+
|
|
53
|
+
protected static getGroupType(localName: string, element: Element): GroupType | null {
|
|
54
|
+
return this.groupTypes.upsert(element, () => {
|
|
55
|
+
if (localName !== 'group') {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const ref = element.getAttribute('ref');
|
|
60
|
+
|
|
61
|
+
if (ref != null) {
|
|
62
|
+
const repeat = getRepeatElement(element);
|
|
63
|
+
|
|
64
|
+
if (repeat == null) {
|
|
65
|
+
return 'logical-group';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (repeat.getAttribute('nodeset') === ref) {
|
|
69
|
+
return 'repeat-group';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
throw new Error('Unexpected <repeat> child of unrelated <group>');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const label = getLabelElement(element);
|
|
76
|
+
|
|
77
|
+
if (label == null) {
|
|
78
|
+
return 'structural-group';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return 'presentation-group';
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override readonly category = 'structure';
|
|
86
|
+
|
|
87
|
+
readonly children: BodyElementDefinitionArray;
|
|
88
|
+
|
|
89
|
+
override readonly reference: string | null;
|
|
90
|
+
override readonly label: LabelDefinition | null;
|
|
91
|
+
|
|
92
|
+
constructor(
|
|
93
|
+
form: XFormDefinition,
|
|
94
|
+
parent: BodyElementParentContext,
|
|
95
|
+
element: Element,
|
|
96
|
+
children?: BodyElementDefinitionArray
|
|
97
|
+
) {
|
|
98
|
+
super(form, parent, element);
|
|
99
|
+
|
|
100
|
+
this.children = children ?? this.getChildren(element);
|
|
101
|
+
this.reference = element.getAttribute('ref');
|
|
102
|
+
this.label = LabelDefinition.forGroup(form, this);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getChildren(element: Element): BodyElementDefinitionArray {
|
|
106
|
+
const { form } = this;
|
|
107
|
+
const children = Array.from(element.children).filter((child) => {
|
|
108
|
+
const childName = child.localName;
|
|
109
|
+
|
|
110
|
+
return childName !== 'label' && childName !== 'repeat';
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return BodyDefinition.getChildElementDefinitions(form, this, element, children);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
+
export const repeatGroup = <T extends BaseGroupDefinition<any>>(
|
|
119
|
+
groupDefinition: T
|
|
120
|
+
): Extract<T, BaseGroupDefinition<'repeat-group'>> | null => {
|
|
121
|
+
if (groupDefinition.type === 'repeat-group') {
|
|
122
|
+
return groupDefinition as Extract<T, BaseGroupDefinition<'repeat-group'>>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
+
export const nonRepeatGroup = <T extends BaseGroupDefinition<any>>(
|
|
130
|
+
groupDefinition: T
|
|
131
|
+
): Exclude<T, BaseGroupDefinition<'repeat-group'>> | null => {
|
|
132
|
+
if (groupDefinition.type === 'repeat-group') {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return groupDefinition as Exclude<T, BaseGroupDefinition<'repeat-group'>>;
|
|
137
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseGroupDefinition } from './BaseGroupDefinition.ts';
|
|
2
|
+
|
|
3
|
+
export class LogicalGroupDefinition extends BaseGroupDefinition<'logical-group'> {
|
|
4
|
+
static override isCompatible(localName: string, element: Element): boolean {
|
|
5
|
+
return this.getGroupType(localName, element) === 'logical-group';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
readonly type = 'logical-group';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type LogicalGroupDefinitionClass = typeof LogicalGroupDefinition;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
2
|
+
import type { BodyElementParentContext } from '../BodyDefinition.ts';
|
|
3
|
+
import { LabelDefinition } from '../text/LabelDefinition.ts';
|
|
4
|
+
import { BaseGroupDefinition } from './BaseGroupDefinition.ts';
|
|
5
|
+
|
|
6
|
+
export class PresentationGroupDefinition extends BaseGroupDefinition<'presentation-group'> {
|
|
7
|
+
static override isCompatible(localName: string, element: Element): boolean {
|
|
8
|
+
return this.getGroupType(localName, element) === 'presentation-group';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
readonly type = 'presentation-group';
|
|
12
|
+
|
|
13
|
+
override readonly label: LabelDefinition;
|
|
14
|
+
|
|
15
|
+
constructor(form: XFormDefinition, parent: BodyElementParentContext, element: Element) {
|
|
16
|
+
super(form, parent, element);
|
|
17
|
+
|
|
18
|
+
const label = LabelDefinition.forGroup(form, this);
|
|
19
|
+
|
|
20
|
+
if (label == null) {
|
|
21
|
+
throw new Error('Invalid presentation-group: missing label');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.label = label;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type PresentationGroupDefinitionClass = typeof PresentationGroupDefinition;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getRepeatElement } from '../../lib/dom/query.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
3
|
+
import type { BodyElementDefinitionArray, BodyElementParentContext } from '../BodyDefinition.ts';
|
|
4
|
+
import { RepeatDefinition } from '../RepeatDefinition.ts';
|
|
5
|
+
import { BaseGroupDefinition } from './BaseGroupDefinition.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TODO: The `RepeatGroupDefinition`/`RepeatDefinition` types are currently
|
|
9
|
+
* expected to always pair, mirroring the corresponding XForm structure, e.g.
|
|
10
|
+
*
|
|
11
|
+
* ```xml
|
|
12
|
+
* <group ref="/root/rep"> <!-- RepeatGroupDefinition -->
|
|
13
|
+
* <repeat nodeset="/root/rep" /> <!-- RepeatDefinition -->
|
|
14
|
+
* </group>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* This structure has already been a source of one bug (where a recursive walk
|
|
18
|
+
* through a `BodyDefinition`'s descendants failed to reach the children of
|
|
19
|
+
* `RepeatDefinition`s). It seems likely this will continue to be a footgun.
|
|
20
|
+
* After some discussion with @lognaturel, I concluded that the pairing isn't
|
|
21
|
+
* strictly necessary, as this should be considered invalid:
|
|
22
|
+
*
|
|
23
|
+
* ```xml
|
|
24
|
+
* <group ref="/root/rep">
|
|
25
|
+
* <repeat nodeset="/root/rep" />
|
|
26
|
+
* <input ref="/root/rep/not-a-repeat-child" />
|
|
27
|
+
* </group>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* It **may** make sense to collapse these types in the future, but I've held
|
|
31
|
+
* off, following another discussion with @lognaturel. There's some potential
|
|
32
|
+
* for group/repeat labeling ambiguity.
|
|
33
|
+
*
|
|
34
|
+
* - The current design accommodates sharing a repeat-containing group's label
|
|
35
|
+
* across multiple repeat instances, but
|
|
36
|
+
* - The
|
|
37
|
+
* {@link https://github.com/getodk/xforms-spec/blob/791753a09fabd3d64d8cb95776dc0cef71fa4446/_sections/60-repeats.md?plain=1#L46 | ODK XForms spec suggests}
|
|
38
|
+
* a repeat's first group might be a better candidate
|
|
39
|
+
*
|
|
40
|
+
* Example of such a structure:
|
|
41
|
+
*
|
|
42
|
+
* ```xml
|
|
43
|
+
* <group ref="/root/foo">
|
|
44
|
+
* <label>Foo (outer label)</label>
|
|
45
|
+
* <repeat nodeset="/root/foo">
|
|
46
|
+
* <group>
|
|
47
|
+
* <label>Foo (inner label)</label>
|
|
48
|
+
* <!-- ... -->
|
|
49
|
+
* </group>
|
|
50
|
+
* </repeat>
|
|
51
|
+
* </group>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
55
|
+
// @ts-ignore - All this so I could attach a JSDoc comment to something other
|
|
56
|
+
// than the actual class...
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-unused-vars
|
|
58
|
+
interface IgnoreMeIAmJustHereForTheJSDoc {}
|
|
59
|
+
|
|
60
|
+
export class RepeatGroupDefinition extends BaseGroupDefinition<'repeat-group'> {
|
|
61
|
+
static override isCompatible(localName: string, element: Element): boolean {
|
|
62
|
+
return this.getGroupType(localName, element) === 'repeat-group';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
readonly type = 'repeat-group';
|
|
66
|
+
|
|
67
|
+
readonly repeat: RepeatDefinition;
|
|
68
|
+
|
|
69
|
+
get repeatChildren(): BodyElementDefinitionArray {
|
|
70
|
+
return this.repeat.children;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
constructor(form: XFormDefinition, parent: BodyElementParentContext, element: Element) {
|
|
74
|
+
// TODO: this has already been queried at least twice before reaching this
|
|
75
|
+
// point!
|
|
76
|
+
const repeat = getRepeatElement(element);
|
|
77
|
+
|
|
78
|
+
// TODO: and as such, this should not happen
|
|
79
|
+
if (repeat == null) {
|
|
80
|
+
throw new Error('Invalid repeat-group');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
super(form, parent, element);
|
|
84
|
+
|
|
85
|
+
const repeatDefinition = new RepeatDefinition(form, this, repeat);
|
|
86
|
+
|
|
87
|
+
this.repeat = repeatDefinition;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type RepeatGroupDefinitionClass = typeof RepeatGroupDefinition;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseGroupDefinition } from './BaseGroupDefinition.ts';
|
|
2
|
+
|
|
3
|
+
export class StructuralGroupDefinition extends BaseGroupDefinition<'structural-group'> {
|
|
4
|
+
static override isCompatible(localName: string, element: Element): boolean {
|
|
5
|
+
return this.getGroupType(localName, element) === 'structural-group';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
readonly type = 'structural-group';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type StructuralGroupDefinitionClass = typeof StructuralGroupDefinition;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getHintElement } from '../../lib/dom/query.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
3
|
+
import type { AnyControlDefinition } from '../control/ControlDefinition.ts';
|
|
4
|
+
import type { TextElement } from './TextElementDefinition.ts';
|
|
5
|
+
import { TextElementDefinition } from './TextElementDefinition.ts';
|
|
6
|
+
|
|
7
|
+
export interface HintElement extends TextElement {
|
|
8
|
+
readonly localName: 'hint';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class HintDefinition extends TextElementDefinition<'hint'> {
|
|
12
|
+
static forElement(
|
|
13
|
+
form: XFormDefinition,
|
|
14
|
+
definition: AnyControlDefinition
|
|
15
|
+
): HintDefinition | null {
|
|
16
|
+
const hintElement = getHintElement(definition.element);
|
|
17
|
+
|
|
18
|
+
if (hintElement == null) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return new this(form, definition, hintElement);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
readonly type = 'hint';
|
|
26
|
+
}
|