@getodk/xforms-engine 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/BaseNode.d.ts +69 -4
- package/dist/client/EngineConfig.d.ts +0 -1
- package/dist/client/GroupNode.d.ts +4 -3
- package/dist/client/ModelValueNode.d.ts +36 -0
- package/dist/client/NodeAppearances.d.ts +1 -2
- package/dist/client/NoteNode.d.ts +52 -0
- package/dist/client/RootNode.d.ts +4 -3
- package/dist/client/SelectNode.d.ts +6 -5
- package/dist/client/StringNode.d.ts +6 -5
- package/dist/client/SubtreeNode.d.ts +3 -2
- package/dist/client/TextRange.d.ts +85 -3
- package/dist/client/TriggerNode.d.ts +25 -0
- package/dist/client/constants.d.ts +8 -0
- package/dist/client/hierarchy.d.ts +19 -10
- package/dist/client/index.d.ts +0 -1
- package/dist/client/node-types.d.ts +3 -1
- package/dist/client/{RepeatRangeNode.d.ts → repeat/BaseRepeatRangeNode.d.ts} +18 -18
- package/dist/client/{RepeatInstanceNode.d.ts → repeat/RepeatInstanceNode.d.ts} +9 -9
- package/dist/client/repeat/RepeatRangeControlledNode.d.ts +18 -0
- package/dist/client/repeat/RepeatRangeUncontrolledNode.d.ts +19 -0
- package/dist/client/unsupported/RangeNode.d.ts +9 -0
- package/dist/client/unsupported/RankNode.d.ts +9 -0
- package/dist/client/unsupported/UnsupportedControlNode.d.ts +32 -0
- package/dist/client/unsupported/UploadNode.d.ts +9 -0
- package/dist/client/validation.d.ts +162 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.js +39696 -36151
- package/dist/index.js.map +1 -1
- package/dist/instance/Group.d.ts +3 -2
- package/dist/instance/ModelValue.d.ts +39 -0
- package/dist/instance/Note.d.ts +41 -0
- package/dist/instance/Root.d.ts +5 -4
- package/dist/instance/SelectField.d.ts +11 -6
- package/dist/instance/StringField.d.ts +12 -7
- package/dist/instance/Subtree.d.ts +2 -1
- package/dist/instance/TriggerControl.d.ts +40 -0
- package/dist/instance/abstract/DescendantNode.d.ts +8 -10
- package/dist/instance/abstract/InstanceNode.d.ts +3 -2
- package/dist/instance/abstract/UnsupportedControl.d.ts +46 -0
- package/dist/instance/children.d.ts +0 -1
- package/dist/instance/hierarchy.d.ts +15 -6
- package/dist/instance/index.d.ts +0 -1
- package/dist/instance/internal-api/EvaluationContext.d.ts +0 -1
- package/dist/instance/internal-api/InstanceConfig.d.ts +0 -1
- package/dist/instance/internal-api/SubscribableDependency.d.ts +0 -1
- package/dist/instance/internal-api/TranslationContext.d.ts +0 -1
- package/dist/instance/internal-api/ValidationContext.d.ts +28 -0
- package/dist/instance/internal-api/ValueContext.d.ts +3 -4
- package/dist/instance/{RepeatRange.d.ts → repeat/BaseRepeatRange.d.ts} +46 -46
- package/dist/instance/{RepeatInstance.d.ts → repeat/RepeatInstance.d.ts} +13 -13
- package/dist/instance/repeat/RepeatRangeControlled.d.ts +15 -0
- package/dist/instance/repeat/RepeatRangeUncontrolled.d.ts +34 -0
- package/dist/instance/resource.d.ts +0 -1
- package/dist/instance/text/TextChunk.d.ts +0 -1
- package/dist/instance/text/TextRange.d.ts +4 -5
- package/dist/instance/unsupported/RangeControl.d.ts +4 -0
- package/dist/instance/unsupported/RankControl.d.ts +4 -0
- package/dist/instance/unsupported/UploadControl.d.ts +4 -0
- package/dist/lib/TokenListParser.d.ts +3 -3
- package/dist/lib/dom/query.d.ts +1 -2
- package/dist/lib/reactivity/createChildrenState.d.ts +0 -1
- package/dist/lib/reactivity/createComputedExpression.d.ts +7 -3
- package/dist/lib/reactivity/createNoteReadonlyThunk.d.ts +4 -0
- package/dist/lib/reactivity/createSelectItems.d.ts +0 -1
- package/dist/lib/reactivity/createValueState.d.ts +0 -1
- package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +0 -1
- package/dist/lib/reactivity/node-state/createClientState.d.ts +0 -1
- package/dist/lib/reactivity/node-state/createCurrentState.d.ts +0 -1
- package/dist/lib/reactivity/node-state/createEngineState.d.ts +0 -1
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +1 -2
- package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +0 -1
- package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +1 -2
- package/dist/lib/reactivity/node-state/representations.d.ts +0 -1
- package/dist/lib/reactivity/scope.d.ts +0 -1
- package/dist/lib/reactivity/text/createFieldHint.d.ts +3 -4
- package/dist/lib/reactivity/text/createNodeLabel.d.ts +3 -4
- package/dist/lib/reactivity/text/createNoteText.d.ts +24 -0
- package/dist/lib/reactivity/text/createTextRange.d.ts +5 -8
- package/dist/lib/reactivity/types.d.ts +0 -1
- package/dist/lib/reactivity/validation/createAggregatedViolations.d.ts +8 -0
- package/dist/lib/reactivity/validation/createValidation.d.ts +17 -0
- package/dist/{XFormDOM.d.ts → parse/XFormDOM.d.ts} +1 -2
- package/dist/{XFormDataType.d.ts → parse/XFormDataType.d.ts} +2 -4
- package/dist/{XFormDefinition.d.ts → parse/XFormDefinition.d.ts} +2 -3
- package/dist/{body → parse/body}/BodyDefinition.d.ts +15 -10
- package/dist/{body → parse/body}/BodyElementDefinition.d.ts +8 -7
- package/dist/{body → parse/body}/RepeatElementDefinition.d.ts +4 -5
- package/dist/{body → parse/body}/UnsupportedBodyElementDefinition.d.ts +0 -1
- package/dist/{body → parse/body}/appearance/inputAppearanceParser.d.ts +1 -2
- package/dist/{body → parse/body}/appearance/selectAppearanceParser.d.ts +1 -2
- package/dist/{body → parse/body}/appearance/structureElementAppearanceParser.d.ts +1 -2
- package/dist/parse/body/appearance/unknownAppearanceParser.d.ts +3 -0
- package/dist/{body → parse/body}/control/ControlDefinition.d.ts +3 -4
- package/dist/{body → parse/body}/control/InputDefinition.d.ts +1 -2
- package/dist/parse/body/control/RangeControlDefinition.d.ts +11 -0
- package/dist/parse/body/control/RankControlDefinition.d.ts +11 -0
- package/dist/parse/body/control/TriggerControlDefinition.d.ts +11 -0
- package/dist/parse/body/control/UploadControlDefinition.d.ts +11 -0
- package/dist/{body → parse/body}/control/select/ItemDefinition.d.ts +3 -4
- package/dist/{body → parse/body}/control/select/ItemsetDefinition.d.ts +7 -7
- package/dist/{body → parse/body}/control/select/ItemsetNodesetContext.d.ts +2 -3
- package/dist/{body → parse/body}/control/select/SelectDefinition.d.ts +3 -12
- package/dist/{body → parse/body}/group/BaseGroupDefinition.d.ts +3 -5
- package/dist/{body → parse/body}/group/LogicalGroupDefinition.d.ts +0 -1
- package/dist/{body → parse/body}/group/PresentationGroupDefinition.d.ts +1 -2
- package/dist/{body → parse/body}/group/StructuralGroupDefinition.d.ts +0 -1
- package/dist/{model/BindComputation.d.ts → parse/expression/BindComputationExpression.d.ts} +5 -6
- package/dist/{body/control/select → parse/expression}/ItemsetNodesetExpression.d.ts +2 -3
- package/dist/{body/control/select → parse/expression}/ItemsetValueExpression.d.ts +2 -3
- package/dist/parse/expression/RepeatCountControlExpression.d.ts +18 -0
- package/dist/parse/expression/TextLiteralExpression.d.ts +9 -0
- package/dist/parse/expression/TextOutputExpression.d.ts +7 -0
- package/dist/parse/expression/TextReferenceExpression.d.ts +7 -0
- package/dist/parse/expression/TextTranslationExpression.d.ts +8 -0
- package/dist/{expression → parse/expression/abstract}/DependencyContext.d.ts +0 -1
- package/dist/{expression → parse/expression/abstract}/DependentExpression.d.ts +13 -10
- package/dist/parse/expression/abstract/TextChunkExpression.d.ts +17 -0
- package/dist/parse/model/BindDefinition.d.ts +39 -0
- package/dist/{model → parse/model}/BindElement.d.ts +1 -0
- package/dist/{model → parse/model}/DescendentNodeDefinition.d.ts +0 -1
- package/dist/{model/ValueNodeDefinition.d.ts → parse/model/LeafNodeDefinition.d.ts} +3 -4
- package/dist/{model → parse/model}/ModelBindMap.d.ts +0 -1
- package/dist/{model → parse/model}/ModelDefinition.d.ts +1 -2
- package/dist/{model → parse/model}/NodeDefinition.d.ts +8 -9
- package/dist/parse/model/NoteNodeDefinition.d.ts +30 -0
- package/dist/{model → parse/model}/RepeatInstanceDefinition.d.ts +3 -4
- package/dist/{model → parse/model}/RepeatRangeDefinition.d.ts +14 -5
- package/dist/{model → parse/model}/RepeatTemplateDefinition.d.ts +2 -3
- package/dist/{model → parse/model}/RootDefinition.d.ts +2 -3
- package/dist/{model → parse/model}/SubtreeDefinition.d.ts +1 -2
- package/dist/parse/text/HintDefinition.d.ts +8 -0
- package/dist/parse/text/ItemLabelDefinition.d.ts +8 -0
- package/dist/parse/text/ItemsetLabelDefinition.d.ts +12 -0
- package/dist/parse/text/LabelDefinition.d.ts +14 -0
- package/dist/parse/text/MessageDefinition.d.ts +14 -0
- package/dist/parse/text/abstract/TextElementDefinition.d.ts +22 -0
- package/dist/parse/text/abstract/TextRangeDefinition.d.ts +34 -0
- package/dist/parse/xpath/dependency-analysis.d.ts +40 -0
- package/dist/parse/xpath/path-resolution.d.ts +69 -0
- package/dist/parse/xpath/predicate-analysis.d.ts +29 -0
- package/dist/parse/xpath/reference-parsing.d.ts +17 -0
- package/dist/parse/xpath/semantic-analysis.d.ts +97 -0
- package/dist/parse/xpath/syntax-traversal.d.ts +68 -0
- package/dist/solid.js +8745 -5186
- package/dist/solid.js.map +1 -1
- package/package.json +15 -16
- package/src/client/BaseNode.ts +74 -8
- package/src/client/GroupNode.ts +4 -2
- package/src/client/ModelValueNode.ts +40 -0
- package/src/client/NodeAppearances.ts +1 -1
- package/src/client/NoteNode.ts +74 -0
- package/src/client/README.md +1 -0
- package/src/client/RootNode.ts +4 -2
- package/src/client/SelectNode.ts +6 -4
- package/src/client/StringNode.ts +6 -4
- package/src/client/SubtreeNode.ts +3 -1
- package/src/client/TextRange.ts +98 -2
- package/src/client/TriggerNode.ts +29 -0
- package/src/client/constants.ts +10 -0
- package/src/client/hierarchy.ts +43 -15
- package/src/client/node-types.ts +17 -2
- 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/unsupported/RangeNode.ts +14 -0
- package/src/client/unsupported/RankNode.ts +14 -0
- package/src/client/unsupported/UnsupportedControlNode.ts +40 -0
- package/src/client/unsupported/UploadNode.ts +14 -0
- package/src/client/validation.ts +199 -0
- package/src/index.ts +21 -9
- 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 +16 -6
- package/src/instance/SelectField.ts +29 -7
- package/src/instance/StringField.ts +36 -10
- package/src/instance/Subtree.ts +9 -3
- package/src/instance/TriggerControl.ts +134 -0
- package/src/instance/abstract/DescendantNode.ts +9 -10
- package/src/instance/abstract/InstanceNode.ts +29 -7
- package/src/instance/abstract/UnsupportedControl.ts +151 -0
- package/src/instance/children.ts +113 -16
- package/src/instance/hierarchy.ts +42 -5
- package/src/instance/index.ts +1 -1
- package/src/instance/internal-api/EvaluationContext.ts +1 -1
- package/src/instance/internal-api/ValidationContext.ts +32 -0
- package/src/instance/internal-api/ValueContext.ts +3 -3
- 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/instance/unsupported/RangeControl.ts +5 -0
- package/src/instance/unsupported/RankControl.ts +5 -0
- package/src/instance/unsupported/UploadControl.ts +5 -0
- package/src/lib/TokenListParser.ts +11 -7
- package/src/lib/dom/query.ts +1 -1
- package/src/lib/reactivity/createComputedExpression.ts +25 -27
- package/src/lib/reactivity/createNoteReadonlyThunk.ts +33 -0
- package/src/lib/reactivity/createSelectItems.ts +23 -16
- package/src/lib/reactivity/createValueState.ts +3 -3
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +1 -1
- package/src/lib/reactivity/node-state/createSpecifiedState.ts +1 -1
- package/src/lib/reactivity/text/createFieldHint.ts +9 -7
- package/src/lib/reactivity/text/createNodeLabel.ts +8 -6
- 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 +75 -0
- package/src/lib/reactivity/validation/createValidation.ts +196 -0
- package/src/{XFormDataType.ts → parse/XFormDataType.ts} +1 -3
- package/src/{XFormDefinition.ts → parse/XFormDefinition.ts} +2 -2
- package/src/{body → parse/body}/BodyDefinition.ts +44 -27
- package/src/{body → parse/body}/BodyElementDefinition.ts +13 -6
- package/src/{body → parse/body}/RepeatElementDefinition.ts +10 -20
- package/src/{body → parse/body}/appearance/inputAppearanceParser.ts +1 -1
- package/src/{body → parse/body}/appearance/selectAppearanceParser.ts +1 -1
- package/src/{body → parse/body}/appearance/structureElementAppearanceParser.ts +1 -1
- package/src/parse/body/appearance/unknownAppearanceParser.ts +5 -0
- package/src/{body → parse/body}/control/ControlDefinition.ts +5 -4
- package/src/parse/body/control/RangeControlDefinition.ts +26 -0
- package/src/parse/body/control/RankControlDefinition.ts +27 -0
- package/src/parse/body/control/TriggerControlDefinition.ts +26 -0
- package/src/parse/body/control/UploadControlDefinition.ts +26 -0
- package/src/{body → parse/body}/control/select/ItemDefinition.ts +4 -4
- package/src/parse/body/control/select/ItemsetDefinition.ts +53 -0
- package/src/{body → parse/body}/control/select/ItemsetNodesetContext.ts +2 -2
- package/src/{body → parse/body}/control/select/SelectDefinition.ts +3 -11
- package/src/{body → parse/body}/group/BaseGroupDefinition.ts +11 -21
- package/src/{body → parse/body}/group/PresentationGroupDefinition.ts +1 -1
- package/src/{model/BindComputation.ts → parse/expression/BindComputationExpression.ts} +8 -12
- package/src/parse/expression/ItemsetNodesetExpression.ts +8 -0
- package/src/{body/control/select → parse/expression}/ItemsetValueExpression.ts +2 -2
- package/src/parse/expression/RepeatCountControlExpression.ts +44 -0
- package/src/parse/expression/TextLiteralExpression.ts +19 -0
- package/src/parse/expression/TextOutputExpression.ts +25 -0
- package/src/parse/expression/TextReferenceExpression.ts +14 -0
- package/src/parse/expression/TextTranslationExpression.ts +38 -0
- package/src/{expression → parse/expression/abstract}/DependentExpression.ts +46 -28
- package/src/parse/expression/abstract/TextChunkExpression.ts +38 -0
- package/src/{model → parse/model}/BindDefinition.ts +30 -27
- package/src/{model → parse/model}/BindElement.ts +1 -0
- package/src/{model/ValueNodeDefinition.ts → parse/model/LeafNodeDefinition.ts} +4 -4
- package/src/{model → parse/model}/ModelBindMap.ts +4 -0
- package/src/{model → parse/model}/NodeDefinition.ts +12 -12
- package/src/parse/model/NoteNodeDefinition.ts +70 -0
- package/src/{model → parse/model}/RepeatInstanceDefinition.ts +2 -2
- package/src/parse/model/RepeatRangeDefinition.ts +94 -0
- package/src/{model → parse/model}/RootDefinition.ts +8 -4
- 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/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/dist/model/BindDefinition.d.ts +0 -32
- package/src/body/control/select/ItemsetDefinition.ts +0 -36
- package/src/body/control/select/ItemsetNodesetExpression.ts +0 -8
- 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
- package/src/model/RepeatRangeDefinition.ts +0 -53
- package/src/{XFormDOM.ts → parse/XFormDOM.ts} +0 -0
- package/src/{body → parse/body}/UnsupportedBodyElementDefinition.ts +0 -0
- package/src/{body → parse/body}/control/InputDefinition.ts +1 -1
- /package/src/{body → parse/body}/group/LogicalGroupDefinition.ts +0 -0
- /package/src/{body → parse/body}/group/StructuralGroupDefinition.ts +0 -0
- /package/src/{expression → parse/expression/abstract}/DependencyContext.ts +0 -0
- /package/src/{model → parse/model}/DescendentNodeDefinition.ts +0 -0
- /package/src/{model → parse/model}/ModelDefinition.ts +0 -0
- /package/src/{model → parse/model}/RepeatTemplateDefinition.ts +0 -0
- /package/src/{model → parse/model}/SubtreeDefinition.ts +0 -0
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { UnreachableError } from '@getodk/common/lib/error/UnreachableError.ts';
|
|
2
|
+
import type {
|
|
3
|
+
AbsoluteLocationPathNode,
|
|
4
|
+
AbsoluteRootLocationPathNode,
|
|
5
|
+
FilterExprNode,
|
|
6
|
+
PredicateNode,
|
|
7
|
+
RelativeStepSyntaxLiteralNode,
|
|
8
|
+
StepNode,
|
|
9
|
+
} from '@getodk/xpath/static/grammar/SyntaxNode.js';
|
|
10
|
+
import type { PathExpressionNode } from './semantic-analysis.ts';
|
|
11
|
+
import { isCurrentPath } from './semantic-analysis.ts';
|
|
12
|
+
|
|
13
|
+
type AbsolutePathHead =
|
|
14
|
+
/** / - as first character in LocationPath */
|
|
15
|
+
| AbsoluteRootLocationPathNode
|
|
16
|
+
|
|
17
|
+
/** // - as first characters in LocationPath */
|
|
18
|
+
| RelativeStepSyntaxLiteralNode;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* fn(...args) - as first (and potentially only) part of a path expression,
|
|
22
|
+
* where the function is either known to produce a node-set result, or where
|
|
23
|
+
* other aspects of the exression's syntax are inherently node-set producing.
|
|
24
|
+
*/
|
|
25
|
+
type FilterPathExprHead = FilterExprNode;
|
|
26
|
+
|
|
27
|
+
type StepLikeNode =
|
|
28
|
+
/** // - shorthand for `/descendant-or-self::node()/` */
|
|
29
|
+
| RelativeStepSyntaxLiteralNode
|
|
30
|
+
|
|
31
|
+
/** Any _actual_ Step in a LocationPath */
|
|
32
|
+
| StepNode;
|
|
33
|
+
|
|
34
|
+
type PathNodeListHead = AbsolutePathHead | FilterPathExprHead | StepLikeNode;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A path node list is a semi-flattened representation of...
|
|
38
|
+
*
|
|
39
|
+
* - Any XPath LocationPath expression:
|
|
40
|
+
* - AbsoluteLocationPath
|
|
41
|
+
* - RelativeLocationPath
|
|
42
|
+
*
|
|
43
|
+
* - Any expression beginning with a FilterExpr which is known to produce a
|
|
44
|
+
* node-set result
|
|
45
|
+
*
|
|
46
|
+
* The flattening of these syntax representations is used to perform various
|
|
47
|
+
* aspects of path resolution logic, accounting for complexities of XPath syntax
|
|
48
|
+
* and semantics in a roughly linear/list processing manner.
|
|
49
|
+
*/
|
|
50
|
+
// prettier-ignore
|
|
51
|
+
export type PathNodeList<
|
|
52
|
+
Head extends PathNodeListHead = PathNodeListHead
|
|
53
|
+
> = readonly [
|
|
54
|
+
head: Head,
|
|
55
|
+
...tail: StepLikeNode[]
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Produces a semi-flattened representation of an AbsoluteLocationPath.
|
|
60
|
+
*
|
|
61
|
+
* @see {@link PathNodeList}
|
|
62
|
+
*/
|
|
63
|
+
const absolutePathNodeList = (
|
|
64
|
+
pathNode: AbsoluteLocationPathNode
|
|
65
|
+
): PathNodeList<AbsolutePathHead> => {
|
|
66
|
+
const [head, ...tail] = pathNode.children;
|
|
67
|
+
|
|
68
|
+
switch (head.type) {
|
|
69
|
+
case 'abbreviated_absolute_location_path': {
|
|
70
|
+
return head.children;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case 'absolute_root_location_path':
|
|
74
|
+
return [head, ...tail];
|
|
75
|
+
|
|
76
|
+
default:
|
|
77
|
+
throw new UnreachableError(head);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const pathNodeList = (pathNode: PathExpressionNode): PathNodeList => {
|
|
82
|
+
switch (pathNode.type) {
|
|
83
|
+
case 'absolute_location_path':
|
|
84
|
+
return absolutePathNodeList(pathNode);
|
|
85
|
+
|
|
86
|
+
case 'filter_path_expr':
|
|
87
|
+
return pathNode.children satisfies PathNodeList<FilterExprNode>;
|
|
88
|
+
|
|
89
|
+
case 'relative_location_path':
|
|
90
|
+
return pathNode.children satisfies PathNodeList<StepLikeNode>;
|
|
91
|
+
|
|
92
|
+
default:
|
|
93
|
+
throw new UnreachableError(pathNode);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const optionalPathNodeList = (pathNode: PathExpressionNode | null): PathNodeList | null => {
|
|
98
|
+
if (pathNode == null) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return pathNodeList(pathNode);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
type UnresolvedContextualizedPathListNode = FilterExprNode | StepLikeNode;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Like {@link PathNodeList}, provides a semi-flattened representation of path
|
|
109
|
+
* syntax. Distinct from that type, an unresolved list represents an
|
|
110
|
+
* intermediate concatenation of:
|
|
111
|
+
*
|
|
112
|
+
* - A **contextual** {@link PathNodeList} against which the to-be-resolved path
|
|
113
|
+
* will eventually be resolved.
|
|
114
|
+
*
|
|
115
|
+
* - The same structural representation of the to-be-resolved path.
|
|
116
|
+
*/
|
|
117
|
+
type UnresolvedPathNodeList = readonly [
|
|
118
|
+
head: PathNodeListHead,
|
|
119
|
+
...tail: UnresolvedContextualizedPathListNode[],
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
type ResolvableTraversalType = 'parent' | 'self';
|
|
123
|
+
|
|
124
|
+
const abbreviatedStepTextToResolvableStepType = {
|
|
125
|
+
'..': 'parent',
|
|
126
|
+
'.': 'self',
|
|
127
|
+
} as const satisfies Record<string, ResolvableTraversalType>;
|
|
128
|
+
|
|
129
|
+
const getResolvableTraversalType = (syntaxNode: StepNode): ResolvableTraversalType | null => {
|
|
130
|
+
const [stepTest, ...predicates] = syntaxNode.children;
|
|
131
|
+
|
|
132
|
+
// Ensure that we preserve self/parent traversal Steps with predicates
|
|
133
|
+
if (predicates.length > 0) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (stepTest.type === 'abbreviated_step') {
|
|
138
|
+
return abbreviatedStepTextToResolvableStepType[stepTest.text];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (stepTest.type !== 'axis_test' || predicates.length > 0) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const [axisNameNode, axisTest] = stepTest.children;
|
|
146
|
+
const axisName = axisNameNode.text;
|
|
147
|
+
|
|
148
|
+
if (axisName !== 'parent' && axisName !== 'self') {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const axisTestType = axisTest.type;
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
// `self::*`, `parent::*`
|
|
156
|
+
axisTestType === 'unprefixed_wildcard_name_test' ||
|
|
157
|
+
// `self::node()`, `parent::node()`
|
|
158
|
+
(axisTestType === 'node_type_test' && axisTest.text.startsWith('node'))
|
|
159
|
+
) {
|
|
160
|
+
return axisName;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resolves the component parts of an XPath LocationPath/path-like expression
|
|
168
|
+
* (as represented by an {@link UnresolvedPathNodeList}
|
|
169
|
+
*/
|
|
170
|
+
const resolvePathNodeList = (nodes: UnresolvedPathNodeList): PathNodeList => {
|
|
171
|
+
const [head, ...tail] = nodes;
|
|
172
|
+
const lastTailIndex = tail.length - 1;
|
|
173
|
+
|
|
174
|
+
// Local representation, mutable during resolution
|
|
175
|
+
type PathNodeResolution = [...PathNodeList];
|
|
176
|
+
|
|
177
|
+
return tail.reduce<PathNodeResolution>(
|
|
178
|
+
(acc, node, index) => {
|
|
179
|
+
// Because we've filtered non-`current()` cases before we reach this point,
|
|
180
|
+
// we can safely assume this is a `current()` call. For resolving **nodeset
|
|
181
|
+
// references** specifically, we treat this as equivalent to `.`.
|
|
182
|
+
//
|
|
183
|
+
// TODO: can we make this check/result more self-documenting, and reduce
|
|
184
|
+
// coupling with the `current()` check? Probably! We could move the check
|
|
185
|
+
// here, returning `acc` for `current()`, and returning `[node]` (i.e.
|
|
186
|
+
// establishing a new absolute-like context) otherwise. It seems like we
|
|
187
|
+
// could even do a similar check for the actual AbsoluteLocationPath case,
|
|
188
|
+
// and probably eliminate a bunch of the special cases in the main
|
|
189
|
+
// `resolvePathExpression` body.
|
|
190
|
+
if (node.type === 'filter_expr') {
|
|
191
|
+
return acc;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const [currentHead, ...currentTail] = acc;
|
|
195
|
+
|
|
196
|
+
// Special case `//` shorthand (1):
|
|
197
|
+
if (
|
|
198
|
+
// - Current accumulated path is a single head node.
|
|
199
|
+
currentTail.length === 0 &&
|
|
200
|
+
// - Head is `//` shorthand.
|
|
201
|
+
currentHead.type === '//' &&
|
|
202
|
+
// - Current node is last in source expression.
|
|
203
|
+
index === lastTailIndex
|
|
204
|
+
) {
|
|
205
|
+
// Even if current node could otherwise be resolved, we must append
|
|
206
|
+
// because `//` without a following Step-like node does not produce a
|
|
207
|
+
// valid XPath expression.
|
|
208
|
+
acc.push(node);
|
|
209
|
+
|
|
210
|
+
return acc;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Special case `//` shorthand (2):
|
|
214
|
+
//
|
|
215
|
+
// Current node is `//`, and will be guaranteed (by `tree-sitter-xpath`
|
|
216
|
+
// grammar) to be followed by a valid Step-like node. Append and continue.
|
|
217
|
+
if (node.type === '//') {
|
|
218
|
+
acc.push(node);
|
|
219
|
+
|
|
220
|
+
return acc;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Any further cases are Step syntax
|
|
224
|
+
node satisfies StepNode;
|
|
225
|
+
|
|
226
|
+
const traversalType = getResolvableTraversalType(node);
|
|
227
|
+
|
|
228
|
+
if (traversalType == null) {
|
|
229
|
+
acc.push(node);
|
|
230
|
+
|
|
231
|
+
return acc;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (traversalType === 'self') {
|
|
235
|
+
return acc;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// All further cases are parent traversal, which we will resolve where
|
|
239
|
+
// possible. Each variation is detailed below.
|
|
240
|
+
traversalType satisfies 'parent';
|
|
241
|
+
|
|
242
|
+
// For our resolution purposes, the following expressions are
|
|
243
|
+
// functionally equivalent:
|
|
244
|
+
//
|
|
245
|
+
// - `$head/$currentTail/$Step-like/..`
|
|
246
|
+
// - `$head/$currentTail`
|
|
247
|
+
//
|
|
248
|
+
// As such, when we encounter `..` which is currently preceded by
|
|
249
|
+
// any non-head, Step-like node, we can remove that trailing node
|
|
250
|
+
// rather than appending `..`.
|
|
251
|
+
//
|
|
252
|
+
// Note: `$head/..` (without any intervening Step-like nodes) is
|
|
253
|
+
// more complicated. It is handled below, explaining each case in
|
|
254
|
+
// greater detail.
|
|
255
|
+
if (currentTail.length > 0) {
|
|
256
|
+
return [currentHead, ...currentTail.slice(0, -1)];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Resolving `$head/..`, with no intervening Step-like nodes...
|
|
260
|
+
switch (currentHead.type) {
|
|
261
|
+
// Head of expression is `/`. `/..` cannot resolve to any node-set,
|
|
262
|
+
// but it **is a valid LocationPath expression**. We concatenate
|
|
263
|
+
// here to produce that expression, as a concession that this is
|
|
264
|
+
// the only valid outcome.
|
|
265
|
+
case 'absolute_root_location_path':
|
|
266
|
+
return [currentHead, node];
|
|
267
|
+
|
|
268
|
+
// Head of expression is a FilterExpr. Attempting to resolve parent step
|
|
269
|
+
// would lose that original context. Concatenating instead is a
|
|
270
|
+
// concession to this fact: we cannot fully resolve the nodeset/path
|
|
271
|
+
// path in this context.
|
|
272
|
+
//
|
|
273
|
+
// If the head FilterExpr is a `current()` call, it is possible that the
|
|
274
|
+
// path may be further resolved against another context (likely one from
|
|
275
|
+
// an outer element in the form definition, or any other case which
|
|
276
|
+
// would establish further context and be reached by recursing up the
|
|
277
|
+
// definition hierarchy). For now, we must accept that the resulting
|
|
278
|
+
// LocationPath will not be fully resolved.
|
|
279
|
+
//
|
|
280
|
+
// If the FilterExpr is an `instance("id")` call, this will effectively
|
|
281
|
+
// terminate resolution of the step:
|
|
282
|
+
// `instance("id")/../$remainingTail` **is a valid expression**;
|
|
283
|
+
// however, downstream evaluation of that expression is expected to
|
|
284
|
+
// produce an empty node-set (as it traverses into a part of the
|
|
285
|
+
// document outside of the subtree[s] handled by the `@getodk/xpath`
|
|
286
|
+
// evaluator).
|
|
287
|
+
case 'filter_expr':
|
|
288
|
+
return [currentHead, node];
|
|
289
|
+
|
|
290
|
+
// Head of expression is relative. Resolution is as follows:
|
|
291
|
+
case 'step': {
|
|
292
|
+
// - If head is a self-reference, we replace that head with this step.
|
|
293
|
+
// This is safe (and correct) because `./../$remainingTail` is
|
|
294
|
+
// functionally equivalent to `../$remainingTail`.
|
|
295
|
+
if (getResolvableTraversalType(currentHead) === 'self') {
|
|
296
|
+
return [node];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// - If head is any other Step-like node we concatenate (e.g.
|
|
300
|
+
// where head is `foo`, we will produce `foo/..`), with the same
|
|
301
|
+
// reasoning as the above FilterExpr case. And as with that
|
|
302
|
+
// case, it is possible some downstream processing may allow
|
|
303
|
+
// further resolution.
|
|
304
|
+
return [currentHead, node];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Head of current path is `//`. Above, we already handled the special
|
|
308
|
+
// case where this parent traversal would be the end of the path
|
|
309
|
+
// expression, so here we can safely collapse `//../$remainingTail`
|
|
310
|
+
// to `//$remainingTail`
|
|
311
|
+
case '//': {
|
|
312
|
+
return acc;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
default:
|
|
316
|
+
throw new UnreachableError(currentHead);
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
[head]
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export const resolvePath = (
|
|
324
|
+
contextNode: PathExpressionNode | null,
|
|
325
|
+
pathNode: PathExpressionNode
|
|
326
|
+
): PathNodeList => {
|
|
327
|
+
const contextNodes = optionalPathNodeList(contextNode) ?? [];
|
|
328
|
+
|
|
329
|
+
// Path expression is **resolved without context** (or as its own context) in
|
|
330
|
+
// any of the following conditions...
|
|
331
|
+
//
|
|
332
|
+
// - No context is available
|
|
333
|
+
// - Expression path is absolute
|
|
334
|
+
// - Expression begins with any FilterExpr besides a `current()` call
|
|
335
|
+
//
|
|
336
|
+
// We still resolve the Step-like syntax parts **within the path**, e.g.
|
|
337
|
+
// allowing us to collapse `Step/..` pairs.
|
|
338
|
+
if (
|
|
339
|
+
// - We have no context, so there's nothing to resolve the expression path
|
|
340
|
+
// against, regardless of any other factor
|
|
341
|
+
contextNode == null ||
|
|
342
|
+
// - Expression path is absolute, so it is its own context
|
|
343
|
+
pathNode.type === 'absolute_location_path' ||
|
|
344
|
+
// - Expression path has leading FilterExpr, which is **not** a call to
|
|
345
|
+
// `current()`, in which case it is treated as if it were absolute.
|
|
346
|
+
(pathNode.type === 'filter_path_expr' && !isCurrentPath(pathNode))
|
|
347
|
+
) {
|
|
348
|
+
const pathNodes = pathNodeList(pathNode);
|
|
349
|
+
|
|
350
|
+
return resolvePathNodeList(pathNodes);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const contextualizedNodes: UnresolvedPathNodeList = [...contextNodes, ...pathNode.children];
|
|
354
|
+
|
|
355
|
+
return resolvePathNodeList(contextualizedNodes);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Resolves the parsed path {@link predicatePathNode}, in the context of:
|
|
360
|
+
*
|
|
361
|
+
* - The {@link contextNode} context, representing the original expression's
|
|
362
|
+
* context (if one was available)
|
|
363
|
+
*
|
|
364
|
+
* - The {@link stepContextNodes} context, representing the cumulative portion
|
|
365
|
+
* of the source path where {@link predicatePathNode} was parsed from a
|
|
366
|
+
* Predicate sub-expression
|
|
367
|
+
*
|
|
368
|
+
* Both contexts are necessary for resolution to ensure that:
|
|
369
|
+
*
|
|
370
|
+
* - A `current()` call within the predicate's sub-expression is contextualized
|
|
371
|
+
* to the current `nodeset` reference associated with the original expression
|
|
372
|
+
*
|
|
373
|
+
* - A `.` self-reference within the predicate's sub-expression is
|
|
374
|
+
* contextualized to the Step in which it occurred
|
|
375
|
+
*/
|
|
376
|
+
export const resolvePredicateReference = (
|
|
377
|
+
contextNode: PathExpressionNode | null,
|
|
378
|
+
stepContextNodes: PathNodeList,
|
|
379
|
+
predicatePathNode: PathExpressionNode
|
|
380
|
+
): PathNodeList => {
|
|
381
|
+
const predicatePathNodes = pathNodeList(predicatePathNode);
|
|
382
|
+
|
|
383
|
+
const [head, ...tail] = predicatePathNodes;
|
|
384
|
+
|
|
385
|
+
if (head.type === 'absolute_root_location_path' || head.type === '//') {
|
|
386
|
+
return predicatePathNodes;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let contextNodes: PathNodeList;
|
|
390
|
+
|
|
391
|
+
if (
|
|
392
|
+
contextNode != null &&
|
|
393
|
+
predicatePathNode.type === 'filter_path_expr' &&
|
|
394
|
+
isCurrentPath(predicatePathNode)
|
|
395
|
+
) {
|
|
396
|
+
contextNodes = pathNodeList(contextNode);
|
|
397
|
+
} else {
|
|
398
|
+
contextNodes = stepContextNodes;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const contextualizedNodes: UnresolvedPathNodeList = [...contextNodes, head, ...tail];
|
|
402
|
+
|
|
403
|
+
return resolvePathNodeList(contextualizedNodes);
|
|
404
|
+
};
|
|
405
|
+
interface PathSerializationOptions {
|
|
406
|
+
/**
|
|
407
|
+
* @default false
|
|
408
|
+
*/
|
|
409
|
+
readonly stripPredicates: boolean;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
type AnyPathNode = PathNodeList[number];
|
|
413
|
+
|
|
414
|
+
const serializePathNode = (node: AnyPathNode, options: PathSerializationOptions): string => {
|
|
415
|
+
const { type, text } = node;
|
|
416
|
+
if (type === 'step') {
|
|
417
|
+
switch (getResolvableTraversalType(node)) {
|
|
418
|
+
case 'self':
|
|
419
|
+
return '.';
|
|
420
|
+
|
|
421
|
+
case 'parent':
|
|
422
|
+
return '..';
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (options.stripPredicates) {
|
|
427
|
+
switch (type) {
|
|
428
|
+
case 'absolute_root_location_path':
|
|
429
|
+
case '//':
|
|
430
|
+
return text;
|
|
431
|
+
|
|
432
|
+
case 'filter_expr':
|
|
433
|
+
case 'step': {
|
|
434
|
+
const [head, ..._predicates] = node.children;
|
|
435
|
+
|
|
436
|
+
_predicates satisfies readonly PredicateNode[];
|
|
437
|
+
|
|
438
|
+
return head.text;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return text;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Serializes a resolved {@link PathNodeList} to its XPath expression
|
|
448
|
+
* representation, optionally stripping predicates.
|
|
449
|
+
*/
|
|
450
|
+
export const serializeNodesetReference = (
|
|
451
|
+
nodes: PathNodeList,
|
|
452
|
+
options: PathSerializationOptions
|
|
453
|
+
): string => {
|
|
454
|
+
const [head, ...tail] = nodes;
|
|
455
|
+
const strings: string[] = [serializePathNode(head, options)];
|
|
456
|
+
|
|
457
|
+
let previousNode = head;
|
|
458
|
+
|
|
459
|
+
for (const node of tail) {
|
|
460
|
+
const previousNodeType = previousNode.type;
|
|
461
|
+
|
|
462
|
+
if (
|
|
463
|
+
previousNodeType !== 'absolute_root_location_path' &&
|
|
464
|
+
previousNodeType !== '//' &&
|
|
465
|
+
node.type !== '//'
|
|
466
|
+
) {
|
|
467
|
+
strings.push('/');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
strings.push(serializePathNode(node, options));
|
|
471
|
+
previousNode = node;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return strings.join('');
|
|
475
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { PathNodeList } from './path-resolution.ts';
|
|
2
|
+
import type { PathExpressionNode } from './semantic-analysis.ts';
|
|
3
|
+
import { findLocationPathSubExpressionNodes } from './semantic-analysis.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a pair of:
|
|
7
|
+
*
|
|
8
|
+
* - Path expression syntax referenced within a particular predicate, on any
|
|
9
|
+
* Step within a source LocationPath and/or FilterExpr path. This
|
|
10
|
+
* sub-expression is resolved as a member of the dependencies which may be
|
|
11
|
+
* referenced by any arbitrary form expression.
|
|
12
|
+
*
|
|
13
|
+
* - The cumulative set of path nodes, from the start of the source path
|
|
14
|
+
* expression, to the Step and Predicate where the sub-expression reference
|
|
15
|
+
* was identified. This representation is used as **part** of the context used
|
|
16
|
+
* to resolve the identified Predicate sub-expression for downstream
|
|
17
|
+
* dependency subscriptions. (Each predicate sub-expression is **further
|
|
18
|
+
* contextualized** by the original context in which the source path is
|
|
19
|
+
* defined.)
|
|
20
|
+
*/
|
|
21
|
+
export interface PredicateReference {
|
|
22
|
+
readonly predicatePathNode: PathExpressionNode;
|
|
23
|
+
readonly stepContextNodes: PathNodeList;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Identifies path sub-expressions within any of a path's Predicates, along with
|
|
28
|
+
* the step context in which they are found.
|
|
29
|
+
*
|
|
30
|
+
* @see {@link PredicateReference} for details on the produced structures.
|
|
31
|
+
*/
|
|
32
|
+
export const findPredicateReferences = (pathNodes: PathNodeList): readonly PredicateReference[] => {
|
|
33
|
+
const [head, ...tail] = pathNodes;
|
|
34
|
+
|
|
35
|
+
return pathNodes.flatMap((targetNode, i) => {
|
|
36
|
+
const cumulativePath: PathNodeList = [head, ...tail.slice(0, i)];
|
|
37
|
+
|
|
38
|
+
switch (targetNode.type) {
|
|
39
|
+
case 'absolute_root_location_path':
|
|
40
|
+
case '//':
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const [, ...predicates] = targetNode.children;
|
|
45
|
+
|
|
46
|
+
if (predicates.length === 0) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return predicates.flatMap((predicate) => {
|
|
51
|
+
const predicateSubExpressions = findLocationPathSubExpressionNodes(predicate);
|
|
52
|
+
|
|
53
|
+
return predicateSubExpressions.map((predicatePathNode) => {
|
|
54
|
+
return {
|
|
55
|
+
predicatePathNode,
|
|
56
|
+
stepContextNodes: cumulativePath,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
};
|