@jinntec/fore 2.2.0 → 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 +1 -1
  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 +3 -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 +4 -6
  45. package/src/functions/fx-functionlib.js +39 -42
  46. package/src/functions/registerFunction.js +95 -86
  47. package/src/fx-bind.js +19 -24
  48. package/src/fx-connection.js +226 -0
  49. package/src/fx-fore.js +846 -815
  50. package/src/fx-header.js +4 -4
  51. package/src/fx-instance.js +23 -27
  52. package/src/fx-model.js +405 -383
  53. package/src/fx-submission.js +399 -397
  54. package/src/fx-var.js +6 -5
  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 +74 -56
  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 -959
  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,45 +524,47 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, o
497
524
  }),
498
525
  );
499
526
  */
500
- // Return 'nothing' in hope the rest of the page can forgive this
501
- return [];
502
- }
527
+ // Return 'nothing' in hope the rest of the page can forgive this
528
+ return [];
529
+ }
503
530
  }
504
531
  /**
505
532
  * Evaluate an XPath to the first Node
506
533
  *
507
534
  * @param {string} xpath The XPath to run
508
535
  * @param {Node} contextNode The start of the XPath
509
- * @param {Node} formElement The form element associated to the XPath
510
- * @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
511
538
  */
512
539
  export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
513
- try{
514
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
515
- const variablesInScope = getVariablesInScope(formElement);
516
- const result = fxEvaluateXPathToFirstNode(xpath, contextNode, null, variablesInScope, {
517
- defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
518
- moduleImports: {
519
- xf: XFORMS_NAMESPACE_URI,
520
- },
521
- currentContext: {formElement},
522
- functionNameResolver,
523
- namespaceResolver,
524
- });
525
- // console.log('evaluateXPathToFirstNode',xpath, result);
526
- return result;
527
- } catch (e){
528
- formElement.dispatchEvent(new CustomEvent('error', {
529
- composed: false,
530
- bubbles: true,
531
- detail: {
532
- origin: formElement,
533
- message: `Expression '${xpath}' failed`,
534
- expr:xpath,
535
- level:'Error'
536
- },
537
- }));
538
- }
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
+ }
539
568
  }
540
569
 
541
570
  /**
@@ -543,36 +572,38 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
543
572
  *
544
573
  * @param {string} xpath The XPath to run
545
574
  * @param {Node} contextNode The start of the XPath
546
- * @param {Node} formElement The form element associated to the XPath
575
+ * @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
547
576
  * @return {Node[]} All nodes
548
577
  */
549
578
  export function evaluateXPathToNodes(xpath, contextNode, formElement) {
550
- try{
551
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
552
- const variablesInScope = getVariablesInScope(formElement);
579
+ try {
580
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
581
+ const variablesInScope = getVariablesInScope(formElement);
553
582
 
554
- const result = fxEvaluateXPathToNodes(xpath, contextNode, null, variablesInScope, {
555
- currentContext: {formElement},
556
- functionNameResolver,
557
- moduleImports: {
558
- xf: XFORMS_NAMESPACE_URI,
559
- },
560
- namespaceResolver,
561
- });
562
- // console.log('evaluateXPathToNodes',xpath, result);
563
- return result;
564
- }catch (e){
565
- formElement.dispatchEvent(new CustomEvent('error', {
566
- composed: false,
567
- bubbles: true,
568
- detail: {
569
- origin: formElement,
570
- message: `Expression '${xpath}' failed`,
571
- expr:xpath,
572
- level:'Error'
573
- },
574
- }));
575
- }
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
+ }
576
607
  }
577
608
 
578
609
  /**
@@ -580,34 +611,36 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
580
611
  *
581
612
  * @param {string} xpath The XPath to run
582
613
  * @param {Node} contextNode The start of the XPath
583
- * @param {Node} formElement The form element associated to the XPath
614
+ * @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
584
615
  * @return {boolean}
585
616
  */
586
617
  export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
