@jinntec/fore 2.1.1 → 2.3.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.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/dist/fore-dev.js +2 -2
  3. package/dist/fore-dev.js.map +1 -1
  4. package/dist/fore.js +2 -2
  5. package/dist/fore.js.map +1 -1
  6. package/index.js +4 -2
  7. package/package.json +10 -38
  8. package/resources/fore.css +263 -262
  9. package/resources/vars.css +8 -0
  10. package/src/DependencyNotifyingDomFacade.js +1 -0
  11. package/src/ForeElementMixin.js +243 -228
  12. package/src/actions/abstract-action.js +407 -398
  13. package/src/actions/fx-action.js +6 -6
  14. package/src/actions/fx-append.js +1 -1
  15. package/src/actions/fx-call.js +63 -62
  16. package/src/actions/fx-confirm.js +11 -11
  17. package/src/actions/fx-construct-done.js +4 -4
  18. package/src/actions/fx-copy.js +36 -36
  19. package/src/actions/fx-delete.js +77 -79
  20. package/src/actions/fx-dispatch.js +14 -14
  21. package/src/actions/fx-hide.js +15 -15
  22. package/src/actions/fx-insert.js +9 -12
  23. package/src/actions/fx-insertchild.js +88 -0
  24. package/src/actions/fx-load.js +188 -185
  25. package/src/actions/fx-message.js +18 -19
  26. package/src/actions/fx-refresh.js +10 -10
  27. package/src/actions/fx-reload.js +16 -14
  28. package/src/actions/fx-replace.js +0 -1
  29. package/src/actions/fx-reset.js +31 -31
  30. package/src/actions/fx-send.js +59 -52
  31. package/src/actions/fx-setattribute.js +48 -49
  32. package/src/actions/fx-setfocus.js +54 -58
  33. package/src/actions/fx-setvalue.js +85 -81
  34. package/src/actions/fx-show.js +11 -11
  35. package/src/actions/fx-toggle.js +2 -2
  36. package/src/actions/fx-toggleboolean.js +38 -39
  37. package/src/actions/fx-unmodified.js +27 -0
  38. package/src/actions/fx-update.js +6 -6
  39. package/src/dep_graph.js +1 -1
  40. package/src/drawdown.js +23 -30
  41. package/src/events.js +19 -19
  42. package/src/fore.js +143 -125
  43. package/src/functions/common-function.js +24 -25
  44. package/src/functions/fx-function.js +6 -99
  45. package/src/functions/fx-functionlib.js +56 -0
  46. package/src/functions/registerFunction.js +112 -0
  47. package/src/fx-bind.js +19 -24
  48. package/src/fx-connection.js +226 -0
  49. package/src/fx-fore.js +844 -815
  50. package/src/fx-header.js +4 -4
  51. package/src/fx-instance.js +25 -29
  52. package/src/fx-model.js +405 -380
  53. package/src/fx-submission.js +399 -397
  54. package/src/fx-var.js +13 -12
  55. package/src/getInScopeContext.js +102 -89
  56. package/src/json-util.js +24 -24
  57. package/src/lab/fore-component.js +48 -52
  58. package/src/lab/instance-inspector.js +13 -16
  59. package/src/modelitem.js +48 -10
  60. package/src/relevance.js +12 -16
  61. package/src/tools/adi.js +858 -812
  62. package/src/tools/fx-action-log.js +377 -295
  63. package/src/tools/fx-devtools.js +210 -210
  64. package/src/tools/fx-dom-inspector.js +123 -122
  65. package/src/tools/fx-json-instance.js +262 -253
  66. package/src/tools/fx-log-item.js +6 -11
  67. package/src/tools/fx-log-settings.js +358 -301
  68. package/src/tools/fx-minimap.js +186 -177
  69. package/src/tools/helpers.js +1 -1
  70. package/src/ui/abstract-control.js +73 -53
  71. package/src/ui/fx-case.js +59 -7
  72. package/src/ui/fx-container.js +13 -14
  73. package/src/ui/fx-control.js +572 -538
  74. package/src/ui/fx-dialog.js +3 -3
  75. package/src/ui/fx-droptarget.js +3 -4
  76. package/src/ui/fx-group.js +4 -2
  77. package/src/ui/fx-hint.js +3 -0
  78. package/src/ui/fx-inspector.js +5 -5
  79. package/src/ui/fx-items.js +11 -11
  80. package/src/ui/fx-output.js +10 -12
  81. package/src/ui/fx-repeat-attributes.js +23 -27
  82. package/src/ui/fx-repeat.js +347 -344
  83. package/src/ui/fx-repeatitem.js +75 -68
  84. package/src/ui/fx-switch.js +39 -14
  85. package/src/ui/fx-trigger.js +4 -4
  86. package/src/withDraggability.js +193 -191
  87. package/src/xpath-evaluation.js +969 -958
  88. package/src/xpath-util.js +161 -134
@@ -1,18 +1,18 @@
1
1
  import {
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,
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
13
 
14
- import {XPathUtil} from './xpath-util.js';
15
- import {prettifyXml} from './functions/common-function.js';
14
+ import { XPathUtil } from './xpath-util.js';
15
+ import { prettifyXml } from './functions/common-function.js';
16
16
 
17
17
  const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
18
18
 
@@ -23,35 +23,42 @@ const createdNamespaceResolversByXPathQueryAndNode = new Map();
23
23
  export const globallyDeclaredFunctionLocalNames = [];
24
24
 
25
25
  function getCachedNamespaceResolver(xpath, node) {
26
- if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
27
- return null;
28
- }
29
- return createdNamespaceResolversByXPathQueryAndNode.get(xpath).get(node) || null;
26
+ if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
27
+ return null;
28
+ }
29
+ return createdNamespaceResolversByXPathQueryAndNode.get(xpath).get(node) || null;
30
30
  }
31
31
 
32
32
  function setCachedNamespaceResolver(xpath, node, resolver) {
33
- if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
34
- return createdNamespaceResolversByXPathQueryAndNode.set(xpath, new Map());
35
- }
36
- return createdNamespaceResolversByXPathQueryAndNode.get(xpath).set(node, resolver);
33
+ if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
34
+ return createdNamespaceResolversByXPathQueryAndNode.set(xpath, new Map());
35
+ }
36
+ return createdNamespaceResolversByXPathQueryAndNode.get(xpath).set(node, resolver);
37
37
  }
38
38
 
39
39
  const xhtmlNamespaceResolver = prefix => {
40
- if (!prefix) {
41
- return 'http://www.w3.org/1999/xhtml';
42
- }
43
- return undefined;
40
+ if (!prefix) {
41
+ return 'http://www.w3.org/1999/xhtml';
42
+ }
43
+ return undefined;
44
44
  };
45
45
  export function isInShadow(node) {
46
- return node.getRootNode() instanceof ShadowRoot;
46
+ return node.getRootNode() instanceof ShadowRoot;
47
47
  }
48
48
 
49
49
  /**
50
50
  * Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
51
+ *
52
+ * @param {string} id
53
+ * @param {Node} sourceObject
54
+ * @param {string} nodeName
55
+ *
56
+ * @returns {HTMLElement} The element with that ID, resolved with respect to repeats
51
57
  */
