@jinntec/fore 1.0.0-1 → 1.0.0-2
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 +10 -0
- package/dist/fore-all.js +10 -10
- package/dist/fore.js +1 -1
- package/index.js +3 -0
- package/package.json +4 -1
- package/resources/fore.css +73 -1
- package/src/DependencyNotifyingDomFacade.js +9 -1
- package/src/ForeElementMixin.js +15 -15
- package/src/actions/abstract-action.js +6 -5
- package/src/actions/fx-action.js +1 -0
- package/src/actions/fx-dispatch.js +10 -2
- package/src/actions/fx-insert.js +8 -8
- package/src/actions/fx-send.js +1 -1
- package/src/actions/fx-setvalue.js +1 -3
- package/src/dep_graph.js +9 -0
- package/src/drawdown.js +172 -0
- package/src/fore.js +53 -3
- package/src/fx-bind.js +8 -7
- package/src/fx-fore.js +180 -31
- package/src/fx-instance.js +27 -6
- package/src/fx-model.js +175 -34
- package/src/fx-submission.js +26 -7
- package/src/getInScopeContext.js +1 -1
- package/src/ui/abstract-control.js +8 -3
- 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-inspector.js +44 -0
- package/src/ui/fx-items.js +117 -0
- package/src/ui/fx-output.js +42 -2
- package/src/ui/fx-repeat.js +47 -15
- package/src/ui/fx-repeatitem.js +11 -4
- package/src/xpath-evaluation.js +242 -80
- package/src/xpath-util.js +47 -12
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;
|
|
83
|
+
}
|
|
84
|
+
|
|
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'];
|
|
31
116
|
}
|
|
32
117
|
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
51
|
-
|
|
161
|
+
const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
|
|
162
|
+
if (prefix === '') {
|
|
163
|
+
return xpathDefaultNamespace;
|
|
164
|
+
}
|
|
165
|
+
|
|
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!
|
|
52
169
|
|
|
53
|
-
|
|
54
|
-
|
|
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':
|
|
@@ -92,13 +226,15 @@ function functionNameResolver({ prefix, localName }, _arity) {
|
|
|
92
226
|
* @param {{parentNode}|ForeElementMixin} formElement The form element associated to the XPath
|
|
93
227
|
*/
|
|
94
228
|
export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
|
|
229
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
230
|
+
|
|
95
231
|
return fxEvaluateXPath(xpath, contextNode, null, variables, 'xs:anyType', {
|
|
96
232
|
currentContext: { formElement, variables },
|
|
97
233
|
moduleImports: {
|
|
98
234
|
xf: XFORMS_NAMESPACE_URI,
|
|
99
235
|
},
|
|
100
236
|
functionNameResolver,
|
|
101
|
-
namespaceResolver
|
|
237
|
+
namespaceResolver,
|
|
102
238
|
});
|
|
103
239
|
}
|
|
104
240
|
|
|
@@ -111,18 +247,19 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
|
|
|
111
247
|
* @return {Node} The first node found by the XPath
|
|
112
248
|
*/
|
|
113
249
|
export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
250
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
114
251
|
return fxEvaluateXPathToFirstNode(
|
|
115
252
|
xpath,
|
|
116
253
|
contextNode,
|
|
117
254
|
null,
|
|
118
255
|
{},
|
|
119
256
|
{
|
|
120
|
-
namespaceResolver: prefix => resolveNamespacePrefix(formElement, prefix),
|
|
121
257
|
defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
|
|
122
258
|
moduleImports: {
|
|
123
259
|
xf: XFORMS_NAMESPACE_URI,
|
|
124
260
|
},
|
|
125
261
|
currentContext: { formElement },
|
|
262
|
+
namespaceResolver,
|
|
126
263
|
},
|
|
127
264
|
);
|
|
128
265
|
}
|
|
@@ -136,6 +273,7 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
|
136
273
|
* @return {Node[]} All nodes
|
|
137
274
|
*/
|
|
138
275
|
export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
276
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
139
277
|
return fxEvaluateXPathToNodes(
|
|
140
278
|
xpath,
|
|
141
279
|
contextNode,
|
|
@@ -147,7 +285,7 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
|
147
285
|
moduleImports: {
|
|
148
286
|
xf: XFORMS_NAMESPACE_URI,
|
|
149
287
|
},
|
|
150
|
-
namespaceResolver
|
|
288
|
+
namespaceResolver,
|
|
151
289
|
},
|
|
152
290
|
);
|
|
153
291
|
}
|
|
@@ -161,6 +299,7 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
|
161
299
|
* @return {boolean}
|
|
162
300
|
*/
|
|
163
301
|
export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
302
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
164
303
|
return fxEvaluateXPathToBoolean(
|
|
165
304
|
xpath,
|
|
166
305
|
contextNode,
|
|
@@ -172,7 +311,7 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
|
172
311
|
moduleImports: {
|
|
173
312
|
xf: XFORMS_NAMESPACE_URI,
|
|
174
313
|
},
|
|
175
|
-
namespaceResolver
|
|
314
|
+
namespaceResolver,
|
|
176
315
|
},
|
|
177
316
|
);
|
|
178
317
|
}
|
|
@@ -195,6 +334,7 @@ export function evaluateXPathToString(
|
|
|
195
334
|
domFacade = null,
|
|
196
335
|
namespaceReferenceNode = formElement,
|
|
197
336
|
) {
|
|
337
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
198
338
|
return fxEvaluateXPathToString(
|
|
199
339
|
xpath,
|
|
200
340
|
contextNode,
|
|
@@ -207,7 +347,7 @@ export function evaluateXPathToString(
|
|
|
207
347
|
moduleImports: {
|
|
208
348
|
xf: XFORMS_NAMESPACE_URI,
|
|
209
349
|
},
|
|
210
|
-
namespaceResolver
|
|
350
|
+
namespaceResolver,
|
|
211
351
|
},
|
|
212
352
|
);
|
|
213
353
|
}
|
|
@@ -230,6 +370,7 @@ export function evaluateXPathToNumber(
|
|
|
230
370
|
domFacade = null,
|
|
231
371
|
namespaceReferenceNode = formElement,
|
|
232
372
|
) {
|
|
373
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
233
374
|
return fxEvaluateXPathToNumber(
|
|
234
375
|
xpath,
|
|
235
376
|
contextNode,
|
|
@@ -241,22 +382,15 @@ export function evaluateXPathToNumber(
|
|
|
241
382
|
moduleImports: {
|
|
242
383
|
xf: XFORMS_NAMESPACE_URI,
|
|
243
384
|
},
|
|
244
|
-
namespaceResolver
|
|
385
|
+
namespaceResolver,
|
|
245
386
|
},
|
|
246
387
|
);
|
|
247
388
|
}
|
|
248
389
|
|
|
249
|
-
const xhtmlNamespaceResolver = prefix => {
|
|
250
|
-
if (!prefix) {
|
|
251
|
-
return 'http://www.w3.org/1999/xhtml';
|
|
252
|
-
}
|
|
253
|
-
return undefined;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
390
|
/**
|
|
257
391
|
* Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
|
|
258
392
|
*/
|
|
259
|
-
function resolveId(id, sourceObject, nodeName = null) {
|
|
393
|
+
export function resolveId(id, sourceObject, nodeName = null) {
|
|
260
394
|
const allMatchingTargetObjects = fxEvaluateXPathToNodes(
|
|
261
395
|
'outermost(ancestor-or-self::fx-fore[1]/(descendant::xf-fore|descendant::*[@id = $id]))[not(self::fx-fore)]',
|
|
262
396
|
sourceObject,
|
|
@@ -315,41 +449,64 @@ function resolveId(id, sourceObject, nodeName = null) {
|
|
|
315
449
|
const foundTargetObjects = allMatchingTargetObjects.filter(to =>
|
|
316
450
|
ancestorRepeatItem.contains(to),
|
|
317
451
|
);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
452
|
+
switch (foundTargetObjects.length) {
|
|
453
|
+
case 0:
|
|
454
|
+
// Nothing found: ignore
|
|
455
|
+
break;
|
|
456
|
+
case 1: {
|
|
457
|
+
// A single one is found: the target object is directly in a common repeat
|
|
458
|
+
const targetObject = foundTargetObjects[0];
|
|
459
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
return targetObject;
|
|
463
|
+
}
|
|
464
|
+
default: {
|
|
465
|
+
// Multiple target objects are found: they are in a repeat that is not common with the source object
|
|
466
|
+
// 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
|
|
467
|
+
const targetObject = foundTargetObjects.find(to =>
|
|
468
|
+
fxEvaluateXPathToNodes(
|
|
469
|
+
'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
|
|
470
|
+
to,
|
|
471
|
+
null,
|
|
472
|
+
{},
|
|
473
|
+
),
|
|
474
|
+
);
|
|
475
|
+
if (!targetObject) {
|
|
476
|
+
// Nothing valid found for whatever reason. This might be something dynamic?
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
return targetObject;
|
|
326
483
|
}
|
|
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
484
|
}
|
|
347
|
-
return targetObject;
|
|
348
485
|
}
|
|
349
486
|
// We found no target objects in common repeats. The id is unresolvable
|
|
350
487
|
return null;
|
|
351
488
|
}
|
|
352
489
|
|
|
490
|
+
/**
|
|
491
|
+
* @param id as string
|
|
492
|
+
* @return instance data for given id serialized to string.
|
|
493
|
+
*/
|
|
494
|
+
registerCustomXPathFunction(
|
|
495
|
+
{ namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
|
|
496
|
+
[],
|
|
497
|
+
'item()?',
|
|
498
|
+
(dynamicContext, string) => {
|
|
499
|
+
const caller = dynamicContext.currentContext.formElement;
|
|
500
|
+
const parent = XPathUtil.getParentBindingElement(caller);
|
|
501
|
+
// const instance = resolveId('default', caller, 'fx-instance');
|
|
502
|
+
const p = caller.nodeName;
|
|
503
|
+
// const p = dynamicContext.domFacade.getParentElement();
|
|
504
|
+
|
|
505
|
+
if (parent) return parent;
|
|
506
|
+
return caller.getInScopeContext();
|
|
507
|
+
},
|
|
508
|
+
);
|
|
509
|
+
|
|
353
510
|
/**
|
|
354
511
|
* @param id as string
|
|
355
512
|
* @return instance data for given id serialized to string.
|
|
@@ -362,8 +519,13 @@ registerCustomXPathFunction(
|
|
|
362
519
|
const { formElement } = dynamicContext.currentContext;
|
|
363
520
|
const instance = resolveId(string, formElement, 'fx-instance');
|
|
364
521
|
if (instance) {
|
|
365
|
-
|
|
366
|
-
|
|
522
|
+
if (instance.getAttribute('type') === 'json') {
|
|
523
|
+
console.warn('log() does not work for JSON yet');
|
|
524
|
+
// return JSON.stringify(instance.getDefaultContext());
|
|
525
|
+
} else {
|
|
526
|
+
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
527
|
+
return prettifyXml(def);
|
|
528
|
+
}
|
|
367
529
|
}
|
|
368
530
|
return null;
|
|
369
531
|
},
|
|
@@ -500,7 +662,7 @@ registerCustomXPathFunction(
|
|
|
500
662
|
if (repeat) {
|
|
501
663
|
return repeat.getAttribute('index');
|
|
502
664
|
}
|
|
503
|
-
return 1;
|
|
665
|
+
return Number(1);
|
|
504
666
|
},
|
|
505
667
|
);
|
|
506
668
|
|
|
@@ -554,26 +716,26 @@ registerXQueryModule(`
|
|
|
554
716
|
|
|
555
717
|
// How to run XQUERY:
|
|
556
718
|
/**
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
719
|
+
registerXQueryModule(`
|
|
720
|
+
module namespace my-custom-namespace = "my-custom-uri";
|
|
721
|
+
(:~
|
|
722
|
+
Insert attribute somewhere
|
|
723
|
+
~:)
|
|
724
|
+
declare %public %updating function my-custom-namespace:do-something ($ele as element()) as xs:boolean {
|
|
563
725
|
if ($ele/@done) then false() else
|
|
564
726
|
(insert node
|
|
565
727
|
attribute done {"true"}
|
|
566
728
|
into $ele, true())
|
|
567
729
|
};
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
730
|
+
`)
|
|
731
|
+
// At some point:
|
|
732
|
+
const contextNode = null;
|
|
733
|
+
const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
|
|
572
734
|
|
|
573
|
-
|
|
735
|
+
console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
|
|
574
736
|
|
|
575
|
-
|
|
576
|
-
|
|
737
|
+
executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
|
|
738
|
+
*/
|
|
577
739
|
|
|
578
740
|
/**
|
|
579
741
|
* @param input as string
|
package/src/xpath-util.js
CHANGED
|
@@ -9,14 +9,54 @@ import * as fx from 'fontoxpath';
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export class XPathUtil {
|
|
12
|
+
static getParentBindingElement(start) {
|
|
13
|
+
/* if (start.parentNode.host) {
|
|
14
|
+
const { host } = start.parentNode;
|
|
15
|
+
if (host.hasAttribute('ref')) {
|
|
16
|
+
return host;
|
|
17
|
+
}
|
|
18
|
+
} else */
|
|
19
|
+
if (start.parentNode && start.parentNode.nodeType !== Node.DOCUMENT_NODE) {
|
|
20
|
+
if (start.parentNode.hasAttribute('ref')) {
|
|
21
|
+
return start.parentNode;
|
|
22
|
+
}
|
|
23
|
+
XPathUtil.getParentBindingElement(start.parentNode);
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
static isAbsolutePath(path) {
|
|
13
29
|
return path != null && (path.startsWith('/') || path.startsWith('instance('));
|
|
14
30
|
}
|
|
15
31
|
|
|
32
|
+
static isRepeated(element) {
|
|
33
|
+
return element.parentElement.closest('fx-repeatitem');
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
static isSelfReference(ref) {
|
|
17
37
|
return ref === '.' || ref === './text()' || ref === 'text()' || ref === '' || ref === null;
|
|
18
38
|
}
|
|
19
39
|
|
|
40
|
+
static getDefaultInstance(boundElement) {
|
|
41
|
+
// const fore = boundElement.closest('fx-fore');
|
|
42
|
+
const fore = XPathUtil.getForeElement(boundElement);
|
|
43
|
+
const defaultInstance = fore.querySelector('fx-instance');
|
|
44
|
+
if (!defaultInstance) {
|
|
45
|
+
throw new Error('no default instance present');
|
|
46
|
+
}
|
|
47
|
+
return defaultInstance;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static getForeElement(start) {
|
|
51
|
+
if (start.nodeName === 'FX-FORE') {
|
|
52
|
+
return start;
|
|
53
|
+
}
|
|
54
|
+
if (start.parentNode) {
|
|
55
|
+
return XPathUtil.getForeElement(start.parentNode);
|
|
56
|
+
}
|
|
57
|
+
throw new Error('no Fore element present');
|
|
58
|
+
}
|
|
59
|
+
|
|
20
60
|
// todo: this will need more work to look upward for instance() expr.
|
|
21
61
|
static getInstanceId(ref) {
|
|
22
62
|
if (!ref) {
|
|
@@ -32,25 +72,20 @@ export class XPathUtil {
|
|
|
32
72
|
// todo: certainly not ideal to rely on duplicating instance id on instance document - better way later ;)
|
|
33
73
|
static getPath(node) {
|
|
34
74
|
const path = fx.evaluateXPath('path()', node);
|
|
75
|
+
/*
|
|
35
76
|
const instanceId = node.ownerDocument.firstElementChild.getAttribute('id');
|
|
36
77
|
if (instanceId !== null && instanceId !== 'default') {
|
|
37
78
|
return `#${instanceId}${XPathUtil.shortenPath(path)}`;
|
|
38
79
|
}
|
|
80
|
+
*/
|
|
39
81
|
return XPathUtil.shortenPath(path);
|
|
40
82
|
}
|
|
41
83
|
|
|
42
84
|
static shortenPath(path) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const q = step.split('{}');
|
|
49
|
-
result += `/${q[1]}`;
|
|
50
|
-
} else {
|
|
51
|
-
result += `/${step}`;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
85
|
+
const tmp = path.replaceAll(/(Q{(.*?)\})/g, '');
|
|
86
|
+
// cut off leading slash
|
|
87
|
+
const tmp1 = tmp.substring(1, tmp.length);
|
|
88
|
+
// ### cut-off root node ref
|
|
89
|
+
return tmp1.substring(tmp1.indexOf('/'), tmp.length);
|
|
55
90
|
}
|
|
56
91
|
}
|