587
- try{
588
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
589
- const variablesInScope = getVariablesInScope(formElement);
618
+ try {
619
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
620
+ const variablesInScope = getVariablesInScope(formElement);
590
621
 
591
- return fxEvaluateXPathToBoolean(xpath, contextNode, null, variablesInScope, {
592
- currentContext: {formElement},
593
- functionNameResolver,
594
- moduleImports: {
595
- xf: XFORMS_NAMESPACE_URI,
596
- },
597
- namespaceResolver,
598
- });
599
- }catch (e){
600
- formElement.dispatchEvent(new CustomEvent('error', {
601
- composed: false,
602
- bubbles: true,
603
- detail: {
604
- origin: formElement,
605
- message: `Expression '${xpath}' failed`,
606
- expr:xpath,
607
- level:'Error'
608
- },
609
- }));
610
- }
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
+ }
611
644
  }
612
645
 
613
646
  /**
@@ -616,42 +649,38 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
616
649
  * @param {string} xpath The XPath to run
617
650
  * @param {Node} contextNode The start of the XPath
618
651
  * @param {Node} formElement The form element associated to the XPath
619
- * @param {DomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
620
- * access. This is used to determine dependencies between bind elements.
621
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.
622
655
  * @return {string}
623
656
  */
624
- export function evaluateXPathToString(
625
- xpath,
626
- contextNode,
627
- formElement,
628
- domFacade = null,
629
- namespaceReferenceNode = formElement,
630
- ) {
631
- try{
632
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
633
- const variablesInScope = getVariablesInScope(formElement);
634
-
635
- return fxEvaluateXPathToString(xpath, contextNode, domFacade, variablesInScope, {
636
- currentContext: {formElement},
637
- functionNameResolver,
638
- moduleImports: {
639
- xf: XFORMS_NAMESPACE_URI,
640
- },
641
- namespaceResolver,
642
- });
643
- }catch (e) {
644
- formElement.dispatchEvent(new CustomEvent('error', {
645
- composed: false,
646
- bubbles: true,
647
- detail: {
648
- origin: formElement,
649
- message: `Expression '${xpath}' failed`,
650
- expr:xpath,
651
- level:'Error'
652
- },
653
- }));
654
- }
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
+ }
655
684
  }
656
685
 
657
686
  /**
@@ -660,43 +689,42 @@ export function evaluateXPathToString(
660
689
  * @param {string} xpath The XPath to run
661
690
  * @param {Node} contextNode The start of the XPath
662
691
  * @param {Node} formElement The form element associated to the XPath
663
- * @param {DomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
664
- * access. This is used to determine dependencies between bind elements.
665
692
  * @param {Node} formElement The element where the XPath is defined: used for namespace resolving
666
- * @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[]}
667
696
  */
668
- export function evaluateXPathToStrings(
669
- xpath,
670
- contextNode,
671
- formElement,
672
- domFacade = null,
673
- namespaceReferenceNode = formElement,
674
- ) {
675
- try{
676
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
677
- return fxEvaluateXPathToStrings( xpath, contextNode, domFacade,
678
- {},
679
- {
680
- currentContext: {formElement},
681
- functionNameResolver,
682
- moduleImports: {
683
- xf: XFORMS_NAMESPACE_URI,
684
- },
685
- namespaceResolver,
686
- },
687
- );
688
- } catch (e){
689
- formElement.dispatchEvent(new CustomEvent('error', {
690
- composed: false,
691
- bubbles: true,
692
- detail: {
693
- origin: formElement,
694
- message: `Expression '${xpath}' failed`,
695
- expr:xpath,
696
- level:'Error'
697
- },
698
- }));
699
- }
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
+ }
700
728
  }
701
729
 
702
730
  /**
@@ -705,78 +733,74 @@ export function evaluateXPathToStrings(
705
733
  * @param {string} xpath The XPath to run
706
734
  * @param {Node} contextNode The start of the XPath
707
735
  * @param {Node} formElement The form element associated to the XPath
708
- * @param {DomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
709
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
710
738
  * access. This is used to determine dependencies between bind elements.
711
- * @return {Number}
739
+ * @return {number}
712
740
  */