52
58
  export function resolveId(id, sourceObject, nodeName = null) {
53
- const query = 'outermost(ancestor-or-self::fx-fore[1]/(descendant::fx-fore|descendant::*[@id = $id]))[not(self::fx-fore)]';
54
- /*
59
+ const query =
60
+ 'outermost(ancestor-or-self::fx-fore[1]/(descendant::fx-fore|descendant::*[@id = $id]))[not(self::fx-fore)]';
61
+ /*
55
62
  if (nodeName === 'fx-instance') {
56
63
  // Instance elements can only be in the `model` element
57
64
  // query = 'ancestor-or-self::fx-fore[1]/fx-model/fx-instance[@id = $id]';
@@ -63,119 +70,122 @@ export function resolveId(id, sourceObject, nodeName = null) {
63
70
  return document.getElementById(id);
64
71
  }
65
72
  */
66
- if (sourceObject.nodeType === Node.TEXT_NODE) {
67
- sourceObject = sourceObject.parentNode;
68
- }
69
- if (sourceObject.nodeType === Node.ATTRIBUTE_NODE) {
70
- sourceObject = sourceObject.ownerElement;
71
- }
72
- if(sourceObject.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE){
73
- sourceObject = sourceObject.parentNode.host;
73
+ if (sourceObject.nodeType === Node.TEXT_NODE) {
74
+ sourceObject = sourceObject.parentNode;
75
+ }
76
+ if (sourceObject.nodeType === Node.ATTRIBUTE_NODE) {
77
+ sourceObject = sourceObject.ownerElement;
78
+ }
79
+ if (sourceObject.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
80
+ sourceObject = sourceObject.parentNode.host;
81
+ }
82
+ const ownerForm =
83
+ sourceObject.localName === 'fx-fore' ? sourceObject : sourceObject.closest('fx-fore');
84
+ const elementsWithId = ownerForm.querySelectorAll(`[id='${id}']`);
85
+ if (elementsWithId.length === 1) {
86
+ // A single one is found. Assume no ID reuse.
87
+ const targetObject = elementsWithId[0];
88
+ if (nodeName && targetObject.localName !== nodeName) {
89
+ return null;
90
+ }
91
+ return targetObject;
92
+ }
93
+
94
+ const allMatchingTargetObjects = fxEvaluateXPathToNodes(
95
+ query,
96
+ sourceObject,
97
+ null,
98
+ { id },
99
+ { namespaceResolver: xhtmlNamespaceResolver },
100
+ );
101
+
102
+ if (allMatchingTargetObjects.length === 0) {
103
+ return null;
104
+ }
105
+
106
+ if (
107
+ allMatchingTargetObjects.length === 1 &&
108
+ fxEvaluateXPathToBoolean(
109
+ '(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore',
110
+ allMatchingTargetObjects[0],
111
+ null,
112
+ null,
113
+ { namespaceResolver: xhtmlNamespaceResolver },
114
+ )
115
+ ) {
116
+ // If the target element is not repeated, then the search for the target object is trivial since
117
+ // there is only one associated with the target element that bears the matching ID. This is true
118
+ // regardless of whether or not the source object is repeated. However, if the target element is
119
+ // repeated, then additional information must be used to help select a target object from among
120
+ // those associated with the identified target element.
121
+ const targetObject = allMatchingTargetObjects[0];
122
+ if (nodeName && targetObject.localName !== nodeName) {
123
+ return null;
74
124
  }
75
- const ownerForm = sourceObject.localName === 'fx-fore' ? sourceObject : sourceObject.closest('fx-fore');
76
- const elementsWithId = ownerForm.querySelectorAll(`[id='${id}']`);
77
- if (elementsWithId.length === 1) {
78
- // A single one is found. Assume no ID reuse.
79
- const targetObject = elementsWithId[0];
125
+ return targetObject;
126
+ }
127
+
128
+ // SPEC:
129
+
130
+ // 12.2.1 References to Elements within a repeat Element
131
+
132
+ // When the target element that is identified by the IDREF of a source object has one or more
133
+ // repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
134
+ // subsets, those in common with the source element and those that are not in common. Any ancestor
135
+ // repeat elements of the target element not in common with the source element are descendants of
136
+ // the repeat elements that the source and target element have in common, if any.
137
+
138
+ // For the repeat elements that are in common, the desired target object exists in the same set of
139
+ // run-time objects that contains the source object. Then, for each ancestor repeat of the target
140
+ // element that is not in common with the source element, the current index of the repeat
141
+ // determines the set of run-time objects that contains the desired target object.
142
+ for (const ancestorRepeatItem of fxEvaluateXPathToNodes(
143
+ 'ancestor::fx-repeatitem => reverse()',
144
+ sourceObject,
145
+ null,
146
+ null,
147
+ { namespaceResolver: xhtmlNamespaceResolver },
148
+ )) {
149
+ const foundTargetObjects = allMatchingTargetObjects.filter(to =>
150
+ XPathUtil.contains(ancestorRepeatItem, to),
151
+ );
152
+ switch (foundTargetObjects.length) {
153
+ case 0:
154
+ // Nothing found: ignore
155
+ break;
156
+ case 1: {
157
+ // A single one is found: the target object is directly in a common repeat
158
+ const targetObject = foundTargetObjects[0];
80
159
  if (nodeName && targetObject.localName !== nodeName) {
81
- return null;
160
+ return null;
82
161
  }
83
162
  return targetObject;
84
- }
85
-
86
- const allMatchingTargetObjects = fxEvaluateXPathToNodes(query,
87
- sourceObject,
88
- null,
89
- {id},
90
- {namespaceResolver: xhtmlNamespaceResolver},
91
- );
92
-
93
- if (allMatchingTargetObjects.length === 0) {
94
- return null;
95
- }
96
-
97
- if (
98
- allMatchingTargetObjects.length === 1 &&
99
- fxEvaluateXPathToBoolean(
100
- '(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore',
101
- allMatchingTargetObjects[0],
163
+ }
164
+ default: {
165
+ // Multiple target objects are found: they are in a repeat that is not common with the
166
+ // source object We found a target object in a common repeat! We now need to find the one
167
+ // that is in the repeatitem identified at the current index
168
+ const targetObject = foundTargetObjects.find(to =>
169
+ fxEvaluateXPathToNodes(
170
+ 'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
171
+ to,
102
172
  null,
103
- null,
104
- {namespaceResolver: xhtmlNamespaceResolver},
105
- )
106
- ) {
107
- // If the target element is not repeated, then the search for the target object is trivial since
108
- // there is only one associated with the target element that bears the matching ID. This is true
109
- // regardless of whether or not the source object is repeated. However, if the target element is
110
- // repeated, then additional information must be used to help select a target object from among
111
- // those associated with the identified target element.
112
- const targetObject = allMatchingTargetObjects[0];
173
+ {},
174
+ ),
175
+ );
176
+ if (!targetObject) {
177
+ // Nothing valid found for whatever reason. This might be something dynamic?
178
+ return null;
179
+ }
113
180
  if (nodeName && targetObject.localName !== nodeName) {
114
- return null;
181
+ return null;
115
182
  }
116
183
  return targetObject;
184
+ }
117
185
  }
118
-
119
- // SPEC:
120
-
121
- // 12.2.1 References to Elements within a repeat Element
122
-
123
- // When the target element that is identified by the IDREF of a source object has one or more
124
- // repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
125
- // subsets, those in common with the source element and those that are not in common. Any ancestor
126
- // repeat elements of the target element not in common with the source element are descendants of
127
- // the repeat elements that the source and target element have in common, if any.
128
-
129
- // For the repeat elements that are in common, the desired target object exists in the same set of
130
- // run-time objects that contains the source object. Then, for each ancestor repeat of the target
131
- // element that is not in common with the source element, the current index of the repeat
132
- // determines the set of run-time objects that contains the desired target object.
133
- for (const ancestorRepeatItem of fxEvaluateXPathToNodes(
134
- 'ancestor::fx-repeatitem => reverse()',
135
- sourceObject,
136
- null,
137
- null,
138
- {namespaceResolver: xhtmlNamespaceResolver},
139
- )) {
140
- const foundTargetObjects = allMatchingTargetObjects.filter(to =>
141
- XPathUtil.contains(ancestorRepeatItem, to),
142
- );
143
- switch (foundTargetObjects.length) {
144
- case 0:
145
- // Nothing found: ignore
146
- break;
147
- case 1: {
148
- // A single one is found: the target object is directly in a common repeat
149
- const targetObject = foundTargetObjects[0];
150
- if (nodeName && targetObject.localName !== nodeName) {
151
- return null;
152
- }
153
- return targetObject;
154
- }
155
- default: {
156
- // Multiple target objects are found: they are in a repeat that is not common with the source object
157
- // 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
158
- const targetObject = foundTargetObjects.find(to =>
159
- fxEvaluateXPathToNodes(
160
- 'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
161
- to,
162
- null,
163
- {},
164
- ),
165
- );
166
- if (!targetObject) {
167
- // Nothing valid found for whatever reason. This might be something dynamic?
168
- return null;
169
- }
170
- if (nodeName && targetObject.localName !== nodeName) {
171
- return null;
172
- }
173
- return targetObject;
174
- }
175
- }
176
- }
177
- // We found no target objects in common repeats. The id is unresolvable
178
- return null;
186
+ }
187
+ // We found no target objects in common repeats. The id is unresolvable
188
+ return null;
179
189
  }
180
190
 
181
191
  // Make namespace resolving use the `instance` element that is related to here
@@ -184,35 +194,39 @@ const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
184
194
  const instanceReferencesByQuery = new Map();
185
195
 
186
196
  function findInstanceReferences(xpathQuery) {
187
- if (!xpathQuery.includes('instance')) {
188
- // No call to the instance function anyway: short-circuit and prevent AST processing
189
- return [];
190
- }
191
- if (instanceReferencesByQuery.has(xpathQuery)) {
192
- return instanceReferencesByQuery.get(xpathQuery);
193
- }
194
- const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
195
- const instanceReferences = fxEvaluateXPathToStrings(
196
- `descendant::xqx:functionCallExpr
197
+ if (!xpathQuery.includes('instance')) {
198
+ // No call to the instance function anyway: short-circuit and prevent AST processing
199
+ return [];
200
+ }
201
+ if (instanceReferencesByQuery.has(xpathQuery)) {
202
+ return instanceReferencesByQuery.get(xpathQuery);
203
+ }
204
+ const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
205
+ const instanceReferences = fxEvaluateXPathToStrings(
206
+ `descendant::xqx:functionCallExpr
197
207
  [xqx:functionName = "instance"]
198
208
  /xqx:arguments
199
209
  /xqx:stringConstantExpr
200
210
  /xqx:value`,
201
- xpathAST,
202
- null,
203
- {},
204
- {
205
- namespaceResolver: prefix =>
206
- prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
207
- },
208
- );
211
+ xpathAST,
212
+ null,
213
+ {},
214
+ {
215
+ namespaceResolver: prefix =>
216
+ prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
217
+ },
218
+ );
209
219
 
210
- instanceReferencesByQuery.set(xpathQuery, instanceReferences);
220
+ instanceReferencesByQuery.set(xpathQuery, instanceReferences);
211
221
 
212
- return instanceReferences;
222
+ return instanceReferences;
213
223
  }
224
+ /**
225
+ * @typedef {function(string):string} NamespaceResolver
226
+ */
214
227
 
215
228
  /**
229
+ * @function
216
230
  * Resolve a namespace. Needs a namespace prefix and the element that is most closely related to the
217
231
  * XPath in which the namespace is being resolved. The prefix will be resolved by using the
218
232
  * ancestry of said element.
@@ -220,157 +234,167 @@ function findInstanceReferences(xpathQuery) {
220
234
  * It has two ways of doing so:
221
235
  *
222
236
  * - If the prefix is defined in an `xmlns:XXX="YYY"` namespace declaration, it will return 'YYY'.
223
- * - If the prefix is the empty prefix and there is an `xpath-default-namespace="YYY"` attribute in the
224
- * - ancestry, that attribute will be used and 'YYY' will be returned
237
+ * - If the prefix is the empty prefix and there is an `xpath-default-namespace="YYY"` attribute in
238
+ * - the * ancestry, that attribute will be used and 'YYY' will be returned
225
239
  *
226
- * @param {Node} contextElement The element that is most closely related with the XPath in which this prefix is resolved.
227
- * @param {string} prefix The prefix to resolve
240
+ * @param {string} xpathQuery
241
+ * @param {HTMLElement} formElement
242
+ * @returns {NamespaceResolver} The namespace resolver for this context
228
243
  */
229
244
  function createNamespaceResolver(xpathQuery, formElement) {
230
- const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
231
- if (cachedResolver) {
232
- return cachedResolver;
233
- }
234
- let instanceReferences = findInstanceReferences(xpathQuery);
235
- if (instanceReferences.length === 0) {
236
- // No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
237
- const ancestorComponent = formElement.parentNode &&
238
- formElement.parentNode.nodeType === formElement.ELEMENT &&
239
- formElement.parentNode.closest('[ref]');
240
- if (ancestorComponent) {
241
- const resolver = createNamespaceResolver(
242
- ancestorComponent.getAttribute('ref'),
243
- ancestorComponent,
244
- );
245
- setCachedNamespaceResolver(xpathQuery, formElement, resolver);
246
- return resolver;
247
- }
248
- // Nothing found: let's just assume we're supposed to use the `default` instance
249
- instanceReferences = ['default'];
245
+ const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
246
+ if (cachedResolver) {
247
+ return cachedResolver;
248
+ }
249
+ let instanceReferences = findInstanceReferences(xpathQuery);
250
+ if (instanceReferences.length === 0) {
251
+ // No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
252
+ const ancestorComponent =
253
+ formElement.parentNode &&
254
+ formElement.parentNode.nodeType === formElement.ELEMENT_NODE &&
255
+ formElement.parentNode.closest('[ref]');
256
+ if (ancestorComponent) {
257
+ const resolver = createNamespaceResolver(
258
+ ancestorComponent.getAttribute('ref'),
259
+ ancestorComponent,
260
+ );
261
+ setCachedNamespaceResolver(xpathQuery, formElement, resolver);
262
+ return resolver;
250
263
  }
264
+ // Nothing found: let's just assume we're supposed to use the `default` instance
265
+ instanceReferences = ['default'];
266
+ }
267
+
268
+ if (instanceReferences.length === 1) {
269
+ // console.log(`resolving ${xpathQuery} with ${instanceReferences[0]}`);
270
+ let instance;
271
+ if (instanceReferences[0] === 'default') {
272
+ /**
273
+ * @type {HTMLElement}
274
+ */
275
+ const actualForeElement = fxEvaluateXPathToFirstNode(
276
+ 'ancestor-or-self::fx-fore[1]',
277
+ formElement,
278
+ null,
279
+ null,
280
+ { namespaceResolver: xhtmlNamespaceResolver },
281
+ );
251
282
 
252
- if (instanceReferences.length === 1) {
253
- // console.log(`resolving ${xpathQuery} with ${instanceReferences[0]}`);
254
- let instance;
255
- if (instanceReferences[0] === 'default') {
256
- const actualForeElement = fxEvaluateXPathToFirstNode(
257
- 'ancestor-or-self::fx-fore[1]',
258
- formElement,
259
- null,
260
- null,
261
- {namespaceResolver: xhtmlNamespaceResolver},
262
- );
263
-
264
- instance = actualForeElement && actualForeElement.querySelector('fx-instance');
265
- } else {
266
- instance = resolveId(instanceReferences[0], formElement, 'fx-instance');
267
- }
268
- if (instance && instance.hasAttribute('xpath-default-namespace')) {
269
- const xpathDefaultNamespace = instance.getAttribute('xpath-default-namespace');
270
- /*
283
+ instance = actualForeElement && actualForeElement.querySelector('fx-instance');
284
+ } else {
285
+ instance = resolveId(instanceReferences[0], formElement, 'fx-instance');
286
+ }
287
+ if (instance && instance.hasAttribute('xpath-default-namespace')) {
288
+ const xpathDefaultNamespace = instance.getAttribute('xpath-default-namespace');
289
+ /*
271
290
  console.log(
272
291
  `Resolving the xpath ${xpathQuery} with the default namespace set to ${xpathDefaultNamespace}`,
273
292
  );
274
- */
275
- const resolveNamespacePrefix = prefix => {
276
- if (!prefix) {
277
- return xpathDefaultNamespace;
278
- }
279
- return undefined;
280
- };
281
- setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
282
- return resolveNamespacePrefix;
293
+ */
294
+ /**
295
+ * @type {NamespaceResolver}
296
+ */
297
+ const resolveNamespacePrefix = prefix => {
298
+ if (!prefix) {
299
+ return xpathDefaultNamespace;
283
300
  }
301
+ return undefined;
302
+ };
303
+ setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
304
+ return resolveNamespacePrefix;
284
305
  }
285
- if (instanceReferences.length > 1) {
286
- console.warn(
287
- `More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
288
- );
306
+ }
307
+ if (instanceReferences.length > 1) {
308
+ console.warn(
309
+ `More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
310
+ );
311
+ }
312
+
313
+ const xpathDefaultNamespace =
314
+ fxEvaluateXPathToString('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) ||
315
+ '';
316
+
317
+ /**
318
+ * @type {NamespaceResolver}
319
+ */
320
+ const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
321
+ if (prefix === '') {
322
+ return xpathDefaultNamespace;
289
323
  }
290
324
 
291
- const xpathDefaultNamespace =
292
- fxEvaluateXPathToString('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) ||
293
- '';
325
+ // Note: ideally we should use Node#lookupNamespaceURI. However, the nodes we are passed are
326
+ // XML. The best we can do is emulate the `xmlns:xxx` namespace declarations by regarding them as
327
+ // attributes. Which they technically ARE NOT!
294
328
 
295
- const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
296
- if (prefix === '') {
297
- return xpathDefaultNamespace;
298
- }
299
-
300
- // Note: ideally we should use Node#lookupNamespaceURI. However, the nodes we are passed are
301
- // XML. The best we can do is emulate the `xmlns:xxx` namespace declarations by regarding them as
302
- // attributes. Which they technically ARE NOT!
303
-
304
- return fxEvaluateXPathToString(
305
- 'ancestor-or-self::*/@*[name() = "xmlns:" || $prefix][last()]',
306
- formElement,
307
- null,
308
- {prefix},
309
- );
310
- };
329
+ return fxEvaluateXPathToString(
330
+ 'ancestor-or-self::*/@*[name() = "xmlns:" || $prefix][last()]',
331
+ formElement,
332
+ null,
333
+ { prefix },
334
+ );
335
+ };
311
336
 
312
- setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
313
- return resolveNamespacePrefix;
337
+ setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
338
+ return resolveNamespacePrefix;
314
339
  }
315
340
 
316
341
  function createNamespaceResolverForNode(query, contextNode, formElement) {
317
- if (((contextNode && contextNode.ownerDocument) || contextNode) === window.document) {
318
- // Running a query on the HTML DOM. Don't bother resolving namespaces in any other way
319
- return xhtmlNamespaceResolver;
320
- }
321
- return createNamespaceResolver(query, formElement);
342
+ if (((contextNode && contextNode.ownerDocument) || contextNode) === window.document) {
343
+ // Running a query on the HTML DOM. Don't bother resolving namespaces in any other way
344
+ return xhtmlNamespaceResolver;
345
+ }
346
+ return createNamespaceResolver(query, formElement);
322
347
  }
323
348
 
324
-
325
349
  /**
326
350
  * Implementation of the functionNameResolver passed to FontoXPath to
327
351
  * redirect function resolving for unprefixed functions to either the fn or the xf namespace
328
352
  */
329
353
  // eslint-disable-next-line no-unused-vars
330
- function functionNameResolver({prefix, localName}, _arity) {
331
- switch (localName) {
332
- // TODO: put the full XForms library functions set here
333
- case 'context':
334
- case 'base64encode':
335
- case 'boolean-from-string':
336
- case 'current':
337
- case 'depends':
338
- case 'event':
339
- case 'fore-attr':
340
- case 'index':
341
- case 'instance':
342
- case 'json2xml':
343
- case 'xml2Json':
344
- case 'log':
345
- case 'parse':
346
- case 'local-date':
347
- case 'local-dateTime':
348
- case 'logtree':
349
- case 'uri':
350
- case 'uri-fragment':
351
- case 'uri-host':
352
- case 'uri-param':
353
- case 'uri-path':
354
- case 'uri-relpath':
355
- case 'uri-port':
356
- case 'uri-query':
357
- case 'uri-scheme':
358
- case 'uri-scheme-specific-part':
359
- return {namespaceURI: XFORMS_NAMESPACE_URI, localName};
360
- default:
361
- if (prefix === '' && globallyDeclaredFunctionLocalNames.includes(localName)) {
362
- // The function has been declared without a prefix and is called here without a prefix.
363
- // Just make this work. It is the developer-friendly way
364
- return {namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName};
365
- }
366
- if (prefix === 'fn' || prefix === '') {
367
- return {namespaceURI: 'http://www.w3.org/2005/xpath-functions', localName};
368
- }
369
- if (prefix === 'local') {
370
- return {namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName};
371
- }
372
- return null;
373
- }
354
+ function functionNameResolver({ prefix, localName }, _arity) {
355
+ switch (localName) {
356
+ // TODO: put the full XForms library functions set here
357
+ case 'context':
358
+ case 'base64encode':
359
+ case 'boolean-from-string':
360
+ case 'current':
361
+ case 'depends':
362
+ case 'event':
363
+ case 'fore-attr':
364
+ case 'index':
365
+ case 'instance':
366
+ case 'json2xml':
367
+ case 'xml2Json':
368
+ case 'log':
369
+ case 'parse':
370
+ case 'local-date':
371
+ case 'local-dateTime':
372
+ case 'logtree':
373
+ case 'uri':
374
+ case 'uri-fragment':
375
+ case 'uri-host':
376
+ case 'uri-param':
377
+ case 'uri-path':
378
+ case 'uri-relpath':
379
+ case 'uri-port':
380
+ case 'uri-query':
381
+ case 'uri-scheme':
382
+ case 'uri-scheme-specific-part':
383
+ return { namespaceURI: XFORMS_NAMESPACE_URI, localName };
384
+ default:
385
+ if (prefix === '' && globallyDeclaredFunctionLocalNames.includes(localName)) {
386
+ // The function has been declared without a prefix and is called here without a prefix.
387
+ // Just make this work. It is the developer-friendly way
388
+ return { namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName };
389
+ }
390
+ if (prefix === 'fn' || prefix === '') {
391
+ return { namespaceURI: 'http://www.w3.org/2005/xpath-functions', localName };
392
+ }
393
+ if (prefix === 'local') {
394
+ return { namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName };
395
+ }
396
+ return null;
397
+ }
374
398
  }
375
399
 
376
400
  /**
@@ -379,40 +403,39 @@ function functionNameResolver({prefix, localName}, _arity) {
379
403
  *
380
404
  * @param {Node} formElement The element that declares the XPath
381
405
  *
382
- * @return {Object} A key-value mapping of the variables
406
+ * @returns {Object} A key-value mapping of the variables
383
407
  */
384
408
  function getVariablesInScope(formElement) {
385
- let closestActualFormElement = formElement;
386
- while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
387
- closestActualFormElement = closestActualFormElement.nodeType === Node.ATTRIBUTE_NODE ?
388
- closestActualFormElement.ownerElement :
389
- closestActualFormElement.parentNode;
390
- }
391
-
392
- if (!closestActualFormElement) {
393
- return {};
394
- }
395
-
396
- const variables = {};
397
- if (closestActualFormElement.inScopeVariables) {
398
- for (const key of closestActualFormElement.inScopeVariables.keys()) {
399
- const varElementOrValue = closestActualFormElement.inScopeVariables.get(key);
400
- if (!varElementOrValue) {
401
- continue;
402
-
403
- }
404
- if (varElementOrValue.nodeType) {
405
- // We are a var element, set the value to the value computed there
406
- variables[key] = varElementOrValue.value;
407
- // variables[key] = varElementOrValue.inScopeVariables.get(key);
408
- } else {
409
- // We are a direct value. This is used to leak in event variables
410
- variables[key] = varElementOrValue;
411
- }
412
-
413
- }
409
+ let closestActualFormElement = formElement;
410
+ while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
411
+ closestActualFormElement =
412
+ closestActualFormElement.nodeType === Node.ATTRIBUTE_NODE
413
+ ? closestActualFormElement.ownerElement
414
+ : closestActualFormElement.parentNode;
415
+ }
416
+
417
+ if (!closestActualFormElement) {
418
+ return {};
419
+ }
420
+
421
+ const variables = {};
422
+ if (closestActualFormElement.inScopeVariables) {
423
+ for (const key of closestActualFormElement.inScopeVariables.keys()) {
424
+ const varElementOrValue = closestActualFormElement.inScopeVariables.get(key);
425
+ if (!varElementOrValue) {
426
+ continue;
427
+ }
428
+ if (varElementOrValue.nodeType) {
429
+ // We are a var element, set the value to the value computed there
430
+ variables[key] = varElementOrValue.value;
431
+ // variables[key] = varElementOrValue.inScopeVariables.get(key);
432
+ } else {
433
+ // We are a direct value. This is used to leak in event variables
434
+ variables[key] = varElementOrValue;
435
+ }
414
436
  }
415
- return variables;
437
+ }
438
+ return variables;
416
439
  }
417
440
 
418
441
  /**
@@ -421,7 +444,9 @@ function getVariablesInScope(formElement) {
421
444
  *
422
445
  * @param {string} xpath The XPath to run
423
446
  * @param {Node} contextNode The start of the XPath
424
- * @param {{parentNode}|ForeElementMixin} formElement The form element associated to the XPath
447
+ * @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
448
+ * @param {Object} variables Any variables to pass to the XPath
449
+ * @param {Object} options Any options to pass to the XPath
425
450
  */
426
451
  /*
427
452
  export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options={}, domFacade = null) {
@@ -447,43 +472,45 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, o
447
472
  );
448
473
  }
449
474
  */
450
- export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options={}) {
451
- try{
452
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
453
- const variablesInScope = getVariablesInScope(formElement);
454
-
455
- const result = fxEvaluateXPath(
456
- xpath,
457
- contextNode,
458
- null,
459
- {...variablesInScope, ...variables},
460
- fxEvaluateXPath.ALL_RESULTS_TYPE,
461
- {
462
- debug: true,
463
- currentContext: {formElement, variables},
464
- moduleImports: {
465
- xf: XFORMS_NAMESPACE_URI,
466
- },
467
- functionNameResolver,
468
- namespaceResolver,
469
- language: options.language || evaluateXPath.XPATH_3_1
470
- },
471
- );
472
- // console.log('evaluateXPath',xpath, result);
473
- return result;
474
- }catch (e){
475
- formElement.dispatchEvent(new CustomEvent('error', {
476
- composed: false,
477
- bubbles: true,
478
- detail: {
479
- origin: formElement,
480
- message: `Expression '${xpath}' failed`,
481
- expr:xpath,
482
- level:'Error'
483
- },
484
- }));
475
+ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options = {}) {
476
+ try {
477
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
478
+ const variablesInScope = getVariablesInScope(formElement);
485
479
 
486
- /*
480
+ const result = fxEvaluateXPath(
481
+ xpath,
482
+ contextNode,
483
+ null,
484
+ { ...variablesInScope, ...variables },
485
+ fxEvaluateXPath.ALL_RESULTS_TYPE,
486
+ {
487
+ debug: true,
488
+ currentContext: { formElement, variables },
489
+ moduleImports: {
490
+ xf: XFORMS_NAMESPACE_URI,
491
+ },
492
+ functionNameResolver,
493
+ namespaceResolver,
494
+ language: options.language || fxEvaluateXPath.XPATH_3_1_LANGUAGE,
495
+ },
496
+ );
497
+ // console.log('evaluateXPath',xpath, result);
498
+ return result;
499
+ } catch (e) {
500
+ formElement.dispatchEvent(
501
+ new CustomEvent('error', {
502
+ composed: false,
503
+ bubbles: true,
504
+ detail: {
505
+ origin: formElement,
506
+ message: `Expression '${xpath}' failed`,
507
+ expr: xpath,
508
+ level: 'Error',
509
+ },
510
+ }),
511
+ );
512
+
513
+ /*
487
514
  formElement.dispatchEvent(
488
515
  new CustomEvent('error', {
489
516
  composed: false,
@@ -497,44 +524,47 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, o
497
524
  }),
498
525
  );
499
526
  */
500
- return false;
501
- }
527
+ // Return 'nothing' in hope the rest of the page can forgive this
528
+ return [];
529
+ }
502
530
  }
503
531
  /**
504
532
  * Evaluate an XPath to the first Node
505
533
  *
506
534
  * @param {string} xpath The XPath to run
507
535
  * @param {Node} contextNode The start of the XPath
508
- * @param {Node} formElement The form element associated to the XPath
509
- * @return {Node} The first node found by the XPath
536
+ * @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
537
+ * @returns {Node} The first node found in the XPath
510
538
  */
511
539
  export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
512
- try{
513
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
514
- const variablesInScope = getVariablesInScope(formElement);
515
- const result = fxEvaluateXPathToFirstNode(xpath, contextNode, null, variablesInScope, {
516
- defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
517
- moduleImports: {
518
- xf: XFORMS_NAMESPACE_URI,
519
- },
520
- currentContext: {formElement},
521
- functionNameResolver,
522
- namespaceResolver,
523
- });
524
- // console.log('evaluateXPathToFirstNode',xpath, result);
525
- return result;
526
- } catch (e){
527
- formElement.dispatchEvent(new CustomEvent('error', {
528
- composed: false,
529
- bubbles: true,
530
- detail: {
531
- origin: formElement,
532
- message: `Expression '${xpath}' failed`,
533
- expr:xpath,
534
- level:'Error'
535
- },
536
- }));
537
- }
540
+ try {
541
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
542
+ const variablesInScope = getVariablesInScope(formElement);
543
+ const result = fxEvaluateXPathToFirstNode(xpath, contextNode, null, variablesInScope, {
544
+ defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
545
+ moduleImports: {
546
+ xf: XFORMS_NAMESPACE_URI,
547
+ },
548
+ currentContext: { formElement },
549
+ functionNameResolver,
550
+ namespaceResolver,
551
+ });
552
+ // console.log('evaluateXPathToFirstNode',xpath, result);
553
+ return result;
554
+ } catch (e) {
555
+ formElement.dispatchEvent(
556
+ new CustomEvent('error', {
557
+ composed: false,
558
+ bubbles: true,
559
+ detail: {
560
+ origin: formElement,
561
+ message: `Expression '${xpath}' failed`,
562
+ expr: xpath,
563
+ level: 'Error',
564
+ },
565
+ }),
566
+ );
567
+ }
538
568
  }
539
569
 
540
570
  /**
@@ -542,36 +572,38 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
542
572
  *
543
573
  * @param {string} xpath The XPath to run
544
574
  * @param {Node} contextNode The start of the XPath
545
- * @param {Node} formElement The form element associated to the XPath
575
+ * @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
546
576
  * @return {Node[]} All nodes
547
577
  */
548
578
  export function evaluateXPathToNodes(xpath, contextNode, formElement) {
549
- try{
550
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
551
- const variablesInScope = getVariablesInScope(formElement);
579
+ try {
580
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
581
+ const variablesInScope = getVariablesInScope(formElement);
552
582
 
553
- const result = fxEvaluateXPathToNodes(xpath, contextNode, null, variablesInScope, {
554
- currentContext: {formElement},
555
- functionNameResolver,
556
- moduleImports: {
557
- xf: XFORMS_NAMESPACE_URI,
558
- },
559
- namespaceResolver,
560
- });
561
- // console.log('evaluateXPathToNodes',xpath, result);
562
- return result;
563
- }catch (e){
564
- formElement.dispatchEvent(new CustomEvent('error', {
565
- composed: false,
566
- bubbles: true,
567
- detail: {
568
- origin: formElement,
569
- message: `Expression '${xpath}' failed`,
570
- expr:xpath,
571
- level:'Error'
572
- },
573
- }));
574
- }
583
+ const result = fxEvaluateXPathToNodes(xpath, contextNode, null, variablesInScope, {
584
+ currentContext: { formElement },
585
+ functionNameResolver,
586
+ moduleImports: {
587
+ xf: XFORMS_NAMESPACE_URI,
588
+ },
589
+ namespaceResolver,
590
+ });
591
+ // console.log('evaluateXPathToNodes',xpath, result);
592
+ return result;
593
+ } catch (e) {
594
+ formElement.dispatchEvent(
595
+ new CustomEvent('error', {
596
+ composed: false,
597
+ bubbles: true,
598
+ detail: {
599
+ origin: formElement,
600
+ message: `Expression '${xpath}' failed`,
601
+ expr: xpath,
602
+ level: 'Error',
603
+ },
604
+ }),
605
+ );
606
+ }
575
607
  }
576
608
 
577
609
  /**
@@ -579,34 +611,36 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
579
611
  *
580
612
  * @param {string} xpath The XPath to run
581
613
  * @param {Node} contextNode The start of the XPath
582
- * @param {Node} formElement The form element associated to the XPath
614
+ * @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
583
615
  * @return {boolean}
584
616
  */
585
617
  export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
586
- try{
587
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
588
- const variablesInScope = getVariablesInScope(formElement);
618
+ try {
619
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
620
+ const variablesInScope = getVariablesInScope(formElement);
589
621
 
590
- return fxEvaluateXPathToBoolean(xpath, contextNode, null, variablesInScope, {
591
- currentContext: {formElement},
592
- functionNameResolver,
593
- moduleImports: {
594
- xf: XFORMS_NAMESPACE_URI,
595
- },
596
- namespaceResolver,
597
- });
598
- }catch (e){
599
- formElement.dispatchEvent(new CustomEvent('error', {
600
- composed: false,
601
- bubbles: true,
602
- detail: {
603
- origin: formElement,
604
- message: `Expression '${xpath}' failed`,
605
- expr:xpath,
606
- level:'Error'
607
- },
608
- }));
609
- }
622
+ return fxEvaluateXPathToBoolean(xpath, contextNode, null, variablesInScope, {
623
+ currentContext: { formElement },
624
+ functionNameResolver,
625
+ moduleImports: {
626
+ xf: XFORMS_NAMESPACE_URI,
627
+ },
628
+ namespaceResolver,
629
+ });
630
+ } catch (e) {
631
+ formElement.dispatchEvent(
632
+ new CustomEvent('error', {
633
+ composed: false,
634
+ bubbles: true,
635
+ detail: {
636
+ origin: formElement,
637
+ message: `Expression '${xpath}' failed`,
638
+ expr: xpath,
639
+ level: 'Error',
640
+ },
641
+ }),
642
+ );
643
+ }
610
644
  }
611
645
 
612
646
  /**
@@ -615,42 +649,38 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
615
649
  * @param {string} xpath The XPath to run
616
650
  * @param {Node} contextNode The start of the XPath
617
651
  * @param {Node} formElement The form element associated to the XPath
618
- * @param {DomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
619
- * access. This is used to determine dependencies between bind elements.
620
652
  * @param {Node} formElement The element where the XPath is defined: used for namespace resolving
653
+ * @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
654
+ * access. This is used to determine dependencies between bind elements.
621
655
  * @return {string}
622
656
  */
623
- export function evaluateXPathToString(
624
- xpath,
625
- contextNode,
626
- formElement,
627
- domFacade = null,
628
- namespaceReferenceNode = formElement,
629
- ) {
630
- try{
631
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
632
- const variablesInScope = getVariablesInScope(formElement);
633
-
634
- return fxEvaluateXPathToString(xpath, contextNode, domFacade, variablesInScope, {
635
- currentContext: {formElement},
636
- functionNameResolver,
637
- moduleImports: {
638
- xf: XFORMS_NAMESPACE_URI,
639
- },
640
- namespaceResolver,
641
- });
642
- }catch (e) {
643
- formElement.dispatchEvent(new CustomEvent('error', {
644
- composed: false,
645
- bubbles: true,
646
- detail: {
647
- origin: formElement,
648
- message: `Expression '${xpath}' failed`,
649
- expr:xpath,
650
- level:'Error'
651
- },
652
- }));
653
- }
657
+ export function evaluateXPathToString(xpath, contextNode, formElement, domFacade = null) {
658
+ try {
659
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
660
+ const variablesInScope = getVariablesInScope(formElement);
661
+
662
+ return fxEvaluateXPathToString(xpath, contextNode, domFacade, variablesInScope, {
663
+ currentContext: { formElement },
664
+ functionNameResolver,
665
+ moduleImports: {
666
+ xf: XFORMS_NAMESPACE_URI,
667
+ },
668
+ namespaceResolver,
669
+ });
670
+ } catch (e) {
671
+ formElement.dispatchEvent(
672
+ new CustomEvent('error', {
673
+ composed: false,
674
+ bubbles: true,
675
+ detail: {
676
+ origin: formElement,
677
+ message: `Expression '${xpath}' failed`,
678
+ expr: xpath,
679
+ level: 'Error',
680
+ },
681
+ }),
682
+ );
683
+ }
654
684
  }
655
685
 
656
686
  /**
@@ -659,43 +689,42 @@ export function evaluateXPathToString(
659
689
  * @param {string} xpath The XPath to run
660
690
  * @param {Node} contextNode The start of the XPath
661
691
  * @param {Node} formElement The form element associated to the XPath
662
- * @param {DomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
663
- * access. This is used to determine dependencies between bind elements.
664
692
  * @param {Node} formElement The element where the XPath is defined: used for namespace resolving
665
- * @return {string}
693
+ * @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
694
+ * access. This is used to determine dependencies between bind elements.
695
+ * @return {string[]}
666
696
  */
667
- export function evaluateXPathToStrings(
668
- xpath,
669
- contextNode,
670
- formElement,
671
- domFacade = null,
672
- namespaceReferenceNode = formElement,
673
- ) {
674
- try{
675
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
676
- return fxEvaluateXPathToStrings( xpath, contextNode, domFacade,
677
- {},
678
- {
679
- currentContext: {formElement},
680
- functionNameResolver,
681
- moduleImports: {
682
- xf: XFORMS_NAMESPACE_URI,
683
- },
684
- namespaceResolver,
685
- },
686
- );
687
- } catch (e){
688
- formElement.dispatchEvent(new CustomEvent('error', {
689
- composed: false,
690
- bubbles: true,
691
- detail: {
692
- origin: formElement,
693
- message: `Expression '${xpath}' failed`,
694
- expr:xpath,
695
- level:'Error'
696
- },
697
- }));
698
- }
697
+ export function evaluateXPathToStrings(xpath, contextNode, formElement, domFacade = null) {
698
+ try {
699
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
700
+ return fxEvaluateXPathToStrings(
701
+ xpath,
702
+ contextNode,
703
+ domFacade,
704
+ {},
705
+ {
706
+ currentContext: { formElement },
707
+ functionNameResolver,
708
+ moduleImports: {
709
+ xf: XFORMS_NAMESPACE_URI,
710
+ },
711
+ namespaceResolver,
712
+ },
713
+ );
714
+ } catch (e) {
715
+ formElement.dispatchEvent(
716
+ new CustomEvent('error', {
717
+ composed: false,
718
+ bubbles: true,
719
+ detail: {
720
+ origin: formElement,
721
+ message: `Expression '${xpath}' failed`,
722
+ expr: xpath,
723
+ level: 'Error',
724
+ },
725
+ }),
726
+ );
727
+ }
699
728
  }
700
729
 
701
730
  /**
@@ -704,78 +733,74 @@ export function evaluateXPathToStrings(
704
733
  * @param {string} xpath The XPath to run
705
734
  * @param {Node} contextNode The start of the XPath
706
735
  * @param {Node} formElement The form element associated to the XPath
707
- * @param {DomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
708
736
  * @param {Node} formElement The element where the XPath is defined: used for namespace resolving
737
+ * @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
709
738
  * access. This is used to determine dependencies between bind elements.
710
- * @return {Number}
739
+ * @return {number}
711
740
  */
712
- export function evaluateXPathToNumber(
713
- xpath,
714
- contextNode,
715
- formElement,
716
- domFacade = null,
717
- namespaceReferenceNode = formElement,
718
- ) {
719
- try{
720
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
721
- const variablesInScope = getVariablesInScope(formElement);
722
-
723
- return fxEvaluateXPathToNumber(xpath, contextNode, domFacade, variablesInScope, {
724
- currentContext: {formElement},
725
- functionNameResolver,
726
- moduleImports: {
727
- xf: XFORMS_NAMESPACE_URI,
728
- },
729
- namespaceResolver,
730
- });
731
- }catch (e) {
732
- formElement.dispatchEvent(new CustomEvent('error', {
733
- composed: false,
734
- bubbles: true,
735
- detail: {
736
- origin: formElement,
737
- message: `Expression '${xpath}' failed`,
738
- expr:xpath,
739
- level:'Error'
740
- },
741
- }));
742
- }
741
+ export function evaluateXPathToNumber(xpath, contextNode, formElement, domFacade = null) {
742
+ try {
743
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
744
+ const variablesInScope = getVariablesInScope(formElement);
745
+
746
+ return fxEvaluateXPathToNumber(xpath, contextNode, domFacade, variablesInScope, {
747
+ currentContext: { formElement },
748
+ functionNameResolver,
749
+ moduleImports: {
750
+ xf: XFORMS_NAMESPACE_URI,
751
+ },
752
+ namespaceResolver,
753
+ });
754
+ } catch (e) {
755
+ formElement.dispatchEvent(
756
+ new CustomEvent('error', {
757
+ composed: false,
758
+ bubbles: true,
759
+ detail: {
760
+ origin: formElement,
761
+ message: `Expression '${xpath}' failed`,
762
+ expr: xpath,
763
+ level: 'Error',
764
+ },
765
+ }),
766
+ );
767
+ }
743
768
  }
744
769
 
745
770
  const contextFunction = (dynamicContext, string) => {
746
- const caller = dynamicContext.currentContext.formElement;
747
- let instance = null;
748
- if (string) {
749
- instance = resolveId(string, caller);
750
- } else {
751
- instance = XPathUtil.getParentBindingElement(caller);
752
- }
753
- if (instance) {
754
- if (instance.nodeName === 'FX-REPEAT') {
755
- const {nodeset} = instance;
756
- for (let parent = caller; parent; parent = parent.parentNode) {
757
- if (parent.parentNode === instance) {
758
- const offset = Array.from(parent.parentNode.children).indexOf(parent);
759
- return nodeset[offset];
760
- }
761
- }
771
+ const caller = dynamicContext.currentContext.formElement;
772
+ let instance = null;
773
+ if (string) {
774
+ instance = resolveId(string, caller);
775
+ } else {
776
+ instance = XPathUtil.getParentBindingElement(caller);
777
+ }
778
+ if (instance) {
779
+ if (instance.nodeName === 'FX-REPEAT') {
780
+ const { nodeset } = instance;
781
+ for (let parent = caller; parent; parent = parent.parentNode) {
782
+ if (parent.parentNode === instance) {
783
+ const offset = Array.from(parent.parentNode.children).indexOf(parent);
784
+ return nodeset[offset];
762
785
  }
763
- return instance.nodeset;
786
+ }
764
787
  }
788
+ return instance.nodeset;
789
+ }
765
790
 
766
- return caller.getInScopeContext();
791
+ return caller.getInScopeContext();
767
792
  };
768
793
 
769
794
  // todo: implement
770
795
  const currentFunction = (dynamicContext, string) => {
771
- const caller = dynamicContext.currentContext.formElement;
772
- return null;
796
+ const caller = dynamicContext.currentContext.formElement;
797
+ return null;
773
798
  };
774
799
 
775
800
  const elementFunction = (dynamicContext, string) => {
776
- const caller = dynamicContext.currentContext.formElement;
777
- const newElement = document.createElement(string);
778
- return newElement;
801
+ const caller = dynamicContext.currentContext.formElement;
802
+ const newElement = document.createElement(string);
803
+ return newElement;
779
804
  };
780
805
 
781
806
  /**
@@ -783,10 +808,10 @@ const elementFunction = (dynamicContext, string) => {
783
808
  * @return instance data for given id serialized to string.
784
809
  */
785
810
  registerCustomXPathFunction(
786
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context'},
787
- [],
788
- 'item()?',
789
- contextFunction,
811
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
812
+ [],
813
+ 'item()?',
814
+ contextFunction,
790
815
  );
791
816
 
792
817
  /**
@@ -794,24 +819,24 @@ registerCustomXPathFunction(
794
819
  * @return instance data for given id serialized to string.
795
820
  */
796
821
  registerCustomXPathFunction(
797
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context'},
798
- ['xs:string'],
799
- 'item()?',
800
- contextFunction,
822
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
823
+ ['xs:string'],
824
+ 'item()?',
825
+ contextFunction,
801
826
  );
802
827
 
803
828
  registerCustomXPathFunction(
804
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'current'},
805
- ['xs:string'],
806
- 'item()?',
807
- currentFunction,
829
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'current' },
830
+ ['xs:string'],
831
+ 'item()?',
832
+ currentFunction,
808
833
  );
809
834
 
810
835
  registerCustomXPathFunction(
811
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'element'},
812
- ['xs:string'],
813
- 'item()?',
814
- elementFunction,
836
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'element' },
837
+ ['xs:string'],
838
+ 'item()?',
839
+ elementFunction,
815
840
  );
816
841
 
817
842
  /**
@@ -819,53 +844,53 @@ registerCustomXPathFunction(
819
844
  * @return instance data for given id serialized to string.
820
845
  */
821
846
  registerCustomXPathFunction(
822
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'log'},
823
- ['xs:string?'],
824
- 'xs:string?',
825
- (dynamicContext, string) => {
826
- const {formElement} = dynamicContext.currentContext;
827
- const instance = resolveId(string, formElement, 'fx-instance');
828
- if (instance) {
829
- if (instance.getAttribute('type') === 'json') {
830
- console.warn('log() does not work for JSON yet');
831
- // return JSON.stringify(instance.getDefaultContext());
832
- } else {
833
- const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
834
- return prettifyXml(def);
835
- }
836
- }
837
- return null;
838
- },
847
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'log' },
848
+ ['xs:string?'],
849
+ 'xs:string?',
850
+ (dynamicContext, string) => {
851
+ const { formElement } = dynamicContext.currentContext;
852
+ const instance = resolveId(string, formElement, 'fx-instance');
853
+ if (instance) {
854
+ if (instance.getAttribute('type') === 'json') {
855
+ console.warn('log() does not work for JSON yet');
856
+ // return JSON.stringify(instance.getDefaultContext());
857
+ } else {
858
+ const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
859
+ return prettifyXml(def);
860
+ }
861
+ }
862
+ return null;
863
+ },
839
864
  );
840
865
  registerCustomXPathFunction(
841
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'fore-attr'},
842
- ['xs:string?'],
843
- 'xs:string?',
844
- (dynamicContext, string) => {
845
- const {formElement} = dynamicContext.currentContext;
846
-
847
- let parent = formElement;
848
- if(formElement.nodeType === Node.TEXT_NODE){
849
- parent = formElement.parentNode;
850
- }
851
- const foreElement = parent.closest('fx-fore');
852
- if(foreElement.hasAttribute(string)){
853
- return foreElement.getAttribute(string);
854
- }
855
- return null;
856
- },
866
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'fore-attr' },
867
+ ['xs:string?'],
868
+ 'xs:string?',
869
+ (dynamicContext, string) => {
870
+ const { formElement } = dynamicContext.currentContext;
871
+
872
+ let parent = formElement;
873
+ if (formElement.nodeType === Node.TEXT_NODE) {
874
+ parent = formElement.parentNode;
875
+ }
876
+ const foreElement = parent.closest('fx-fore');
877
+ if (foreElement.hasAttribute(string)) {
878
+ return foreElement.getAttribute(string);
879
+ }
880
+ return null;
881
+ },
857
882
  );
858
883
 
859
884
  registerCustomXPathFunction(
860
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'parse'},
861
- ['xs:string?'],
862
- 'element()?',
863
- (dynamicContext, string) => {
864
- const parser = new DOMParser();
865
- const out = parser.parseFromString(string, "application/xml");
866
- console.log('parse', out);
867
-
868
- /*
885
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'parse' },
886
+ ['xs:string?'],
887
+ 'element()?',
888
+ (_dynamicContext, string) => {
889
+ const parser = new DOMParser();
890
+ const out = parser.parseFromString(string, 'application/xml');
891
+ console.log('parse', out);
892
+
893
+ /*
869
894
  const {formElement} = dynamicContext.currentContext;
870
895
  const instance = resolveId(string, formElement, 'fx-instance');
871
896
  if (instance) {
@@ -878,53 +903,53 @@ registerCustomXPathFunction(
878
903
  }
879
904
  }
880
905
  */
881
- return out.firstElementChild;
882
- },
906
+ return out.firstElementChild;
907
+ },
883
908
  );
884
909
 
885
910
  function buildTree(tree, data) {
886
- if (!data) return;
887
- if (data.nodeType === Node.ELEMENT_NODE) {
888
- if (data.children) {
889
- const details = document.createElement('details');
890
- details.setAttribute('data-path', data.nodeName);
891
- const summary = document.createElement('summary');
892
-
893
- let display = ` <${data.nodeName}`;
894
- Array.from(data.attributes).forEach(attr => {
895
- display += ` ${attr.nodeName}="${attr.nodeValue}"`;
896
- });
897
-
898
- let contents;
899
- if (
900
- data.firstChild &&
901
- data.firstChild.nodeType === Node.TEXT_NODE &&
902
- data.firstChild.data.trim() !== ''
903
- ) {
904
- // console.log('whoooooooooopp');
905
- contents = data.firstChild.nodeValue;
906
- display += `>${contents}</${data.nodeName}>`;
907
- } else {
908
- display += '>';
909
- }
910
- summary.textContent = display;
911
-
912
- details.appendChild(summary);
913
- if (data.childElementCount !== 0) {
914
- details.setAttribute('open', 'open');
915
- } else {
916
- summary.setAttribute('style', 'list-style:none;');
917
- }
918
- tree.appendChild(details);
919
-
920
- Array.from(data.children).forEach(child => {
921
- // if(child.nodeType === Node.ELEMENT_NODE){
922
- // child.parentNode.appendChild(buildTree(child));
923
- buildTree(details, child);
924
- // }
925
- });
926
- }
927
- } /* else if(data.nodeType === Node.ATTRIBUTE_NODE){
911
+ if (!data) return;
912
+ if (data.nodeType === Node.ELEMENT_NODE) {
913
+ if (data.children) {
914
+ const details = document.createElement('details');
915
+ details.setAttribute('data-path', data.nodeName);
916
+ const summary = document.createElement('summary');
917
+
918
+ let display = ` <${data.nodeName}`;
919
+ Array.from(data.attributes).forEach(attr => {
920
+ display += ` ${attr.nodeName}="${attr.nodeValue}"`;
921
+ });
922
+
923
+ let contents;
924
+ if (
925
+ data.firstChild &&
926
+ data.firstChild.nodeType === Node.TEXT_NODE &&
927
+ data.firstChild.data.trim() !== ''
928
+ ) {
929
+ // console.log('whoooooooooopp');
930
+ contents = data.firstChild.nodeValue;
931
+ display += `>${contents}</${data.nodeName}>`;
932
+ } else {
933
+ display += '>';
934
+ }
935
+ summary.textContent = display;
936
+
937
+ details.appendChild(summary);
938
+ if (data.childElementCount !== 0) {
939
+ details.setAttribute('open', 'open');
940
+ } else {
941
+ summary.setAttribute('style', 'list-style:none;');
942
+ }
943
+ tree.appendChild(details);
944
+
945
+ Array.from(data.children).forEach(child => {
946
+ // if(child.nodeType === Node.ELEMENT_NODE){
947
+ // child.parentNode.appendChild(buildTree(child));
948
+ buildTree(details, child);
949
+ // }
950
+ });
951
+ }
952
+ } /* else if(data.nodeType === Node.ATTRIBUTE_NODE){
928
953
  //create span for now
929
954
  // const span = document.createElement('span');
930
955
  // span.style.background = 'grey';
@@ -935,229 +960,220 @@ function buildTree(tree, data) {
935
960
  tree.textContent = data;
936
961
  } */
937
962
 
938
- // return tree;
963
+ // return tree;
939
964
  }
940
965
 
941
966
  registerCustomXPathFunction(
942
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'logtree'},
943
- ['xs:string?'],
944
- 'element()?',
945
- (dynamicContext, string) => {
946
- const {formElement} = dynamicContext.currentContext;
947
- const instance = resolveId(string, formElement, 'fx-instance');
948
-
949
- if (instance) {
950
- // const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
951
- // const def = JSON.stringify(instance.getDefaultContext());
952
-
953
- const treeDiv = document.createElement('div');
954
- treeDiv.setAttribute('class', 'logtree');
955
- // const datatree = buildTree(tree,instance.getDefaultContext());
956
- // return tree.appendChild(datatree);
957
- // return buildTree(root,instance.getDefaultContext());;
958
- const form = dynamicContext.currentContext.formElement;
959
- const logtree = form.querySelector('.logtree');
960
- if (logtree) {
961
- logtree.parentNode.removeChild(logtree);
962
- }
963
- const tree = buildTree(treeDiv, instance.getDefaultContext());
964
- if (tree) {
965
- form.appendChild(tree);
966
- }
967
- }
968
- return null;
969
- },
967
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'logtree' },
968
+ ['xs:string?'],
969
+ 'element()?',
970
+ (dynamicContext, string) => {
971
+ const { formElement } = dynamicContext.currentContext;
972
+ const instance = resolveId(string, formElement, 'fx-instance');
973
+
974
+ if (instance) {
975
+ // const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
976
+ // const def = JSON.stringify(instance.getDefaultContext());
977
+
978
+ const treeDiv = document.createElement('div');
979
+ treeDiv.setAttribute('class', 'logtree');
980
+ // const datatree = buildTree(tree,instance.getDefaultContext());
981
+ // return tree.appendChild(datatree);
982
+ // return buildTree(root,instance.getDefaultContext());;
983
+ const form = dynamicContext.currentContext.formElement;
984
+ const logtree = form.querySelector('.logtree');
985
+ if (logtree) {
986
+ logtree.parentNode.removeChild(logtree);
987
+ }
988
+ const tree = buildTree(treeDiv, instance.getDefaultContext());
989
+ if (tree) {
990
+ form.appendChild(tree);
991
+ }
992
+ }
993
+ return null;
994
+ },
970
995
  );
971
996
 
972
997
  const instance = (dynamicContext, string) => {
973
- // Spec: https://www.w3.org/TR/xforms-xpath/#The_XForms_Function_Library#The_instance.28.29_Function
974
- // TODO: handle no string passed (null will be passed instead)
975
-
976
- const formElement = fxEvaluateXPathToFirstNode(
977
- 'ancestor-or-self::fx-fore[1]',
978
- dynamicContext.currentContext.formElement,
979
- null,
980
- null,
981
- {namespaceResolver: xhtmlNamespaceResolver},
982
- );
983
-
984
- let lookup = null;
985
- if(string === null || string === 'default'){
986
- lookup = formElement.getModel().getDefaultInstance();
987
- }else{
988
- lookup = formElement.getModel().getInstance(string);
989
- if(!lookup){
990
- document.querySelector('fx-fore').dispatchEvent(new CustomEvent('error', {
991
- composed: true,
992
- bubbles: true,
993
- detail: {
994
- origin: 'functions',
995
- message: `Instance not found '${localName}'`,
996
- level:'Error'
997
- },
998
- }));
999
- }
998
+ // Spec: https://www.w3.org/TR/xforms-xpath/#The_XForms_Function_Library#The_instance.28.29_Function
999
+ // TODO: handle no string passed (null will be passed instead)
1000
+
1001
+ /**
1002
+ * @type {import('./fx-fore.js').FxFore}
1003
+ */
1004
+ const formElement = fxEvaluateXPathToFirstNode(
1005
+ 'ancestor-or-self::fx-fore[1]',
1006
+ dynamicContext.currentContext.formElement,
1007
+ null,
1008
+ null,
1009
+ { namespaceResolver: xhtmlNamespaceResolver },
1010
+ );
1011
+
1012
+ let lookup = null;
1013
+ if (string === null || string === 'default') {
1014
+ lookup = formElement.getModel().getDefaultInstance();
1015
+ } else {
1016
+ lookup = formElement.getModel().getInstance(string);
1017
+ if (!lookup) {
1018
+ document.querySelector('fx-fore').dispatchEvent(
1019
+ new CustomEvent('error', {
1020
+ composed: true,
1021
+ bubbles: true,
1022
+ detail: {
1023
+ origin: 'functions',
1024
+ message: `Instance not found '${string}'`,
1025
+ level: 'Error',
1026
+ },
1027
+ }),
1028
+ );
1000
1029
  }
1030
+ }
1001
1031
 
1002
- const context = lookup.getDefaultContext();
1003
- if (!context) {
1004
- debugger;
1005
- return null;
1006
- }
1007
- return context;
1032
+ const context = lookup.getDefaultContext();
1033
+ if (!context) {
1034
+ return null;
1035
+ }
1036
+ return context;
1008
1037
  };
1009
1038
 
1010
1039
  registerCustomXPathFunction(
1011
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'index'},
1012
- ['xs:string?'],
1013
- 'xs:integer?',
1014
- (dynamicContext, string) => {
1015
- const {formElement} = dynamicContext.currentContext;
1016
- if (string === null) {
1017
- return 1;
1018
- }
1019
- const repeat = resolveId(string, formElement, 'fx-repeat');
1040
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'index' },
1041
+ ['xs:string?'],
1042
+ 'xs:integer?',
1043
+ (dynamicContext, string) => {
1044
+ const { formElement } = dynamicContext.currentContext;
1045
+ if (string === null) {
1046
+ return 1;
1047
+ }
1048
+ const repeat = resolveId(string, formElement, 'fx-repeat');
1020
1049
 
1021
- // const def = instance.getInstanceData();
1022
- if (repeat) {
1023
- return repeat.getAttribute('index');
1024
- }
1025
- return Number(1);
1026
- },
1050
+ // const def = instance.getInstanceData();
1051
+ if (repeat) {
1052
+ return repeat.getAttribute('index');
1053
+ }
1054
+ return Number(1);
1055
+ },
1027
1056
  );
1028
1057
 
1029
1058
  // Note that this is not to spec. The spec enforces elements to be returned from the
1030
1059
  // instance. However, we allow instances to actually be JSON!
1031
1060
  registerCustomXPathFunction(
1032
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance'},
1033
- [],
1034
- 'item()?',
1035
- domFacade => instance(domFacade, null),
1061
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance' },
1062
+ [],
1063
+ 'item()?',
1064
+ domFacade => instance(domFacade, null),
1036
1065
  );
1037
1066
 
1038
1067
  registerCustomXPathFunction(
1039
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance'},
1040
- ['xs:string?'],
1041
- 'item()?',
1042
- instance,
1068
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance' },
1069
+ ['xs:string?'],
1070
+ 'item()?',
1071
+ instance,
1043
1072
  );
1044
- const getAttributes = (value) => {
1045
- if (Array.isArray(value)) {
1046
- return ` type="array"`;
1047
- } else if (typeof value === 'number') {
1048
- return ` type="number"`;
1049
- } else if (typeof value === 'boolean') {
1050
- return ` type="boolean"`;
1051
- }
1052
- return '';
1053
- };
1054
1073
 
1055
- const jsonToXml = (dynamicContext, json) => {
1056
- const escapeXml = (str) => {
1057
- return str.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]/g, (char) => {
1058
- return `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`;
1059
- });
1060
- };
1074
+ const jsonToXml = (_dynamicContext, json) => {
1075
+ const escapeXml = str =>
1076
+ str.replace(
1077
+ /[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]/g,
1078
+ char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`,
1079
+ );
1061
1080
 
1062
- const convert = (obj, parent) => {
1063
- const type = typeof obj;
1064
- if (type === 'number') {
1065
- parent.setAttribute('type', 'number');
1066
- parent.textContent = obj.toString();
1067
- } else if (type === 'boolean') {
1068
- parent.setAttribute('type', 'boolean');
1069
- parent.textContent = obj.toString();
1070
- } else if (obj === null) {
1071
- const node = document.createElement('_');
1072
- node.setAttribute('type', 'null');
1073
- parent.appendChild(node);
1074
- } else if (type === 'string') {
1075
- parent.textContent = escapeXml(obj);
1076
- } else if (Array.isArray(obj)) {
1077
- parent.setAttribute('type', 'array');
1078
- obj.forEach((item) => {
1079
- const node = document.createElement('_');
1080
- convert(item, node);
1081
- node.textContent = item;
1082
- parent.appendChild(node);
1083
- });
1084
- } else if (type === 'object') {
1085
- parent.setAttribute('type', 'object');
1086
- Object.entries(obj).forEach(([key, value]) => {
1087
- if(value){
1088
- const childNode = document.createElement(key.replace(/[^a-zA-Z0-9_]/g, '_'));
1089
- convert(value, childNode);
1090
- parent.appendChild(childNode);
1091
- }
1092
- });
1081
+ const convert = (obj, parent) => {
1082
+ const type = typeof obj;
1083
+ if (type === 'number') {
1084
+ parent.setAttribute('type', 'number');
1085
+ parent.textContent = obj.toString();
1086
+ } else if (type === 'boolean') {
1087
+ parent.setAttribute('type', 'boolean');
1088
+ parent.textContent = obj.toString();
1089
+ } else if (obj === null) {
1090
+ const node = document.createElement('_');
1091
+ node.setAttribute('type', 'null');
1092
+ parent.appendChild(node);
1093
+ } else if (type === 'string') {
1094
+ parent.textContent = escapeXml(obj);
1095
+ } else if (Array.isArray(obj)) {
1096
+ parent.setAttribute('type', 'array');
1097
+ obj.forEach(item => {
1098
+ const node = document.createElement('_');
1099
+ convert(item, node);
1100
+ node.textContent = item;
1101
+ parent.appendChild(node);
1102
+ });
1103
+ } else if (type === 'object') {
1104
+ parent.setAttribute('type', 'object');
1105
+ Object.entries(obj).forEach(([key, value]) => {
1106
+ if (value) {
1107
+ const childNode = document.createElement(key.replace(/[^a-zA-Z0-9_]/g, '_'));
1108
+ convert(value, childNode);
1109
+ parent.appendChild(childNode);
1093
1110
  }
1094
- };
1095
-
1096
- const root = document.createElement('json');
1097
- if(Array.isArray(json)){
1098
- root.setAttribute('type', 'array');
1099
- }else{
1100
- root.setAttribute('type', 'object');
1111
+ });
1101
1112
  }
1102
- convert(json, root);
1103
- // return root.outerHTML;
1104
- console.log('xml',root)
1105
- return root;
1113
+ };
1114
+
1115
+ const root = document.createElement('json');
1116
+ if (Array.isArray(json)) {
1117
+ root.setAttribute('type', 'array');
1118
+ } else {
1119
+ root.setAttribute('type', 'object');
1120
+ }
1121
+ convert(json, root);
1122
+ // return root.outerHTML;
1123
+ console.log('xml', root);
1124
+ return root;
1106
1125
  };
1107
1126
 
1108
1127
  registerCustomXPathFunction(
1109
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'json2xml'},
1110
- ['item()?'],
1111
- 'item()?',
1112
- jsonToXml
1128
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'json2xml' },
1129
+ ['item()?'],
1130
+ 'item()?',
1131
+ jsonToXml,
1113
1132
  );
1114
- const xmlToJson = (dynamicContext, xml) => {
1115
- const isElementNode = (node) => {
1116
- return node.nodeType === Node.ELEMENT_NODE;
1117
- };
1118
-
1119
- const isTextNode = (node) => {
1120
- return node.nodeType === Node.TEXT_NODE;
1121
- };
1122
-
1123
- const parseNode = (node) => {
1124
- if (isElementNode(node)) {
1125
- const obj = {};
1126
- if (node.hasAttributes()) {
1127
- obj['type'] = node.getAttribute('type');
1128
- }
1129
- if (node.childNodes.length === 1 && isTextNode(node.firstChild)) {
1130
- return node.textContent;
1131
- }
1132
- for (const child of node.childNodes) {
1133
- const childName = child.nodeName;
1134
- const childValue = parseNode(child);
1135
- if (obj[childName]) {
1136
- if (!Array.isArray(obj[childName])) {
1137
- obj[childName] = [obj[childName]];
1138
- }
1139
- obj[childName].push(childValue);
1140
- } else {
1141
- obj[childName] = childValue;
1142
- }
1143
- }
1144
- return obj;
1145
- } else if (isTextNode(node)) {
1146
- return node.textContent;
1133
+ const xmlToJson = (_dynamicContext, xml) => {
1134
+ const isElementNode = node => node.nodeType === Node.ELEMENT_NODE;
1135
+
1136
+ const isTextNode = node => node.nodeType === Node.TEXT_NODE;
1137
+
1138
+ const parseNode = node => {
1139
+ if (isElementNode(node)) {
1140
+ const obj = {};
1141
+ if (node.hasAttributes()) {
1142
+ obj.type = node.getAttribute('type');
1143
+ }
1144
+ if (node.childNodes.length === 1 && isTextNode(node.firstChild)) {
1145
+ return node.textContent;
1146
+ }
1147
+ for (const child of node.childNodes) {
1148
+ const childName = child.nodeName;
1149
+ const childValue = parseNode(child);
1150
+ if (obj[childName]) {
1151
+ if (!Array.isArray(obj[childName])) {
1152
+ obj[childName] = [obj[childName]];
1153
+ }
1154
+ obj[childName].push(childValue);
1155
+ } else {
1156
+ obj[childName] = childValue;
1147
1157
  }
1158
+ }
1159
+ return obj;
1160
+ }
1161
+ if (isTextNode(node)) {
1162
+ return node.textContent;
1163
+ }
1164
+ return undefined;
1165
+ };
1148
1166
 
1149
- };
1150
-
1151
- const parser = new DOMParser();
1152
- const xmlDoc = parser.parseFromString(xml, 'application/xml');
1153
- const root = xmlDoc.documentElement;
1154
- return parseNode(root);
1167
+ const parser = new DOMParser();
1168
+ const xmlDoc = parser.parseFromString(xml, 'application/xml');
1169
+ const root = xmlDoc.documentElement;
1170
+ return parseNode(root);
1155
1171
  };
1156
1172
  registerCustomXPathFunction(
1157
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'xmltoJson'},
1158
- ['item()?'],
1159
- 'item()?',
1160
- xmlToJson
1173
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'xmltoJson' },
1174
+ ['item()?'],
1175
+ 'item()?',
1176
+ xmlToJson,
1161
1177
  );
1162
1178
 
1163
1179
  /*
@@ -1166,54 +1182,58 @@ const xml = '<json type="object"><given>Mark</given><family>Smith</family></json
1166
1182
  console.log(xmlToJson(xml));
1167
1183
  */
1168
1184
 
1169
-
1170
1185
  registerCustomXPathFunction(
1171
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'depends'},
1172
- ['node()*'],
1173
- 'item()?',
1174
- (dynamicContext, nodes) =>
1175
- // console.log('depends on : ', nodes[0]);
1176
- nodes[0],
1186
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'depends' },
1187
+ ['node()*'],
1188
+ 'item()?',
1189
+ (_dynamicContext, nodes) =>
1190
+ // console.log('depends on : ', nodes[0]);
1191
+ nodes[0],
1177
1192
  );
