@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.
Files changed (43) hide show
  1. package/README.md +20 -1
  2. package/dist/fore-all.js +10 -10
  3. package/dist/fore-debug.js +140 -0
  4. package/index.js +8 -0
  5. package/package.json +7 -6
  6. package/resources/{vars.css → fore-styles.css} +292 -0
  7. package/src/DependencyNotifyingDomFacade.js +9 -1
  8. package/src/ForeElementMixin.js +25 -17
  9. package/src/actions/abstract-action.js +8 -7
  10. package/src/actions/fx-action.js +6 -1
  11. package/src/actions/fx-dispatch.js +10 -2
  12. package/src/actions/fx-hide.js +23 -0
  13. package/src/actions/fx-insert.js +9 -9
  14. package/src/actions/fx-send.js +1 -1
  15. package/src/actions/fx-setvalue.js +2 -4
  16. package/src/actions/fx-show.js +23 -0
  17. package/src/dep_graph.js +9 -0
  18. package/src/drawdown.js +172 -0
  19. package/src/fore.js +56 -3
  20. package/src/fx-bind.js +20 -17
  21. package/src/fx-fore.js +297 -33
  22. package/src/fx-instance.js +27 -6
  23. package/src/fx-model.js +182 -37
  24. package/src/fx-submission.js +29 -7
  25. package/src/fx-var.js +43 -0
  26. package/src/getInScopeContext.js +28 -7
  27. package/src/modelitem.js +1 -0
  28. package/src/ui/abstract-control.js +17 -4
  29. package/src/ui/fx-alert.js +16 -20
  30. package/src/ui/fx-container.js +9 -4
  31. package/src/ui/fx-control.js +76 -39
  32. package/src/ui/fx-dialog.js +68 -0
  33. package/src/ui/fx-inspector.js +44 -0
  34. package/src/ui/fx-items.js +120 -0
  35. package/src/ui/fx-output.js +50 -12
  36. package/src/ui/fx-repeat.js +83 -16
  37. package/src/ui/fx-repeatitem.js +11 -4
  38. package/src/ui/fx-trigger.js +3 -0
  39. package/src/xpath-evaluation.js +372 -135
  40. package/src/xpath-util.js +52 -12
  41. package/dist/fore.js +0 -2
  42. package/resources/fore.css +0 -86
  43. package/resources/toastify.css +0 -87
@@ -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 resolveNamespacePrefix(contextElement, prefix) {
29
- if (prefix === 'xhtml') {
30
- return 'http://www.w3.org/1999/xhtml';
79
+ function createNamespaceResolver(xpathQuery, formElement) {
80
+ const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
81
+ if (cachedResolver) {
82
+ return cachedResolver;
31
83
  }
32
84
 
33
- if (prefix === '') {
34
- return (
35
- fxEvaluateXPathToString(
36
- 'ancestor-or-self::*/@xpath-default-namespace[last()]',
37
- contextElement,
38
- ) || null
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
- // Note: ideally we should use Node#lookupNamespaceURI. However, the nodes we are passed are
43
- // XML. The best we can do is emulate the `xmlns:xxx` namespace declarations by regarding them as
44
- // attributes. Which they technically ARE NOT!
157
+ const xpathDefaultNamespace =
158
+ fxEvaluateXPathToString('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) ||
159
+ '';
45
160
 
46
- const result = fxEvaluateXPathToString(
47
- 'ancestor-or-self::*/@*[name() = "xmlns:" || $prefix][last()]',
48
- contextElement,
49
- null,
50
- { prefix },
51
- );
161
+ const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
162
+ if (prefix === '') {
163
+ return xpathDefaultNamespace;
164
+ }
52
165
 
53
- console.log('result', result);
54
- return result;
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
- return fxEvaluateXPath(xpath, contextNode, null, variables, 'xs:anyType', {
96
- currentContext: { formElement, variables },
97
- moduleImports: {
98
- xf: XFORMS_NAMESPACE_URI,
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
- functionNameResolver,
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
- return fxEvaluateXPathToFirstNode(
115
- xpath,
116
- contextNode,
117
- null,
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
- return fxEvaluateXPathToNodes(
140
- xpath,
141
- contextNode,
142
- null,
143
- {},
144
- {
145
- currentContext: { formElement },
146
- functionNameResolver,
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
- return fxEvaluateXPathToBoolean(
165
- xpath,
166
- contextNode,
167
- null,
168
- {},
169
- {
170
- currentContext: { formElement },
171
- functionNameResolver,
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
- return fxEvaluateXPathToString(
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: prefix => resolveNamespacePrefix(namespaceReferenceNode, prefix),
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
- return fxEvaluateXPathToNumber(
234
- xpath,
235
- contextNode,
236
- domFacade,
237
- {},
238
- {
239
- currentContext: { formElement },
240
- functionNameResolver,
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
- if (foundTargetObjects.length === 0) {
319
- continue;
320
- }
321
- if (foundTargetObjects.length === 1) {
322
- // A single one is found: the target object is directly in a common repeat
323
- const targetObject = foundTargetObjects[0];
324
- if (nodeName && targetObject.localName !== nodeName) {
325
- return null;
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
- const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
366
- return def;
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
- registerXQueryModule(`
558
- module namespace my-custom-namespace = "my-custom-uri";
559
- (:~
560
- Insert attribute somewhere
561
- ~:)
562
- declare %public %updating function my-custom-namespace:do-something ($ele as element()) as xs:boolean {
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
- // At some point:
570
- const contextNode = null;
571
- const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
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
- console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
810
+ console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
574
811
 
575
- executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
576
- */
812
+ executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
813
+ */
577
814
 
578
815
  /**
579
816
  * @param input as string