@jinntec/fore 1.0.0-1 → 1.0.0-4
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 +20 -1
- package/dist/fore-all.js +10 -10
- package/dist/fore-debug.js +140 -0
- package/index.js +8 -0
- package/package.json +7 -6
- package/resources/{vars.css → fore-styles.css} +292 -0
- package/src/DependencyNotifyingDomFacade.js +9 -1
- package/src/ForeElementMixin.js +25 -17
- package/src/actions/abstract-action.js +8 -7
- package/src/actions/fx-action.js +6 -1
- package/src/actions/fx-dispatch.js +10 -2
- package/src/actions/fx-hide.js +23 -0
- package/src/actions/fx-insert.js +9 -9
- package/src/actions/fx-send.js +1 -1
- package/src/actions/fx-setvalue.js +2 -4
- package/src/actions/fx-show.js +23 -0
- package/src/dep_graph.js +9 -0
- package/src/drawdown.js +172 -0
- package/src/fore.js +56 -3
- package/src/fx-bind.js +20 -17
- package/src/fx-fore.js +297 -33
- package/src/fx-instance.js +27 -6
- package/src/fx-model.js +182 -37
- package/src/fx-submission.js +29 -7
- package/src/fx-var.js +43 -0
- package/src/getInScopeContext.js +28 -7
- package/src/modelitem.js +1 -0
- package/src/ui/abstract-control.js +17 -4
- package/src/ui/fx-alert.js +16 -20
- package/src/ui/fx-container.js +9 -4
- package/src/ui/fx-control.js +76 -39
- package/src/ui/fx-dialog.js +68 -0
- package/src/ui/fx-inspector.js +44 -0
- package/src/ui/fx-items.js +120 -0
- package/src/ui/fx-output.js +50 -12
- package/src/ui/fx-repeat.js +83 -16
- package/src/ui/fx-repeatitem.js +11 -4
- package/src/ui/fx-trigger.js +3 -0
- package/src/xpath-evaluation.js +372 -135
- package/src/xpath-util.js +52 -12
- package/dist/fore.js +0 -2
- package/resources/fore.css +0 -86
- package/resources/toastify.css +0 -87
package/src/xpath-evaluation.js
CHANGED
|
@@ -1,16 +1,67 @@
|
|
|
1
1
|
import {
|
|
2
2
|
evaluateXPath as fxEvaluateXPath,
|
|
3
|
+
evaluateXPathToBoolean as fxEvaluateXPathToBoolean,
|
|
3
4
|
evaluateXPathToFirstNode as fxEvaluateXPathToFirstNode,
|
|
4
5
|
evaluateXPathToNodes as fxEvaluateXPathToNodes,
|
|
5
|
-
evaluateXPathToBoolean as fxEvaluateXPathToBoolean,
|
|
6
|
-
evaluateXPathToString as fxEvaluateXPathToString,
|
|
7
6
|
evaluateXPathToNumber as fxEvaluateXPathToNumber,
|
|
7
|
+
evaluateXPathToString as fxEvaluateXPathToString,
|
|
8
|
+
evaluateXPathToStrings as fxEvaluateXPathToStrings,
|
|
9
|
+
parseScript,
|
|
8
10
|
registerCustomXPathFunction,
|
|
9
11
|
registerXQueryModule,
|
|
10
12
|
} from 'fontoxpath';
|
|
13
|
+
import { XPathUtil } from './xpath-util.js';
|
|
11
14
|
|
|
12
15
|
const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
13
16
|
|
|
17
|
+
const createdNamespaceResolversByXPathQueryAndNode = new Map();
|
|
18
|
+
|
|
19
|
+
function prettifyXml(source) {
|
|
20
|
+
const xmlDoc = new DOMParser().parseFromString(source, 'application/xml');
|
|
21
|
+
const xsltDoc = new DOMParser().parseFromString(
|
|
22
|
+
[
|
|
23
|
+
// describes how we want to modify the XML - indent everything
|
|
24
|
+
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
|
|
25
|
+
' <xsl:strip-space elements="*"/>',
|
|
26
|
+
' <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
|
|
27
|
+
' <xsl:value-of select="normalize-space(.)"/>',
|
|
28
|
+
' </xsl:template>',
|
|
29
|
+
' <xsl:template match="node()|@*">',
|
|
30
|
+
' <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
|
|
31
|
+
' </xsl:template>',
|
|
32
|
+
' <xsl:output indent="yes"/>',
|
|
33
|
+
'</xsl:stylesheet>',
|
|
34
|
+
].join('\n'),
|
|
35
|
+
'application/xml',
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const xsltProcessor = new XSLTProcessor();
|
|
39
|
+
xsltProcessor.importStylesheet(xsltDoc);
|
|
40
|
+
const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
|
|
41
|
+
const resultXml = new XMLSerializer().serializeToString(resultDoc);
|
|
42
|
+
return resultXml;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getCachedNamespaceResolver(xpath, node) {
|
|
46
|
+
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).get(node) || null;
|
|
50
|
+
}
|
|
51
|
+
function setCachedNamespaceResolver(xpath, node, resolver) {
|
|
52
|
+
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
53
|
+
return createdNamespaceResolversByXPathQueryAndNode.set(xpath, new Map());
|
|
54
|
+
}
|
|
55
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).set(node, resolver);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const xhtmlNamespaceResolver = prefix => {
|
|
59
|
+
if (!prefix) {
|
|
60
|
+
return 'http://www.w3.org/1999/xhtml';
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
};
|
|
64
|
+
|
|
14
65
|
/**
|
|
15
66
|
* Resolve a namespace. Needs a namespace prefix and the element that is most closely related to the
|
|
16
67
|
* XPath in which the namespace is being resolved. The prefix will be resolved by using the
|
|
@@ -25,33 +76,115 @@ const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
|
25
76
|
* @param {Node} contextElement The element that is most closely related with the XPath in which this prefix is resolved.
|
|
26
77
|
* @param {string} prefix The prefix to resolve
|
|
27
78
|
*/
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
79
|
+
function createNamespaceResolver(xpathQuery, formElement) {
|
|
80
|
+
const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
|
|
81
|
+
if (cachedResolver) {
|
|
82
|
+
return cachedResolver;
|
|
31
83
|
}
|
|
32
84
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
85
|
+
// Make namespace resolving use the `instance` element that is related to here
|
|
86
|
+
const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
|
|
87
|
+
|
|
88
|
+
const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
|
|
89
|
+
let instanceReferences = fxEvaluateXPathToStrings(
|
|
90
|
+
`descendant::xqx:functionCallExpr
|
|
91
|
+
[xqx:functionName = "instance"]
|
|
92
|
+
/xqx:arguments
|
|
93
|
+
/xqx:stringConstantExpr
|
|
94
|
+
/xqx:value`,
|
|
95
|
+
xpathAST,
|
|
96
|
+
null,
|
|
97
|
+
{},
|
|
98
|
+
{
|
|
99
|
+
namespaceResolver: prefix =>
|
|
100
|
+
prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
if (instanceReferences.length === 0) {
|
|
104
|
+
// No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
|
|
105
|
+
const ancestorComponent = fxEvaluateXPathToFirstNode('ancestor::*[@ref][1]', formElement);
|
|
106
|
+
if (ancestorComponent) {
|
|
107
|
+
const resolver = createNamespaceResolver(
|
|
108
|
+
ancestorComponent.getAttribute('ref'),
|
|
109
|
+
ancestorComponent,
|
|
110
|
+
);
|
|
111
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolver);
|
|
112
|
+
return resolver;
|
|
113
|
+
}
|
|
114
|
+
// Nothing found: let's just assume we're supposed to use the `default` instance
|
|
115
|
+
instanceReferences = ['default'];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (instanceReferences.length === 1) {
|
|
119
|
+
// console.log(`resolving ${xpathQuery} with ${instanceReferences[0]}`);
|
|
120
|
+
let instance;
|
|
121
|
+
if (instanceReferences[0] === 'default') {
|
|
122
|
+
const actualForeElement = fxEvaluateXPathToFirstNode(
|
|
123
|
+
'ancestor-or-self::fx-fore',
|
|
124
|
+
formElement,
|
|
125
|
+
null,
|
|
126
|
+
null,
|
|
127
|
+
{ namespaceResolver: xhtmlNamespaceResolver },
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
instance = actualForeElement && actualForeElement.querySelector('fx-instance');
|
|
131
|
+
} else {
|
|
132
|
+
instance = resolveId(instanceReferences[0], formElement, 'fx-instance');
|
|
133
|
+
}
|
|
134
|
+
if (instance && instance.hasAttribute('xpath-default-namespace')) {
|
|
135
|
+
const xpathDefaultNamespace = instance.getAttribute('xpath-default-namespace');
|
|
136
|
+
/*
|
|
137
|
+
console.log(
|
|
138
|
+
`Resolving the xpath ${xpathQuery} with the default namespace set to ${xpathDefaultNamespace}`,
|
|
139
|
+
);
|
|
140
|
+
*/
|
|
141
|
+
const resolveNamespacePrefix = prefix => {
|
|
142
|
+
if (!prefix) {
|
|
143
|
+
return xpathDefaultNamespace;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
};
|
|
147
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
148
|
+
return resolveNamespacePrefix;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (instanceReferences.length > 1) {
|
|
152
|
+
console.warn(
|
|
153
|
+
`More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
|
|
39
154
|
);
|
|
40
155
|
}
|
|
41
156
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
157
|
+
const xpathDefaultNamespace =
|
|
158
|
+
fxEvaluateXPathToString('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) ||
|
|
159
|
+
'';
|
|
45
160
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{ prefix },
|
|
51
|
-
);
|
|
161
|
+
const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
|
|
162
|
+
if (prefix === '') {
|
|
163
|
+
return xpathDefaultNamespace;
|
|
164
|
+
}
|
|
52
165
|
|
|
53
|
-
|
|
54
|
-
|
|
166
|
+
// Note: ideally we should use Node#lookupNamespaceURI. However, the nodes we are passed are
|
|
167
|
+
// XML. The best we can do is emulate the `xmlns:xxx` namespace declarations by regarding them as
|
|
168
|
+
// attributes. Which they technically ARE NOT!
|
|
169
|
+
|
|
170
|
+
return fxEvaluateXPathToString(
|
|
171
|
+
'ancestor-or-self::*/@*[name() = "xmlns:" || $prefix][last()]',
|
|
172
|
+
formElement,
|
|
173
|
+
null,
|
|
174
|
+
{ prefix },
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
179
|
+
return resolveNamespacePrefix;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function createNamespaceResolverForNode(query, contextNode, formElement) {
|
|
183
|
+
if (((contextNode && contextNode.ownerDocument) || contextNode) === window.document) {
|
|
184
|
+
// Running a query on the HTML DOM. Don't bother resolving namespaces in any other way
|
|
185
|
+
return xhtmlNamespaceResolver;
|
|
186
|
+
}
|
|
187
|
+
return createNamespaceResolver(query, formElement);
|
|
55
188
|
}
|
|
56
189
|
|
|
57
190
|
/**
|
|
@@ -62,6 +195,7 @@ function resolveNamespacePrefix(contextElement, prefix) {
|
|
|
62
195
|
function functionNameResolver({ prefix, localName }, _arity) {
|
|
63
196
|
switch (localName) {
|
|
64
197
|
// TODO: put the full XForms library functions set here
|
|
198
|
+
case 'context':
|
|
65
199
|
case 'base64encode':
|
|
66
200
|
case 'boolean-from-string':
|
|
67
201
|
case 'current':
|
|
@@ -83,6 +217,32 @@ function functionNameResolver({ prefix, localName }, _arity) {
|
|
|
83
217
|
}
|
|
84
218
|
}
|
|
85
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Get the variables in scope of the form element. These are the values of the variables that
|
|
222
|
+
* logically precede the formElement that declares the XPath
|
|
223
|
+
*
|
|
224
|
+
* @param {Node} formElement The element that declares the XPath
|
|
225
|
+
*
|
|
226
|
+
* @return {Object} A key-value mapping of the variables
|
|
227
|
+
*/
|
|
228
|
+
function getVariablesInScope(formElement) {
|
|
229
|
+
let closestActualFormElement = formElement;
|
|
230
|
+
while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
|
|
231
|
+
closestActualFormElement = closestActualFormElement.parentNode;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!closestActualFormElement) {
|
|
235
|
+
return {};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const variables = {};
|
|
239
|
+
for (const key of closestActualFormElement.inScopeVariables.keys()) {
|
|
240
|
+
const varElement = closestActualFormElement.inScopeVariables.get(key);
|
|
241
|
+
variables[key] = varElement.value;
|
|
242
|
+
}
|
|
243
|
+
return variables;
|
|
244
|
+
}
|
|
245
|
+
|
|
86
246
|
/**
|
|
87
247
|
* Evaluate an XPath to _any_ type. When possible, prefer to use any other function to ensure the
|
|
88
248
|
* type of the output is more predictable.
|
|
@@ -92,14 +252,24 @@ function functionNameResolver({ prefix, localName }, _arity) {
|
|
|
92
252
|
* @param {{parentNode}|ForeElementMixin} formElement The form element associated to the XPath
|
|
93
253
|
*/
|
|
94
254
|
export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
255
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
256
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
257
|
+
|
|
258
|
+
return fxEvaluateXPath(
|
|
259
|
+
xpath,
|
|
260
|
+
contextNode,
|
|
261
|
+
null,
|
|
262
|
+
{ ...variablesInScope, ...variables },
|
|
263
|
+
'xs:anyType',
|
|
264
|
+
{
|
|
265
|
+
currentContext: { formElement, variables },
|
|
266
|
+
moduleImports: {
|
|
267
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
268
|
+
},
|
|
269
|
+
functionNameResolver,
|
|
270
|
+
namespaceResolver,
|
|
99
271
|
},
|
|
100
|
-
|
|
101
|
-
namespaceResolver: prefix => resolveNamespacePrefix(formElement, prefix),
|
|
102
|
-
});
|
|
272
|
+
);
|
|
103
273
|
}
|
|
104
274
|
|
|
105
275
|
/**
|
|
@@ -111,20 +281,16 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
|
|
|
111
281
|
* @return {Node} The first node found by the XPath
|
|
112
282
|
*/
|
|
113
283
|
export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
namespaceResolver: prefix => resolveNamespacePrefix(formElement, prefix),
|
|
121
|
-
defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
|
|
122
|
-
moduleImports: {
|
|
123
|
-
xf: XFORMS_NAMESPACE_URI,
|
|
124
|
-
},
|
|
125
|
-
currentContext: { formElement },
|
|
284
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
285
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
286
|
+
return fxEvaluateXPathToFirstNode(xpath, contextNode, null, variablesInScope, {
|
|
287
|
+
defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
|
|
288
|
+
moduleImports: {
|
|
289
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
126
290
|
},
|
|
127
|
-
|
|
291
|
+
currentContext: { formElement },
|
|
292
|
+
namespaceResolver,
|
|
293
|
+
});
|
|
128
294
|
}
|
|
129
295
|
|
|
130
296
|
/**
|
|
@@ -136,20 +302,17 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
|
136
302
|
* @return {Node[]} All nodes
|
|
137
303
|
*/
|
|
138
304
|
export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{},
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
moduleImports: {
|
|
148
|
-
xf: XFORMS_NAMESPACE_URI,
|
|
149
|
-
},
|
|
150
|
-
namespaceResolver: prefix => resolveNamespacePrefix(formElement, prefix),
|
|
305
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
306
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
307
|
+
|
|
308
|
+
return fxEvaluateXPathToNodes(xpath, contextNode, null, variablesInScope, {
|
|
309
|
+
currentContext: { formElement },
|
|
310
|
+
functionNameResolver,
|
|
311
|
+
moduleImports: {
|
|
312
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
151
313
|
},
|
|
152
|
-
|
|
314
|
+
namespaceResolver,
|
|
315
|
+
});
|
|
153
316
|
}
|
|
154
317
|
|
|
155
318
|
/**
|
|
@@ -161,20 +324,17 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
|
161
324
|
* @return {boolean}
|
|
162
325
|
*/
|
|
163
326
|
export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{},
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
moduleImports: {
|
|
173
|
-
xf: XFORMS_NAMESPACE_URI,
|
|
174
|
-
},
|
|
175
|
-
namespaceResolver: prefix => resolveNamespacePrefix(formElement, prefix),
|
|
327
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
328
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
329
|
+
|
|
330
|
+
return fxEvaluateXPathToBoolean(xpath, contextNode, null, variablesInScope, {
|
|
331
|
+
currentContext: { formElement },
|
|
332
|
+
functionNameResolver,
|
|
333
|
+
moduleImports: {
|
|
334
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
176
335
|
},
|
|
177
|
-
|
|
336
|
+
namespaceResolver,
|
|
337
|
+
});
|
|
178
338
|
}
|
|
179
339
|
|
|
180
340
|
/**
|
|
@@ -195,7 +355,39 @@ export function evaluateXPathToString(
|
|
|
195
355
|
domFacade = null,
|
|
196
356
|
namespaceReferenceNode = formElement,
|
|
197
357
|
) {
|
|
198
|
-
|
|
358
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
359
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
360
|
+
|
|
361
|
+
return fxEvaluateXPathToString(xpath, contextNode, domFacade, variablesInScope, {
|
|
362
|
+
currentContext: { formElement },
|
|
363
|
+
functionNameResolver,
|
|
364
|
+
moduleImports: {
|
|
365
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
366
|
+
},
|
|
367
|
+
namespaceResolver,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Evaluate an XPath to a set of strings
|
|
373
|
+
*
|
|
374
|
+
* @param {string} xpath The XPath to run
|
|
375
|
+
* @param {Node} contextNode The start of the XPath
|
|
376
|
+
* @param {Node} formElement The form element associated to the XPath
|
|
377
|
+
* @param {DomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
|
|
378
|
+
* access. This is used to determine dependencies between bind elements.
|
|
379
|
+
* @param {Node} formElement The element where the XPath is defined: used for namespace resolving
|
|
380
|
+
* @return {string}
|
|
381
|
+
*/
|
|
382
|
+
export function evaluateXPathToStrings(
|
|
383
|
+
xpath,
|
|
384
|
+
contextNode,
|
|
385
|
+
formElement,
|
|
386
|
+
domFacade = null,
|
|
387
|
+
namespaceReferenceNode = formElement,
|
|
388
|
+
) {
|
|
389
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
390
|
+
return fxEvaluateXPathToStrings(
|
|
199
391
|
xpath,
|
|
200
392
|
contextNode,
|
|
201
393
|
domFacade,
|
|
@@ -207,7 +399,7 @@ export function evaluateXPathToString(
|
|
|
207
399
|
moduleImports: {
|
|
208
400
|
xf: XFORMS_NAMESPACE_URI,
|
|
209
401
|
},
|
|
210
|
-
namespaceResolver
|
|
402
|
+
namespaceResolver,
|
|
211
403
|
},
|
|
212
404
|
);
|
|
213
405
|
}
|
|
@@ -230,33 +422,23 @@ export function evaluateXPathToNumber(
|
|
|
230
422
|
domFacade = null,
|
|
231
423
|
namespaceReferenceNode = formElement,
|
|
232
424
|
) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
{},
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
moduleImports: {
|
|
242
|
-
xf: XFORMS_NAMESPACE_URI,
|
|
243
|
-
},
|
|
244
|
-
namespaceResolver: prefix => resolveNamespacePrefix(namespaceReferenceNode, prefix),
|
|
425
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
426
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
427
|
+
|
|
428
|
+
return fxEvaluateXPathToNumber(xpath, contextNode, domFacade, variablesInScope, {
|
|
429
|
+
currentContext: { formElement },
|
|
430
|
+
functionNameResolver,
|
|
431
|
+
moduleImports: {
|
|
432
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
245
433
|
},
|
|
246
|
-
|
|
434
|
+
namespaceResolver,
|
|
435
|
+
});
|
|
247
436
|
}
|
|
248
437
|
|
|
249
|
-
const xhtmlNamespaceResolver = prefix => {
|
|
250
|
-
if (!prefix) {
|
|
251
|
-
return 'http://www.w3.org/1999/xhtml';
|
|
252
|
-
}
|
|
253
|
-
return undefined;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
438
|
/**
|
|
257
439
|
* Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
|
|
258
440
|
*/
|
|
259
|
-
function resolveId(id, sourceObject, nodeName = null) {
|
|
441
|
+
export function resolveId(id, sourceObject, nodeName = null) {
|
|
260
442
|
const allMatchingTargetObjects = fxEvaluateXPathToNodes(
|
|
261
443
|
'outermost(ancestor-or-self::fx-fore[1]/(descendant::xf-fore|descendant::*[@id = $id]))[not(self::fx-fore)]',
|
|
262
444
|
sourceObject,
|
|
@@ -315,41 +497,91 @@ function resolveId(id, sourceObject, nodeName = null) {
|
|
|
315
497
|
const foundTargetObjects = allMatchingTargetObjects.filter(to =>
|
|
316
498
|
ancestorRepeatItem.contains(to),
|
|
317
499
|
);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
500
|
+
switch (foundTargetObjects.length) {
|
|
501
|
+
case 0:
|
|
502
|
+
// Nothing found: ignore
|
|
503
|
+
break;
|
|
504
|
+
case 1: {
|
|
505
|
+
// A single one is found: the target object is directly in a common repeat
|
|
506
|
+
const targetObject = foundTargetObjects[0];
|
|
507
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
return targetObject;
|
|
511
|
+
}
|
|
512
|
+
default: {
|
|
513
|
+
// Multiple target objects are found: they are in a repeat that is not common with the source object
|
|
514
|
+
// 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
|
|
515
|
+
const targetObject = foundTargetObjects.find(to =>
|
|
516
|
+
fxEvaluateXPathToNodes(
|
|
517
|
+
'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
|
|
518
|
+
to,
|
|
519
|
+
null,
|
|
520
|
+
{},
|
|
521
|
+
),
|
|
522
|
+
);
|
|
523
|
+
if (!targetObject) {
|
|
524
|
+
// Nothing valid found for whatever reason. This might be something dynamic?
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
return targetObject;
|
|
326
531
|
}
|
|
327
|
-
return targetObject;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Multiple target objects are found: they are in a repeat that is not common with the source object
|
|
331
|
-
// 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
|
|
332
|
-
const targetObject = foundTargetObjects.find(to =>
|
|
333
|
-
fxEvaluateXPathToNodes(
|
|
334
|
-
'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
|
|
335
|
-
to,
|
|
336
|
-
null,
|
|
337
|
-
{},
|
|
338
|
-
),
|
|
339
|
-
);
|
|
340
|
-
if (!targetObject) {
|
|
341
|
-
// Nothing valid found for whatever reason. This might be something dynamic?
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
if (nodeName && targetObject.localName !== nodeName) {
|
|
345
|
-
return null;
|
|
346
532
|
}
|
|
347
|
-
return targetObject;
|
|
348
533
|
}
|
|
349
534
|
// We found no target objects in common repeats. The id is unresolvable
|
|
350
535
|
return null;
|
|
351
536
|
}
|
|
352
537
|
|
|
538
|
+
const contextFunction = (dynamicContext, string) => {
|
|
539
|
+
const caller = dynamicContext.currentContext.formElement;
|
|
540
|
+
if (string) {
|
|
541
|
+
const instance = resolveId(string, caller);
|
|
542
|
+
if (instance) {
|
|
543
|
+
if (instance.nodeName === 'FX-REPEAT') {
|
|
544
|
+
const { nodeset } = instance;
|
|
545
|
+
for (let parent = caller; parent; parent = parent.parentNode) {
|
|
546
|
+
if (parent.parentNode === instance) {
|
|
547
|
+
const offset = Array.from(parent.parentNode.children).indexOf(parent);
|
|
548
|
+
return nodeset[offset];
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return instance.nodeset;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const parent = XPathUtil.getParentBindingElement(caller);
|
|
556
|
+
const p = caller.nodeName;
|
|
557
|
+
// const p = dynamicContext.domFacade.getParentElement();
|
|
558
|
+
|
|
559
|
+
if (parent) return parent;
|
|
560
|
+
return caller.getInScopeContext();
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* @param id as string
|
|
565
|
+
* @return instance data for given id serialized to string.
|
|
566
|
+
*/
|
|
567
|
+
registerCustomXPathFunction(
|
|
568
|
+
{ namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
|
|
569
|
+
[],
|
|
570
|
+
'item()?',
|
|
571
|
+
contextFunction,
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* @param id as string
|
|
576
|
+
* @return instance data for given id serialized to string.
|
|
577
|
+
*/
|
|
578
|
+
registerCustomXPathFunction(
|
|
579
|
+
{ namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
|
|
580
|
+
['xs:string'],
|
|
581
|
+
'item()?',
|
|
582
|
+
contextFunction,
|
|
583
|
+
);
|
|
584
|
+
|
|
353
585
|
/**
|
|
354
586
|
* @param id as string
|
|
355
587
|
* @return instance data for given id serialized to string.
|
|
@@ -362,8 +594,13 @@ registerCustomXPathFunction(
|
|
|
362
594
|
const { formElement } = dynamicContext.currentContext;
|
|
363
595
|
const instance = resolveId(string, formElement, 'fx-instance');
|
|
364
596
|
if (instance) {
|
|
365
|
-
|
|
366
|
-
|
|
597
|
+
if (instance.getAttribute('type') === 'json') {
|
|
598
|
+
console.warn('log() does not work for JSON yet');
|
|
599
|
+
// return JSON.stringify(instance.getDefaultContext());
|
|
600
|
+
} else {
|
|
601
|
+
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
602
|
+
return prettifyXml(def);
|
|
603
|
+
}
|
|
367
604
|
}
|
|
368
605
|
return null;
|
|
369
606
|
},
|
|
@@ -500,7 +737,7 @@ registerCustomXPathFunction(
|
|
|
500
737
|
if (repeat) {
|
|
501
738
|
return repeat.getAttribute('index');
|
|
502
739
|
}
|
|
503
|
-
return 1;
|
|
740
|
+
return Number(1);
|
|
504
741
|
},
|
|
505
742
|
);
|
|
506
743
|
|
|
@@ -554,26 +791,26 @@ registerXQueryModule(`
|
|
|
554
791
|
|
|
555
792
|
// How to run XQUERY:
|
|
556
793
|
/**
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
794
|
+
registerXQueryModule(`
|
|
795
|
+
module namespace my-custom-namespace = "my-custom-uri";
|
|
796
|
+
(:~
|
|
797
|
+
Insert attribute somewhere
|
|
798
|
+
~:)
|
|
799
|
+
declare %public %updating function my-custom-namespace:do-something ($ele as element()) as xs:boolean {
|
|
563
800
|
if ($ele/@done) then false() else
|
|
564
801
|
(insert node
|
|
565
802
|
attribute done {"true"}
|
|
566
803
|
into $ele, true())
|
|
567
804
|
};
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
805
|
+
`)
|
|
806
|
+
// At some point:
|
|
807
|
+
const contextNode = null;
|
|
808
|
+
const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
|
|
572
809
|
|
|
573
|
-
|
|
810
|
+
console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
|
|
574
811
|
|
|
575
|
-
|
|
576
|
-
|
|
812
|
+
executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
|
|
813
|
+
*/
|
|
577
814
|
|
|
578
815
|
/**
|
|
579
816
|
* @param input as string
|