713
- export function evaluateXPathToNumber(
714
- xpath,
715
- contextNode,
716
- formElement,
717
- domFacade = null,
718
- namespaceReferenceNode = formElement,
719
- ) {
720
- try{
721
- const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
722
- const variablesInScope = getVariablesInScope(formElement);
723
-
724
- return fxEvaluateXPathToNumber(xpath, contextNode, domFacade, variablesInScope, {
725
- currentContext: {formElement},
726
- functionNameResolver,
727
- moduleImports: {
728
- xf: XFORMS_NAMESPACE_URI,
729
- },
730
- namespaceResolver,
731
- });
732
- }catch (e) {
733
- formElement.dispatchEvent(new CustomEvent('error', {
734
- composed: false,
735
- bubbles: true,
736
- detail: {
737
- origin: formElement,
738
- message: `Expression '${xpath}' failed`,
739
- expr:xpath,
740
- level:'Error'
741
- },
742
- }));
743
- }
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
+ }
744
768
  }
745
769
 
746
770
  const contextFunction = (dynamicContext, string) => {
747
- const caller = dynamicContext.currentContext.formElement;
748
- let instance = null;
749
- if (string) {
750
- instance = resolveId(string, caller);
751
- } else {
752
- instance = XPathUtil.getParentBindingElement(caller);
753
- }
754
- if (instance) {
755
- if (instance.nodeName === 'FX-REPEAT') {
756
- const {nodeset} = instance;
757
- for (let parent = caller; parent; parent = parent.parentNode) {
758
- if (parent.parentNode === instance) {
759
- const offset = Array.from(parent.parentNode.children).indexOf(parent);
760
- return nodeset[offset];
761
- }
762
- }
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];
763
785
  }
764
- return instance.nodeset;
786
+ }
765
787
  }
788
+ return instance.nodeset;
789
+ }
766
790
 
767
- return caller.getInScopeContext();
791
+ return caller.getInScopeContext();
768
792
  };
769
793
 
770
794
  // todo: implement
771
795
  const currentFunction = (dynamicContext, string) => {
772
- const caller = dynamicContext.currentContext.formElement;
773
- return null;
796
+ const caller = dynamicContext.currentContext.formElement;
797
+ return null;
774
798
  };
775
799
 
776
800
  const elementFunction = (dynamicContext, string) => {
777
- const caller = dynamicContext.currentContext.formElement;
778
- const newElement = document.createElement(string);
779
- return newElement;
801
+ const caller = dynamicContext.currentContext.formElement;
802
+ const newElement = document.createElement(string);
803
+ return newElement;
780
804
  };
781
805
 
782
806
  /**
@@ -784,10 +808,10 @@ const elementFunction = (dynamicContext, string) => {
784
808
  * @return instance data for given id serialized to string.
785
809
  */
786
810
  registerCustomXPathFunction(
787
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context'},
788
- [],
789
- 'item()?',
790
- contextFunction,
811
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
812
+ [],
813
+ 'item()?',
814
+ contextFunction,
791
815
  );
792
816
 
793
817
  /**
@@ -795,24 +819,24 @@ registerCustomXPathFunction(
795
819
  * @return instance data for given id serialized to string.
796
820
  */
797
821
  registerCustomXPathFunction(
798
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context'},
799
- ['xs:string'],
800
- 'item()?',
801
- contextFunction,
822
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
823
+ ['xs:string'],
824
+ 'item()?',
825
+ contextFunction,
802
826
  );
803
827
 
804
828
  registerCustomXPathFunction(
805
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'current'},
806
- ['xs:string'],
807
- 'item()?',
808
- currentFunction,
829
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'current' },
830
+ ['xs:string'],
831
+ 'item()?',
832
+ currentFunction,
809
833
  );
810
834
 
811
835
  registerCustomXPathFunction(
812
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'element'},
813
- ['xs:string'],
814
- 'item()?',
815
- elementFunction,
836
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'element' },
837
+ ['xs:string'],
838
+ 'item()?',
839
+ elementFunction,
816
840
  );
817
841
 
