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