1178
1193
 
1179
1194
  registerCustomXPathFunction(
1180
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'event'},
1181
- ['xs:string?'],
1182
- 'item()?',
1183
- (dynamicContext, arg) => {
1184
- if (!arg) return null;
1185
-
1186
- for (let ancestor = dynamicContext.currentContext.formElement;
1187
- ancestor;
1188
- ancestor = ancestor.parentNode) {
1189
- if (!ancestor.currentEvent) {
1190
- continue;
1191
- }
1192
-
1193
- // We have a current event. read the property either from detail, or from the event
1194
- // itself.
1195
- // Check detail for custom events! This is how that is passed along
1196
- if (ancestor.currentEvent.detail && typeof ancestor.currentEvent.detail === 'object' && arg in ancestor.currentEvent.detail) {
1197
- return ancestor.currentEvent.detail[arg];
1198
- }
1199
-
1200
- // arg might be `code`, so currentEvent.code should work
1201
- if(arg.includes('.')){
1202
- return _propertyLookup(ancestor.currentEvent, arg);
1203
- }
1204
- return ancestor.currentEvent[arg] || null;
1205
-
1206
- }
1207
- return null;
1208
- },
1195
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'event' },
1196
+ ['xs:string?'],
1197
+ 'item()?',
1198
+ (dynamicContext, arg) => {
1199
+ if (!arg) return null;
1200
+
1201
+ for (
1202
+ let ancestor = dynamicContext.currentContext.formElement;
1203
+ ancestor;
1204
+ ancestor = ancestor.parentNode
1205
+ ) {
1206
+ if (!ancestor.currentEvent) {
1207
+ continue;
1208
+ }
1209
+
1210
+ // We have a current event. read the property either from detail, or from the event
1211
+ // itself.
1212
+ // Check detail for custom events! This is how that is passed along
1213
+ if (
1214
+ ancestor.currentEvent.detail &&
1215
+ typeof ancestor.currentEvent.detail === 'object' &&
1216
+ arg in ancestor.currentEvent.detail
1217
+ ) {
1218
+ return ancestor.currentEvent.detail[arg];
1219
+ }
1220
+
1221
+ // arg might be `code`, so currentEvent.code should work
1222
+ if (arg.includes('.')) {
1223
+ return _propertyLookup(ancestor.currentEvent, arg);
1224
+ }
1225
+ return ancestor.currentEvent[arg] || null;
1226
+ }
1227
+ return null;
1228
+ },
1209
1229
  );