818
842
  /**
@@ -820,53 +844,53 @@ registerCustomXPathFunction(
820
844
  * @return instance data for given id serialized to string.
821
845
  */
822
846
  registerCustomXPathFunction(
823
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'log'},
824
- ['xs:string?'],
825
- 'xs:string?',
826
- (dynamicContext, string) => {
827
- const {formElement} = dynamicContext.currentContext;
828
- const instance = resolveId(string, formElement, 'fx-instance');
829
- if (instance) {
830
- if (instance.getAttribute('type') === 'json') {
831
- console.warn('log() does not work for JSON yet');
832
- // return JSON.stringify(instance.getDefaultContext());
833
- } else {
834
- const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
835
- return prettifyXml(def);
836
- }
837
- }
838
- return null;
839
- },
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
+ },
840
864
  );
841
865
  registerCustomXPathFunction(
842
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'fore-attr'},
843
- ['xs:string?'],
844
- 'xs:string?',
845
- (dynamicContext, string) => {
846
- const {formElement} = dynamicContext.currentContext;
847
-
848
- let parent = formElement;
849
- if(formElement.nodeType === Node.TEXT_NODE){
850
- parent = formElement.parentNode;
851
- }
852
- const foreElement = parent.closest('fx-fore');
853
- if(foreElement.hasAttribute(string)){
854
- return foreElement.getAttribute(string);
855
- }
856
- return null;
857
- },
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
+ },
858
882
  );
859
883
 
860
884
  registerCustomXPathFunction(
861
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'parse'},
862
- ['xs:string?'],
863
- 'element()?',
864
- (dynamicContext, string) => {
865
- const parser = new DOMParser();
866
- const out = parser.parseFromString(string, "application/xml");
867
- console.log('parse', out);
868
-
869
- /*
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
+ /*
870
894
  const {formElement} = dynamicContext.currentContext;
871
895
  const instance = resolveId(string, formElement, 'fx-instance');
872
896
  if (instance) {
@@ -879,53 +903,53 @@ registerCustomXPathFunction(
879
903
  }
880
904
  }
881
905
  */
882
- return out.firstElementChild;
883
- },
906
+ return out.firstElementChild;
907
+ },
884
908
  );
885
909
 
886
910
  function buildTree(tree, data) {
887
- if (!data) return;
888
- if (data.nodeType === Node.ELEMENT_NODE) {
889
- if (data.children) {
890
- const details = document.createElement('details');
891
- details.setAttribute('data-path', data.nodeName);
892
- const summary = document.createElement('summary');
893
-
894
- let display = ` <${data.nodeName}`;
895
- Array.from(data.attributes).forEach(attr => {
896
- display += ` ${attr.nodeName}="${attr.nodeValue}"`;
897
- });
898
-
899
- let contents;
900
- if (
901
- data.firstChild &&
902
- data.firstChild.nodeType === Node.TEXT_NODE &&
903
- data.firstChild.data.trim() !== ''
904
- ) {
905
- // console.log('whoooooooooopp');
906
- contents = data.firstChild.nodeValue;
907
- display += `>${contents}</${data.nodeName}>`;
908
- } else {
909
- display += '>';
910
- }
911
- summary.textContent = display;
912
-
913
- details.appendChild(summary);
914
- if (data.childElementCount !== 0) {
915
- details.setAttribute('open', 'open');
916
- } else {
917
- summary.setAttribute('style', 'list-style:none;');
918
- }
919
- tree.appendChild(details);
920
-
921
- Array.from(data.children).forEach(child => {
922
- // if(child.nodeType === Node.ELEMENT_NODE){
923
- // child.parentNode.appendChild(buildTree(child));
924
- buildTree(details, child);
925
- // }
926
- });
927
- }
928
- } /* 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){
929
953
  //create span for now
930
954
  // const span = document.createElement('span');
931
955
  // span.style.background = 'grey';
@@ -936,229 +960,220 @@ function buildTree(tree, data) {
936
960
  tree.textContent = data;
937
961
  } */
938
962
 
939
- // return tree;
963
+ // return tree;
940
964
  }
941
965
 
