@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
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getodk/xforms-engine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"description": "XForms engine for ODK Web Forms",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "getodk",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/getodk/web-forms",
|
|
11
|
+
"directory": "packages/xforms-engine"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/getodk/web-forms/issues",
|
|
14
|
+
"homepage": "https://getodk.org/",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"solid": "./dist/solid.js",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"browser": "./dist/index.js",
|
|
22
|
+
"development": "./src/index.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": "^18.19.1 || ^20.11.1",
|
|
33
|
+
"yarn": "1.22.19"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "npm-run-all -nl build:*",
|
|
37
|
+
"build:clean": "rimraf dist/",
|
|
38
|
+
"build:js": "vite build",
|
|
39
|
+
"build:solid": "export VITE_BUILD_TARGET=solid && vite build",
|
|
40
|
+
"dev": "vite",
|
|
41
|
+
"docs": "npm-run-all -nl docs:*",
|
|
42
|
+
"docs:clean": "rimraf api-docs/",
|
|
43
|
+
"docs:api": "typedoc --readme none --out api-docs --entryPoints \"src/**/*\"",
|
|
44
|
+
"docs:serve": "http-server api-docs -o modules/client",
|
|
45
|
+
"test": "npm-run-all --print-name --print-label test-node:* test-browser:*",
|
|
46
|
+
"test-node:jsdom": "vitest run",
|
|
47
|
+
"test-browser:chromium": "BROWSER_NAME=chromium vitest run",
|
|
48
|
+
"test-browser:firefox": "BROWSER_NAME=firefox vitest run",
|
|
49
|
+
"test-browser:webkit": "BROWSER_NAME=webkit vitest run",
|
|
50
|
+
"test-watch:jsdom": "vitest",
|
|
51
|
+
"test-watch:chromium": "BROWSER_NAME=chromium vitest",
|
|
52
|
+
"test-watch:firefox": "BROWSER_NAME=firefox vitest",
|
|
53
|
+
"test-watch:webkit": "BROWSER_NAME=webkit vitest",
|
|
54
|
+
"test:types": "tsc --project ./tsconfig.json --emitDeclarationOnly false --noEmit"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"solid-js": "^1.8.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@babel/core": "^7.23.2",
|
|
61
|
+
"@getodk/tree-sitter-xpath": "0.1.0",
|
|
62
|
+
"@getodk/xpath": "0.1.0",
|
|
63
|
+
"@playwright/test": "^1.42.1",
|
|
64
|
+
"@suid/vite-plugin": "^0.1.5",
|
|
65
|
+
"@vitest/browser": "^1.3.1",
|
|
66
|
+
"babel-plugin-transform-jsbi-to-bigint": "^1.4.0",
|
|
67
|
+
"http-server": "^14.1.1",
|
|
68
|
+
"jsdom": "^24.0.0",
|
|
69
|
+
"typedoc": "^0.25.12",
|
|
70
|
+
"unplugin-fonts": "^1.1.1",
|
|
71
|
+
"vite": "^5.1.5",
|
|
72
|
+
"vite-plugin-babel": "^1.2.0",
|
|
73
|
+
"vite-plugin-dts": "^3.7.3",
|
|
74
|
+
"vite-plugin-no-bundle": "^3.0.0",
|
|
75
|
+
"vite-plugin-solid": "^2.10.1",
|
|
76
|
+
"vitest": "^1.3.1",
|
|
77
|
+
"vitest-github-actions-reporter": "^0.11.1"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"solid-js": "^1.8.3"
|
|
81
|
+
},
|
|
82
|
+
"peerDependenciesMeta": {
|
|
83
|
+
"solid-js": {
|
|
84
|
+
"optional": true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/XFormDOM.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { XFORMS_NAMESPACE_URI } from '@getodk/common/constants/xmlns.ts';
|
|
2
|
+
import { XFormsXPathEvaluator } from '@getodk/xpath';
|
|
3
|
+
|
|
4
|
+
const domParser = new DOMParser();
|
|
5
|
+
|
|
6
|
+
const openingTag = (element: Element) => element.outerHTML.replace(/>(.|\n)*/, '>');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Per ODK XForms spec:
|
|
10
|
+
*
|
|
11
|
+
* > To link a body element with its corresponding data node and binding, both
|
|
12
|
+
* > `nodeset` and `ref` attributes can be used. The convention that is helpful is
|
|
13
|
+
* > the one used in XLSForms: use `nodeset="/some/path"` for `<repeat>` and
|
|
14
|
+
* > `<itemset>` elements and use `ref="/some/path"` for everything else.
|
|
15
|
+
*/
|
|
16
|
+
const normalizeBodyRefNodesetAttributes = (body: Element): void => {
|
|
17
|
+
const referenceElements = body.querySelectorAll(
|
|
18
|
+
'itemset[ref], repeat[ref], *[nodeset]:not(itemset, repeat)'
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
for (const element of referenceElements) {
|
|
22
|
+
switch (element.localName) {
|
|
23
|
+
case 'itemset':
|
|
24
|
+
case 'repeat': {
|
|
25
|
+
// Non-null assertion safe by selector
|
|
26
|
+
const ref = element.getAttribute('ref')!;
|
|
27
|
+
|
|
28
|
+
element.setAttribute('nodeset', ref);
|
|
29
|
+
element.removeAttribute('ref');
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
default: {
|
|
34
|
+
// Non-null assertion safe by selector
|
|
35
|
+
const nodeset = element.getAttribute('nodeset')!;
|
|
36
|
+
|
|
37
|
+
element.setAttribute('ref', nodeset);
|
|
38
|
+
element.removeAttribute('nodeset');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const normalizeRepeatGroups = (xformDocument: XMLDocument, body: Element): void => {
|
|
45
|
+
const repeats = body.querySelectorAll('repeat');
|
|
46
|
+
|
|
47
|
+
for (const repeat of repeats) {
|
|
48
|
+
// Non-null assertion safe because `querySelectorAll` returns descendants
|
|
49
|
+
const parent = repeat.parentElement!;
|
|
50
|
+
const repeatNodeset = repeat.getAttribute('nodeset');
|
|
51
|
+
|
|
52
|
+
if (repeatNodeset == null) {
|
|
53
|
+
throw new Error('Found <repeat> without `nodeset` attribute');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let group: Element | null = null;
|
|
57
|
+
|
|
58
|
+
if (parent.localName === 'group') {
|
|
59
|
+
const groupRef = parent.getAttribute('ref');
|
|
60
|
+
|
|
61
|
+
if (groupRef === repeatNodeset) {
|
|
62
|
+
group = parent;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (group == null) {
|
|
67
|
+
group = xformDocument.createElementNS(XFORMS_NAMESPACE_URI, 'group');
|
|
68
|
+
group.setAttribute('ref', repeatNodeset);
|
|
69
|
+
repeat.before(group);
|
|
70
|
+
group.append(repeat);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
interface NormalizedXForm {
|
|
76
|
+
readonly xformDocument: XMLDocument;
|
|
77
|
+
readonly rootEvaluator: XFormsXPathEvaluator;
|
|
78
|
+
readonly html: Element;
|
|
79
|
+
readonly body: Element;
|
|
80
|
+
readonly normalizedXML: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface XFormDOMNormalizationOptions {
|
|
84
|
+
readonly isNormalized: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Performs preprocess operations to normalize certain aspects of an XForm
|
|
89
|
+
* structure for consistency when building up its runtime representations.
|
|
90
|
+
* Currently this preprocessing:
|
|
91
|
+
*
|
|
92
|
+
* - Ensures consistent use of `ref` and `nodeset` where ambiguous in the
|
|
93
|
+
* ODK XForms spec
|
|
94
|
+
* - Ensures `<repeat>` body elements are always enclosed by a `<group>`
|
|
95
|
+
* with the same `ref`
|
|
96
|
+
*/
|
|
97
|
+
const parseNormalizedXForm = (
|
|
98
|
+
sourceXML: string,
|
|
99
|
+
options: XFormDOMNormalizationOptions
|
|
100
|
+
): NormalizedXForm => {
|
|
101
|
+
const xformDocument: XMLDocument = domParser.parseFromString(sourceXML, 'text/xml');
|
|
102
|
+
const rootEvaluator = new XFormsXPathEvaluator({
|
|
103
|
+
rootNode: xformDocument,
|
|
104
|
+
});
|
|
105
|
+
const html = rootEvaluator.evaluateNonNullElement('/h:html');
|
|
106
|
+
const body = rootEvaluator.evaluateNonNullElement('./h:body', {
|
|
107
|
+
contextNode: html,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let normalizedXML: string;
|
|
111
|
+
|
|
112
|
+
if (options.isNormalized) {
|
|
113
|
+
normalizedXML = sourceXML;
|
|
114
|
+
} else {
|
|
115
|
+
normalizeBodyRefNodesetAttributes(body);
|
|
116
|
+
normalizeRepeatGroups(xformDocument, body);
|
|
117
|
+
|
|
118
|
+
normalizedXML = html.outerHTML;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
xformDocument,
|
|
123
|
+
rootEvaluator,
|
|
124
|
+
html,
|
|
125
|
+
body,
|
|
126
|
+
normalizedXML,
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export class XFormDOM {
|
|
131
|
+
static from(sourceXML: string) {
|
|
132
|
+
return new this(sourceXML, { isNormalized: false });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected readonly normalizedXML: string;
|
|
136
|
+
|
|
137
|
+
// XPath
|
|
138
|
+
readonly rootEvaluator: XFormsXPathEvaluator;
|
|
139
|
+
readonly primaryInstanceEvaluator: XFormsXPathEvaluator;
|
|
140
|
+
|
|
141
|
+
// Commonly accessed landmark nodes
|
|
142
|
+
readonly xformDocument: XMLDocument;
|
|
143
|
+
|
|
144
|
+
readonly html: Element;
|
|
145
|
+
|
|
146
|
+
readonly head: Element;
|
|
147
|
+
readonly title: Element;
|
|
148
|
+
|
|
149
|
+
readonly model: Element;
|
|
150
|
+
readonly primaryInstance: Element;
|
|
151
|
+
readonly primaryInstanceRoot: Element;
|
|
152
|
+
|
|
153
|
+
readonly body: Element;
|
|
154
|
+
|
|
155
|
+
protected constructor(
|
|
156
|
+
protected readonly sourceXML: string,
|
|
157
|
+
options: XFormDOMNormalizationOptions
|
|
158
|
+
) {
|
|
159
|
+
const normalizedXForm: NormalizedXForm = parseNormalizedXForm(sourceXML, options);
|
|
160
|
+
const { xformDocument, html, body, rootEvaluator, normalizedXML } = normalizedXForm;
|
|
161
|
+
const head = rootEvaluator.evaluateNonNullElement('./h:head', {
|
|
162
|
+
contextNode: html,
|
|
163
|
+
});
|
|
164
|
+
const title = rootEvaluator.evaluateNonNullElement('./h:title', {
|
|
165
|
+
contextNode: head,
|
|
166
|
+
});
|
|
167
|
+
const model = rootEvaluator.evaluateNonNullElement('./xf:model', {
|
|
168
|
+
contextNode: head,
|
|
169
|
+
});
|
|
170
|
+
// TODO: Evidently primary instance root will not always have an id
|
|
171
|
+
const primaryInstanceRoot = rootEvaluator.evaluateNonNullElement('./xf:instance/*[@id]', {
|
|
172
|
+
contextNode: model,
|
|
173
|
+
});
|
|
174
|
+
// TODO: invert primary instance/root lookups
|
|
175
|
+
const primaryInstance = rootEvaluator.evaluateNonNullElement('..', {
|
|
176
|
+
contextNode: primaryInstanceRoot,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
this.normalizedXML = normalizedXML;
|
|
180
|
+
this.rootEvaluator = rootEvaluator;
|
|
181
|
+
this.primaryInstanceEvaluator = new XFormsXPathEvaluator({
|
|
182
|
+
rootNode: primaryInstance,
|
|
183
|
+
});
|
|
184
|
+
this.xformDocument = xformDocument;
|
|
185
|
+
this.html = html;
|
|
186
|
+
this.head = head;
|
|
187
|
+
this.title = title;
|
|
188
|
+
this.model = model;
|
|
189
|
+
this.primaryInstance = primaryInstance;
|
|
190
|
+
this.primaryInstanceRoot = primaryInstanceRoot;
|
|
191
|
+
this.body = body;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// TODO: anticipating this will be an entry point for edits as well
|
|
195
|
+
createInstance(): XFormDOM {
|
|
196
|
+
return new XFormDOM(this.normalizedXML, { isNormalized: true });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
toJSON() {
|
|
200
|
+
const {
|
|
201
|
+
rootEvaluator,
|
|
202
|
+
primaryInstanceEvaluator,
|
|
203
|
+
html,
|
|
204
|
+
head,
|
|
205
|
+
title,
|
|
206
|
+
model,
|
|
207
|
+
primaryInstance,
|
|
208
|
+
primaryInstanceRoot,
|
|
209
|
+
xformDocument,
|
|
210
|
+
...rest
|
|
211
|
+
} = this;
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
...rest,
|
|
215
|
+
xformDocument: '#document',
|
|
216
|
+
html: openingTag(html),
|
|
217
|
+
head: openingTag(head),
|
|
218
|
+
title: openingTag(title),
|
|
219
|
+
model: openingTag(model),
|
|
220
|
+
primaryInstance: openingTag(primaryInstance),
|
|
221
|
+
primaryInstanceRoot: openingTag(primaryInstanceRoot),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CollectionValues } from '@getodk/common/types/collections/CollectionValues.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Like JavaRosa. Presumably for explicit types which aren't impelemnted?
|
|
5
|
+
*/
|
|
6
|
+
const UNSUPPORTED_DATA_TYPE = 'UNSUPPORTED';
|
|
7
|
+
|
|
8
|
+
export type UnsupportedDataType = typeof UNSUPPORTED_DATA_TYPE;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Like JavaRosa. Presumably for e.g. groups with explicit binds (`relevant` etc)?
|
|
12
|
+
*/
|
|
13
|
+
const NULL_DATA_TYPE = 'NULL';
|
|
14
|
+
|
|
15
|
+
export type NullDataType = typeof NULL_DATA_TYPE;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* As in ODK XForms Spec.
|
|
19
|
+
*
|
|
20
|
+
* TODO: it's unclear why JavaRosa's `DataType` hews closely to the spec, but
|
|
21
|
+
* has certain differences (e.g. string -> TEXT, int -> INTEGER). It's also
|
|
22
|
+
* not immediately clear how additive types like CHOICE and MULTIPLE_ITEMS
|
|
23
|
+
* square with the underlying spec types.
|
|
24
|
+
*/
|
|
25
|
+
export const XFORM_SPEC_DATA_TYPES = [
|
|
26
|
+
'string',
|
|
27
|
+
'int',
|
|
28
|
+
'boolean',
|
|
29
|
+
'decimal',
|
|
30
|
+
'date',
|
|
31
|
+
'time',
|
|
32
|
+
'dateTime',
|
|
33
|
+
'geopoint',
|
|
34
|
+
'geotrace',
|
|
35
|
+
'geoshape',
|
|
36
|
+
'binary',
|
|
37
|
+
'barcode',
|
|
38
|
+
'intent',
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
const isSupportedDataType = (bindType: string): bindType is XFormSpecDataType =>
|
|
42
|
+
XFORM_SPEC_DATA_TYPES.includes(bindType as XFormSpecDataType);
|
|
43
|
+
|
|
44
|
+
export type XFormSpecDataType = CollectionValues<typeof XFORM_SPEC_DATA_TYPES>;
|
|
45
|
+
|
|
46
|
+
export type XFormDataType = NullDataType | UnsupportedDataType | XFormSpecDataType;
|
|
47
|
+
|
|
48
|
+
const DEFAULT_XFORM_DATA_TYPE = 'string';
|
|
49
|
+
|
|
50
|
+
export type DefaultXFormDataType = typeof DEFAULT_XFORM_DATA_TYPE;
|
|
51
|
+
|
|
52
|
+
// TODO: groups -> NULL?
|
|
53
|
+
// TODO: XSD namespace
|
|
54
|
+
export const bindDataType = (bindType: string | null): XFormDataType => {
|
|
55
|
+
if (bindType == null) {
|
|
56
|
+
return DEFAULT_XFORM_DATA_TYPE;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isSupportedDataType(bindType)) {
|
|
60
|
+
return bindType;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return UNSUPPORTED_DATA_TYPE;
|
|
64
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { XFormDOM } from './XFormDOM.ts';
|
|
2
|
+
import { BodyDefinition } from './body/BodyDefinition.ts';
|
|
3
|
+
import { ModelDefinition } from './model/ModelDefinition.ts';
|
|
4
|
+
|
|
5
|
+
export class XFormDefinition {
|
|
6
|
+
readonly xformDOM: XFormDOM;
|
|
7
|
+
readonly xformDocument: XMLDocument;
|
|
8
|
+
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly title: string;
|
|
11
|
+
|
|
12
|
+
readonly rootReference: string;
|
|
13
|
+
|
|
14
|
+
readonly body: BodyDefinition;
|
|
15
|
+
readonly model: ModelDefinition;
|
|
16
|
+
|
|
17
|
+
constructor(readonly sourceXML: string) {
|
|
18
|
+
const xformDOM = XFormDOM.from(sourceXML);
|
|
19
|
+
|
|
20
|
+
this.xformDOM = xformDOM;
|
|
21
|
+
|
|
22
|
+
const { primaryInstanceRoot, title, xformDocument } = xformDOM;
|
|
23
|
+
const id = primaryInstanceRoot.getAttribute('id');
|
|
24
|
+
|
|
25
|
+
if (id == null) {
|
|
26
|
+
throw new Error('Primary instance root has no id');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.xformDocument = xformDocument;
|
|
30
|
+
this.id = id;
|
|
31
|
+
this.title = title.textContent ?? '';
|
|
32
|
+
|
|
33
|
+
// TODO: highly unlikely primary instance root will need a namespace prefix
|
|
34
|
+
// but noting it just in case there is such weird usage...
|
|
35
|
+
this.rootReference = `/${primaryInstanceRoot.localName}`;
|
|
36
|
+
|
|
37
|
+
this.body = new BodyDefinition(this);
|
|
38
|
+
this.model = new ModelDefinition(this);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
2
|
+
import { DependencyContext } from '../expression/DependencyContext.ts';
|
|
3
|
+
import { UnsupportedBodyElementDefinition } from './UnsupportedBodyElementDefinition.ts';
|
|
4
|
+
import { ControlDefinition } from './control/ControlDefinition.ts';
|
|
5
|
+
import { InputDefinition } from './control/InputDefinition.ts';
|
|
6
|
+
import type { AnySelectDefinition } from './control/select/SelectDefinition.ts';
|
|
7
|
+
import { SelectDefinition } from './control/select/SelectDefinition.ts';
|
|
8
|
+
import { LogicalGroupDefinition } from './group/LogicalGroupDefinition.ts';
|
|
9
|
+
import { PresentationGroupDefinition } from './group/PresentationGroupDefinition.ts';
|
|
10
|
+
import { RepeatGroupDefinition } from './group/RepeatGroupDefinition.ts';
|
|
11
|
+
import { StructuralGroupDefinition } from './group/StructuralGroupDefinition.ts';
|
|
12
|
+
|
|
13
|
+
export interface BodyElementParentContext {
|
|
14
|
+
readonly reference: string | null;
|
|
15
|
+
readonly element: Element;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type SupportedBodyElementDefinition =
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/sort-type-constituents
|
|
20
|
+
| RepeatGroupDefinition
|
|
21
|
+
| LogicalGroupDefinition
|
|
22
|
+
| PresentationGroupDefinition
|
|
23
|
+
| StructuralGroupDefinition
|
|
24
|
+
| InputDefinition
|
|
25
|
+
| AnySelectDefinition;
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
type BodyElementDefinitionConstructor = new (...args: any[]) => SupportedBodyElementDefinition;
|
|
29
|
+
|
|
30
|
+
const BodyElementDefinitionConstructors = [
|
|
31
|
+
RepeatGroupDefinition,
|
|
32
|
+
LogicalGroupDefinition,
|
|
33
|
+
PresentationGroupDefinition,
|
|
34
|
+
StructuralGroupDefinition,
|
|
35
|
+
InputDefinition,
|
|
36
|
+
SelectDefinition,
|
|
37
|
+
] as const satisfies readonly BodyElementDefinitionConstructor[];
|
|
38
|
+
|
|
39
|
+
export type AnyBodyElementDefinition =
|
|
40
|
+
| SupportedBodyElementDefinition
|
|
41
|
+
| UnsupportedBodyElementDefinition;
|
|
42
|
+
|
|
43
|
+
export type BodyElementDefinitionArray = readonly AnyBodyElementDefinition[];
|
|
44
|
+
|
|
45
|
+
export type AnyBodyElementType = AnyBodyElementDefinition['type'];
|
|
46
|
+
|
|
47
|
+
export type AnyGroupElementDefinition = Extract<
|
|
48
|
+
AnyBodyElementDefinition,
|
|
49
|
+
{ readonly type: `${string}-group` }
|
|
50
|
+
>;
|
|
51
|
+
|
|
52
|
+
export type NonRepeatGroupElementDefinition = Exclude<
|
|
53
|
+
AnyGroupElementDefinition,
|
|
54
|
+
{ readonly type: 'repeat-group' }
|
|
55
|
+
>;
|
|
56
|
+
|
|
57
|
+
const isGroupElementDefinition = (
|
|
58
|
+
element: AnyBodyElementDefinition
|
|
59
|
+
): element is AnyGroupElementDefinition => {
|
|
60
|
+
return element.type.endsWith('-group');
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const groupElementDefinition = (
|
|
64
|
+
element: AnyBodyElementDefinition
|
|
65
|
+
): AnyGroupElementDefinition | null => {
|
|
66
|
+
return isGroupElementDefinition(element) ? element : null;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type AnyControlElementDefinition = Extract<
|
|
70
|
+
AnyBodyElementDefinition,
|
|
71
|
+
{ readonly category: 'control' }
|
|
72
|
+
>;
|
|
73
|
+
|
|
74
|
+
const isControlElementDefinition = (
|
|
75
|
+
element: AnyBodyElementDefinition
|
|
76
|
+
): element is AnyControlElementDefinition => {
|
|
77
|
+
return element.category === 'control';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const controlElementDefinition = (
|
|
81
|
+
element: AnyBodyElementDefinition
|
|
82
|
+
): AnyControlElementDefinition | null => {
|
|
83
|
+
return isControlElementDefinition(element) ? element : null;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type BodyElementReference = string;
|
|
87
|
+
|
|
88
|
+
class BodyElementMap extends Map<BodyElementReference, AnyBodyElementDefinition> {
|
|
89
|
+
constructor(elements: BodyElementDefinitionArray) {
|
|
90
|
+
super();
|
|
91
|
+
|
|
92
|
+
this.mapElementsByReference(elements);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected mapElementsByReference(elements: BodyElementDefinitionArray) {
|
|
96
|
+
for (const element of elements) {
|
|
97
|
+
const { reference } = element;
|
|
98
|
+
|
|
99
|
+
if (element instanceof RepeatGroupDefinition) {
|
|
100
|
+
if (reference == null) {
|
|
101
|
+
throw new Error('Missing reference for repeat/repeat group');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.set(reference, element);
|
|
105
|
+
this.mapElementsByReference(element.repeatChildren);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
element instanceof LogicalGroupDefinition ||
|
|
110
|
+
element instanceof PresentationGroupDefinition ||
|
|
111
|
+
element instanceof StructuralGroupDefinition
|
|
112
|
+
) {
|
|
113
|
+
if (reference != null) {
|
|
114
|
+
this.set(reference, element);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.mapElementsByReference(element.children);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (element instanceof ControlDefinition) {
|
|
121
|
+
this.set(element.reference, element);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override set(reference: BodyElementReference, element: AnyBodyElementDefinition) {
|
|
127
|
+
if (this.has(reference)) {
|
|
128
|
+
throw new Error(`Multiple body elements for reference: ${reference}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return super.set(reference, element);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
getBodyElementType(reference: BodyElementReference): AnyBodyElementType | null {
|
|
135
|
+
return this.get(reference)?.type ?? null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
toJSON() {
|
|
139
|
+
return Object.fromEntries(this.entries());
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export class BodyDefinition extends DependencyContext {
|
|
144
|
+
static getChildElementDefinitions(
|
|
145
|
+
form: XFormDefinition,
|
|
146
|
+
parent: BodyElementParentContext,
|
|
147
|
+
parentElement: Element,
|
|
148
|
+
children: readonly Element[] = Array.from(parentElement.children)
|
|
149
|
+
): readonly AnyBodyElementDefinition[] {
|
|
150
|
+
return Array.from(children).map((element) => {
|
|
151
|
+
const { localName } = element;
|
|
152
|
+
|
|
153
|
+
for (const Constructor of BodyElementDefinitionConstructors) {
|
|
154
|
+
if (Constructor.isCompatible(localName, element)) {
|
|
155
|
+
return new Constructor(form, parent, element);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return new UnsupportedBodyElementDefinition(form, parent, element);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
readonly element: Element;
|
|
164
|
+
readonly elements: readonly AnyBodyElementDefinition[];
|
|
165
|
+
|
|
166
|
+
protected readonly elementsByReference: BodyElementMap;
|
|
167
|
+
|
|
168
|
+
// DependencyContext
|
|
169
|
+
readonly parentReference = null;
|
|
170
|
+
readonly reference: string;
|
|
171
|
+
|
|
172
|
+
constructor(protected readonly form: XFormDefinition) {
|
|
173
|
+
super();
|
|
174
|
+
|
|
175
|
+
const { body: element } = form.xformDOM;
|
|
176
|
+
|
|
177
|
+
this.reference = form.rootReference;
|
|
178
|
+
this.element = element;
|
|
179
|
+
this.elements = BodyDefinition.getChildElementDefinitions(form, this, element);
|
|
180
|
+
this.elementsByReference = new BodyElementMap(this.elements);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getBodyElement(reference: string): AnyBodyElementDefinition | null {
|
|
184
|
+
return this.elementsByReference.get(reference) ?? null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getRepeatGroup(reference: string): RepeatGroupDefinition | null {
|
|
188
|
+
const element = this.getBodyElement(reference);
|
|
189
|
+
|
|
190
|
+
if (element?.type === 'repeat-group') {
|
|
191
|
+
return element;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
toJSON() {
|
|
198
|
+
const { form, ...rest } = this;
|
|
199
|
+
|
|
200
|
+
return rest;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
2
|
+
import { DependencyContext } from '../expression/DependencyContext.ts';
|
|
3
|
+
import type { BodyElementParentContext } from './BodyDefinition.ts';
|
|
4
|
+
import type { HintDefinition } from './text/HintDefinition.ts';
|
|
5
|
+
import type { LabelDefinition } from './text/LabelDefinition.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* These category names roughly correspond to each of the ODK XForms spec's
|
|
9
|
+
* {@link https://getodk.github.io/xforms-spec/#body-elements | Body Elements}
|
|
10
|
+
* tables.
|
|
11
|
+
*/
|
|
12
|
+
type BodyElementCategory = 'control' | 'structure' | 'support' | 'UNSUPPORTED';
|
|
13
|
+
|
|
14
|
+
export abstract class BodyElementDefinition<Type extends string> extends DependencyContext {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
|
+
static isCompatible(localName: string, element: Element): boolean {
|
|
19
|
+
throw new Error('Must be overridden by BodyElementDefinition subclass');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
abstract readonly category: BodyElementCategory;
|
|
23
|
+
abstract readonly type: Type;
|
|
24
|
+
readonly hint: HintDefinition | null = null;
|
|
25
|
+
readonly label: LabelDefinition | null = null;
|
|
26
|
+
|
|
27
|
+
readonly reference: string | null = null;
|
|
28
|
+
readonly parentReference: string | null;
|
|
29
|
+
|
|
30
|
+
protected constructor(
|
|
31
|
+
protected readonly form: XFormDefinition,
|
|
32
|
+
readonly parent: BodyElementParentContext,
|
|
33
|
+
readonly element: Element
|
|
34
|
+
) {
|
|
35
|
+
super();
|
|
36
|
+
this.parentReference = parent.reference;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
toJSON(): object {
|
|
40
|
+
const { form, parent, ...rest } = this;
|
|
41
|
+
|
|
42
|
+
return rest;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type BodyElementDefinitionClass = Pick<
|
|
47
|
+
typeof BodyElementDefinition,
|
|
48
|
+
keyof typeof BodyElementDefinition
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
// prettier-ignore
|
|
52
|
+
export type BodyElementDefinitionConstructor =
|
|
53
|
+
& BodyElementDefinitionClass
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
& (new (form: XFormDefinition, element: Element) => BodyElementDefinition<any>);
|
|
56
|
+
|
|
57
|
+
type BodyElementDefinitionInstance = InstanceType<BodyElementDefinitionConstructor>;
|
|
58
|
+
|
|
59
|
+
export type TypedBodyElementDefinition<Type extends string> = Extract<
|
|
60
|
+
BodyElementDefinitionInstance,
|
|
61
|
+
{ readonly type: Type }
|
|
62
|
+
>;
|