@getodk/xforms-engine 0.2.0 → 0.3.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/body/BodyElementDefinition.d.ts +4 -3
- package/dist/body/RepeatElementDefinition.d.ts +2 -2
- package/dist/body/control/ControlDefinition.d.ts +2 -2
- package/dist/body/control/select/ItemDefinition.d.ts +2 -2
- package/dist/body/control/select/ItemsetDefinition.d.ts +5 -4
- package/dist/body/group/BaseGroupDefinition.d.ts +1 -1
- package/dist/body/group/PresentationGroupDefinition.d.ts +1 -1
- package/dist/client/BaseNode.d.ts +68 -2
- package/dist/client/GroupNode.d.ts +2 -0
- package/dist/client/ModelValueNode.d.ts +37 -0
- package/dist/client/NoteNode.d.ts +53 -0
- package/dist/client/RootNode.d.ts +2 -0
- package/dist/client/SelectNode.d.ts +5 -3
- package/dist/client/StringNode.d.ts +5 -3
- package/dist/client/SubtreeNode.d.ts +2 -0
- package/dist/client/TextRange.d.ts +85 -2
- package/dist/client/constants.d.ts +9 -0
- package/dist/client/hierarchy.d.ts +14 -9
- package/dist/client/node-types.d.ts +2 -1
- package/dist/client/{RepeatRangeNode.d.ts → repeat/BaseRepeatRangeNode.d.ts} +18 -17
- package/dist/client/{RepeatInstanceNode.d.ts → repeat/RepeatInstanceNode.d.ts} +9 -8
- package/dist/client/repeat/RepeatRangeControlledNode.d.ts +19 -0
- package/dist/client/repeat/RepeatRangeUncontrolledNode.d.ts +20 -0
- package/dist/client/validation.d.ts +163 -0
- package/dist/expression/DependentExpression.d.ts +12 -8
- package/dist/index.d.ts +9 -4
- package/dist/index.js +2635 -678
- package/dist/index.js.map +1 -1
- package/dist/instance/Group.d.ts +3 -1
- package/dist/instance/ModelValue.d.ts +40 -0
- package/dist/instance/Note.d.ts +42 -0
- package/dist/instance/Root.d.ts +2 -0
- package/dist/instance/SelectField.d.ts +10 -4
- package/dist/instance/StringField.d.ts +11 -5
- package/dist/instance/Subtree.d.ts +2 -0
- package/dist/instance/abstract/DescendantNode.d.ts +5 -6
- package/dist/instance/abstract/InstanceNode.d.ts +2 -0
- package/dist/instance/hierarchy.d.ts +10 -5
- package/dist/instance/internal-api/ValidationContext.d.ts +21 -0
- package/dist/instance/{RepeatRange.d.ts → repeat/BaseRepeatRange.d.ts} +46 -45
- package/dist/instance/{RepeatInstance.d.ts → repeat/RepeatInstance.d.ts} +13 -12
- package/dist/instance/repeat/RepeatRangeControlled.d.ts +16 -0
- package/dist/instance/repeat/RepeatRangeUncontrolled.d.ts +35 -0
- package/dist/instance/text/TextRange.d.ts +4 -4
- package/dist/lib/reactivity/createComputedExpression.d.ts +6 -1
- package/dist/lib/reactivity/createNoteReadonlyThunk.d.ts +5 -0
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +1 -1
- package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +1 -1
- package/dist/lib/reactivity/text/createFieldHint.d.ts +3 -3
- package/dist/lib/reactivity/text/createNodeLabel.d.ts +2 -2
- package/dist/lib/reactivity/text/createNoteText.d.ts +25 -0
- package/dist/lib/reactivity/text/createTextRange.d.ts +5 -7
- package/dist/lib/reactivity/validation/createAggregatedViolations.d.ts +9 -0
- package/dist/lib/reactivity/validation/createValidation.d.ts +18 -0
- package/dist/model/BindDefinition.d.ts +4 -2
- package/dist/model/BindElement.d.ts +1 -0
- package/dist/model/{ValueNodeDefinition.d.ts → LeafNodeDefinition.d.ts} +2 -2
- package/dist/model/NodeDefinition.d.ts +8 -8
- package/dist/model/RepeatInstanceDefinition.d.ts +2 -2
- package/dist/model/RepeatRangeDefinition.d.ts +14 -4
- package/dist/parse/NoteNodeDefinition.d.ts +31 -0
- package/dist/parse/expression/RepeatCountControlExpression.d.ts +19 -0
- package/dist/parse/text/HintDefinition.d.ts +9 -0
- package/dist/parse/text/ItemLabelDefinition.d.ts +9 -0
- package/dist/parse/text/ItemsetLabelDefinition.d.ts +13 -0
- package/dist/parse/text/LabelDefinition.d.ts +15 -0
- package/dist/parse/text/MessageDefinition.d.ts +15 -0
- package/dist/parse/text/OutputChunkDefinition.d.ts +8 -0
- package/dist/parse/text/ReferenceChunkDefinition.d.ts +8 -0
- package/dist/parse/text/StaticTextChunkDefinition.d.ts +10 -0
- package/dist/parse/text/TranslationChunkDefinition.d.ts +9 -0
- package/dist/parse/text/abstract/TextChunkDefinition.d.ts +18 -0
- package/dist/parse/text/abstract/TextElementDefinition.d.ts +23 -0
- package/dist/parse/text/abstract/TextRangeDefinition.d.ts +35 -0
- package/dist/parse/xpath/dependency-analysis.d.ts +40 -0
- package/dist/parse/xpath/path-resolution.d.ts +70 -0
- package/dist/parse/xpath/predicate-analysis.d.ts +30 -0
- package/dist/parse/xpath/reference-parsing.d.ts +18 -0
- package/dist/parse/xpath/semantic-analysis.d.ts +98 -0
- package/dist/parse/xpath/syntax-traversal.d.ts +69 -0
- package/dist/solid.js +2636 -679
- package/dist/solid.js.map +1 -1
- package/package.json +14 -15
- package/src/body/BodyElementDefinition.ts +4 -3
- package/src/body/RepeatElementDefinition.ts +5 -17
- package/src/body/control/ControlDefinition.ts +4 -3
- package/src/body/control/select/ItemDefinition.ts +3 -3
- package/src/body/control/select/ItemsetDefinition.ts +29 -12
- package/src/body/control/select/ItemsetNodesetExpression.ts +1 -1
- package/src/body/group/BaseGroupDefinition.ts +3 -2
- package/src/body/group/PresentationGroupDefinition.ts +1 -1
- package/src/client/BaseNode.ts +73 -7
- package/src/client/GroupNode.ts +2 -0
- package/src/client/ModelValueNode.ts +40 -0
- package/src/client/NoteNode.ts +74 -0
- package/src/client/README.md +1 -0
- package/src/client/RootNode.ts +2 -0
- package/src/client/SelectNode.ts +5 -3
- package/src/client/StringNode.ts +5 -3
- package/src/client/SubtreeNode.ts +2 -0
- package/src/client/TextRange.ts +99 -2
- package/src/client/constants.ts +10 -0
- package/src/client/hierarchy.ts +30 -14
- package/src/client/node-types.ts +8 -1
- package/src/client/{RepeatRangeNode.ts → repeat/BaseRepeatRangeNode.ts} +18 -19
- package/src/client/{RepeatInstanceNode.ts → repeat/RepeatInstanceNode.ts} +10 -8
- package/src/client/repeat/RepeatRangeControlledNode.ts +20 -0
- package/src/client/repeat/RepeatRangeUncontrolledNode.ts +21 -0
- package/src/client/validation.ts +199 -0
- package/src/expression/DependentExpression.ts +45 -27
- package/src/index.ts +15 -8
- package/src/instance/Group.ts +10 -4
- package/src/instance/ModelValue.ts +104 -0
- package/src/instance/Note.ts +142 -0
- package/src/instance/Root.ts +9 -3
- package/src/instance/SelectField.ts +28 -6
- package/src/instance/StringField.ts +35 -9
- package/src/instance/Subtree.ts +9 -3
- package/src/instance/abstract/DescendantNode.ts +6 -7
- package/src/instance/abstract/InstanceNode.ts +20 -6
- package/src/instance/children.ts +42 -15
- package/src/instance/hierarchy.ts +21 -2
- package/src/instance/internal-api/ValidationContext.ts +23 -0
- package/src/instance/{RepeatRange.ts → repeat/BaseRepeatRange.ts} +114 -99
- package/src/instance/{RepeatInstance.ts → repeat/RepeatInstance.ts} +27 -22
- package/src/instance/repeat/RepeatRangeControlled.ts +82 -0
- package/src/instance/repeat/RepeatRangeUncontrolled.ts +67 -0
- package/src/instance/text/TextRange.ts +10 -4
- package/src/lib/reactivity/createComputedExpression.ts +22 -24
- package/src/lib/reactivity/createNoteReadonlyThunk.ts +33 -0
- package/src/lib/reactivity/createSelectItems.ts +21 -14
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +1 -1
- package/src/lib/reactivity/text/createFieldHint.ts +9 -7
- package/src/lib/reactivity/text/createNodeLabel.ts +7 -5
- package/src/lib/reactivity/text/createNoteText.ts +72 -0
- package/src/lib/reactivity/text/createTextRange.ts +17 -90
- package/src/lib/reactivity/validation/createAggregatedViolations.ts +70 -0
- package/src/lib/reactivity/validation/createValidation.ts +196 -0
- package/src/model/BindComputation.ts +0 -4
- package/src/model/BindDefinition.ts +8 -6
- package/src/model/BindElement.ts +1 -0
- package/src/model/{ValueNodeDefinition.ts → LeafNodeDefinition.ts} +4 -4
- package/src/model/ModelBindMap.ts +4 -0
- package/src/model/NodeDefinition.ts +12 -12
- package/src/model/RepeatInstanceDefinition.ts +2 -2
- package/src/model/RepeatRangeDefinition.ts +49 -8
- package/src/model/RootDefinition.ts +7 -3
- package/src/parse/NoteNodeDefinition.ts +70 -0
- package/src/parse/TODO.md +3 -0
- package/src/parse/expression/RepeatCountControlExpression.ts +44 -0
- package/src/parse/text/HintDefinition.ts +25 -0
- package/src/parse/text/ItemLabelDefinition.ts +25 -0
- package/src/parse/text/ItemsetLabelDefinition.ts +44 -0
- package/src/parse/text/LabelDefinition.ts +61 -0
- package/src/parse/text/MessageDefinition.ts +49 -0
- package/src/parse/text/OutputChunkDefinition.ts +25 -0
- package/src/parse/text/ReferenceChunkDefinition.ts +14 -0
- package/src/parse/text/StaticTextChunkDefinition.ts +19 -0
- package/src/parse/text/TranslationChunkDefinition.ts +38 -0
- package/src/parse/text/abstract/TextChunkDefinition.ts +38 -0
- package/src/parse/text/abstract/TextElementDefinition.ts +71 -0
- package/src/parse/text/abstract/TextRangeDefinition.ts +70 -0
- package/src/parse/xpath/dependency-analysis.ts +105 -0
- package/src/parse/xpath/path-resolution.ts +475 -0
- package/src/parse/xpath/predicate-analysis.ts +61 -0
- package/src/parse/xpath/reference-parsing.ts +90 -0
- package/src/parse/xpath/semantic-analysis.ts +466 -0
- package/src/parse/xpath/syntax-traversal.ts +129 -0
- package/dist/body/text/HintDefinition.d.ts +0 -11
- package/dist/body/text/LabelDefinition.d.ts +0 -22
- package/dist/body/text/TextElementDefinition.d.ts +0 -33
- package/dist/body/text/TextElementOutputPart.d.ts +0 -13
- package/dist/body/text/TextElementPart.d.ts +0 -13
- package/dist/body/text/TextElementReferencePart.d.ts +0 -7
- package/dist/body/text/TextElementStaticPart.d.ts +0 -7
- package/dist/lib/xpath/analysis.d.ts +0 -23
- package/src/body/text/HintDefinition.ts +0 -26
- package/src/body/text/LabelDefinition.ts +0 -68
- package/src/body/text/TextElementDefinition.ts +0 -97
- package/src/body/text/TextElementOutputPart.ts +0 -27
- package/src/body/text/TextElementPart.ts +0 -31
- package/src/body/text/TextElementReferencePart.ts +0 -21
- package/src/body/text/TextElementStaticPart.ts +0 -26
- package/src/lib/xpath/analysis.ts +0 -241
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { LocalNamedElement } from '@getodk/common/types/dom.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
3
|
+
import type { ItemDefinition } from '../../body/control/select/ItemDefinition.ts';
|
|
4
|
+
import { getLabelElement } from '../../lib/dom/query.ts';
|
|
5
|
+
import { TextElementDefinition } from './abstract/TextElementDefinition.ts';
|
|
6
|
+
|
|
7
|
+
interface LabelElement extends LocalNamedElement<'label'> {}
|
|
8
|
+
|
|
9
|
+
export class ItemLabelDefinition extends TextElementDefinition<'item-label'> {
|
|
10
|
+
static from(form: XFormDefinition, owner: ItemDefinition): ItemLabelDefinition | null {
|
|
11
|
+
const labelElement = getLabelElement(owner.element);
|
|
12
|
+
|
|
13
|
+
if (labelElement == null) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return new this(form, owner, labelElement);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
readonly role = 'item-label';
|
|
21
|
+
|
|
22
|
+
private constructor(form: XFormDefinition, owner: ItemDefinition, element: LabelElement) {
|
|
23
|
+
super(form, owner, element);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { LocalNamedElement } from '@getodk/common/types/dom.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
3
|
+
import type { ItemDefinition } from '../../body/control/select/ItemDefinition.ts';
|
|
4
|
+
import type { ItemsetDefinition } from '../../body/control/select/ItemsetDefinition.ts';
|
|
5
|
+
import { getLabelElement } from '../../lib/dom/query.ts';
|
|
6
|
+
import { ReferenceChunkDefinition } from './ReferenceChunkDefinition.ts';
|
|
7
|
+
import { TranslationChunkDefinition } from './TranslationChunkDefinition.ts';
|
|
8
|
+
import type { RefAttributeChunk } from './abstract/TextElementDefinition.ts';
|
|
9
|
+
import { TextRangeDefinition } from './abstract/TextRangeDefinition.ts';
|
|
10
|
+
|
|
11
|
+
export type ItemLabelOwner = ItemDefinition | ItemsetDefinition;
|
|
12
|
+
|
|
13
|
+
interface LabelElement extends LocalNamedElement<'label'> {}
|
|
14
|
+
|
|
15
|
+
export class ItemsetLabelDefinition extends TextRangeDefinition<'item-label'> {
|
|
16
|
+
static from(form: XFormDefinition, owner: ItemsetDefinition): ItemsetLabelDefinition | null {
|
|
17
|
+
const labelElement = getLabelElement(owner.element);
|
|
18
|
+
|
|
19
|
+
if (labelElement == null) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return new this(form, owner, labelElement);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
readonly role = 'item-label';
|
|
27
|
+
readonly chunks: readonly [RefAttributeChunk];
|
|
28
|
+
|
|
29
|
+
private constructor(form: XFormDefinition, owner: ItemsetDefinition, element: LabelElement) {
|
|
30
|
+
super(form, owner, element);
|
|
31
|
+
|
|
32
|
+
const refExpression = element.getAttribute('ref');
|
|
33
|
+
|
|
34
|
+
if (refExpression == null) {
|
|
35
|
+
throw new Error('<itemset><label> missing ref attribute');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const refChunk =
|
|
39
|
+
TranslationChunkDefinition.from(this, refExpression) ??
|
|
40
|
+
ReferenceChunkDefinition.from(this, refExpression);
|
|
41
|
+
|
|
42
|
+
this.chunks = [refChunk];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { LocalNamedElement } from '@getodk/common/types/dom.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
3
|
+
import type { AnyGroupElementDefinition } from '../../body/BodyDefinition.ts';
|
|
4
|
+
import type { RepeatElementDefinition } from '../../body/RepeatElementDefinition.ts';
|
|
5
|
+
import type { AnyControlDefinition } from '../../body/control/ControlDefinition.ts';
|
|
6
|
+
import type { BaseGroupDefinition } from '../../body/group/BaseGroupDefinition.ts';
|
|
7
|
+
import { getLabelElement, getRepeatGroupLabelElement } from '../../lib/dom/query.ts';
|
|
8
|
+
import { TextElementDefinition } from './abstract/TextElementDefinition.ts';
|
|
9
|
+
|
|
10
|
+
// prettier-ignore
|
|
11
|
+
export type LabelOwner =
|
|
12
|
+
| AnyControlDefinition
|
|
13
|
+
| AnyGroupElementDefinition
|
|
14
|
+
| RepeatElementDefinition;
|
|
15
|
+
|
|
16
|
+
interface LabelElement extends LocalNamedElement<'label'> {}
|
|
17
|
+
|
|
18
|
+
export class LabelDefinition extends TextElementDefinition<'label'> {
|
|
19
|
+
static forControl(form: XFormDefinition, control: AnyControlDefinition): LabelDefinition | null {
|
|
20
|
+
const labelElement = getLabelElement(control.element);
|
|
21
|
+
|
|
22
|
+
if (labelElement == null) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return new this(form, control, labelElement);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static forRepeatGroup(
|
|
30
|
+
form: XFormDefinition,
|
|
31
|
+
repeat: RepeatElementDefinition
|
|
32
|
+
): LabelDefinition | null {
|
|
33
|
+
const repeatGroupLabel = getRepeatGroupLabelElement(repeat.element);
|
|
34
|
+
|
|
35
|
+
if (repeatGroupLabel == null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new this(form, repeat, repeatGroupLabel);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static forGroup(
|
|
43
|
+
form: XFormDefinition,
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
group: BaseGroupDefinition<any>
|
|
46
|
+
): LabelDefinition | null {
|
|
47
|
+
const labelElement = getLabelElement(group.element);
|
|
48
|
+
|
|
49
|
+
if (labelElement == null) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new this(form, group, labelElement);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
readonly role = 'label';
|
|
57
|
+
|
|
58
|
+
private constructor(form: XFormDefinition, owner: LabelOwner, element: LabelElement) {
|
|
59
|
+
super(form, owner, element);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JAVAROSA_NAMESPACE_URI } from '@getodk/common/constants/xmlns.ts';
|
|
2
|
+
import type { BindDefinition } from '../../model/BindDefinition.ts';
|
|
3
|
+
import { StaticTextChunkDefinition } from './StaticTextChunkDefinition.ts';
|
|
4
|
+
import { TranslationChunkDefinition } from './TranslationChunkDefinition.ts';
|
|
5
|
+
import type { TextBindAttributeLocalName, TextSourceNode } from './abstract/TextRangeDefinition.ts';
|
|
6
|
+
import { TextRangeDefinition } from './abstract/TextRangeDefinition.ts';
|
|
7
|
+
|
|
8
|
+
export type MessageSourceNode = TextSourceNode<TextBindAttributeLocalName>;
|
|
9
|
+
|
|
10
|
+
// prettier-ignore
|
|
11
|
+
type MessageChunk =
|
|
12
|
+
| StaticTextChunkDefinition
|
|
13
|
+
| TranslationChunkDefinition;
|
|
14
|
+
|
|
15
|
+
export class MessageDefinition<
|
|
16
|
+
Type extends TextBindAttributeLocalName,
|
|
17
|
+
> extends TextRangeDefinition<Type> {
|
|
18
|
+
static from<Type extends TextBindAttributeLocalName>(
|
|
19
|
+
bind: BindDefinition,
|
|
20
|
+
type: Type
|
|
21
|
+
): MessageDefinition<Type> | null {
|
|
22
|
+
const message = bind.bindElement.getAttributeNS(JAVAROSA_NAMESPACE_URI, type);
|
|
23
|
+
|
|
24
|
+
if (message == null) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return new this(bind, type, message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
readonly chunks: readonly [MessageChunk];
|
|
32
|
+
|
|
33
|
+
private constructor(
|
|
34
|
+
bind: BindDefinition,
|
|
35
|
+
readonly role: Type,
|
|
36
|
+
message: string
|
|
37
|
+
) {
|
|
38
|
+
super(bind.form, bind, null);
|
|
39
|
+
|
|
40
|
+
const chunk: MessageChunk =
|
|
41
|
+
TranslationChunkDefinition.fromMessage(this, message) ??
|
|
42
|
+
StaticTextChunkDefinition.from(this, message);
|
|
43
|
+
|
|
44
|
+
this.chunks = [chunk];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// prettier-ignore
|
|
49
|
+
export type AnyMessageDefinition = MessageDefinition<TextBindAttributeLocalName>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { KnownAttributeLocalNamedElement } from '@getodk/common/types/dom.ts';
|
|
2
|
+
import { TextChunkDefinition } from './abstract/TextChunkDefinition.ts';
|
|
3
|
+
import type { AnyTextRangeDefinition } from './abstract/TextRangeDefinition.ts';
|
|
4
|
+
|
|
5
|
+
interface OutputElement extends KnownAttributeLocalNamedElement<'output', 'value'> {}
|
|
6
|
+
|
|
7
|
+
const isOutputElement = (element: Element): element is OutputElement => {
|
|
8
|
+
return element.localName === 'output' && element.hasAttribute('value');
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class OutputChunkDefinition extends TextChunkDefinition<'output'> {
|
|
12
|
+
static from(context: AnyTextRangeDefinition, element: Element): OutputChunkDefinition | null {
|
|
13
|
+
if (isOutputElement(element)) {
|
|
14
|
+
return new this(context, element);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
readonly source = 'output';
|
|
21
|
+
|
|
22
|
+
private constructor(context: AnyTextRangeDefinition, element: OutputElement) {
|
|
23
|
+
super(context, element.getAttribute('value'));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TextChunkDefinition } from './abstract/TextChunkDefinition.ts';
|
|
2
|
+
import type { AnyTextRangeDefinition } from './abstract/TextRangeDefinition.ts';
|
|
3
|
+
|
|
4
|
+
export class ReferenceChunkDefinition extends TextChunkDefinition<'reference'> {
|
|
5
|
+
static from(context: AnyTextRangeDefinition, refExpression: string): ReferenceChunkDefinition {
|
|
6
|
+
return new this(context, refExpression);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
readonly source = 'reference';
|
|
10
|
+
|
|
11
|
+
private constructor(context: AnyTextRangeDefinition, refExpression: string) {
|
|
12
|
+
super(context, refExpression);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { TextChunkDefinition } from './abstract/TextChunkDefinition.ts';
|
|
2
|
+
import type { AnyTextRangeDefinition } from './abstract/TextRangeDefinition.ts';
|
|
3
|
+
|
|
4
|
+
export type StaticTextChunkSourceNode = Attr | Text;
|
|
5
|
+
|
|
6
|
+
export class StaticTextChunkDefinition extends TextChunkDefinition<'static'> {
|
|
7
|
+
static from(context: AnyTextRangeDefinition, stringValue: string): StaticTextChunkDefinition {
|
|
8
|
+
return new this(context, stringValue);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
readonly source = 'static';
|
|
12
|
+
|
|
13
|
+
private constructor(
|
|
14
|
+
context: AnyTextRangeDefinition,
|
|
15
|
+
override readonly stringValue: string
|
|
16
|
+
) {
|
|
17
|
+
super(context, 'null');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { expressionParser } from '@getodk/xpath/expressionParser.js';
|
|
2
|
+
import type { TranslationExpression } from '../xpath/semantic-analysis.ts';
|
|
3
|
+
import { isTranslationExpression } from '../xpath/semantic-analysis.ts';
|
|
4
|
+
import { TextChunkDefinition } from './abstract/TextChunkDefinition.ts';
|
|
5
|
+
import type { AnyTextRangeDefinition } from './abstract/TextRangeDefinition.ts';
|
|
6
|
+
|
|
7
|
+
export class TranslationChunkDefinition extends TextChunkDefinition<'translation'> {
|
|
8
|
+
static fromMessage(
|
|
9
|
+
context: AnyTextRangeDefinition,
|
|
10
|
+
maybeExpression: string
|
|
11
|
+
): TranslationChunkDefinition | null {
|
|
12
|
+
try {
|
|
13
|
+
expressionParser.parse(maybeExpression);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isTranslationExpression(maybeExpression)) {
|
|
19
|
+
return new this(context, maybeExpression);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static from(context: AnyTextRangeDefinition, maybeExpression: string) {
|
|
26
|
+
if (isTranslationExpression(maybeExpression)) {
|
|
27
|
+
return new this(context, maybeExpression);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
readonly source = 'translation';
|
|
34
|
+
|
|
35
|
+
private constructor(context: AnyTextRangeDefinition, expression: TranslationExpression) {
|
|
36
|
+
super(context, expression, { isTranslated: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TextChunkSource } from '../../../client/TextRange.ts';
|
|
2
|
+
import { DependentExpression } from '../../../expression/DependentExpression.ts';
|
|
3
|
+
import type { OutputChunkDefinition } from '../OutputChunkDefinition.ts';
|
|
4
|
+
import type { ReferenceChunkDefinition } from '../ReferenceChunkDefinition.ts';
|
|
5
|
+
import type { StaticTextChunkDefinition } from '../StaticTextChunkDefinition.ts';
|
|
6
|
+
import type { TranslationChunkDefinition } from '../TranslationChunkDefinition.ts';
|
|
7
|
+
import type { AnyTextRangeDefinition } from './TextRangeDefinition.ts';
|
|
8
|
+
|
|
9
|
+
interface TextChunkDefinitionOptions {
|
|
10
|
+
readonly isTranslated?: true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export abstract class TextChunkDefinition<
|
|
14
|
+
Source extends TextChunkSource,
|
|
15
|
+
> extends DependentExpression<'string'> {
|
|
16
|
+
abstract readonly source: Source;
|
|
17
|
+
readonly stringValue?: string;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
context: AnyTextRangeDefinition,
|
|
21
|
+
expression: string,
|
|
22
|
+
options: TextChunkDefinitionOptions = {}
|
|
23
|
+
) {
|
|
24
|
+
super(context, 'string', expression, {
|
|
25
|
+
semanticDependencies: {
|
|
26
|
+
translations: options.isTranslated,
|
|
27
|
+
},
|
|
28
|
+
ignoreContextReference: true,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// prettier-ignore
|
|
34
|
+
export type AnyTextChunkDefinition =
|
|
35
|
+
| OutputChunkDefinition
|
|
36
|
+
| ReferenceChunkDefinition
|
|
37
|
+
| StaticTextChunkDefinition
|
|
38
|
+
| TranslationChunkDefinition;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { isElementNode, isTextNode } from '@getodk/common/lib/dom/predicates.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../../XFormDefinition.ts';
|
|
3
|
+
import type { ItemDefinition } from '../../../body/control/select/ItemDefinition.ts';
|
|
4
|
+
import type { ElementTextRole } from '../../../client/TextRange.ts';
|
|
5
|
+
import { parseNodesetReference } from '../../xpath/reference-parsing.ts';
|
|
6
|
+
import type { HintDefinition } from '../HintDefinition.ts';
|
|
7
|
+
import type { ItemLabelDefinition } from '../ItemLabelDefinition.ts';
|
|
8
|
+
import type { ItemsetLabelDefinition } from '../ItemsetLabelDefinition.ts';
|
|
9
|
+
import type { LabelDefinition, LabelOwner } from '../LabelDefinition.ts';
|
|
10
|
+
import { OutputChunkDefinition } from '../OutputChunkDefinition.ts';
|
|
11
|
+
import { ReferenceChunkDefinition } from '../ReferenceChunkDefinition.ts';
|
|
12
|
+
import { StaticTextChunkDefinition } from '../StaticTextChunkDefinition.ts';
|
|
13
|
+
import { TranslationChunkDefinition } from '../TranslationChunkDefinition.ts';
|
|
14
|
+
import type { TextSourceNode } from './TextRangeDefinition.ts';
|
|
15
|
+
import { TextRangeDefinition } from './TextRangeDefinition.ts';
|
|
16
|
+
|
|
17
|
+
// prettier-ignore
|
|
18
|
+
export type RefAttributeChunk =
|
|
19
|
+
| ReferenceChunkDefinition
|
|
20
|
+
| TranslationChunkDefinition;
|
|
21
|
+
|
|
22
|
+
// prettier-ignore
|
|
23
|
+
type TextElementChildChunk =
|
|
24
|
+
| OutputChunkDefinition
|
|
25
|
+
| StaticTextChunkDefinition;
|
|
26
|
+
|
|
27
|
+
// prettier-ignore
|
|
28
|
+
type TextElementChunks =
|
|
29
|
+
| readonly [RefAttributeChunk]
|
|
30
|
+
| readonly TextElementChildChunk[];
|
|
31
|
+
|
|
32
|
+
type TextElementOwner = ItemDefinition | LabelOwner;
|
|
33
|
+
|
|
34
|
+
export abstract class TextElementDefinition<
|
|
35
|
+
Role extends ElementTextRole,
|
|
36
|
+
> extends TextRangeDefinition<Role> {
|
|
37
|
+
readonly chunks: TextElementChunks;
|
|
38
|
+
|
|
39
|
+
constructor(form: XFormDefinition, owner: TextElementOwner, sourceNode: TextSourceNode<Role>) {
|
|
40
|
+
super(form, owner, sourceNode);
|
|
41
|
+
|
|
42
|
+
const context = this as AnyTextElementDefinition;
|
|
43
|
+
const refExpression = parseNodesetReference(owner, sourceNode, 'ref');
|
|
44
|
+
|
|
45
|
+
if (refExpression == null) {
|
|
46
|
+
this.chunks = Array.from(sourceNode.childNodes).flatMap((childNode) => {
|
|
47
|
+
if (isElementNode(childNode)) {
|
|
48
|
+
return OutputChunkDefinition.from(context, childNode) ?? [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (isTextNode(childNode)) {
|
|
52
|
+
return StaticTextChunkDefinition.from(context, childNode.data);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [];
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
const refChunk =
|
|
59
|
+
TranslationChunkDefinition.from(context, refExpression) ??
|
|
60
|
+
ReferenceChunkDefinition.from(context, refExpression);
|
|
61
|
+
this.chunks = [refChunk];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// prettier-ignore
|
|
67
|
+
export type AnyTextElementDefinition =
|
|
68
|
+
| HintDefinition
|
|
69
|
+
| ItemLabelDefinition
|
|
70
|
+
| ItemsetLabelDefinition
|
|
71
|
+
| LabelDefinition;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { LocalNamedElement } from '@getodk/common/types/dom.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../../XFormDefinition.ts';
|
|
3
|
+
import type { TextRole } from '../../../client/TextRange.ts';
|
|
4
|
+
import { DependencyContext } from '../../../expression/DependencyContext.ts';
|
|
5
|
+
import type { AnyDependentExpression } from '../../../expression/DependentExpression.ts';
|
|
6
|
+
import type { AnyMessageDefinition } from '../MessageDefinition.ts';
|
|
7
|
+
import type { AnyTextChunkDefinition } from './TextChunkDefinition.ts';
|
|
8
|
+
import type { AnyTextElementDefinition } from './TextElementDefinition.ts';
|
|
9
|
+
|
|
10
|
+
export type TextBindAttributeLocalName = 'constraintMsg' | 'requiredMsg';
|
|
11
|
+
export type TextBodyElementLocalName = 'hint' | 'label';
|
|
12
|
+
|
|
13
|
+
interface TextSourceNodes {
|
|
14
|
+
readonly constraintMsg: null;
|
|
15
|
+
readonly hint: LocalNamedElement<'hint'>;
|
|
16
|
+
readonly label: LocalNamedElement<'label'>;
|
|
17
|
+
readonly 'item-label': LocalNamedElement<'label'>;
|
|
18
|
+
readonly requiredMsg: null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type TextSourceNode<Type extends TextRole> = TextSourceNodes[Type];
|
|
22
|
+
|
|
23
|
+
export abstract class TextRangeDefinition<Role extends TextRole> extends DependencyContext {
|
|
24
|
+
abstract readonly role: Role;
|
|
25
|
+
readonly parentReference: string | null;
|
|
26
|
+
readonly reference: string | null;
|
|
27
|
+
|
|
28
|
+
abstract readonly chunks: readonly AnyTextChunkDefinition[];
|
|
29
|
+
|
|
30
|
+
override get isTranslated(): boolean {
|
|
31
|
+
return (
|
|
32
|
+
this.ownerContext.isTranslated || this.chunks.some((chunk) => chunk.source === 'translation')
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override set isTranslated(value: true) {
|
|
37
|
+
if (this.ownerContext != null) {
|
|
38
|
+
this.ownerContext.isTranslated = value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
super.isTranslated = value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected constructor(
|
|
45
|
+
readonly form: XFormDefinition,
|
|
46
|
+
readonly ownerContext: DependencyContext,
|
|
47
|
+
readonly sourceNode: TextSourceNode<Role>
|
|
48
|
+
) {
|
|
49
|
+
super();
|
|
50
|
+
|
|
51
|
+
this.reference = ownerContext.reference;
|
|
52
|
+
this.parentReference = ownerContext.parentReference;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override registerDependentExpression(expression: AnyDependentExpression): void {
|
|
56
|
+
this.ownerContext.registerDependentExpression(expression);
|
|
57
|
+
super.registerDependentExpression(expression);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
toJSON(): object {
|
|
61
|
+
const { form, ownerContext, ...rest } = this;
|
|
62
|
+
|
|
63
|
+
return rest;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// prettier-ignore
|
|
68
|
+
export type AnyTextRangeDefinition =
|
|
69
|
+
| AnyMessageDefinition
|
|
70
|
+
| AnyTextElementDefinition;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { expressionParser } from '@getodk/xpath/expressionParser.js';
|
|
2
|
+
import {
|
|
3
|
+
resolvePath,
|
|
4
|
+
resolvePredicateReference,
|
|
5
|
+
serializeNodesetReference,
|
|
6
|
+
} from './path-resolution.ts';
|
|
7
|
+
import { findPredicateReferences } from './predicate-analysis.ts';
|
|
8
|
+
import type { PathExpressionNode } from './semantic-analysis.ts';
|
|
9
|
+
import { findLocationPathSubExpressionNodes, getPathExpressionNode } from './semantic-analysis.ts';
|
|
10
|
+
|
|
11
|
+
export interface PathResolutionOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Excludes direct references to the nodeset specified as a context. This flag
|
|
14
|
+
* has earlier precedent as `ignoreContextReference` in similar internal APIs.
|
|
15
|
+
* The intent, in part, is to allow for certain in-spec exceptions to cycle
|
|
16
|
+
* analysis (which we previously did explicitly, and foresee reintroducing in
|
|
17
|
+
* some form relatively soon). As such, the default for this flag should be
|
|
18
|
+
* overridden when a computation is (A) not among the core `<bind>`
|
|
19
|
+
* computations or (B) is a computation which is explicitly expected to have
|
|
20
|
+
* self-references (such as `constraint`).
|
|
21
|
+
*
|
|
22
|
+
* @default false
|
|
23
|
+
*
|
|
24
|
+
* @todo It is highly likely we will eliminate this in the future. We
|
|
25
|
+
* considered removing its precursor (and existing code now referencing this)
|
|
26
|
+
* in {@link https://github.com/getodk/web-forms/pull/67}, but ran into issues
|
|
27
|
+
* with some of the reactive logic introduced there. It's likely that
|
|
28
|
+
* subsequent improvements have addressed those issues, but it's also likely
|
|
29
|
+
* that we will want similar special casing when we reintroduce explicit graph
|
|
30
|
+
* cycle analysis.
|
|
31
|
+
*/
|
|
32
|
+
readonly ignoreReferenceToContextPath?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const defaultPathResolutionOptions: Required<PathResolutionOptions> = {
|
|
36
|
+
ignoreReferenceToContextPath: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolves XPath paths referenced by an arbitrary expression to XForms
|
|
41
|
+
* `nodeset` references:
|
|
42
|
+
*
|
|
43
|
+
* 1. The provided {@link expression} is analyzed to find path sub-expressions
|
|
44
|
+
* (LocationPaths, sub-expressions with a node-set producing FilterExpr).
|
|
45
|
+
*
|
|
46
|
+
* 2. When a {@link contextNodeset} is available, each sub-expression is
|
|
47
|
+
* resolved to that context. See {@link resolvePath} for additional
|
|
48
|
+
* detail.
|
|
49
|
+
*
|
|
50
|
+
* 3. Once resolved, each dependency path is further analyzed to identify
|
|
51
|
+
* sub-expressions in any of its predicates, resolving each to their
|
|
52
|
+
* respective predicates' Steps. See {@link resolvePredicateReference} for
|
|
53
|
+
* additional detail.
|
|
54
|
+
*/
|
|
55
|
+
export const resolveDependencyNodesets = (
|
|
56
|
+
contextNodeset: string | null,
|
|
57
|
+
expression: string,
|
|
58
|
+
options: PathResolutionOptions = {}
|
|
59
|
+
): readonly string[] => {
|
|
60
|
+
const { ignoreReferenceToContextPath } = {
|
|
61
|
+
...defaultPathResolutionOptions,
|
|
62
|
+
...options,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
let contextNode: PathExpressionNode | null = null;
|
|
66
|
+
|
|
67
|
+
if (contextNodeset != null) {
|
|
68
|
+
contextNode = getPathExpressionNode(contextNodeset);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const expressionRootNode = expressionParser.parse(expression).rootNode;
|
|
72
|
+
const subExpressions = findLocationPathSubExpressionNodes(expressionRootNode);
|
|
73
|
+
|
|
74
|
+
const resolvedPathLists = subExpressions.flatMap((subExpression) => {
|
|
75
|
+
const pathNodes = resolvePath(contextNode, subExpression);
|
|
76
|
+
const predicateReferences = findPredicateReferences(pathNodes);
|
|
77
|
+
const resolvedPredicateReferences = predicateReferences.map((reference) => {
|
|
78
|
+
return resolvePredicateReference(
|
|
79
|
+
contextNode,
|
|
80
|
+
reference.stepContextNodes,
|
|
81
|
+
reference.predicatePathNode
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return [pathNodes, ...resolvedPredicateReferences];
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const serializedPaths = Array.from(
|
|
89
|
+
new Set(
|
|
90
|
+
resolvedPathLists.map((resolvedPathList) => {
|
|
91
|
+
return serializeNodesetReference(resolvedPathList, {
|
|
92
|
+
stripPredicates: true,
|
|
93
|
+
});
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (ignoreReferenceToContextPath) {
|
|
99
|
+
return serializedPaths.filter((result) => {
|
|
100
|
+
return result !== contextNodeset;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return serializedPaths;
|
|
105
|
+
};
|