942
966
  registerCustomXPathFunction(
943
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'logtree'},
944
- ['xs:string?'],
945
- 'element()?',
946
- (dynamicContext, string) => {
947
- const {formElement} = dynamicContext.currentContext;
948
- const instance = resolveId(string, formElement, 'fx-instance');
949
-
950
- if (instance) {
951
- // const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
952
- // const def = JSON.stringify(instance.getDefaultContext());
953
-
954
- const treeDiv = document.createElement('div');
955
- treeDiv.setAttribute('class', 'logtree');
956
- // const datatree = buildTree(tree,instance.getDefaultContext());
957
- // return tree.appendChild(datatree);
958
- // return buildTree(root,instance.getDefaultContext());;
959
- const form = dynamicContext.currentContext.formElement;
960
- const logtree = form.querySelector('.logtree');
961
- if (logtree) {
962
- logtree.parentNode.removeChild(logtree);
963
- }
964
- const tree = buildTree(treeDiv, instance.getDefaultContext());
965
- if (tree) {
966
- form.appendChild(tree);
967
- }
968
- }
969
- return null;
970
- },
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
+ },
971
995
  );
972
996
 
973
997
  const instance = (dynamicContext, string) => {
974
- // Spec: https://www.w3.org/TR/xforms-xpath/#The_XForms_Function_Library#The_instance.28.29_Function
975
- // TODO: handle no string passed (null will be passed instead)
976
-
977
- const formElement = fxEvaluateXPathToFirstNode(
978
- 'ancestor-or-self::fx-fore[1]',
979
- dynamicContext.currentContext.formElement,
980
- null,
981
- null,
982
- {namespaceResolver: xhtmlNamespaceResolver},
983
- );
984
-
985
- let lookup = null;
986
- if(string === null || string === 'default'){
987
- lookup = formElement.getModel().getDefaultInstance();
988
- }else{
989
- lookup = formElement.getModel().getInstance(string);
990
- if(!lookup){
991
- document.querySelector('fx-fore').dispatchEvent(new CustomEvent('error', {
992
- composed: true,
993
- bubbles: true,
994
- detail: {
995
- origin: 'functions',
996
- message: `Instance not found '${localName}'`,
997
- level:'Error'
998
- },
999
- }));
1000
- }
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
+ );
1001
1029
  }
1030
+ }
1002
1031
 
1003
- const context = lookup.getDefaultContext();
1004
- if (!context) {
1005
- debugger;
1006
- return null;
1007
- }
1008
- return context;
1032
+ const context = lookup.getDefaultContext();
1033
+ if (!context) {
1034
+ return null;
1035
+ }
1036
+ return context;
1009
1037
  };
1010
1038
 
1011
1039
  registerCustomXPathFunction(
1012
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'index'},
1013
- ['xs:string?'],
1014
- 'xs:integer?',
1015
- (dynamicContext, string) => {
1016
- const {formElement} = dynamicContext.currentContext;
1017
- if (string === null) {
1018
- return 1;
1019
- }
1020
- 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');
1021
1049
 
1022
- // const def = instance.getInstanceData();
1023
- if (repeat) {
1024
- return repeat.getAttribute('index');
1025
- }
1026
- return Number(1);
1027
- },
1050
+ // const def = instance.getInstanceData();
1051
+ if (repeat) {
1052
+ return repeat.getAttribute('index');
1053
+ }
1054
+ return Number(1);
1055
+ },
1028
1056
  );
1029
1057
 
1030
1058
  // Note that this is not to spec. The spec enforces elements to be returned from the
1031
1059
  // instance. However, we allow instances to actually be JSON!
1032
1060
  registerCustomXPathFunction(
1033
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance'},
1034
- [],
1035
- 'item()?',
1036
- domFacade => instance(domFacade, null),
1061
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance' },
1062
+ [],
1063
+ 'item()?',
1064
+ domFacade => instance(domFacade, null),
1037
1065
  );
1038
1066
 