1210
1230
 
1211
- function _propertyLookup(obj,path){
1212
- const parts = path.split(".");
1213
- if (parts.length==1){
1214
- return obj[parts[0]];
1215
- }
1216
- return _propertyLookup(obj[parts[0]], parts.slice(1).join("."));
1231
+ function _propertyLookup(obj, path) {
1232
+ const parts = path.split('.');
1233
+ if (parts.length == 1) {
1234
+ return obj[parts[0]];
1235
+ }
1236
+ return _propertyLookup(obj[parts[0]], parts.slice(1).join('.'));
1217
1237
  }
1218
1238
 
1219
1239
  // Implement the XForms standard functions here.
@@ -1253,103 +1273,94 @@ registerXQueryModule(`
1253
1273
  * @return {string}
1254
1274
  */
1255
1275
  registerCustomXPathFunction(
1256
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'base64encode'},
1257
- ['xs:string?'],
1258
- 'xs:string?',
1259
- (dynamicContext, string) => btoa(string),
1276
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'base64encode' },
1277
+ ['xs:string?'],
1278
+ 'xs:string?',
1279
+ (_dynamicContext, string) => btoa(string),
1260
1280
  );
1261
1281
 
1262
1282
  registerCustomXPathFunction(
1263
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'local-date'},
1264
- [],
1265
- 'xs:string?',
1266
- (dynamicContext, string) => new Date().toLocaleDateString(),
1283
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'local-date' },
1284
+ [],
1285
+ 'xs:string?',
1286
+ (_dynamicContext, _string) => new Date().toLocaleDateString(),
1267
1287
  );
1268
1288
  registerCustomXPathFunction(
1269
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'local-dateTime'},
1270
- [],
1271
- 'xs:string?',
1272
- (dynamicContext, string) => {
1273
- return new Date().toLocaleString();
1274
- },
1289
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'local-dateTime' },
1290
+ [],
1291
+ 'xs:string?',
1292
+ (_dynamicContext, _string) => new Date().toLocaleString(),
1275
1293
  );
1276
1294
  registerCustomXPathFunction(
1277
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri'},
1278
- [],
1279
- 'xs:string?',
1280
- (dynamicContext, string) => window.location.href,
1295
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri' },
1296
+ [],
1297
+ 'xs:string?',
1298
+ (_dynamicContext, _string) => window.location.href,
1281
1299
  );
1282
1300
  registerCustomXPathFunction(
1283
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-fragment'},
1284
- [],
1285
- 'xs:string?',
1286
- (dynamicContext, arg) => window.location.hash,
1301
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-fragment' },
1302
+ [],
1303
+ 'xs:string?',
1304
+ (_dynamicContext, _arg) => window.location.hash,
1287
1305
  );
1288
1306
  registerCustomXPathFunction(
1289
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-host'},
1290
- [],
1291
- 'xs:string?',
1292
- (dynamicContext, arg) => window.location.host,
1307
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-host' },
1308
+ [],
1309
+ 'xs:string?',
1310
+ (_dynamicContext, _arg) => window.location.host,
1293
1311
  );
1294
1312
  registerCustomXPathFunction(
1295
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-query'},
1296
- [],
1297
- 'xs:string?',
1298
- (dynamicContext, arg) => window.location.search,
1313
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-query' },
1314
+ [],
1315
+ 'xs:string?',
1316
+ (_dynamicContext, _arg) => window.location.search,
1299
1317
  );
1300
1318
  registerCustomXPathFunction(
1301
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-relpath'},
1302
- [],
1303
- 'xs:string?',
1304
- (dynamicContext, arg) => {
1305
- const path = new URL(window.location.href).pathname;
1306
- return path.substring(0,path.lastIndexOf('/') + 1);
1307
- },
1319
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-relpath' },
1320
+ [],
1321
+ 'xs:string?',
1322
+ (_dynamicContext, _arg) => {
1323
+ const path = new URL(window.location.href).pathname;
1324
+ return path.substring(0, path.lastIndexOf('/') + 1);
1325
+ },
1308
1326
  );
1309
1327
  registerCustomXPathFunction(
1310
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-path'},
1311
- [],
1312
- 'xs:string?',
1313
- (dynamicContext, arg) => {
1314
- return new URL(window.location.href).pathname;
1315
- },
1328
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-path' },
1329
+ [],
1330
+ 'xs:string?',
1331
+ (_dynamicContext, _arg) => new URL(window.location.href).pathname,
1316
1332
  );
1317
1333
  registerCustomXPathFunction(
1318
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-port'},
1319
- [],
1320
- 'xs:string?',
1321
- (dynamicContext, arg) => {
1322
- return window.location.port;
1323
- },
1334
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-port' },
1335
+ [],
1336
+ 'xs:string?',
1337
+ (_dynamicContext, _arg) => window.location.port,
1324
1338
  );
1325
1339
  registerCustomXPathFunction(
1326
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-param'},
1327
- ['xs:string?'],
1328
- 'xs:string?',
1329
- (dynamicContext, arg) => {
1330
- if (!arg) return null;
1331
-
1332
- const search = window.location.search;
1333
- const urlparams = new URLSearchParams(search);
1334
- const param = urlparams.get(arg);
1335
- return param ? param : '';
1336
-
1337
- },
1340
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-param' },
1341
+ ['xs:string?'],
1342
+ 'xs:string?',
1343
+ (_dynamicContext, arg) => {
1344
+ if (!arg) return null;
1345
+
1346
+ const { search } = window.location;
1347
+ const urlparams = new URLSearchParams(search);
1348
+ const param = urlparams.get(arg);
1349
+ return param || '';
1350
+ },
1338
1351
  );
1339
1352
  registerCustomXPathFunction(
1340
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-scheme'},
1341
- [],
1342
- 'xs:string?',
1343
- (dynamicContext, arg) => {
1344
- return new URL(window.location.href).protocol;
1345
- },
1353
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-scheme' },
1354
+ [],
1355
+ 'xs:string?',
1356
+ (_dynamicContext, _arg) => new URL(window.location.href).protocol,
1346
1357
  );
1347
1358
  registerCustomXPathFunction(
1348
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-scheme-specific-part'},
1349
- [],
1350
- 'xs:string?',
1351
- (dynamicContext, arg) => {
1352
- const uri = window.location.href;
1353
- return uri.substring(uri.indexOf(':') + 1, uri.length);
1354
- },
1359
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-scheme-specific-part' },
1360
+ [],
1361
+ 'xs:string?',
1362
+ (_dynamicContext, _arg) => {
1363
+ const uri = window.location.href;
1364
+ return uri.substring(uri.indexOf(':') + 1, uri.length);
1365
+ },
1355
1366
  );