@jinntec/fore 1.0.0 → 1.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 +1 -1
- package/dist/fore-dev.js +8 -8
- package/dist/fore-dev.js.map +1 -1
- package/dist/fore.js +7 -7
- package/dist/fore.js.map +1 -1
- package/index.js +1 -0
- package/package.json +5 -3
- package/resources/fore.css +5 -0
- package/src/ForeElementMixin.js +1 -1
- package/src/actions/abstract-action.js +14 -1
- package/src/actions/fx-confirm.js +2 -2
- package/src/actions/fx-refresh.js +4 -0
- package/src/actions/fx-replace.js +8 -3
- package/src/actions/fx-setfocus.js +37 -0
- package/src/actions/fx-show.js +5 -1
- package/src/fore.js +2 -1
- package/src/functions/fx-function.js +6 -0
- package/src/fx-fore.js +587 -590
- package/src/fx-model.js +32 -4
- package/src/fx-submission.js +40 -8
- package/src/relevance.js +1 -2
- package/src/ui/abstract-control.js +10 -3
- package/src/ui/fx-case.js +1 -2
- package/src/ui/fx-control.js +36 -11
- package/src/ui/fx-inspector.js +9 -6
- package/src/ui/fx-output.js +2 -0
- package/src/ui/fx-switch.js +2 -2
- package/src/xpath-evaluation.js +609 -554
- package/src/xpath-util.js +1 -1
package/src/xpath-evaluation.js
CHANGED
|
@@ -1,140 +1,151 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
evaluateXPath as fxEvaluateXPath,
|
|
3
|
+
evaluateXPathToBoolean as fxEvaluateXPathToBoolean,
|
|
4
|
+
evaluateXPathToFirstNode as fxEvaluateXPathToFirstNode,
|
|
5
|
+
evaluateXPathToNodes as fxEvaluateXPathToNodes,
|
|
6
|
+
evaluateXPathToNumber as fxEvaluateXPathToNumber,
|
|
7
|
+
evaluateXPathToString as fxEvaluateXPathToString,
|
|
8
|
+
evaluateXPathToStrings as fxEvaluateXPathToStrings,
|
|
9
|
+
parseScript,
|
|
10
|
+
registerCustomXPathFunction,
|
|
11
|
+
registerXQueryModule,
|
|
12
12
|
} from 'fontoxpath';
|
|
13
|
-
import {
|
|
13
|
+
import {Fore} from './fore.js';
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import {XPathUtil} from './xpath-util.js';
|
|
16
16
|
|
|
17
17
|
const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
18
18
|
|
|
19
19
|
const createdNamespaceResolversByXPathQueryAndNode = new Map();
|
|
20
20
|
|
|
21
|
+
// A global registry of function names that are declared in Fore by a developer using the
|
|
22
|
+
// `fx-function` element. These should be available without providing a prefix as well
|
|
23
|
+
export const globallyDeclaredFunctionLocalNames = [];
|
|
24
|
+
|
|
21
25
|
function getCachedNamespaceResolver(xpath, node) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).get(node) || null;
|
|
26
30
|
}
|
|
31
|
+
|
|
27
32
|
function setCachedNamespaceResolver(xpath, node, resolver) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
34
|
+
return createdNamespaceResolversByXPathQueryAndNode.set(xpath, new Map());
|
|
35
|
+
}
|
|
36
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).set(node, resolver);
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
const xhtmlNamespaceResolver = prefix => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
if (!prefix) {
|
|
41
|
+
return 'http://www.w3.org/1999/xhtml';
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
39
44
|
};
|
|
40
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
|
|
48
|
+
*/
|
|
41
49
|
export function resolveId(id, sourceObject, nodeName = null) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
if (allMatchingTargetObjects.length === 0) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
allMatchingTargetObjects.length === 1 &&
|
|
56
|
-
fxEvaluateXPathToBoolean(
|
|
57
|
-
'(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore',
|
|
58
|
-
allMatchingTargetObjects[0],
|
|
59
|
-
null,
|
|
60
|
-
null,
|
|
61
|
-
{ namespaceResolver: xhtmlNamespaceResolver },
|
|
62
|
-
)
|
|
63
|
-
) {
|
|
64
|
-
// If the target element is not repeated, then the search for the target object is trivial since
|
|
65
|
-
// there is only one associated with the target element that bears the matching ID. This is true
|
|
66
|
-
// regardless of whether or not the source object is repeated. However, if the target element is
|
|
67
|
-
// repeated, then additional information must be used to help select a target object from among
|
|
68
|
-
// those associated with the identified target element.
|
|
69
|
-
const targetObject = allMatchingTargetObjects[0];
|
|
70
|
-
if (nodeName && targetObject.localName !== nodeName) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
return targetObject;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// SPEC:
|
|
77
|
-
|
|
78
|
-
// 12.2.1 References to Elements within a repeat Element
|
|
79
|
-
|
|
80
|
-
// When the target element that is identified by the IDREF of a source object has one or more
|
|
81
|
-
// repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
|
|
82
|
-
// subsets, those in common with the source element and those that are not in common. Any ancestor
|
|
83
|
-
// repeat elements of the target element not in common with the source element are descendants of
|
|
84
|
-
// the repeat elements that the source and target element have in common, if any.
|
|
85
|
-
|
|
86
|
-
// For the repeat elements that are in common, the desired target object exists in the same set of
|
|
87
|
-
// run-time objects that contains the source object. Then, for each ancestor repeat of the target
|
|
88
|
-
// element that is not in common with the source element, the current index of the repeat
|
|
89
|
-
// determines the set of run-time objects that contains the desired target object.
|
|
90
|
-
for (const ancestorRepeatItem of fxEvaluateXPathToNodes(
|
|
91
|
-
'ancestor::fx-repeatitem => reverse()',
|
|
92
|
-
sourceObject,
|
|
93
|
-
null,
|
|
94
|
-
null,
|
|
95
|
-
{ namespaceResolver: xhtmlNamespaceResolver },
|
|
96
|
-
)) {
|
|
97
|
-
const foundTargetObjects = allMatchingTargetObjects.filter(to =>
|
|
98
|
-
ancestorRepeatItem.contains(to),
|
|
50
|
+
const allMatchingTargetObjects = fxEvaluateXPathToNodes(
|
|
51
|
+
'outermost(ancestor-or-self::fx-fore[1]/(descendant::fx-fore|descendant::*[@id = $id]))[not(self::fx-fore)]',
|
|
52
|
+
sourceObject,
|
|
53
|
+
null,
|
|
54
|
+
{id},
|
|
55
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
99
56
|
);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
57
|
+
|
|
58
|
+
if (allMatchingTargetObjects.length === 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
allMatchingTargetObjects.length === 1 &&
|
|
64
|
+
fxEvaluateXPathToBoolean(
|
|
65
|
+
'(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore',
|
|
66
|
+
allMatchingTargetObjects[0],
|
|
67
|
+
null,
|
|
68
|
+
null,
|
|
69
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
70
|
+
)
|
|
71
|
+
) {
|
|
72
|
+
// If the target element is not repeated, then the search for the target object is trivial since
|
|
73
|
+
// there is only one associated with the target element that bears the matching ID. This is true
|
|
74
|
+
// regardless of whether or not the source object is repeated. However, if the target element is
|
|
75
|
+
// repeated, then additional information must be used to help select a target object from among
|
|
76
|
+
// those associated with the identified target element.
|
|
77
|
+
const targetObject = allMatchingTargetObjects[0];
|
|
107
78
|
if (nodeName && targetObject.localName !== nodeName) {
|
|
108
|
-
|
|
79
|
+
return null;
|
|
109
80
|
}
|
|
110
81
|
return targetObject;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// SPEC:
|
|
85
|
+
|
|
86
|
+
// 12.2.1 References to Elements within a repeat Element
|
|
87
|
+
|
|
88
|
+
// When the target element that is identified by the IDREF of a source object has one or more
|
|
89
|
+
// repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
|
|
90
|
+
// subsets, those in common with the source element and those that are not in common. Any ancestor
|
|
91
|
+
// repeat elements of the target element not in common with the source element are descendants of
|
|
92
|
+
// the repeat elements that the source and target element have in common, if any.
|
|
93
|
+
|
|
94
|
+
// For the repeat elements that are in common, the desired target object exists in the same set of
|
|
95
|
+
// run-time objects that contains the source object. Then, for each ancestor repeat of the target
|
|
96
|
+
// element that is not in common with the source element, the current index of the repeat
|
|
97
|
+
// determines the set of run-time objects that contains the desired target object.
|
|
98
|
+
for (const ancestorRepeatItem of fxEvaluateXPathToNodes(
|
|
99
|
+
'ancestor::fx-repeatitem => reverse()',
|
|
100
|
+
sourceObject,
|
|
101
|
+
null,
|
|
102
|
+
null,
|
|
103
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
104
|
+
)) {
|
|
105
|
+
const foundTargetObjects = allMatchingTargetObjects.filter(to =>
|
|
106
|
+
ancestorRepeatItem.contains(to),
|
|
122
107
|
);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
108
|
+
switch (foundTargetObjects.length) {
|
|
109
|
+
case 0:
|
|
110
|
+
// Nothing found: ignore
|
|
111
|
+
break;
|
|
112
|
+
case 1: {
|
|
113
|
+
// A single one is found: the target object is directly in a common repeat
|
|
114
|
+
const targetObject = foundTargetObjects[0];
|
|
115
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return targetObject;
|
|
119
|
+
}
|
|
120
|
+
default: {
|
|
121
|
+
// Multiple target objects are found: they are in a repeat that is not common with the source object
|
|
122
|
+
// We found a target object in a common repeat! We now need to find the one that is in the repeatitem identified at the current index
|
|
123
|
+
const targetObject = foundTargetObjects.find(to =>
|
|
124
|
+
fxEvaluateXPathToNodes(
|
|
125
|
+
'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
|
|
126
|
+
to,
|
|
127
|
+
null,
|
|
128
|
+
{},
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
if (!targetObject) {
|
|
132
|
+
// Nothing valid found for whatever reason. This might be something dynamic?
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return targetObject;
|
|
139
|
+
}
|
|
129
140
|
}
|
|
130
|
-
return targetObject;
|
|
131
|
-
}
|
|
132
141
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return null;
|
|
142
|
+
// We found no target objects in common repeats. The id is unresolvable
|
|
143
|
+
return null;
|
|
136
144
|
}
|
|
137
145
|
|
|
146
|
+
// Make namespace resolving use the `instance` element that is related to here
|
|
147
|
+
const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
|
|
148
|
+
|
|
138
149
|
/**
|
|
139
150
|
* Resolve a namespace. Needs a namespace prefix and the element that is most closely related to the
|
|
140
151
|
* XPath in which the namespace is being resolved. The prefix will be resolved by using the
|
|
@@ -150,153 +161,148 @@ export function resolveId(id, sourceObject, nodeName = null) {
|
|
|
150
161
|
* @param {string} prefix The prefix to resolve
|
|
151
162
|
*/
|
|
152
163
|
function createNamespaceResolver(xpathQuery, formElement) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// Make namespace resolving use the `instance` element that is related to here
|
|
159
|
-
const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
|
|
164
|
+
const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
|
|
165
|
+
if (cachedResolver) {
|
|
166
|
+
return cachedResolver;
|
|
167
|
+
}
|
|
160
168
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
|
|
170
|
+
let instanceReferences = fxEvaluateXPathToStrings(
|
|
171
|
+
`descendant::xqx:functionCallExpr
|
|
164
172
|
[xqx:functionName = "instance"]
|
|
165
173
|
/xqx:arguments
|
|
166
174
|
/xqx:stringConstantExpr
|
|
167
175
|
/xqx:value`,
|
|
168
|
-
|
|
169
|
-
null,
|
|
170
|
-
{},
|
|
171
|
-
{
|
|
172
|
-
namespaceResolver: prefix =>
|
|
173
|
-
prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
if (instanceReferences.length === 0) {
|
|
177
|
-
// No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
|
|
178
|
-
const ancestorComponent = fxEvaluateXPathToFirstNode('ancestor::*[@ref][1]', formElement);
|
|
179
|
-
if (ancestorComponent) {
|
|
180
|
-
const resolver = createNamespaceResolver(
|
|
181
|
-
ancestorComponent.getAttribute('ref'),
|
|
182
|
-
ancestorComponent,
|
|
183
|
-
);
|
|
184
|
-
setCachedNamespaceResolver(xpathQuery, formElement, resolver);
|
|
185
|
-
return resolver;
|
|
186
|
-
}
|
|
187
|
-
// Nothing found: let's just assume we're supposed to use the `default` instance
|
|
188
|
-
instanceReferences = ['default'];
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (instanceReferences.length === 1) {
|
|
192
|
-
// console.log(`resolving ${xpathQuery} with ${instanceReferences[0]}`);
|
|
193
|
-
let instance;
|
|
194
|
-
if (instanceReferences[0] === 'default') {
|
|
195
|
-
const actualForeElement = fxEvaluateXPathToFirstNode(
|
|
196
|
-
'ancestor-or-self::fx-fore[1]',
|
|
197
|
-
formElement,
|
|
198
|
-
null,
|
|
176
|
+
xpathAST,
|
|
199
177
|
null,
|
|
200
|
-
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return xpathDefaultNamespace;
|
|
178
|
+
{},
|
|
179
|
+
{
|
|
180
|
+
namespaceResolver: prefix =>
|
|
181
|
+
prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
if (instanceReferences.length === 0) {
|
|
185
|
+
// No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
|
|
186
|
+
const ancestorComponent = fxEvaluateXPathToFirstNode('ancestor::*[@ref][1]', formElement);
|
|
187
|
+
if (ancestorComponent) {
|
|
188
|
+
const resolver = createNamespaceResolver(
|
|
189
|
+
ancestorComponent.getAttribute('ref'),
|
|
190
|
+
ancestorComponent,
|
|
191
|
+
);
|
|
192
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolver);
|
|
193
|
+
return resolver;
|
|
217
194
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
221
|
-
return resolveNamespacePrefix;
|
|
195
|
+
// Nothing found: let's just assume we're supposed to use the `default` instance
|
|
196
|
+
instanceReferences = ['default'];
|
|
222
197
|
}
|
|
223
|
-
}
|
|
224
|
-
if (instanceReferences.length > 1) {
|
|
225
|
-
console.warn(
|
|
226
|
-
`More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
198
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
199
|
+
if (instanceReferences.length === 1) {
|
|
200
|
+
// console.log(`resolving ${xpathQuery} with ${instanceReferences[0]}`);
|
|
201
|
+
let instance;
|
|
202
|
+
if (instanceReferences[0] === 'default') {
|
|
203
|
+
const actualForeElement = fxEvaluateXPathToFirstNode(
|
|
204
|
+
'ancestor-or-self::fx-fore[1]',
|
|
205
|
+
formElement,
|
|
206
|
+
null,
|
|
207
|
+
null,
|
|
208
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
209
|
+
);
|
|
233
210
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
211
|
+
instance = actualForeElement && actualForeElement.querySelector('fx-instance');
|
|
212
|
+
} else {
|
|
213
|
+
instance = resolveId(instanceReferences[0], formElement, 'fx-instance');
|
|
214
|
+
}
|
|
215
|
+
if (instance && instance.hasAttribute('xpath-default-namespace')) {
|
|
216
|
+
const xpathDefaultNamespace = instance.getAttribute('xpath-default-namespace');
|
|
217
|
+
/*
|
|
218
|
+
console.log(
|
|
219
|
+
`Resolving the xpath ${xpathQuery} with the default namespace set to ${xpathDefaultNamespace}`,
|
|
220
|
+
);
|
|
221
|
+
*/
|
|
222
|
+
const resolveNamespacePrefix = prefix => {
|
|
223
|
+
if (!prefix) {
|
|
224
|
+
return xpathDefaultNamespace;
|
|
225
|
+
}
|
|
226
|
+
return undefined;
|
|
227
|
+
};
|
|
228
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
229
|
+
return resolveNamespacePrefix;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (instanceReferences.length > 1) {
|
|
233
|
+
console.warn(
|
|
234
|
+
`More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
|
|
235
|
+
);
|
|
237
236
|
}
|
|
238
237
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
const xpathDefaultNamespace =
|
|
239
|
+
fxEvaluateXPathToString('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) ||
|
|
240
|
+
'';
|
|
242
241
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
{ prefix },
|
|
248
|
-
);
|
|
249
|
-
};
|
|
242
|
+
const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
|
|
243
|
+
if (prefix === '') {
|
|
244
|
+
return xpathDefaultNamespace;
|
|
245
|
+
}
|
|
250
246
|
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
// Note: ideally we should use Node#lookupNamespaceURI. However, the nodes we are passed are
|
|
248
|
+
// XML. The best we can do is emulate the `xmlns:xxx` namespace declarations by regarding them as
|
|
249
|
+
// attributes. Which they technically ARE NOT!
|
|
250
|
+
|
|
251
|
+
return fxEvaluateXPathToString(
|
|
252
|
+
'ancestor-or-self::*/@*[name() = "xmlns:" || $prefix][last()]',
|
|
253
|
+
formElement,
|
|
254
|
+
null,
|
|
255
|
+
{prefix},
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
260
|
+
return resolveNamespacePrefix;
|
|
253
261
|
}
|
|
254
262
|
|
|
255
263
|
function createNamespaceResolverForNode(query, contextNode, formElement) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
if (((contextNode && contextNode.ownerDocument) || contextNode) === window.document) {
|
|
265
|
+
// Running a query on the HTML DOM. Don't bother resolving namespaces in any other way
|
|
266
|
+
return xhtmlNamespaceResolver;
|
|
267
|
+
}
|
|
268
|
+
return createNamespaceResolver(query, formElement);
|
|
261
269
|
}
|
|
262
270
|
|
|
263
|
-
// A global registry of function names that are declared in Fore by a developer using the
|
|
264
|
-
// `fx-function` element. These should be available without providing a prefix as well
|
|
265
|
-
export const globallyDeclaredFunctionLocalNames = [];
|
|
266
271
|
|
|
267
272
|
/**
|
|
268
273
|
* Implementation of the functionNameResolver passed to FontoXPath to
|
|
269
274
|
* redirect function resolving for unprefixed functions to either the fn or the xf namespace
|
|
270
275
|
*/
|
|
271
276
|
// eslint-disable-next-line no-unused-vars
|
|
272
|
-
function functionNameResolver({
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
277
|
+
function functionNameResolver({prefix, localName}, _arity) {
|
|
278
|
+
switch (localName) {
|
|
279
|
+
// TODO: put the full XForms library functions set here
|
|
280
|
+
case 'context':
|
|
281
|
+
case 'base64encode':
|
|
282
|
+
case 'boolean-from-string':
|
|
283
|
+
case 'current':
|
|
284
|
+
case 'depends':
|
|
285
|
+
case 'event':
|
|
286
|
+
case 'index':
|
|
287
|
+
case 'instance':
|
|
288
|
+
case 'log':
|
|
289
|
+
case 'parse':
|
|
290
|
+
case 'logtree':
|
|
291
|
+
return {namespaceURI: XFORMS_NAMESPACE_URI, localName};
|
|
292
|
+
default:
|
|
293
|
+
if (prefix === '' && globallyDeclaredFunctionLocalNames.includes(localName)) {
|
|
294
|
+
// The function has been declared without a prefix and is called here without a prefix.
|
|
295
|
+
// Just make this work. It is the developer-friendly way
|
|
296
|
+
return {namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName};
|
|
297
|
+
}
|
|
298
|
+
if (prefix === 'fn' || prefix === '') {
|
|
299
|
+
return {namespaceURI: 'http://www.w3.org/2005/xpath-functions', localName};
|
|
300
|
+
}
|
|
301
|
+
if (prefix === 'local') {
|
|
302
|
+
return {namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName};
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
300
306
|
}
|
|
301
307
|
|
|
302
308
|
/**
|
|
@@ -308,23 +314,23 @@ function functionNameResolver({ prefix, localName }, _arity) {
|
|
|
308
314
|
* @return {Object} A key-value mapping of the variables
|
|
309
315
|
*/
|
|
310
316
|
function getVariablesInScope(formElement) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
317
|
+
let closestActualFormElement = formElement;
|
|
318
|
+
while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
|
|
319
|
+
closestActualFormElement = closestActualFormElement.parentNode;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!closestActualFormElement) {
|
|
323
|
+
return {};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const variables = {};
|
|
327
|
+
if (closestActualFormElement.inScopeVariables) {
|
|
328
|
+
for (const key of closestActualFormElement.inScopeVariables.keys()) {
|
|
329
|
+
const varElement = closestActualFormElement.inScopeVariables.get(key);
|
|
330
|
+
variables[key] = varElement.value;
|
|
331
|
+
}
|
|
325
332
|
}
|
|
326
|
-
|
|
327
|
-
return variables;
|
|
333
|
+
return variables;
|
|
328
334
|
}
|
|
329
335
|
|
|
330
336
|
/**
|
|
@@ -335,25 +341,26 @@ function getVariablesInScope(formElement) {
|
|
|
335
341
|
* @param {Node} contextNode The start of the XPath
|
|
336
342
|
* @param {{parentNode}|ForeElementMixin} formElement The form element associated to the XPath
|
|
337
343
|
*/
|
|
338
|
-
export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
|
|
339
|
-
|
|
340
|
-
|
|
344
|
+
export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options={}) {
|
|
345
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
346
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
341
347
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
348
|
+
return fxEvaluateXPath(
|
|
349
|
+
xpath,
|
|
350
|
+
contextNode,
|
|
351
|
+
null,
|
|
352
|
+
{...variablesInScope, ...variables},
|
|
353
|
+
fxEvaluateXPath.ALL_RESULTS_TYPE,
|
|
354
|
+
{
|
|
355
|
+
currentContext: {formElement, variables},
|
|
356
|
+
moduleImports: {
|
|
357
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
358
|
+
},
|
|
359
|
+
functionNameResolver,
|
|
360
|
+
namespaceResolver,
|
|
361
|
+
language: options.language || evaluateXPath.XPATH_3_1
|
|
362
|
+
},
|
|
363
|
+
);
|
|
357
364
|
}
|
|
358
365
|
|
|
359
366
|
/**
|
|
@@ -365,16 +372,17 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
|
|
|
365
372
|
* @return {Node} The first node found by the XPath
|
|
366
373
|
*/
|
|
367
374
|
export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
375
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
376
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
377
|
+
return fxEvaluateXPathToFirstNode(xpath, contextNode, null, variablesInScope, {
|
|
378
|
+
defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
|
|
379
|
+
moduleImports: {
|
|
380
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
381
|
+
},
|
|
382
|
+
currentContext: {formElement},
|
|
383
|
+
functionNameResolver,
|
|
384
|
+
namespaceResolver,
|
|
385
|
+
});
|
|
378
386
|
}
|
|
379
387
|
|
|
380
388
|
/**
|
|
@@ -386,17 +394,17 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
|
386
394
|
* @return {Node[]} All nodes
|
|
387
395
|
*/
|
|
388
396
|
export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
397
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
398
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
399
|
+
|
|
400
|
+
return fxEvaluateXPathToNodes(xpath, contextNode, null, variablesInScope, {
|
|
401
|
+
currentContext: {formElement},
|
|
402
|
+
functionNameResolver,
|
|
403
|
+
moduleImports: {
|
|
404
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
405
|
+
},
|
|
406
|
+
namespaceResolver,
|
|
407
|
+
});
|
|
400
408
|
}
|
|
401
409
|
|
|
402
410
|
/**
|
|
@@ -408,17 +416,17 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
|
408
416
|
* @return {boolean}
|
|
409
417
|
*/
|
|
410
418
|
export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
419
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
420
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
421
|
+
|
|
422
|
+
return fxEvaluateXPathToBoolean(xpath, contextNode, null, variablesInScope, {
|
|
423
|
+
currentContext: {formElement},
|
|
424
|
+
functionNameResolver,
|
|
425
|
+
moduleImports: {
|
|
426
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
427
|
+
},
|
|
428
|
+
namespaceResolver,
|
|
429
|
+
});
|
|
422
430
|
}
|
|
423
431
|
|
|
424
432
|
/**
|
|
@@ -433,23 +441,23 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
|
433
441
|
* @return {string}
|
|
434
442
|
*/
|
|
435
443
|
export function evaluateXPathToString(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
444
|
+
xpath,
|
|
445
|
+
contextNode,
|
|
446
|
+
formElement,
|
|
447
|
+
domFacade = null,
|
|
448
|
+
namespaceReferenceNode = formElement,
|
|
441
449
|
) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
450
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
451
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
452
|
+
|
|
453
|
+
return fxEvaluateXPathToString(xpath, contextNode, domFacade, variablesInScope, {
|
|
454
|
+
currentContext: {formElement},
|
|
455
|
+
functionNameResolver,
|
|
456
|
+
moduleImports: {
|
|
457
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
458
|
+
},
|
|
459
|
+
namespaceResolver,
|
|
460
|
+
});
|
|
453
461
|
}
|
|
454
462
|
|
|
455
463
|
/**
|
|
@@ -464,28 +472,28 @@ export function evaluateXPathToString(
|
|
|
464
472
|
* @return {string}
|
|
465
473
|
*/
|
|
466
474
|
export function evaluateXPathToStrings(
|
|
467
|
-
xpath,
|
|
468
|
-
contextNode,
|
|
469
|
-
formElement,
|
|
470
|
-
domFacade = null,
|
|
471
|
-
namespaceReferenceNode = formElement,
|
|
472
|
-
) {
|
|
473
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
474
|
-
return fxEvaluateXPathToStrings(
|
|
475
475
|
xpath,
|
|
476
476
|
contextNode,
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
477
|
+
formElement,
|
|
478
|
+
domFacade = null,
|
|
479
|
+
namespaceReferenceNode = formElement,
|
|
480
|
+
) {
|
|
481
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
482
|
+
return fxEvaluateXPathToStrings(
|
|
483
|
+
xpath,
|
|
484
|
+
contextNode,
|
|
485
|
+
domFacade,
|
|
486
|
+
{},
|
|
487
|
+
|
|
488
|
+
{
|
|
489
|
+
currentContext: {formElement},
|
|
490
|
+
functionNameResolver,
|
|
491
|
+
moduleImports: {
|
|
492
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
493
|
+
},
|
|
494
|
+
namespaceResolver,
|
|
495
|
+
},
|
|
496
|
+
);
|
|
489
497
|
}
|
|
490
498
|
|
|
491
499
|
/**
|
|
@@ -500,52 +508,54 @@ export function evaluateXPathToStrings(
|
|
|
500
508
|
* @return {Number}
|
|
501
509
|
*/
|
|
502
510
|
export function evaluateXPathToNumber(
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
511
|
+
xpath,
|
|
512
|
+
contextNode,
|
|
513
|
+
formElement,
|
|
514
|
+
domFacade = null,
|
|
515
|
+
namespaceReferenceNode = formElement,
|
|
508
516
|
) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
517
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
518
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
519
|
+
|
|
520
|
+
return fxEvaluateXPathToNumber(xpath, contextNode, domFacade, variablesInScope, {
|
|
521
|
+
currentContext: {formElement},
|
|
522
|
+
functionNameResolver,
|
|
523
|
+
moduleImports: {
|
|
524
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
525
|
+
},
|
|
526
|
+
namespaceResolver,
|
|
527
|
+
});
|
|
520
528
|
}
|
|
521
529
|
|
|
522
|
-
/**
|
|
523
|
-
* Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
|
|
524
|
-
*/
|
|
525
|
-
|
|
526
530
|
const contextFunction = (dynamicContext, string) => {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
531
|
+
const caller = dynamicContext.currentContext.formElement;
|
|
532
|
+
if (string) {
|
|
533
|
+
const instance = resolveId(string, caller);
|
|
534
|
+
if (instance) {
|
|
535
|
+
if (instance.nodeName === 'FX-REPEAT') {
|
|
536
|
+
const {nodeset} = instance;
|
|
537
|
+
for (let parent = caller; parent; parent = parent.parentNode) {
|
|
538
|
+
if (parent.parentNode === instance) {
|
|
539
|
+
const offset = Array.from(parent.parentNode.children).indexOf(parent);
|
|
540
|
+
return nodeset[offset];
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return instance.nodeset;
|
|
538
545
|
}
|
|
539
|
-
}
|
|
540
|
-
return instance.nodeset;
|
|
541
546
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
547
|
+
const parent = XPathUtil.getParentBindingElement(caller);
|
|
548
|
+
// const p = caller.nodeName;
|
|
549
|
+
// const p = dynamicContext.domFacade.getParentElement();
|
|
550
|
+
|
|
551
|
+
if (parent) return parent.nodeset;
|
|
552
|
+
return caller.getInScopeContext();
|
|
553
|
+
};
|
|
546
554
|
|
|
547
|
-
|
|
548
|
-
|
|
555
|
+
// todo: implement
|
|
556
|
+
const currentFunction = (dynamicContext, string) => {
|
|
557
|
+
const caller = dynamicContext.currentContext.formElement;
|
|
558
|
+
return null;
|
|
549
559
|
};
|
|
550
560
|
|
|
551
561
|
/**
|
|
@@ -553,10 +563,10 @@ const contextFunction = (dynamicContext, string) => {
|
|
|
553
563
|
* @return instance data for given id serialized to string.
|
|
554
564
|
*/
|
|
555
565
|
registerCustomXPathFunction(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
566
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context'},
|
|
567
|
+
[],
|
|
568
|
+
'item()?',
|
|
569
|
+
contextFunction,
|
|
560
570
|
);
|
|
561
571
|
|
|
562
572
|
/**
|
|
@@ -564,10 +574,17 @@ registerCustomXPathFunction(
|
|
|
564
574
|
* @return instance data for given id serialized to string.
|
|
565
575
|
*/
|
|
566
576
|
registerCustomXPathFunction(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
577
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context'},
|
|
578
|
+
['xs:string'],
|
|
579
|
+
'item()?',
|
|
580
|
+
contextFunction,
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
registerCustomXPathFunction(
|
|
584
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'current'},
|
|
585
|
+
['xs:string'],
|
|
586
|
+
'item()?',
|
|
587
|
+
currentFunction,
|
|
571
588
|
);
|
|
572
589
|
|
|
573
590
|
/**
|
|
@@ -575,68 +592,94 @@ registerCustomXPathFunction(
|
|
|
575
592
|
* @return instance data for given id serialized to string.
|
|
576
593
|
*/
|
|
577
594
|
registerCustomXPathFunction(
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'log'},
|
|
596
|
+
['xs:string?'],
|
|
597
|
+
'xs:string?',
|
|
598
|
+
(dynamicContext, string) => {
|
|
599
|
+
const {formElement} = dynamicContext.currentContext;
|
|
600
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
601
|
+
if (instance) {
|
|
602
|
+
if (instance.getAttribute('type') === 'json') {
|
|
603
|
+
console.warn('log() does not work for JSON yet');
|
|
604
|
+
// return JSON.stringify(instance.getDefaultContext());
|
|
605
|
+
} else {
|
|
606
|
+
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
607
|
+
return Fore.prettifyXml(def);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
},
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
registerCustomXPathFunction(
|
|
615
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'parse'},
|
|
616
|
+
['xs:string?'],
|
|
617
|
+
'element()?',
|
|
618
|
+
(dynamicContext, string) => {
|
|
619
|
+
const parser = new DOMParser();
|
|
620
|
+
const out = parser.parseFromString(string, "application/xml");
|
|
621
|
+
console.log('parse', out);
|
|
622
|
+
|
|
623
|
+
/*
|
|
624
|
+
const {formElement} = dynamicContext.currentContext;
|
|
625
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
626
|
+
if (instance) {
|
|
627
|
+
if (instance.getAttribute('type') === 'json') {
|
|
628
|
+
console.warn('log() does not work for JSON yet');
|
|
629
|
+
// return JSON.stringify(instance.getDefaultContext());
|
|
630
|
+
} else {
|
|
631
|
+
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
632
|
+
return Fore.prettifyXml(def);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
*/
|
|
636
|
+
return out.firstElementChild;
|
|
637
|
+
},
|
|
595
638
|
);
|
|
596
639
|
|
|
597
640
|
function buildTree(tree, data) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
641
|
+
if (!data) return;
|
|
642
|
+
if (data.nodeType === Node.ELEMENT_NODE) {
|
|
643
|
+
if (data.children) {
|
|
644
|
+
const details = document.createElement('details');
|
|
645
|
+
details.setAttribute('data-path', data.nodeName);
|
|
646
|
+
const summary = document.createElement('summary');
|
|
647
|
+
|
|
648
|
+
let display = ` <${data.nodeName}`;
|
|
649
|
+
Array.from(data.attributes).forEach(attr => {
|
|
650
|
+
display += ` ${attr.nodeName}="${attr.nodeValue}"`;
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
let contents;
|
|
654
|
+
if (
|
|
655
|
+
data.firstChild &&
|
|
656
|
+
data.firstChild.nodeType === Node.TEXT_NODE &&
|
|
657
|
+
data.firstChild.data.trim() !== ''
|
|
658
|
+
) {
|
|
659
|
+
// console.log('whoooooooooopp');
|
|
660
|
+
contents = data.firstChild.nodeValue;
|
|
661
|
+
display += `>${contents}</${data.nodeName}>`;
|
|
662
|
+
} else {
|
|
663
|
+
display += '>';
|
|
664
|
+
}
|
|
665
|
+
summary.textContent = display;
|
|
666
|
+
|
|
667
|
+
details.appendChild(summary);
|
|
668
|
+
if (data.childElementCount !== 0) {
|
|
669
|
+
details.setAttribute('open', 'open');
|
|
670
|
+
} else {
|
|
671
|
+
summary.setAttribute('style', 'list-style:none;');
|
|
672
|
+
}
|
|
673
|
+
tree.appendChild(details);
|
|
674
|
+
|
|
675
|
+
Array.from(data.children).forEach(child => {
|
|
676
|
+
// if(child.nodeType === Node.ELEMENT_NODE){
|
|
677
|
+
// child.parentNode.appendChild(buildTree(child));
|
|
678
|
+
buildTree(details, child);
|
|
679
|
+
// }
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
} /* else if(data.nodeType === Node.ATTRIBUTE_NODE){
|
|
640
683
|
//create span for now
|
|
641
684
|
// const span = document.createElement('span');
|
|
642
685
|
// span.style.background = 'grey';
|
|
@@ -647,127 +690,139 @@ function buildTree(tree, data) {
|
|
|
647
690
|
tree.textContent = data;
|
|
648
691
|
} */
|
|
649
692
|
|
|
650
|
-
|
|
693
|
+
// return tree;
|
|
651
694
|
}
|
|
652
695
|
|
|
653
696
|
registerCustomXPathFunction(
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
697
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'logtree'},
|
|
698
|
+
['xs:string?'],
|
|
699
|
+
'element()?',
|
|
700
|
+
(dynamicContext, string) => {
|
|
701
|
+
const {formElement} = dynamicContext.currentContext;
|
|
702
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
703
|
+
|
|
704
|
+
if (instance) {
|
|
705
|
+
// const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
706
|
+
// const def = JSON.stringify(instance.getDefaultContext());
|
|
707
|
+
|
|
708
|
+
const treeDiv = document.createElement('div');
|
|
709
|
+
treeDiv.setAttribute('class', 'logtree');
|
|
710
|
+
// const datatree = buildTree(tree,instance.getDefaultContext());
|
|
711
|
+
// return tree.appendChild(datatree);
|
|
712
|
+
// return buildTree(root,instance.getDefaultContext());;
|
|
713
|
+
const form = dynamicContext.currentContext.formElement;
|
|
714
|
+
const logtree = form.querySelector('.logtree');
|
|
715
|
+
if (logtree) {
|
|
716
|
+
logtree.parentNode.removeChild(logtree);
|
|
717
|
+
}
|
|
718
|
+
const tree = buildTree(treeDiv, instance.getDefaultContext());
|
|
719
|
+
if (tree) {
|
|
720
|
+
form.appendChild(tree);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return null;
|
|
724
|
+
},
|
|
682
725
|
);
|
|
683
726
|
|
|
684
727
|
const instance = (dynamicContext, string) => {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const formElement = fxEvaluateXPathToFirstNode(
|
|
689
|
-
'ancestor-or-self::fx-fore[1]',
|
|
690
|
-
dynamicContext.currentContext.formElement,
|
|
691
|
-
null,
|
|
692
|
-
null,
|
|
693
|
-
{ namespaceResolver: xhtmlNamespaceResolver },
|
|
694
|
-
);
|
|
695
|
-
|
|
696
|
-
// console.log('fnInstance dynamicContext: ', dynamicContext);
|
|
697
|
-
// console.log('fnInstance string: ', string);
|
|
698
|
-
|
|
699
|
-
const inst = string
|
|
700
|
-
? resolveId(string, formElement, 'fx-instance')
|
|
701
|
-
: formElement.querySelector(`fx-instance`);
|
|
702
|
-
|
|
703
|
-
// const def = instance.getInstanceData();
|
|
704
|
-
if (inst) {
|
|
705
|
-
const def = inst.getDefaultContext();
|
|
706
|
-
// console.log('target instance root node: ', def);
|
|
707
|
-
|
|
708
|
-
return def;
|
|
709
|
-
}
|
|
710
|
-
return null;
|
|
711
|
-
};
|
|
728
|
+
// Spec: https://www.w3.org/TR/xforms-xpath/#The_XForms_Function_Library#The_instance.28.29_Function
|
|
729
|
+
// TODO: handle no string passed (null will be passed instead)
|
|
712
730
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
731
|
+
const formElement = fxEvaluateXPathToFirstNode(
|
|
732
|
+
'ancestor-or-self::fx-fore[1]',
|
|
733
|
+
dynamicContext.currentContext.formElement,
|
|
734
|
+
null,
|
|
735
|
+
null,
|
|
736
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
// console.log('fnInstance dynamicContext: ', dynamicContext);
|
|
740
|
+
// console.log('fnInstance string: ', string);
|
|
741
|
+
|
|
742
|
+
const inst = string
|
|
743
|
+
? resolveId(string, formElement, 'fx-instance')
|
|
744
|
+
: formElement.querySelector(`fx-instance`);
|
|
723
745
|
|
|
724
746
|
// const def = instance.getInstanceData();
|
|
725
|
-
if (
|
|
726
|
-
|
|
747
|
+
if (inst) {
|
|
748
|
+
const def = inst.getDefaultContext();
|
|
749
|
+
// console.log('target instance root node: ', def);
|
|
750
|
+
|
|
751
|
+
return def;
|
|
727
752
|
}
|
|
728
|
-
return
|
|
729
|
-
|
|
753
|
+
return null;
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
registerCustomXPathFunction(
|
|
757
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'index'},
|
|
758
|
+
['xs:string?'],
|
|
759
|
+
'xs:integer?',
|
|
760
|
+
(dynamicContext, string) => {
|
|
761
|
+
const {formElement} = dynamicContext.currentContext;
|
|
762
|
+
if (string === null) {
|
|
763
|
+
return 1;
|
|
764
|
+
}
|
|
765
|
+
const repeat = resolveId(string, formElement, 'fx-repeat');
|
|
766
|
+
|
|
767
|
+
// const def = instance.getInstanceData();
|
|
768
|
+
if (repeat) {
|
|
769
|
+
return repeat.getAttribute('index');
|
|
770
|
+
}
|
|
771
|
+
return Number(1);
|
|
772
|
+
},
|
|
730
773
|
);
|
|
731
774
|
|
|
732
775
|
// Note that this is not to spec. The spec enforces elements to be returned from the
|
|
733
776
|
// instance. However, we allow instances to actually be JSON!
|
|
734
777
|
registerCustomXPathFunction(
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
778
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance'},
|
|
779
|
+
[],
|
|
780
|
+
'item()?',
|
|
781
|
+
domFacade => instance(domFacade, null),
|
|
739
782
|
);
|
|
740
783
|
|
|
741
784
|
registerCustomXPathFunction(
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
785
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance'},
|
|
786
|
+
['xs:string?'],
|
|
787
|
+
'item()?',
|
|
788
|
+
instance,
|
|
746
789
|
);
|
|
747
790
|
|
|
748
791
|
registerCustomXPathFunction(
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
792
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'depends'},
|
|
793
|
+
['node()*'],
|
|
794
|
+
'item()?',
|
|
795
|
+
(dynamicContext, nodes) =>
|
|
796
|
+
// console.log('depends on : ', nodes[0]);
|
|
797
|
+
nodes[0],
|
|
755
798
|
);
|
|
756
799
|
|
|
757
800
|
registerCustomXPathFunction(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
801
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'event'},
|
|
802
|
+
['xs:string?'],
|
|
803
|
+
'item()?',
|
|
804
|
+
(dynamicContext, arg) => {
|
|
805
|
+
if (!arg) return [];
|
|
806
|
+
|
|
807
|
+
if (dynamicContext.currentContext.variables) {
|
|
808
|
+
const payload = dynamicContext.currentContext.variables[arg];
|
|
809
|
+
if (payload.nodeType) {
|
|
810
|
+
console.log('got some node as js object');
|
|
811
|
+
}
|
|
812
|
+
if (payload) {
|
|
813
|
+
return dynamicContext.currentContext.variables[arg];
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (dynamicContext.currentContext.formElement.inScopeVariables) {
|
|
818
|
+
console.log('event()', dynamicContext.currentContext.formElement.inScopeVariables);
|
|
819
|
+
console.log('event()', dynamicContext.currentContext.formElement.inScopeVariables.get(arg));
|
|
820
|
+
// dynamicContext.currentContext.variables = dynamicContext.currentContext.formElement.inScopeVariables;
|
|
821
|
+
return dynamicContext.currentContext.formElement.inScopeVariables.get(arg);
|
|
822
|
+
}
|
|
768
823
|
|
|
769
|
-
|
|
770
|
-
|
|
824
|
+
return [];
|
|
825
|
+
},
|
|
771
826
|
);
|
|
772
827
|
|
|
773
828
|
// Implement the XForms standard functions here.
|
|
@@ -781,34 +836,34 @@ registerXQueryModule(`
|
|
|
781
836
|
|
|
782
837
|
// How to run XQUERY:
|
|
783
838
|
/**
|
|
784
|
-
registerXQueryModule(`
|
|
785
|
-
module namespace my-custom-namespace = "my-custom-uri";
|
|
786
|
-
(:~
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
declare %public %updating function my-custom-namespace:do-something ($ele as element()) as xs:boolean {
|
|
839
|
+
registerXQueryModule(`
|
|
840
|
+
module namespace my-custom-namespace = "my-custom-uri";
|
|
841
|
+
(:~
|
|
842
|
+
Insert attribute somewhere
|
|
843
|
+
~:)
|
|
844
|
+
declare %public %updating function my-custom-namespace:do-something ($ele as element()) as xs:boolean {
|
|
790
845
|
if ($ele/@done) then false() else
|
|
791
846
|
(insert node
|
|
792
847
|
attribute done {"true"}
|
|
793
848
|
into $ele, true())
|
|
794
849
|
};
|
|
795
|
-
`)
|
|
796
|
-
// At some point:
|
|
797
|
-
const contextNode = null;
|
|
798
|
-
const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
|
|
850
|
+
`)
|
|
851
|
+
// At some point:
|
|
852
|
+
const contextNode = null;
|
|
853
|
+
const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
|
|
799
854
|
|
|
800
|
-
console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
|
|
855
|
+
console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
|
|
801
856
|
|
|
802
|
-
executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
|
|
803
|
-
*/
|
|
857
|
+
executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
|
|
858
|
+
*/
|
|
804
859
|
|
|
805
860
|
/**
|
|
806
861
|
* @param input as string
|
|
807
862
|
* @return {string}
|
|
808
863
|
*/
|
|
809
864
|
registerCustomXPathFunction(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
865
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'base64encode'},
|
|
866
|
+
['xs:string?'],
|
|
867
|
+
'xs:string?',
|
|
868
|
+
(dynamicContext, string) => btoa(string),
|
|
814
869
|
);
|