1039
1067
  registerCustomXPathFunction(
1040
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance'},
1041
- ['xs:string?'],
1042
- 'item()?',
1043
- instance,
1068
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance' },
1069
+ ['xs:string?'],
1070
+ 'item()?',
1071
+ instance,
1044
1072
  );
1045
- const getAttributes = (value) => {
1046
- if (Array.isArray(value)) {
1047
- return ` type="array"`;
1048
- } else if (typeof value === 'number') {
1049
- return ` type="number"`;
1050
- } else if (typeof value === 'boolean') {
1051
- return ` type="boolean"`;
1052
- }
1053
- return '';
1054
- };
1055
1073
 
1056
- const jsonToXml = (dynamicContext, json) => {
1057
- const escapeXml = (str) => {
1058
- return str.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]/g, (char) => {
1059
- return `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`;
1060
- });
1061
- };
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
+ );
1062
1080
 
1063
- const convert = (obj, parent) => {
1064
- const type = typeof obj;
1065
- if (type === 'number') {
1066
- parent.setAttribute('type', 'number');
1067
- parent.textContent = obj.toString();
1068
- } else if (type === 'boolean') {
1069
- parent.setAttribute('type', 'boolean');
1070
- parent.textContent = obj.toString();
1071
- } else if (obj === null) {
1072
- const node = document.createElement('_');
1073
- node.setAttribute('type', 'null');
1074
- parent.appendChild(node);
1075
- } else if (type === 'string') {
1076
- parent.textContent = escapeXml(obj);
1077
- } else if (Array.isArray(obj)) {
1078
- parent.setAttribute('type', 'array');
1079
- obj.forEach((item) => {
1080
- const node = document.createElement('_');
1081
- convert(item, node);
1082
- node.textContent = item;
1083
- parent.appendChild(node);
1084
- });
1085
- } else if (type === 'object') {
1086
- parent.setAttribute('type', 'object');
1087
- Object.entries(obj).forEach(([key, value]) => {
1088
- if(value){
1089
- const childNode = document.createElement(key.replace(/[^a-zA-Z0-9_]/g, '_'));
1090
- convert(value, childNode);
1091
- parent.appendChild(childNode);
1092
- }
1093
- });
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);
1094
1110
  }
1095
- };
1096
-
1097
- const root = document.createElement('json');
1098
- if(Array.isArray(json)){
1099
- root.setAttribute('type', 'array');
1100
- }else{
1101
- root.setAttribute('type', 'object');
1111
+ });
1102
1112
  }
1103
- convert(json, root);
1104
- // return root.outerHTML;
1105
- console.log('xml',root)
1106
- 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;
1107
1125
  };
1108
1126
 
1109
1127
  registerCustomXPathFunction(
1110
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'json2xml'},
1111
- ['item()?'],
1112
- 'item()?',
1113
- jsonToXml
1128
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'json2xml' },
1129
+ ['item()?'],
1130
+ 'item()?',
1131
+ jsonToXml,
1114
1132
  );
1115
- const xmlToJson = (dynamicContext, xml) => {
1116
- const isElementNode = (node) => {
1117
- return node.nodeType === Node.ELEMENT_NODE;
1118
- };
1119
-
1120
- const isTextNode = (node) => {
1121
- return node.nodeType === Node.TEXT_NODE;
1122
- };
1123
-
1124
- const parseNode = (node) => {
1125
- if (isElementNode(node)) {
1126
- const obj = {};
1127
- if (node.hasAttributes()) {
1128
- obj['type'] = node.getAttribute('type');
1129
- }
1130
- if (node.childNodes.length === 1 && isTextNode(node.firstChild)) {
1131
- return node.textContent;
1132
- }
1133
- for (const child of node.childNodes) {
1134
- const childName = child.nodeName;
1135
- const childValue = parseNode(child);
1136
- if (obj[childName]) {
1137
- if (!Array.isArray(obj[childName])) {
1138
- obj[childName] = [obj[childName]];
1139
- }
1140
- obj[childName].push(childValue);
1141
- } else {
1142
- obj[childName] = childValue;
1143
- }
1144
- }
1145
- return obj;
1146
- } else if (isTextNode(node)) {
1147
- 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;
1148
1157
  }
1158
+ }
1159
+ return obj;
1160
+ }
1161
+ if (isTextNode(node)) {
1162
+ return node.textContent;
1163
+ }
1164
+ return undefined;
1165
+ };
1149
1166
 
1150
- };
1151
-
1152
- const parser = new DOMParser();
1153
- const xmlDoc = parser.parseFromString(xml, 'application/xml');
1154
- const root = xmlDoc.documentElement;
1155
- 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);
1156
1171
  };
1157
1172
  registerCustomXPathFunction(
1158
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'xmltoJson'},
1159
- ['item()?'],
1160
- 'item()?',
1161
- xmlToJson
1173
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'xmltoJson' },
1174
+ ['item()?'],
1175
+ 'item()?',
1176
+ xmlToJson,
1162
1177
  );
1163
1178
 
1164
1179
  /*
@@ -1167,54 +1182,58 @@ const xml = '<json type="object"><given>Mark</given><family>Smith</family></json
1167
1182
  console.log(xmlToJson(xml));
1168
1183
  */
1169
1184
 
1170
-
1171
1185
  registerCustomXPathFunction(
1172
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'depends'},
1173
- ['node()*'],
1174
- 'item()?',
1175
- (dynamicContext, nodes) =>
1176
- // console.log('depends on : ', nodes[0]);
1177
- 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],
1178
1192
  );
1179
1193
 
1180
1194
  registerCustomXPathFunction(
1181
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'event'},
1182
- ['xs:string?'],
1183
- 'item()?',
1184
- (dynamicContext, arg) => {
1185
- if (!arg) return null;
1186
-
1187
- for (let ancestor = dynamicContext.currentContext.formElement;
1188
- ancestor;
1189
- ancestor = ancestor.parentNode) {
1190
- if (!ancestor.currentEvent) {
1191
- continue;
1192
- }
1193
-
1194
- // We have a current event. read the property either from detail, or from the event
1195
- // itself.
1196
- // Check detail for custom events! This is how that is passed along
1197
- if (ancestor.currentEvent.detail && typeof ancestor.currentEvent.detail === 'object' && arg in ancestor.currentEvent.detail) {
1198
- return ancestor.currentEvent.detail[arg];
1199
- }
1200
-
1201
- // arg might be `code`, so currentEvent.code should work
1202
- if(arg.includes('.')){
1203
- return _propertyLookup(ancestor.currentEvent, arg);
1204
- }
1205
- return ancestor.currentEvent[arg] || null;
1206
-
1207
- }
1208
- return null;
1209
- },
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
+ },
1210
1229
  );
1211
1230
 
1212
- function _propertyLookup(obj,path){
1213
- const parts = path.split(".");
1214
- if (parts.length==1){
1215
- return obj[parts[0]];
1216
- }
1217
- 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('.'));
1218
1237
  }
1219
1238
 
1220
1239
  // Implement the XForms standard functions here.
@@ -1254,103 +1273,94 @@ registerXQueryModule(`
1254
1273
  * @return {string}
1255
1274
  */
1256
1275
  registerCustomXPathFunction(
1257
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'base64encode'},
1258
- ['xs:string?'],
1259
- 'xs:string?',
1260
- (dynamicContext, string) => btoa(string),
1276
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'base64encode' },
1277
+ ['xs:string?'],
1278
+ 'xs:string?',
1279
+ (_dynamicContext, string) => btoa(string),
1261
1280
  );
1262
1281
 
1263
1282
  registerCustomXPathFunction(
1264
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'local-date'},
1265
- [],
1266
- 'xs:string?',
1267
- (dynamicContext, string) => new Date().toLocaleDateString(),
1283
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'local-date' },
1284
+ [],
1285
+ 'xs:string?',
1286
+ (_dynamicContext, _string) => new Date().toLocaleDateString(),
1268
1287
  );
1269
1288
  registerCustomXPathFunction(
1270
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'local-dateTime'},
1271
- [],
1272
- 'xs:string?',
1273
- (dynamicContext, string) => {
1274
- return new Date().toLocaleString();
1275
- },
1289
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'local-dateTime' },
1290
+ [],
1291
+ 'xs:string?',
1292
+ (_dynamicContext, _string) => new Date().toLocaleString(),
1276
1293
  );
1277
1294
  registerCustomXPathFunction(
1278
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri'},
1279
- [],
1280
- 'xs:string?',
1281
- (dynamicContext, string) => window.location.href,
1295
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri' },
1296
+ [],
1297
+ 'xs:string?',
1298
+ (_dynamicContext, _string) => window.location.href,
1282
1299
  );
1283
1300
  registerCustomXPathFunction(
1284
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-fragment'},
1285
- [],
1286
- 'xs:string?',
1287
- (dynamicContext, arg) => window.location.hash,
1301
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-fragment' },
1302
+ [],
1303
+ 'xs:string?',
1304
+ (_dynamicContext, _arg) => window.location.hash,
1288
1305
  );
1289
1306
  registerCustomXPathFunction(
1290
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-host'},
1291
- [],
1292
- 'xs:string?',
1293
- (dynamicContext, arg) => window.location.host,
1307
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-host' },
1308
+ [],
1309
+ 'xs:string?',
1310
+ (_dynamicContext, _arg) => window.location.host,
1294
1311
  );
1295
1312
  registerCustomXPathFunction(
1296
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-query'},
1297
- [],
1298
- 'xs:string?',
1299
- (dynamicContext, arg) => window.location.search,
1313
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-query' },
1314
+ [],
1315
+ 'xs:string?',
1316
+ (_dynamicContext, _arg) => window.location.search,
1300
1317
  );
1301
1318
  registerCustomXPathFunction(
1302
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-relpath'},
1303
- [],
1304
- 'xs:string?',
1305
- (dynamicContext, arg) => {
1306
- const path = new URL(window.location.href).pathname;
1307
- return path.substring(0,path.lastIndexOf('/') + 1);
1308
- },
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
+ },
1309
1326
  );
1310
1327
  registerCustomXPathFunction(
1311
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-path'},
1312
- [],
1313
- 'xs:string?',
1314
- (dynamicContext, arg) => {
1315
- return new URL(window.location.href).pathname;
1316
- },
1328
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-path' },
1329
+ [],
1330
+ 'xs:string?',
1331
+ (_dynamicContext, _arg) => new URL(window.location.href).pathname,
1317
1332
  );
1318
1333
  registerCustomXPathFunction(
1319
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-port'},
1320
- [],
1321
- 'xs:string?',
1322
- (dynamicContext, arg) => {
1323
- return window.location.port;
1324
- },
1334
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-port' },
1335
+ [],
1336
+ 'xs:string?',
1337
+ (_dynamicContext, _arg) => window.location.port,
1325
1338
  );
1326
1339
  registerCustomXPathFunction(
1327
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-param'},
1328
- ['xs:string?'],
1329
- 'xs:string?',
1330
- (dynamicContext, arg) => {
1331
- if (!arg) return null;
1332
-
1333
- const search = window.location.search;
1334
- const urlparams = new URLSearchParams(search);
1335
- const param = urlparams.get(arg);
1336
- return param ? param : '';
1337
-
1338
- },
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
+ },
1339
1351
  );
1340
1352
  registerCustomXPathFunction(
1341
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-scheme'},
1342
- [],
1343
- 'xs:string?',
1344
- (dynamicContext, arg) => {
1345
- return new URL(window.location.href).protocol;
1346
- },
1353
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-scheme' },
1354
+ [],
1355
+ 'xs:string?',
1356
+ (_dynamicContext, _arg) => new URL(window.location.href).protocol,
1347
1357
  );
1348
1358
  registerCustomXPathFunction(
1349
- {namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-scheme-specific-part'},
1350
- [],
1351
- 'xs:string?',
1352
- (dynamicContext, arg) => {
1353
- const uri = window.location.href;
1354
- return uri.substring(uri.indexOf(':') + 1, uri.length);
1355
- },
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
+ },
1356
1366
  );